Skip to content

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 | podman
workstation: 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:8448

mode: — 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 either port (default 9099, when the daemon and homeserver share a host or network) or advertise_url (a routable address, when the daemon lives on a separate machine, pod, or cluster). The two are mutually exclusive.
  • clientpull. The daemon fetches events itself, so it needs no inbound address — port/advertise_url don’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 network
transports:
matrix:
homeserver: http://localhost:8448
mode: appservice
port: 9099
# pull, remote: laptop daemon against a homeserver elsewhere
transports:
matrix:
homeserver: https://your-homeserver.example
mode: client

Full field list: MatrixTransportConfig.

HTTP transport

A plain HTTP API transport for webhook-driven setups:

transports:
my-http:
type: http
port: 3000

Full 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-server

Full 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-macbook
agents:
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.reviewer

Display 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, gemini
acp:
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: true

Full shape: AcpMount.