zymi-core
Compile what your agent figured out into pipelines that run without it — declarative, event-sourced, approval-gated, callable from any MCP host.
Pronounced zoomi — like dog zoomies.
---
## Why zymi-core?
You ask your coding agent to add a VPN user, deploy a service, or clean up a server. It figures it out — after some trial and error. A week later you ask again, and it figures it out again, slightly differently. Then the agent is rate-limited, offline, or simply not there, and nobody remembers the steps.
zymi is for the moment you notice that. The procedure the agent converged on gets **compiled** into a YAML pipeline — most steps are plain deterministic tool calls, an LLM appears only where judgment is actually needed — and from then on:
- **It runs without the agent.** `zymi run add_vpn_user` from any terminal. Tool-only pipelines need no model and no API key at all, and missing inputs are asked interactively.
- **Your agent still uses it** — as one MCP tool, via `zymi mcp serve`, instead of re-deriving the procedure every time.
- **Dangerous steps wait for a human.** Steps emit *intentions* (shell, file write, HTTP) that pass policy, contracts and optional approval before anything happens. Over MCP the approval is a native approve/deny form in the calling agent's UI.
- **Every run is on the record.** Each state change is an immutable, hash-chained event: replay it, fork-resume it from any step, browse it in a TUI, or let the agent read its own run trace to explain a failure.
zymi is deliberately *not* an autonomous agent, an IDE plugin or a chat UI. It is the governed, reproducible layer *underneath* them: the place where the behaviour you want to keep stops being emergent.
---
## Two minutes: a personal pipeline library
```bash
uv tool install zymi-core # one-time; puts `zymi` on PATH globally
zymi init --home # your library at ~/.zymi
zymi ls # from any directory
zymi run hello # asks for its input, runs, no LLM involved
```
> Don't have `uv`? `curl -LsSf https://astral.sh/uv/install.sh | sh` (macOS/Linux) or `irm https://astral.sh/uv/install.ps1 | iex` (Windows).
`~/.zymi` is an ordinary zymi project that every command falls back to when you're not inside another one ([ADR-0044](adr/0044-home-project-and-named-providers.md)). Add your own pipeline — say, a site check:
```yaml
# ~/.zymi/tools/http_status.yml
name: http_status
description: "HTTP status code of a URL"
parameters:
type: object
properties:
url: { type: string }
required: [url]
implementation:
kind: shell
command_template: "curl -sS -o /dev/null -w '%{http_code}' ${args.url}"
```
```yaml
# ~/.zymi/pipelines/site_check.yml
name: site_check
description: "Is the site up?"
inputs:
- name: url
required: true
description: "Full URL, e.g. https://example.com"
steps:
- id: status
tool: http_status
args: { url: "${inputs.url}" }
output:
step: status
```
Allow the command in `~/.zymi/project.yml` (`policy.allow: ["curl *"]` — anything unlisted asks for approval first), then `zymi run site_check`. The run, its arguments and its output are now in `~/.zymi/.zymi/events.db`; `zymi observe` shows them.
**Hand it to your agent.** Point any MCP host at the library and every pipeline with an `expose.mcp:` block becomes a tool:
```json
{ "command": "zymi", "args": ["mcp", "serve", "--dir", "/Users/you/.zymi"] }
```
**Add a model only where it earns its place.** Declare endpoints once per machine in `~/.zymi/providers.yml` and reference them by name from any project — `llm: neuraldeep`, or `llm: { use: neuraldeep, model: other }`. Keys live in `~/.zymi/.env`. A provider that can't be resolved only disables agent steps; the tool-only pipelines keep working.
Full walkthrough in [docs/getting-started.md](docs/getting-started.md).
---
## What's in the box
### Pipelines — DAGs of agent, tool and ask steps
A pipeline is a list of steps with `depends_on:` edges; independent steps run in parallel. Each step is one of:
- a **deterministic tool step** ([ADR-0024](adr/0024-deterministic-tool-steps.md)) — direct dispatch with templated args, no LLM hop;
- an **agent step** — an LLM ReAct loop with a tool allowlist;
- an **ask step** ([ADR-0042](adr/0042-mcp-sampling-ask-step.md)) — the run parks and asks *whoever called it* (a human at the terminal under `zymi run`, the connected agent under `zymi mcp serve`), then resumes with the answer. No second model to configure.
```yaml
steps:
- id: recon # deterministic — no LLM
tool: disk_report
args: { host: "${inputs.host}" }
- id: analyse # LLM, only for the judgment call
agent: sysadmin
task: "What is eating the disk? ${steps.recon.output}"
depends_on: [recon]
- id: confirm # the caller answers
ask: "Clean up as proposed?\n${steps.analyse.output}"
depends_on: [analyse]
```
**Branches** ([ADR-0028](adr/0028-conditional-dag-edges.md)) — a step can carry `when:`; skipped branches cascade to descendants and land in the trace as `StepSkipped` events. One pipeline can serve several actions:
```yaml
- id: add_client
tool: vpn_add_client
args: { email: "${inputs.email}" }
depends_on: [route]
when: "${inputs.action} == 'add'"
```
Schema, examples, gotchas → [docs/pipelines.md](docs/pipelines.md).
### Tools — four kinds, one catalogue
- **Declarative shell / HTTP** in `tools/.yml` — no code.
- **Python `@tool`** in `tools/.py` — sync or async, signature → JSON Schema, auto-discovered; runs in the project's own `.venv` ([ADR-0032](adr/0032-install-ux-fetch.md)).
- **MCP servers** — one `mcp_servers:` entry gives N tools, namespaced `mcp____` ([ADR-0023](adr/0023-mcp-client-integration.md)).
- **Builtins** — `read_file`, `write_file`, `write_memory`, `execute_shell_command`, `spawn_sub_agent`.
All four emit the same `ToolCallRequested` / `ToolCallCompleted` events. → [docs/tools.md](docs/tools.md)
### zymi as an MCP server
`zymi mcp serve` exposes pipelines as MCP tools over stdio to Claude Code, Claude Desktop, Cursor, or any framework with an MCP adapter ([ADR-0033](adr/0033-mcp-server-pipelines-as-tools.md)). Exposure is opt-in per pipeline:
```yaml
expose:
mcp:
name: vpn_provision
description: "Provision a VPN client: preflight, then an approval-gated add."
mode: sync # or async — the caller task-augments (SEP-1686)
```
- **Approvals render in the caller's UI** — a gated step sends `elicitation/create` back through the live `tools/call`; in Claude Code that's an approve/deny form. Clients without elicitation fail closed.
- **`ask:` steps borrow the caller's brain** — on a task-augmented call the task goes `input_required` with `{ prompt, resume_token }`, the agent answers via `zymi/reasoning/resume`. The answer is recorded, so replay is byte-identical.
- **The agent can debug its own runs** — `--expose-observability` adds read-only `zymi.runs.list` / `.get` / `.events` / `.step_io` ([ADR-0034](adr/0034-mcp-observability-tools.md)).
Honest limits: approvals inside *async* tasks wait on host adoption and time out (sync calls are fully interactive); cancellation is best-effort; arguments cross the boundary as strings; `mcp serve` is Unix-only for now.
### Approvals — event-sourced, restart-safe
`requires_approval: true` on a tool publishes `ApprovalRequested`; a channel routes the human decision back. Channels: `terminal`, `http`, `telegram`, `mcp_elicitation` ([ADR-0022](adr/0022-event-sourced-approvals.md)). Resolution: pipeline override → project default → fail-closed. A crash mid-approval is repaired on restart. → [docs/approvals.md](docs/approvals.md)
### Replay, resume, observe
```bash
zymi runs # pipeline runs
zymi events --stream # every event of one run
zymi verify # hash-chain integrity, with its denominator
zymi observe # TUI: runs / DAG / events, live
zymi resume --from-step # fork-resume; upstream steps stay frozen
```
→ [docs/events-and-replay.md](docs/events-and-replay.md)
### Long-running services
`zymi serve` reacts to events from declarative connectors — `http_inbound`, `http_poll`, `cron`, `file_read`, `stdin` — and answers through outputs (`http_post`, `file_append`, `stdout`). That's enough for a full chat bot in YAML: `zymi init --example telegram` scaffolds one with approvals over Telegram buttons. SQLite by default; one `store: postgres://…` line for multi-process serving against shared state. → [docs/connectors.md](docs/connectors.md) · [docs/store-backends.md](docs/store-backends.md)
### Context window management
An agent's context is reconstructed from the event log each iteration, not accumulated in a buffer: older observations are masked in place, and only when the budget is still tight does one summarisation call compact the oldest batch ([ADR-0016](adr/0016-context-window-management.md)). → [docs/context.md](docs/context.md)
---
## Python embedding
With `zymi-core` in a project's venv (`uv add zymi-core`), the same wheel exposes `Runtime`, `Event`, `EventBus`, `EventStore`, `ToolRegistry` and the `@tool` decorator:
```python
from zymi import Runtime
rt = Runtime.for_project(".", approval="terminal")
result = rt.run_pipeline("site_check", {"url": "https://example.com"})
print(result.success, result.final_output)
```
Cross-process patterns (a web app driving `zymi serve` over the shared store) → [docs/python-api.md](docs/python-api.md).
---
## CLI cheatsheet
```bash
zymi init [--home | --example telegram] # scaffold a project, or the ~/.zymi library
zymi ls # what can I run (falls back to ~/.zymi)
zymi run [-i key=value …] # one-shot run; asks for missing inputs on a TTY
zymi fetch # uv sync — build ./.venv for @tool deps
zymi serve | --all # long-running, event-driven
zymi pipelines # step-level view of every pipeline
zymi runs · zymi events · zymi verify · zymi observe · zymi resume
zymi mcp serve [--expose-observability] # pipelines as MCP tools
zymi mcp probe -- [args …] # smoke a third-party MCP server
zymi schema {project|agent|pipeline|tool|--all}
```
Commands use `--dir`, else the current directory if it is a project, else `~/.zymi`. Full reference → [docs/cli.md](docs/cli.md).
---
## Documentation
- [Getting started](docs/getting-started.md) · [CLI reference](docs/cli.md) · [Project YAML](docs/project-yaml.md)
- [Pipelines](docs/pipelines.md) · [Tools](docs/tools.md) · [Agents](docs/agents.md) · [Approvals](docs/approvals.md)
- [Connectors](docs/connectors.md) · [Store backends](docs/store-backends.md) · [Events and replay](docs/events-and-replay.md) · [Python API](docs/python-api.md)
- [zymi-skill](https://github.com/metravod/zymi-skill) — an Agent Skill that teaches your coding assistant to write zymi-native YAML
- [llms.txt](llms.txt) — flat index of these docs for LLMs and RAG tools
- [`adr/`](adr/) — one short record per architectural decision
---
## Contributing & License
zymi-core is built in Rust and shipped via PyPI. Bug reports, examples and PRs welcome — see [CONTRIBUTING.md](CONTRIBUTING.md) for the dev loop, test matrix and ADR workflow.
MIT — see [LICENSE](LICENSE).