Skip to content

Safe Spawn

How agent-initiated child sessions are bounded by lineage, TTL, caps, workspace inheritance, and permission narrowing.

For people running agent work7 pages in this section

compozy spawn lets a managed session ask the daemon to create a child agent session. Spawn is the delegation surface for the autonomy MVP, but it is safe by default: the daemon owns lineage, TTL, caps, workspace inheritance, permission narrowing, and cleanup.

Parent lineage is also used as informational creation provenance for some daemon-managed system sessions, including Loop Goals. Those sessions can appear beneath their origin in session views, but they do not inherit the origin's TTL, parent-stop behavior, permission policy, or spawn budget. Spawned children remain governed by their own lineage. Coordinator roots retain their daemon-owned TTL, caps, and permission policy.

Required constraints

ConstraintBehavior
Valid parent identityThe caller must be a CompozyOS-managed session resolved from daemon-issued identity.
TTL--ttl-seconds is required and must be positive. Spawned sessions cannot be unbounded.
Depth capThe MVP default max spawn depth is 1.
Child capThe MVP default max children per parent is 5. Only live children count; subagents never do.
Workspace targetChildren inherit the parent workspace. --workspace may target another workspace when the policy allows.
Coordinator role denial--role coordinator is rejected. Coordinators are daemon-managed root sessions, not child sessions.
Permission subsetChild tools, skills, MCP servers, and workspace paths must be a subset of the parent set.
Unknown atomsUnknown child permission atoms count as widening and reject the spawn.
Parent stopChildren auto-stop when the parent stops unless configured otherwise within hard caps.

The daemon rejects invalid spawn requests. It does not silently narrow a child request and continue.

Subagents reuse the permission-subset rules but not the TTL, depth, or child caps: a delegated subagent has no TTL and no depth or count limit, and it inherits the caller's permission budget when the narrowing lists are omitted.

Spawning a worker

compozy spawn --agent reviewer --ttl-seconds 1800

Add narrowed permissions and a role when the parent has those atoms:

compozy spawn \
  --agent reviewer \
  --role reviewer \
  --ttl-seconds 1800 \
  --prompt-overlay "Review only the implementation diff." \
  --tool read \
  --skill code-review

Permission flags are repeatable:

  • --tool
  • --skill
  • --mcp-server
  • --workspace-path

--workspace-path narrows the workspace-path permission atoms a child may use. It is a permission grant checked against the parent set, not a placement flag — it does not choose which workspace the child runs in. Use --workspace for that.

Inherit the parent's permissions

Without flags, a child gets no native tools, skills, or MCP servers. --inherit-permissions keeps the parent's grant for every list you do not pass, and a list you pass replaces only its own category. --toolset picks the parent-granted members of a named toolset:

compozy spawn \
  --agent general \
  --ttl-seconds 3600 \
  --inherit-permissions \
  --toolset compozy__sessions

Inheritance and toolsets never widen the parent. These grants cover CompozyOS-native tools only; the child's provider keeps its own shell and file tools under its sandbox and permission mode.

When the daemon refuses a request, the error names the field, the refused value, the rule (subset_of_parent, blank_atom, or known_toolset), and a recovery hint.

The response includes the child session, lineage, and safe permission projection. It does not create or claim task work by itself.

Give the child its own worktree

Bind the child to a worktree so it changes code on its own branch:

compozy spawn \
  --agent general \
  --ttl-seconds 3600 \
  --new-worktree=feature-auth \
  --base main \
  --branch feat/auth

--worktree <ref> binds an existing ready worktree instead. The two flags are mutually exclusive, and --base and --branch apply only to --new-worktree. Combined with --workspace, the worktree belongs to that workspace: the daemon authorizes the cross-workspace spawn first and only then resolves or creates the worktree there, so a denied request leaves no worktree behind. The child's working directory, and so its provider sandbox root, is the worktree checkout. A worktree created for a refused spawn is removed again. The response's worktree object reports its ID, branch, path, and base commit. Use this instead of --workspace-path to place a worker in another checkout.

Send the first prompt

--prompt sends the child's first prompt from the parent with a reply watch:

compozy spawn \
  --agent general \
  --ttl-seconds 3600 \
  --new-worktree=feature-auth \
  --prompt "Implement the auth task, commit on this branch, and report the commit."

When the child finishes that turn, the parent receives one reply wake that carries the child's final answer. The response's prompt object reports the message ID and the reply watch. If the prompt fails after the child starts, the spawn still succeeds with prompt.status set to failed and an error; send the prompt again with session_prompt instead of spawning a second child.

Wake the creator

CompozyOS queues a sanitized synthetic turn on the parent when a child completes a turn, stops, fails, or enters a needs-you state; a canceled child turn sends no completed wake. This notify_creator behavior defaults to on and never interrupts an active parent prompt. It is suppressed when the parent is no longer live or the delivery would wake the same session.

Opt out for one child when a parent wake is unnecessary:

compozy spawn \
  --agent reviewer \
  --ttl-seconds 1800 \
  --no-notify-creator

HTTP, UDS, and compozy__session_spawn use the presence-aware notify_creator boolean: omission keeps the default, while explicit false opts out. There is no config.toml key for this default.

While the parent holds an open reply watch on the child, from --prompt or a session_prompt with notify_on_complete, the reply wake already carries the child's answer, so the plain completed wake is skipped. Wakes for failure, stop, and needs-you states still arrive.

Cross-workspace spawn

--workspace overrides the workspace the child runs in. It takes a workspace id, a workspace name, or an absolute path to a registered workspace root; a path inside a registered root resolves to that workspace. Omit the flag and the child inherits the parent workspace.

compozy spawn --agent reviewer --ttl-seconds 1800 --workspace ws_4f2c9a1b7d3e5086
compozy spawn --agent reviewer --ttl-seconds 1800 --workspace billing-api
compozy spawn --agent reviewer --ttl-seconds 1800 --workspace /Users/dev/code/billing-api

The daemon resolves the reference to a canonical workspace id before anything else. A reference that does not resolve to a registered workspace is denied — spawn never registers a new workspace. When the resolved target equals the parent workspace, nothing changes. When it differs, the request goes through the workspace-access policy on the spawn seam, and the parent session's effective permission mode decides:

Parent permission modeCross-workspace spawn
approve-allAllowed.
approve-readsDenied, unless the session already recorded a cross-workspace allow answer.
deny-allDenied.

The spawn seam never prompts. A denial the policy marks prompt-eligible is still returned as an error here — no approval request reaches the operator. The consent an approve-reads session can reuse is captured elsewhere: the tool seam does prompt, and an allow answer there is cached for the rest of that session's lifetime. Absent such an answer — or with a recorded reject answer — approve-reads denies. deny-all denies regardless of any recorded answer.

The daemon rejects a denied request with 403 Forbidden and a message naming the target workspace, the parent workspace, and the operator fix — set the agent's permissions.mode to approve-all, or approve the prompt when one is asked.

Workspace access is re-checked after spawn.pre_create hook patches. Hooks cannot move a child into a workspace the policy denies.

What crossing does and does not change:

  • The child's home is the target workspace. Its agent process starts with that workspace's root as cwd and that workspace's additional roots, not the parent's.
  • Permissions do not widen. Child tools, skills, MCP servers, and workspace paths are still subset-checked against the parent, in the target workspace as in the parent's.
  • Every decision is audited. Allow and deny both append a workspace.access_granted or workspace.access_denied event carrying the spawn seam, the decision source, and the effective permission mode.

Cleanup and active leases

The reaper wins over active leases. If TTL expiry or parent-stop cleanup terminates a spawned session, CompozyOS first releases active task-run leases owned by that session with a structured reason, then stops the child. Stale child claim tokens cannot complete or heartbeat recovered work.

Hooks

Spawn exposes these hook events:

  • spawn.pre_create
  • spawn.created
  • spawn.parent_stopped
  • spawn.ttl_expired
  • spawn.reaped

spawn.pre_create may deny or narrow a request, but hook patches cannot widen child permissions. The daemon enforces the permission subset after hook patches.

On this page