venom
Main product. A terminal-native orchestrator for existing AI coding agents (Claude Code, Codex app-server, generic ACP, opencode); think of it as a terminal-native t3code. It is headless and event-sourced: a pure decider, then a transactional outbox, then an effect worker. It handles workspaces (folders the user adds, holding 0..n repos), agents (one run of a harness on a task) and their turns; fan-out; a git worktree per agent per repo; hidden-ref checkpoints; approval routing and an inbox; scheduled agent jobs; and an MCP server so agents can orchestrate agents. It ships its own TUI and places agents in venomx panes when venomx is available.
Part of Venom. Status: alpha; interfaces may still change. Documentation: venom.tools.
Build from source with the Zig version in .zigversion (see Build).
Licensed under the MIT License. To report a vulnerability, see SECURITY.md.
Build
Zig 0.17 (see .zigversion). zig build fetches venom-std and venom-term at the commits pinned in build.zig.zon; to build against local checkouts instead, add --fork=../venom-std --fork=../venom-term to any zig build command:
zig build # zig-out/bin/venom
zig build test # unit tests + agent-contract conformance
venom serve # start the daemon
venom doctor --json # health report
venom mcp # MCP server over stdio
venom --help # every verb, generated from src/verbs.zon
End-to-end demo: 3 agents, 3 worktrees, approved from the CLI
scripts/demo.py runs the demo headless. It makes a temp workspace with three git repos (alpha, beta, gamma), starts its own venom serve with every XDG directory under the temp root (your daemon, config and agents are untouched), and then, all through the CLI:
venom workspace add --path=<ws>venom agent spawn --workspace=<ws> --repos='["<repo>"]' --harness=<h> --isolation=worktree --task=…three times: claude-code onalpha, codex onbeta, claude-code ongamma. Each agent gets its own worktree (<repo>-<agent-id>on branchvenom/<agent-id>).- Each agent asks to run a command and turns
needs_you;venom inbox listshows three approvals. venom inbox answer --agent=<id> --answer=allow_oncefor each one.- All three settle; the script checks each ran inside its own git worktree.
zig build test-demo # the demo against fake harnesses (also part of zig build test)
python3 scripts/demo.py # the same, using zig-out/bin/venom
python3 scripts/demo.py --real # real claude and codex from PATH (signed in; spends a few model turns)
python3 scripts/demo.py --keep # keep the temp root (daemon log, worktrees) for a look
By default the harnesses are src/testdata/fake_mcp_harness.py (modes claude-approve and codex-approve), set as harness.claude_code.path and harness.codex.path in the demo’s config.zon. They are driven by venom’s real claude-code and codex adapters: the claude-code approvals go through venom’s permission prompt server the way Claude Code’s do, and the codex ones through app-server’s item/commandExecution/requestApproval. zig build test runs this without vendor binaries or sign-ins.
With --real the config is left at its defaults (claude and codex from PATH), and each agent is asked to write a file outside its worktree, which both harnesses must ask about. The script allows whatever they ask (up to five times per agent) and fails if an agent settles without asking. Run it by hand; zig build test never runs it.
Chrome modes: own and host
ui.chrome (venom config set --key=ui.chrome --value=.auto) says where the TUI runs. venom never starts venomx.
.own: full-screen TUI on the terminal..host: a control pane in venomx (role = control), or.ownwhen no venomx daemon is reachable. In a venomx pane, that pane becomes the control pane. Outside venomx, venom makes sessionvenomwith the control pane (or finds it), and the terminal becomesvenomx attachon it. Started that way, the process outside venomx registers thevenomkey table before it becomesvenomx attach; the table has no expiry, and goes when the daemon stops. Started only inside a pane you opened, there may be no table, and the bar says so. On venomx, a control pane has a pane’s default rights: it can write only itself and the panes it creates. venom gives it those rights explicitly, so they hold even with venomx’sgrants.isolationoff. On venom, that outside process binds a principal to its process, with everything butadmin, and it holds no token. Shells and take-over are offered only in an agent’s read-only pane, not the control pane, until they are panes of their own..auto(default):.hostwhen a daemon is reachable, else.own.
venom’s bar is the bottom row of the area it draws in both modes; venomx’s own line is separate, and venom publishes no venom.* segments. In host mode:
⏎on an agent opens a sibling pane (role = agent,group = <agent>, kept on exit) runningvenom agent view <agent> --solo=true: that agent, no list, no bar. Closing it leaves the agent running.venom --agent=<id>,--inboxor--usagewith a control pane already running hands those to it.- Inbox items set
pane.attentionon the agent’s pane, or on the control pane while it has none, and clear when answered anywhere. - From any pane, after the leader,
v sopens settings (venom.settings),v dthe diff of the focused pane’s agent (venom.diff,venom agent diff --pane <p>),v ithe inbox,v uthe usage. The diff uses the control pane’s selected agent when that pane has focus, and says so when the pane has no agent.keys.prefixesin venomx’s config movesv.
zig build test-host # both modes, two agents, throwaway daemons; fails on a stale ../venomx build
zig build test-host -Dvenomx=<path> # the same with that venomx binary
venom in a venomx pane
A process in a venomx pane, or in a split of venom’s own TUI, is refused venom’s owner file, and an agent is refused venomx’s. A pane looks the same whether a person or an agent runs in it. So:
- Start
venomin a terminal outside venomx. With a venomx daemon up you get a control pane, with everything butadmin. Its agents keep running if that pane ends. - To save settings, stop the daemon or read the audit log, use a terminal outside venomx:
venom settings, orVENOM_TOKEN=@owner venom …. The settings and inbox popups (ctrl+a v s,v i) only read; save and answer in the control pane. - To use venom from a pane of your own, issue a token outside venomx, then set it in the pane:
Anything run from that shell can use the token. Anyone withVENOM_TOKEN=@owner venom grant issue --namespace=person --label=my-pane --json \ --grants='[{"right":"read","scope":"*"},{"right":"write","scope":"*"},{"right":"respond","scope":"*"},{"right":"files","scope":"*"},{"right":"spawn","scope":"*"}]' read -rs VENOM_TOKEN; export VENOM_TOKEN # in the pane: paste it at the silent prompt venomreadcan see a pane’s screen, so don’t print the token in a pane; if it did show, clear that pane’s screen and history. With nothing set,venomin a pane only reads. - A token you issued ends after
grants.max_token_ttl(24 hours by default) or whenvenom serverestarts. The TUI says so; issue another. A control pane has no token: its rights last as long as its process, across a restart ofvenom serve, and nothing in its environment can be stolen. Its agent views are bound the same way and lose their rights when it quits. These rights are that one process’s: a shell or take-over opened from an agent view isunknown, and only reads.
venomx isolation
venomx ships with grants.isolation off, like tmux: every process of yours may write and spawn anywhere in venomx. venom’s own agents stay capped: the agent panes venom splits are created with read and nothing else, and an agent venom starts outside venomx stays read-only there. venom creates its control pane with explicit grants too, so it can write only itself and its agent panes. A pane of your own that you start venom in keeps that pane’s rights. An agent that escapes its pane’s process tree, though, resolves to venomx’s unknown and gets those wide rights. venom doctor warns (venomx.isolation) when the venomx it drives has isolation off. Turn it on before running agents in venomx:
VENOMX_TOKEN=@owner venomx config set --key grants.isolation --value true && VENOMX_TOKEN=@owner venomx config reload
venom itself keeps isolation on (grants.isolation, the Permissions and grants settings section).
Durable log snapshots
The reactor checkpoints after 4096 commits since the last snapshot or 16 MiB of active log. The
events.log.snapshot v1 envelope contains version, commit, a JSON-encoded
state string, and a SHA-256 checksum covering the format domain, commit number
(little-endian u64), and state bytes. State maps are ordered {key,value} arrays;
lists are arrays and transcript events retain their log schema. All projection
fields are required; future incompatible changes need a snapshot version migration.
The snapshot retains transcripts, usage, inbox state, bindings, ID counters, and both pending and completed effects. Pending effects keep their original keys and at-least-once recovery behavior. Completed keys remain available for deduplication. Transcripts remain accessible through the existing agent views, indefinitely. Snapshot size and loading cost therefore scale with retained state/history; the active log and event-folding work are bounded by the checkpoint interval.
A temporary snapshot is written and fsynced, renamed atomically, and its directory
fsynced before the locked log is truncated and fsynced. Recovery skips any prefix
already covered by the snapshot and replays the suffix. Unpublished temporary
files are ignored. Corrupt or unsupported published snapshots fail closed.
Compaction failures do not turn an already committed command into a failure;
maintenance is retried on the next commit. doctor’s events.durable check reports
the snapshot commit, active log bytes, and snapshot-plus-log recovery milliseconds.