zooid.yaml
The zooid.yaml file is the declarative manifest for your Zooid daemon. It defines the runtime that wraps each agent’s ACP shim, the transports that connect the workforce to the outside world, and the agents themselves.
This page orients you with annotated examples. For the authoritative field-by-field reference, see the generated API pages — they are emitted directly from the TypeScript types the daemon parses, so they cannot drift from the implementation.
Shape at a glance
runtime: podman # local | docker | podmanworkstation: my-macbook # optional; names this daemon and scopes its agents
container: # optional workforce-wide container defaults (no env) image: ghcr.io/zooid-ai/agent-opencode:latest
transports: # at least one entry, keyed by an operator-chosen name matrix: homeserver: http://localhost:8448
agents: # at least one entry, keyed by the agent's name assistant: acp: preset: opencode matrix: rooms: - '#general'See ZooidConfig for every top-level field.
Workstation
The optional top-level workstation: names this daemon — a short, lowercase, hyphen-separated slug (my-macbook, ec2-prod). Setting it turns the daemon into a named workstation: the Matrix transport derives its sender_localpart and an exclusive @{workstation}.* user namespace from the slug, so several daemons can share one homeserver without their agent IDs colliding.
workstation: my-macbook# agents become @my-macbook.assistant:your-server, etc.zooid init scaffolds workstation: dev, so agents start out namespaced (@dev.*). The field is optional — omit it for the shared @.* namespace — but each daemon sharing a homeserver needs its own slug.
Transports
The transports: block lets the daemon expose multiple endpoints or connect to multiple servers at once. Each entry is discriminated by type.
Matrix transport
The Matrix transport runs as an Application Service and impersonates each agent’s user via ?user_id=. Most fields (type, sender_localpart, user_namespace, as_token, hs_token) default sensibly:
transports: matrix: homeserver: http://localhost:8448mode: — push or pull
mode: chooses which side opens the connection (see Workstations for the concept):
appservice(default) — push. The homeserver delivers events to the daemon, so the daemon needs an address the homeserver can reach. Set eitherport(default9099, when the daemon and homeserver share a host or network) oradvertise_url(a routable address, when the daemon lives on a separate machine, pod, or cluster). The two are mutually exclusive.client— pull. The daemon fetches events itself, so it needs no inbound address —port/advertise_urldon’t apply. This is the mode for a laptop or any daemon behind a firewall.
# push, co-located: homeserver reaches the daemon on a shared networktransports: matrix: homeserver: http://localhost:8448 mode: appservice port: 9099
# pull, remote: laptop daemon against a homeserver elsewheretransports: matrix: homeserver: https://your-homeserver.example mode: clientFull field list: MatrixTransportConfig.
HTTP transport
A plain HTTP API transport for webhook-driven setups:
transports: my-http: type: http port: 3000Full field list: HttpTransportConfig.
Agents
Each entry under agents: declares one agent. transport, matrix_user_id, and workdir default from the agent key when omitted:
agents: assistant: acp: preset: opencode matrix: user_id: '@assistant' # shorthand for @assistant:your-server display_name: 'Code Assistant' # optional: human-readable name rooms: - '#general' # shorthand for #general:your-serverFull field list: AgentConfig.
Identifiers and Implicit Servers
Inside zooid.yaml, you can omit the homeserver suffix from user IDs and room aliases. The daemon automatically infers the correct server from your transport’s user_namespace. For example, @docs resolves to @docs:localhost (or whatever your configured server is). This keeps the YAML terse and reduces errors.
When you set a workstation:, an agent with no explicit user_id is automatically scoped to it — architect becomes @my-macbook.architect, landing inside the exclusive namespace. Set user_id only to override the derived name:
workstation: my-macbookagents: architect: # → @my-macbook.architect:your-server acp: { preset: claude } reviewer: acp: { preset: claude } matrix: user_id: '@my-macbook.qa' # override the auto-derived @my-macbook.reviewerDisplay Names
You can provide an optional display_name field for each agent under the matrix: block. This sets the human-readable name written to the agent’s Matrix profile, causing the agent to render nicely in the Zooid web client (“Code Assistant” instead of @assistant). If omitted, it falls back to the user ID’s localpart.
The acp: block
acp: is an XOR — either a registered preset or an explicit command:
acp: preset: claude # claude, codex, opencode, cline, kiro, geminiacp: command: node args: ['./my-custom-acp-shim.js']Full shape: AcpAgentSpec.
Environment variables
Zooid expands ${VAR} references in your YAML against the daemon’s process.env (and an adjacent .env). For security, env vars are never forwarded into agent containers automatically — list them explicitly under container.env:
agents: assistant: container: image: 'ghcr.io/zooid-ai/agent-claude-code:latest' env: ANTHROPIC_API_KEY: ${ANTHROPIC_API_KEY}ZOOID_* keys and ${ZOOID_*} references are rejected. See ContainerConfig for the full field list.
Mounts
The agent’s workdir is auto-mounted into its container. To expose additional host paths (e.g. a source repo the agent needs to read), declare them under container.mounts:
agents: assistant: container: mounts: - source: /host/path/to/repo target: /workspace/repo readonly: trueFull shape: AcpMount.