Skip to main content

Event-sourced agent engine — CLI and Python bindings for auditable AI workflows

Project description

zymi

zymi-core

dbt for AI workflows — declarative agents, deterministic replay, human-in-the-loop, all from YAML.

Pronounced zoomi — like dog zoomies.

PyPI Python versions CI License: MIT llms.txt


Why zymi-core?

Most agent frameworks are imperative Python: write a script that makes LLM calls, persist some messages, hope you logged enough to debug a bad run later.

zymi-core inverts that:

  • Declarative, like dbt. Agents, pipelines, tools, connectors, approvals — all YAML. The engine validates and runs them as a DAG.
  • Event-sourced. Every state change is an immutable, hash-chained event. Runs are replayable, resumable, and auditable without extra logging.
  • Boundary-safe. Agents emit intentions (run shell, write file, call HTTP) that pass through policy + contracts + optional human approval before execution. The risky thing doesn't happen until someone says yes.

Bring a useful agent online in minutes without writing code. A year later, still answer exactly what this agent did on any past run.

📚 AI-assistant friendly out of the box. Every zymi init scaffold drops an AGENTS.md into the user's project — vocabulary, file map, task→file routing. Claude Code / Cursor / Aider read it automatically; the YAML they help you write gets noticeably more correct. For agents that build zymi projects (rather than work inside one), install zymi-skill into your assistant — opinionated Agent Skill with activation rules + progressive disclosure references, so the assistant produces zymi-native YAML instead of generic agent advice.


Run a Telegram agent in two minutes

This is the canonical demo — a real chat bot, wired declaratively.

uv tool install zymi-core    # one-time; puts `zymi` on PATH globally

mkdir telegram-agent && cd telegram-agent
zymi init --example telegram

# 1. Create a bot via @BotFather in Telegram; copy the token.
# 2. Fill .env:
cp .env.example .env         # edit TELEGRAM_BOT_TOKEN + OPENAI_API_KEY
# 3. Open project.yml, replace "your_username_here" with your actual
#    Telegram username (no @). Keeps strangers out of the bot.

zymi fetch                   # uv sync — builds ./.venv from pyproject.toml
zymi serve chat              # .env is auto-loaded; pipeline runs in ./.venv

Why uv tool install and zymi fetch? zymi is a global CLI; your project keeps its own pyproject.toml + .venv for any Python deps your @tool files import. zymi fetch wraps uv sync to build that venv, and pipeline-run commands transparently re-exec inside it (ADR-0032). Don't have uv yet? curl -LsSf https://astral.sh/uv/install.sh | sh (macOS/Linux) or irm https://astral.sh/uv/install.ps1 | iex (Windows).

Message the bot. It replies in seconds. Every inbound message, LLM call, approval decision, and outbound reply is in .zymi/events.db; watch live with zymi observe.

The whole wiring — Telegram I/O, two-step DAG (assistant drafts, reviewer polishes), declarative + Python tools, approval channel — lives in YAML. The scaffold also drops AGENTS.md so an AI coding assistant can extend the project safely. Concrete demo of:

  • http_poll connector — long-polls Telegram's getUpdates, no HTTPS / ngrok needed
  • http_post output — sends each ResponseReady back to the user
  • Telegram approval channel — DMs admins with ✅ / ❌ buttons when the agent calls broadcast (requires_approval: true)
  • Python @tool auto-discovery — drop tools/get_weather.py (sync) or tools/translate.py (async) and the agent picks them up

Ask the bot to "announce that we're closing at 5pm" — the agent calls broadcast, you get a DM with approve/deny buttons, nothing goes out until you click. End-to-end audit trail in zymi events.

Full setup in docs/getting-started.md. Connector deep-dive in docs/connectors.md. Approvals in docs/approvals.md.


What's in the box

Pipelines — DAGs, agent steps, deterministic tool steps

A pipeline is a list of steps with depends_on: edges. Independent steps run in parallel. Each step is either an agent step (LLM ReAct loop) or a deterministic tool step (ADR-0024) — direct dispatch with templated args, no LLM hop, but the same event envelope.

Mix them freely:

steps:
  - id: fetch                            # deterministic — no LLM
    tool: http_get
    args: { url: "https://api.example.com/${inputs.id}" }

  - id: classify                         # LLM
    agent: classifier
    task: "${steps.fetch.output}"
    depends_on: [fetch]

Conditional branches (ADR-0028) — a step can gate on an upstream output. Skipped branches cascade to descendants and emit StepSkipped events, so routing decisions land in the trace, not in the LLM's head:

- id: router
  agent: concierge
  task: "Pick: ${inputs.q}"   # calls route('short' | 'rag')

- id: rag_lookup
  tool: pinecone_query
  args: { query: "${inputs.q}" }
  depends_on: [router]
  when: "${steps.router.output} == 'rag'"

Schema, examples, gotchas → docs/pipelines.md.

Tools — four kinds, one catalogue

All four kinds emit identical ToolCallRequested / ToolCallCompleted events; the agent doesn't know which catalogue a tool came from.

  • Declarative HTTP / shell in tools/<name>.yml — no code.
  • Python @tool in tools/<name>.py — sync or async, signature → JSON Schema, auto-discovered.
  • MCP servers — one mcp_servers: entry gives N tools, namespaced mcp__<server>__<tool> (ADR-0023).
  • Builtinsread_file, write_file, write_memory, execute_shell_command, spawn_sub_agent.
# tools/get_weather.py — auto-discovered at runtime startup.
from zymi import tool

@tool
def get_weather(city: str) -> str:
    """Return the current weather for a city."""
    return f"sunny in {city}"

Schema and the four kinds in detail → docs/tools.md.

Connectors and outputs

Inbound: http_inbound (webhook), http_poll (long-poll), cron, file_read, stdin. Outbound: http_post, file_append, stdout.

All declarative, all emit events. Filter recipes (docs/connectors.md):

# GitHub — only react to PR opens
filter:
  "$.action":              { equals: "opened" }
  "$.pull_request.draft":  { equals: false }

429 + Retry-After handled automatically. Cursors persist across restarts. Multi-process zymi serve against shared Postgres sees one cursor table, no double-fire.

Approvals — event-sourced, restart-safe

Tools with requires_approval: true publish ApprovalRequested on the bus; an approval channel routes a human decision back. Three channels in the box: terminal, http, telegram (ADR-0022).

Resolution order: pipeline override → project default → fail-closed. A zymi serve crash mid-approval is repaired on next start: in-flight requests are redelivered to live channels; expired ones are sealed with ApprovalDenied{reason: restart_timeout}.

Full schemas + telegram setup → docs/approvals.md.

Replay, resume, observe

zymi runs                                   # all pipeline runs
zymi events --stream pipeline-chat-abc      # every event in one run
zymi verify --stream pipeline-chat-abc      # hash-chain integrity check
zymi observe                                # 3-panel TUI: runs / DAG / events live

# Fork-resume from a chosen step. Upstream steps are frozen; the fork
# step + DAG-descendants re-run against current configs on disk.
zymi resume pipeline-chat-abc --from-step polish
zymi resume pipeline-chat-abc --from-step polish --dry-run

Useful when you're iterating on a prompt: don't re-burn the expensive early steps every time you tweak the later ones. → docs/events-and-replay.md.

Store backends

SQLite (default, zero-config) for single-process / dev. Postgres for multi-process zymi serve against shared state — one store: postgres://… line in project.yml (ADR-0012). Same hash-chain semantics either way. → docs/store-backends.md.

Context window management

The agent's working context is reconstructed from the event log each iteration, not accumulated in a buffer. Older tool observations are masked in-place (~2× cost reduction, no extra LLM calls). When the budget still gets tight, hybrid compaction summarises the oldest masked batch with one fast LLM call. Tunable in runtime.context: — see docs/context.md for recommended chat / coding / evals profiles (ADR-0016).

JSON Schemas for configs

IDE autocomplete and LLM-assisted YAML come free:

zymi schema project          # draft-07 JSON Schema for project.yml
zymi schema --all

Python embedding

When zymi-core is in your project's venv (uv add zymi-core in a uv project, or pip install zymi-core in a traditional venv), the same wheel exposes a Python API: Runtime, Event, EventBus, EventStore, Subscription, ToolRegistry, plus the @tool decorator.

from zymi import Runtime

rt = Runtime.for_project(".", approval="terminal")
result = rt.run_pipeline("chat", {"message": "hello"})
print(result.success, result.final_output)

rt.bus() and rt.store() share Arc-handles with the runtime — Python subscribers see exactly what the handler publishes.

Cross-process pattern (Django view / Celery task drives zymi serve over the shared store):

import uuid
from zymi import Event, EventBus, EventStore

store = EventStore(".zymi/events.db")
bus = EventBus(store)

corr = str(uuid.uuid4())
sub = bus.subscribe_correlation(corr)

ev = Event(
    stream_id=f"web-{corr}",
    kind={"type": "PipelineRequested",
          "data": {"pipeline": "research", "inputs": {"topic": "rust event sourcing"}}},
    source="django",
)
ev.with_correlation(corr)
bus.publish(ev)

result = sub.recv(timeout_secs=300)

Full surface → docs/python-api.md.


CLI cheatsheet

zymi init [--example telegram]              # scaffold a project (writes pyproject.toml too)
zymi fetch                                  # uv sync — build ./.venv from pyproject.toml
zymi run <pipeline> -i key=value           # one-shot run (re-execs in ./.venv if present)
zymi serve <pipeline>                       # long-running: react to PipelineRequested

zymi runs                                   # list pipeline runs
zymi events [--stream ID] [--kind TAG]      # query event log
zymi verify [--stream ID]                   # hash-chain integrity check
zymi observe [--run ID]                     # interactive TUI
zymi resume <run-id> --from-step <id>       # fork-resume

zymi mcp probe <name> -- <cmd> [args ]     # smoke an MCP server
zymi schema {project|agent|pipeline|tool|--all}

Full reference → docs/cli.md.


Documentation


Contributing & License

zymi-core is built in Rust and shipped via PyPI. Bug reports, examples, PRs welcome — see CONTRIBUTING.md for the dev loop, test matrix, ADR workflow, and how to build from source.

MIT — see LICENSE.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

zymi_core-0.6.11.tar.gz (957.8 kB view details)

Uploaded Source

Built Distributions

If you're not sure about the file name format, learn more about wheel file names.

zymi_core-0.6.11-cp39-abi3-win_amd64.whl (6.2 MB view details)

Uploaded CPython 3.9+Windows x86-64

zymi_core-0.6.11-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (5.9 MB view details)

Uploaded CPython 3.9+manylinux: glibc 2.17+ x86-64

zymi_core-0.6.11-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl (5.5 MB view details)

Uploaded CPython 3.9+manylinux: glibc 2.17+ ARM64

zymi_core-0.6.11-cp39-abi3-macosx_11_0_arm64.whl (5.3 MB view details)

Uploaded CPython 3.9+macOS 11.0+ ARM64

zymi_core-0.6.11-cp39-abi3-macosx_10_12_x86_64.whl (5.7 MB view details)

Uploaded CPython 3.9+macOS 10.12+ x86-64

File details

Details for the file zymi_core-0.6.11.tar.gz.

File metadata

  • Download URL: zymi_core-0.6.11.tar.gz
  • Upload date:
  • Size: 957.8 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for zymi_core-0.6.11.tar.gz
Algorithm Hash digest
SHA256 4846a24c9bffe8d9db3892bd79c32e0cb024b9a0a80624400a8c081d118b83fa
MD5 6d80adc7d0262b4d1d6e40d022867fad
BLAKE2b-256 5aaf0a161a72a0e873d81024e7cf817a4074727b6ef74519217e69c0162fd8e6

See more details on using hashes here.

Provenance

The following attestation bundles were made for zymi_core-0.6.11.tar.gz:

Publisher: release.yml on metravod/zymi-core

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file zymi_core-0.6.11-cp39-abi3-win_amd64.whl.

File metadata

  • Download URL: zymi_core-0.6.11-cp39-abi3-win_amd64.whl
  • Upload date:
  • Size: 6.2 MB
  • Tags: CPython 3.9+, Windows x86-64
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for zymi_core-0.6.11-cp39-abi3-win_amd64.whl
Algorithm Hash digest
SHA256 10b8fec08465587c10f4da74271ce25b273f5540f829f0c69763712218d0b3e9
MD5 b7ea91609fb5b237db15aee13da86028
BLAKE2b-256 b68693fd2adce003074bef37a288247cadc98453803a4abad5e1c0585c228932

See more details on using hashes here.

Provenance

The following attestation bundles were made for zymi_core-0.6.11-cp39-abi3-win_amd64.whl:

Publisher: release.yml on metravod/zymi-core

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file zymi_core-0.6.11-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.

File metadata

File hashes

Hashes for zymi_core-0.6.11-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
Algorithm Hash digest
SHA256 0a6ad013ed5516f31a9c8ff197de60a44968c68ba2dd8f15c572ce11c9fb3a25
MD5 840a705ff816c346cda2380cc2a9b60f
BLAKE2b-256 62e6d1a6d9e0c73ea66c39bb02826d8e09a00b1f65ea5cb65d2a940a2baefde9

See more details on using hashes here.

Provenance

The following attestation bundles were made for zymi_core-0.6.11-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl:

Publisher: release.yml on metravod/zymi-core

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file zymi_core-0.6.11-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl.

File metadata

File hashes

Hashes for zymi_core-0.6.11-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl
Algorithm Hash digest
SHA256 76c54d216a454f0887e562edfa4f0f231c1d7f3ef2c878e9358bfc36e1b887d5
MD5 015bf983b10cc6c9de7ea600af60bd2c
BLAKE2b-256 3e02fb933e1b6bfebdd3fc45c56c58fc0fafd6d454327f6ac4c8a1bed3e708ca

See more details on using hashes here.

Provenance

The following attestation bundles were made for zymi_core-0.6.11-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl:

Publisher: release.yml on metravod/zymi-core

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file zymi_core-0.6.11-cp39-abi3-macosx_11_0_arm64.whl.

File metadata

File hashes

Hashes for zymi_core-0.6.11-cp39-abi3-macosx_11_0_arm64.whl
Algorithm Hash digest
SHA256 4a98df0086c2789c0504e5a785636c3e698c513f00ac9505e7e50947952205b6
MD5 ef33d075d14de69d570299df37710bf1
BLAKE2b-256 e2dda225031c3f49c6c56c1e0c3877b13c3bdfaddfce1340f2fa00cf0f8c63f7

See more details on using hashes here.

Provenance

The following attestation bundles were made for zymi_core-0.6.11-cp39-abi3-macosx_11_0_arm64.whl:

Publisher: release.yml on metravod/zymi-core

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file zymi_core-0.6.11-cp39-abi3-macosx_10_12_x86_64.whl.

File metadata

File hashes

Hashes for zymi_core-0.6.11-cp39-abi3-macosx_10_12_x86_64.whl
Algorithm Hash digest
SHA256 b3bf1a9b29172aed6e569c0989de16c54445ee5f9e7a4286e9ad08afdd6806c5
MD5 c486bf56587f8fd40afed37ca43ff47f
BLAKE2b-256 cc5d1d95c74856342c18c1ce0d6731d15ea6b869268585da98ce4b5dbd7cfec7

See more details on using hashes here.

Provenance

The following attestation bundles were made for zymi_core-0.6.11-cp39-abi3-macosx_10_12_x86_64.whl:

Publisher: release.yml on metravod/zymi-core

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page