Yian - The Agent Composer
Yian is a small language and runtime for readable agent flows. You author a fixed graph in Docker-Compose-shaped YAML; the framework infers data dependencies, applies explicit ordering constraints, validates types, and runs through an append-only log.
In this framework, an agent is a composable transformation from a named
input record to an output value. It can be represented by one node, a flow of
nodes, or a child flow used inside another flow. The YAML AGENT node
(kind: agent) is one concrete implementation: an LLM-backed node with a task
prompt and optional tool grants, model configuration, and long-term memory.
The author fixes the graph. LLMs generate values and may choose among explicitly granted tools, but they do not rewrite the flow structure at run time.
# hello.yaml — a compact one-node flow
id: hello
name: hello
input:
name: str
output: str
kind: agent
prompt: Write a warm one-sentence greeting to ${name}.
yian run hello.yaml --input name=Ada --provider ollama --model llama3.2:3b
See Agent for the conceptual model and
Flow syntax for the complete YAML surface.
AGENT nodes can declare typed local variables
with validated defaults and reference them in prompts as ${name}.
Why fixed flows?
- Readable: the flow is the plan. Humans and models can inspect the same artifact before it runs.
- Composable: typed nodes build flows; CALL, MAP, and LOOP apply child flows recursively within the runtime depth limit.
- Predictable: data references determine the data edges, and the LLM never changes the authored call graph.
- Recoverable: every accepted transition is appended before it is applied, so an interrupted run can replay a validated record prefix.
- Evolvable: fixed flow and node identities give learning stable targets, so recorded outcomes from completed runs can improve later behavior without mutating the graph mid-run.
Install
Early stage. Yian is under active development. The API, YAML surface, and CLI may change quickly.
The current documentation describes the main branch. PyPI release 0.0.1
contains an older, incompatible package, so install the current version from a
clone until the next release:
git clone https://github.com/ngocbh/yian.git
cd yian
python -m pip install ".[all]"
For development, use python -m pip install -e ".[all,dev]" instead.
Provider extras can be installed separately: anthropic, openai, google,
or ollama. The core engine and CLI do not require a provider SDK; a flow needs
one only when it contains an AGENT node.
Connect OpenAI with yian auth openai, choose ChatGPT subscription sign-in or an
API key, and select a default model. Both methods use provider: openai with
Yian's ordinary tools and memory. OpenAI is the default provider; successful
setup saves your chosen model for subsequent runs. See
authentication setup.
Run a flow
yian run FLOW.yaml [--inputs inputs.json] [--input k=v]... \
[--provider P] [--model M] [-w N] [--log PATH]
--inputs accepts a JSON object, including lists, records, and explicit nulls.
Repeatable --input k=v supplies scalar overrides. In a terminal, the CLI
prompts for declared inputs that are still missing.
New runs use up to eight workers for independent ready nodes. Set
--num-workers N / -w N to override the count, or -w 0 for serial execution.
Resuming without a worker flag preserves the count recorded in the run log.
The CLI reports its selected JSONL path before execution. Once runtime
initialization commits RunStarted, that file is the authoritative log. The
default is ~/.yian/runs/<run_id>.jsonl; choose another new path with
--log.
Resume an interrupted run against the same flow:
yian run FLOW.yaml --resume PATH
Run logs can contain inputs, model/tool/human data, outputs, memory snapshots,
configuration, and tracebacks. Treat them as sensitive. See The yian
CLI for input behavior, diagnostics, concurrency, and recovery.
For an interactive flow-backed chat:
yian chat [FLOW] [--workspace DIR] [--provider P] [--model M]
The default yian chat can inspect, validate, run, and—after explicit approval—write flows inside its workspace. A supplied chat flow owns its own typed exit behavior.
Use the Python API
from pathlib import Path
from yian import load_flow, run_flow
path = Path("hello.yaml")
flow = load_flow(
path.read_text(),
search_paths=[path.parent],
source_label=path.name,
)
result = run_flow(
flow,
{"name": "Ada"},
llm_config={"provider": "ollama", "model": "llama3.2:3b"},
)
print(result.status, result.output)
load_flow parses, validates, and compiles the YAML into a reusable
ComposedFlow. run_flow makes a private graph for one run and returns a
RunResult with either the flow output or an expected Failure. Host, control,
log, and invariant faults propagate as exceptions.
A library host can inject per-run tools (including its ask_user handler and
named tool groups that an AGENT grants by name) with tools=, provider
configuration, and named memory implementations. To pass a provider key without an
environment variable, add it to the run's config, as in
llm_config={"provider": "anthropic", "model": "claude-sonnet-4-5", "api_key": key};
the key is never recorded or echoed, so pass it again to resume_flow(..., api_key=key) or result.backward(..., api_key=key). A base_url beside the key,
such as an Azure OpenAI resource's https://<resource>.openai.azure.com/openai/v1/
with "provider": "openai", is where that key is sent; flow files cannot set it.
Without one, the provider's default or environment endpoint applies.
Passing learnable=True
retains accepted execution for a later explicit result.backward(feedback) call,
with or without memory. Each call is a new
attempt, so feedback can arrive several times, days later. Forward
execution returns before any learning or commit; memory recall is independent.
The returned BackwardOutcome and result.backward_outcome keep feedback and
learning separate from the forward output and failure. See the
Python API for feedback shapes, memory,
and silent terminal recovery.
Pass on_stream= to receive an AGENT's reply text as it is generated. Pass
on_event= to receive node transitions plus, while an AGENT runs, per-call
ModelCallFinished token usage and tool call observers. Both are live callbacks
only; see Python API for payloads.
A host can steer or interrupt a running tool_calling AGENT that declares
steerable: true. Pass steering=inbox, any object with drain(node_id) -> list[str] and interrupted(node_id) -> bool. Queued steers reach the model as
user messages at the top of the next loop iteration. An interrupt ends the loop
at the next safe point, including mid-stream, and the node succeeds with its text
so far. See Steering.
The built-in EpisodicMemory keeps bounded,
model-free execution history for later runs. A flow declares a named memory,
long_term_memory: {notes: episodic}; its host builds the declared backends
with build_memories (in RAM unless given storage) and commits retained
episodes through explicit backward:
from yian import build_memories
with build_memories(flow) as memory:
result = run_flow(flow, inputs, memory=memory, learnable=True)
result.backward()
ProfileMemory keeps one markdown document,
such as a person's profile, in a versioned file. Every AGENT under the declaring
flow sees the whole document; backward rewrites it with one model call and a
compare-and-set write. Declare long_term_memory: {about: profile} and supply
the backend under its key:
from yian import build_memories
from yian.memory.profile import ProfileMemory
from yian.memory.storage import FileMarkdownStore
profile = ProfileMemory(FileMarkdownStore("people/ada.md"))
with build_memories(flow, supplied={"about": profile}) as memory:
result = run_flow(flow, inputs, memory=memory, learnable=True)
result.backward("Ada prefers short replies.")
SessionMemory supplies a structured session
transcript through the same memory context as notes from other memories. It keeps
conversation history across runs in the same process. Declare
long_term_memory: {chat: session} on the AGENT and reuse one registry:
from yian import build_memories
with build_memories(flow) as memory:
first = run_flow(flow, first_inputs, memory=memory)
second = run_flow(flow, next_inputs, memory=memory)
memory["chat"].reset() # Starts fresh; fails while any turn is active or queued.
History advances automatically, without learnable=True or backward. Every call
site of the declaring AGENT shares its one conversation, and calls queue in
admission order. Two AGENTs cannot share a session key, so each keeps a separate
conversation. Explicit backward learning reuses API self-feedback
as committed notes for future runs, without another model call. API history uses a
soft 32,000-character context budget and preserves the latest four whole turns;
older context can be summarized without deleting raw history. Configure these
defaults on SessionMemory(history_budget_chars=32_000, recent_turns=4).
Run logs do not reconstruct history. The episodic, reflective, and session
backends optionally retain their state across restarts in a SQLite file.
build_memories places one memory with storage={key: path}, or every memory
under root= (the flow id: must be usable as one directory name):
with build_memories(flow, storage={"chat": "./session.sqlite"}) as memory:
result = run_flow(flow, inputs, memory=memory)
An AGENT can also use Claude Code and Codex skills. skills: [release-notes] (or
skills: all) lists each granted skill's name and description in the system
message, and the model reads a skill's SKILL.md or bundled files through the
built-in load_skill tool when it needs them. yian run supplies the skills in
~/.claude/skills, ~/.agents/skills, and the project's .claude/skills and
.agents/skills; a library host passes skills=load_skills([...]) from
yian.compose ({} when a granting flow should get none). See Skills.
Each local SQLite file has one owning backend until close() or context exit.
Idle run handles do not block closing; active work or queued turns do. Omit
storage to keep memory process-local. See durable memory
for ownership and recovery.
yian run builds every declared built-in memory (episodic, reflective,
session, profile). They stay in RAM unless a CLI option places them:
yian run flow.yaml --memory-root ./mem
yian run flow.yaml --resume ./run.jsonl --memory-root ./mem
--memory-root DIR keeps every memory under DIR/<flow id>/ (the flow id:
must be usable as one directory name), and repeatable --memory-storage KEY=PATH
places one declared memory.
Use the same storage on resume; a run log is not a memory backup. The CLI does
not run backward learning. A flow that declares a custom memory type runs from
Python with build_memories; yian run exits 1 with a setup error for it.
See CLI memory for lifetime and examples.
Documentation
- Agent — what “agent,” node, flow, child flow, memory, and run mean in this framework.
- Architecture — how YAML becomes a compiled flow and a
RunResult. - Flow syntax — the complete authoring reference.
- Examples — annotated flows from
examples/. - Compose — parsing, lowering, validation, and source origins.
- The engine — scheduling, records, recovery, and learning.
- Runtime nodes — the
Node/NodeResultcontract. - Engine design — contributor invariants and package ownership.
Develop and test
pip install -e ".[all,dev]"
PYTHONPATH=src python -m pytest
See Contributing for the issue/branch/PR workflow and Installation for local environment setup.
License
Apache 2.0 — see LICENSE.
Metadata
Release files for yian 0.0.3
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | |
|---|---|---|---|---|
| yian-0.0.3.tar.gz | 683.2 kB | With provenance | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| yian-0.0.3-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 1.4 MB
Release files / yian-0.0.3.tar.gz
| Download URL | yian-0.0.3.tar.gz |
|---|---|
| Size | 683.2 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
9cf0539f03942fa116d02dc9f0a152c032321c1a15742e724fcca31332422a52
|
|
BLAKE2b-256 checksum How to use checksums |
f25a209dee6bcda60ba878e12989bd1ac646e28e03bbef03c5570dc75700049c
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 9, 2026.
Transparency logRelease files / yian-0.0.3-py3-none-any.whl
| Download URL | yian-0.0.3-py3-none-any.whl |
|---|---|
| Size | 753.2 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
c8f9f46f19b5532437b4f302d145c71fd24b7e08cfdaf752079b3714edf70734
|
|
BLAKE2b-256 checksum How to use checksums |
b2070d6a99b878a4c1a4347b5f16eafda535444fe7e03c38e05ed7982bebbb08
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 9, 2026.
Transparency logSecurity for yian 0.0.3
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
Full provenance
All 2 files in this release have been uploaded with provenance metadata.
Attestation for yian-0.0.3.tar.gz
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 9, 2026.
Transparency logA note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.