Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Runtime internals

This page is the architecture-depth companion to the rest of the Agents section: how the runtime enforces per-agent permissions, scopes memory, and attributes logs. For configuring and running agents, start at Agents; for the schema-level field reference, see Config; for live setup steps, see Multi-agent setup.

Permissions model

Each agent’s effective SecurityPolicy is built by SecurityPolicy::for_agent(config, alias):

  1. Start from the agent’s risk profile ([risk_profiles.<profile>]).
  2. Set the boundary to the per-agent workspace dir (<install>/agents/<alias>/workspace/).
  3. Walk [agents.<alias>.workspace.access]:
    • Read → sibling’s workspace lands in the read-only allowlist.
    • Write → sibling’s workspace lands in the write-only allowlist.
    • ReadWrite → sibling’s workspace lands in the read-write allowlist.
  4. If [agents.<alias>.workspace.unrestricted_filesystem] is true, flip workspace_only off.

The read-only allowlist is honored by file_read and other read-side tools, including git_operations status, diff, log, branch, stash-list, and worktree-list commands. Worktree list returns only entries whose worktree path resolves and whose Git metadata can be validated through the applicable read grant. The read-write allowlist gates file_write, file_edit, write-classified git_operations commands (including worktree add/remove/prune), and the shell tool’s path-touching invocations. A write-only sibling workspace is not available to git_operations: Git must read repository metadata before any operation. Worktree targets and linked-worktree .git/gitdir/commondir metadata must be within the applicable grant. git_operations rejects symlinks anywhere in that metadata instead of following them, even when their eventual target would otherwise be allowed. Git invocations discard inherited GIT_* variables; configure commit identity in repository or global Git configuration rather than environment variables. Read-only commands suppress repository-configured executable integrations and mailmap identity resolution; their fixed log format also uses raw author fields, so those commands never read .mailmap or mailmap.file. They prohibit Git transport and disable implicit promisor-object fetches, so a read that needs an unavailable partial-clone object fails instead of launching repository-selected transport. Write commands retain ordinary Git hooks and filters when the operator grants read-write access, and apply the configured per-agent sandbox boundary. With [runtime] kind = "docker", write-classified git_operations commands are rejected before any write-classified Git command is launched because Git does not enter the runtime container; read-classified commands are unchanged, and the shell tool remains the in-container command path. In native runtime mode, a no-op sandbox therefore does not confine Git. Only the Landlock backend extends that sandbox to configured sibling roots; other sandbox backends can reject writes outside the agent workspace, mount the workspace read-only, omit the workspace, or hide global Git configuration. See Sandboxing for backend behavior. POSIX device files (/dev/null, /dev/zero, /dev/random, /dev/urandom) are always readable so shell idioms keep working without per-agent config.

SubAgent spawns enforce the rule that a child cannot escalate beyond its parent. The validator’s full axis list and the budget-sharing behavior are documented at Delegation → Permission inheritance.

Single-tool rounds

Single-tool rounds request at most one native tool call per provider response. The existing agent loop executes that call, records its result, and asks the provider for the next response within the same user turn. The model may narrate between calls, but narration is not required or generated by ZeroClaw.

This mode is off by default. In ZeroCode Config, set runtime_profiles.<profile>.single_tool_rounds to true and select that profile with agents.<alias>.runtime_profile. An unset value or false keeps batch behavior. The setting is independent of parallel_tools, which controls execution concurrency for an admitted batch rather than how many calls the provider may return.

Only supported provider routes activate the policy. If the selected route or any reachable fallback lacks support, ZeroClaw keeps batch behavior and displays a limitation notice once per turn. Under an active policy, a response containing multiple native or recovered tool calls fails before any of those calls execute or enter tool-exchange history. Usage from the rejected provider request still counts toward token and cost accounting. Streamed narration may already be visible; ZeroClaw does not replay a violating response.

Tool approval, receipts, cancellation, and iteration limits keep their existing behavior. Sequential calls can require more provider requests, increase latency, and reach max_tool_iterations sooner. To roll back, set single_tool_rounds to false or unset it in the selected runtime profile.

Memory model

Each agent has its own Arc<dyn Memory> instance. The factory (zeroclaw_memory::create_memory_for_agent) dispatches by backend kind:

  • SQLite / Postgres / Lucid: shared install-wide store. The agents table maps alias → UUID, and the memories table carries agent_id referencing that UUID. The factory wraps the inner backend in AgentScopedMemory, which stamps the bound agent’s UUID on every store via store_with_agent and filters every recall via recall_for_agents with the resolved allowlist. Structured grants can further filter each sibling to exact category names; the bound agent always sees all of its own categories.
  • Markdown: per-agent dir. Each agent’s MarkdownMemory writes to <install>/agents/<alias>/workspace/MEMORY.md and memory/YYYY-MM-DD.md. Because Markdown does not preserve per-row custom categories, the config validator and factory reject category-scoped grants for this backend; only unrestricted sibling grants are accepted. The wrapper still filters any direct peer construction defensively.
  • Qdrant: shared collection, payload-keyed. The agent_id payload field is the per-agent attribution; recall_for_agents over-fetches and post-filters by payload.
  • None: no-op stub. The wrapper still exists so the runtime path is uniform.

Cross-backend cross-agent memory is not supported: the schema validator at config load rejects read_memory_from entries that point at a sibling on a different backend. Category grants are exact-match and non-transitive; an unknown category matches no rows.

Rename and delete lifecycle

Use the gateway dashboard’s agent controls or the dedicated zeroclaw agents CLI for rename and delete. In the standard build with gateway and agent-runtime enabled, both surfaces run the reference and owned-state cascades; directly removing or re-keying agents.<alias> in TOML or through a generic config setter does not. A reduced-feature CLI still updates config references but warns that owned state was not cascaded, so use a build with both features enabled for lifecycle operations.

Both operations make the config change durable before running owned-state side effects. Rename rewrites config references first, then moves the default per-alias workspace and re-points memory, cron, ACP, and session state. Delete first refuses hard references and live ACP sessions, then removes the config entry and soft references before attempting workspace archival, owned-state export and cleanup, and session-attribution clearing.

The post-persist side effects are best-effort and report surfaced failures, but archive-file write failures may appear only in gateway logs. Rename warnings call for retrying the same gateway API rename to converge residue left under the old alias. After deletion, verify the archive contents and logs before relying on the archive for recovery. Automated restore is not supported.

See Multi-agent setup walkthrough for the current controls, blockers, archive layout, and operator checks.

Not supported today

  1. Cross-backend cross-agent memory access (e.g. SQLite agent reading a Postgres agent’s rows).
  2. Automated restore from an agent deletion archive.
  3. Per-agent secret namespacing: there is a single workspace-wide SecretStore.
  4. Lucid wire-format extensions for cross-agent scoping.