Nested thread and delegation capability audit
Date: 2026-08-23
Question: What are the complete operational, lifecycle, permission, and isolation properties of a nested thread / subagent delegation tool? Where does the same capability already exist in OpenAgents, where is it only modeled, and what invariant and Effect-shaped seams should own it?
Method: Cleanroom behavioral analysis of a reference agent runtime. The observations below are translated into OpenAgents vocabulary. No source code, internal identifiers, file paths, or implementation-specific names from the reference system are reproduced here.
0. Verdict
A full delegation capability is more than a "run this prompt in the background" surface. It is a family of execution shapes, each with a different contract for context sharing, prompt-cache behavior, permission authority, transcript ownership, and terminal cleanup. The reference runtime supports five distinct shapes, and they are not interchangeable.
OpenAgents already owns the durable primitives that could support all five:
OpenAgents.Delegationsprovides the target-kind dispatch and auth boundary forboxandcomputertargets (IDENTITY-009).OpenAgents.Workprovideswork_jobswithkindvalues fordeep_work,delegation,coding, andscv.OpenAgents.Agentsprovides agent identity and human-linked grants.- The
threadsandnested threadconcepts are reserved indocs/taxonomy.mdbut not yet implemented.
The gap is not a missing table. It is a missing nested-thread ledger and a
delegation turn-contract that records a child turn's authority snapshot,
budget, transcript link, and completion receipt independently of the parent
conversation. The reference runtime keeps all of that in a per-session
subagents/ transcript tree and a task registry. OpenAgents should hold the
same evidence in durable PostgreSQL rows.
1. Vocabulary and scope
This audit uses the following OpenAgents-native terms. Terms that are
proposed in docs/taxonomy.md are treated as reserved vocabulary, not as
live features.
- Parent turn — the turn that calls the delegation tool.
- Nested thread — a child thread created when a parent turn spawns another agent during the same body of work.
- Delegation target — the execution substrate: a
box, acomputer, or an in-process worker. - Worker — the agent runtime that executes the nested thread.
- Directive — the task prompt given to the worker.
- Work job — the durable
work_jobsrow that outlives the parent turn. - Receipt — the append-only durable evidence that a delegation happened.
- Sidechain transcript — a transcript that belongs to the worker, not the main conversation.
- Effect contract — the OpenAgents Effect Schema that authorizes and records the delegation.
2. What the tool does
The delegation tool lets a parent turn spawn a worker to perform a complex, multi-step task. The worker receives a directive, a scoped tool pool, and a selected execution mode. When the worker finishes, the parent receives a result, a terminal receipt, and optionally a notification.
A cleanroom analysis of the reference runtime reveals five execution shapes:
- Synchronous worker — the parent turn blocks until the worker completes.
- Background worker — the worker runs independently; the parent receives a notification later.
- Forked worker — the worker inherits the parent's full conversation context and prompt-cache prefix.
- Isolated worker — the worker runs in a separate worktree or working directory.
- Remote worker — the worker runs in a cloud environment, polled by the local runtime.
- Teammate — a named, addressable worker that can send and receive messages and coordinate with other workers.
3. Invocation shape
The tool accepts a common input set. The fields below are translated to
OpenAgents parameter names. Optional fields are omitted in some contexts (for
example, a teammate context removes run_in_background and name is not
available to a worker that is already a teammate).
Core parameters
description— short summary (3–5 words) used for the work-job label and notifications.prompt— the directive the worker follows.worker_type— which worker definition to use. In OpenAgents, this is theagent_typeorroleselector.model— optional model override; takes precedence over the worker definition and the parent.run_in_background— force background execution.
Multi-agent parameters
name— human-readable name for the worker, used for message routing.team_name— the team context for coordinated workers.mode— permission mode for a teammate (for example,planrequires plan approval).
Isolation parameters
isolation—worktreeorremote.cwd— absolute working-directory override. Mutually exclusive withisolation: "worktree".
Output
The tool can return one of the following, depending on mode:
completed— the worker result and metadata.async_launched— the work-job id, the output artifact path, and a flag indicating whether the parent can read the output.teammate_spawned— the teammate id, name, color, and routing address.remote_launched— the remote session id, session URL, and output path.
4. Worker catalog and selection
Definition sources
The worker catalog is built from overlapping sources. Later sources win on type name conflicts. This is the reference runtime's discovery order, translated to OpenAgents:
- Built-in workers — packaged with the runtime and always available.
- Plugin workers — provided by installed plugins.
- User workers — defined by the account owner in user settings.
- Project workers — defined in the repository under a project-scoped directory.
- Policy workers — organization-managed definitions from policy settings.
- Flag workers — definitions injected through feature flags or CLI flags.
Filtering pipeline
Before a worker is selectable, it passes a pipeline:
- External dependency requirement — if a worker declares a required external tool provider, it is hidden until that provider is connected.
- Permission-deny rules — workers denied by
Worker(type)permission rules are removed. - Worker-type restriction — a parent can declare a restricted allowlist
(for example,
Worker(explorer, reviewer)); only those types appear. - Feature gating — some built-in workers are hidden behind feature flags.
Selection logic
- If
worker_typeis present, the catalog is searched. A missing type returns the available list. A denied type returns the denying rule and source. - If
worker_typeis omitted and fork mode is enabled, the worker is treated as a fork of the parent. - If
worker_typeis omitted and fork mode is disabled, the default general-purpose worker is used.
Worker definition fields
A worker definition carries the following fields:
worker_type— the selector.when_to_use— one-line guidance for the model.tools— allowed tools, or*for all after filtering.disallowed_tools— explicit denylist.model— preferred model, orinheritto use the parent's.permission_mode— default permission mode for the worker.max_turns— upper bound on worker turns.isolation— default isolation mode.background— whether the worker always runs in the background.skills— skills to preload into the worker's initial context.memory— persistent memory scope:user,project, orlocal.required_providers— required external tool providers.hooks— lifecycle hooks to register while the worker runs.
5. Execution modes
The mode is decided at call time from the parameters and the worker definition.
Synchronous worker
- The parent turn blocks.
- The worker shares the parent's abort controller, so a parent abort kills the worker.
- Permission prompts can surface in the parent's UI.
- The result is returned directly to the parent.
Background worker
- The worker gets its own unlinked abort controller.
- The parent receives an
async_launchedresult immediately. - A work job is registered and tracks progress.
- On terminal state, a notification is enqueued for the parent.
- Permission prompts are auto-denied or awaited through automated checks.
Forked worker
- Enabled only when the fork experiment is active and
worker_typeis omitted. - Always runs in the background.
- Inherits the parent's full conversation and rendered system prompt bytes.
- The parent's prompt-cache prefix is preserved for cache sharing.
- Recursive forking is blocked: a fork child cannot spawn another fork.
Worktree-isolated worker
- A temporary worktree is created.
- The worker receives a notice to translate paths from the parent context and to re-read potentially stale files.
- If the worker makes no changes, the worktree is automatically removed.
- If the worker makes changes, the worktree path and branch are returned in the result.
Remote worker
- Always a background task.
- Precondition checks include authentication, cloud environment availability, a git repository, a forge remote, and an installed GitHub app.
- The remote session is launched; the local side polls for status and output.
Teammate
- Triggered when both
nameandteam_nameare provided. - Two substrate variants exist:
- In-process teammate — runs in the same runtime with context isolation.
- Pane-based teammate — runs in a separate terminal multiplexer pane.
- Teammates can send and receive messages and share a task list.
- A teammate cannot spawn another teammate; the team roster is flat.
6. Lifecycle
Spawn phase
- Validate parameter combinations.
- Resolve the worker definition.
- Check permission-deny rules.
- Create an isolated
ToolUseContextwith cloned mutable state. - Assemble the worker's tool pool.
- Register a
work_jobsrow or task state. - Create the abort controller.
- Initialize the sidechain transcript.
Execution phase
- Execute
SubagentStarthooks. - Connect worker-specific external tool providers and merge their tools.
- Compose the worker's system prompt and initial messages.
- Run the worker's query loop.
- Track token counts, tool uses, and activity descriptions.
- Record every message to the sidechain transcript.
Completion phase
- Execute
SubagentStophooks. - Finalize the result: extract text, count tool uses, compute usage.
- Mark the work job as completed.
- Enqueue a notification to the parent.
- Emit cache-eviction hints for the worker's prompt-cache prefix.
- Disconnect external tool providers and clean up the worktree if unchanged.
- Schedule output artifact eviction.
Cancellation phase
- Signal the worker's abort controller.
- Cancel in-flight inference requests.
- Mark the work job as killed.
- Extract any partial result from the sidechain transcript.
- Enqueue a killed notification.
- Disconnect external tool providers and remove the worktree if applicable.
7. Context model
The worker's context is a controlled subset of the parent context. The exact subset depends on the mode.
Message context
- Synchronous worker — starts fresh, with only the directive.
- Forked worker — receives the parent's full conversation, including the assistant message that triggered the tool use.
- Teammate — starts with empty messages to avoid pinning the parent's conversation for the teammate's lifetime.
- Resumed worker — receives the prior sidechain transcript.
File-state cache
The parent's file-state cache is cloned for all workers. This keeps read budgets, tool-result replacement state, and path containment coherent without letting the worker mutate the parent cache.
Tool pool
The worker's tool pool is assembled independently of the parent's. The process:
- Start from the global tool catalog.
- Apply permission-deny rules.
- Apply the worker's
toolsanddisallowed_tools. - Apply async-worker restrictions when the worker is in the background.
- Merge tools from worker-specific external tool providers.
- Deduplicate by tool name; built-in tools win over provider tools.
Permissions
- The worker's
permission_modecan override the parent's, except when the parent is inbypassPermissions,acceptEdits, orautomode. - Synchronous workers can surface permission prompts in the parent's UI.
- Background workers cannot show prompts; they either auto-deny or await automated checks (classifier, hooks).
- Teammates route permission requests to the team leader's UI.
- SDK-level
alwaysAllowRulesare preserved across all workers.
Model
Resolution order:
- Explicit
modelparameter. - Worker definition's
model. - Parent's model.
Forked workers inherit the parent's model to preserve context-length parity and prompt-cache compatibility.
Prompt cache
Forked workers maximize cache sharing by using the parent's exact rendered
system prompt bytes and the same tool-pool ordering. Any change to model,
max_tokens, thinking configuration, or tool order can break the cache.
8. Prompt and system context composition
The worker's system prompt is built from several layers:
- Worker prompt — the definition's system prompt.
- User context — project-specific guidelines, scope notes, and persistent memory.
- System context — environment details such as working directory and git status.
- Hook output — additional context from
SubagentStarthooks. - Memory injection — persistent worker memory if
memoryis enabled. - Teammate addendum — extra instructions for in-process teammates.
For forked workers, the system prompt is replaced by the parent's rendered bytes. This avoids recomputing a prompt that may diverge and break cache.
Prompt style
- For fresh workers, the directive should be a complete briefing.
- For forked workers, the directive is a narrow scope statement because the worker already has the parent's context.
- The prompt should not push synthesis back onto the worker. It must be specific about file paths, line numbers, and expected changes.
9. Permission and tool scoping
Worker allow/deny lists
tools— explicit allowlist or*for all after filtering.disallowed_tools— explicit denylist.- Global disallow lists — for example, the worker tool is denied to subagents to prevent unbounded recursion.
- Async restrictions — only a safe subset of tools is available to background workers.
Tool-spec restrictions
A parent tool can declare a restricted worker set:
Worker(explorer,reviewer)— only those worker types may be spawned.Worker(code-reviewer)— denies all worker types except the named one.
External tool providers
- Workers can declare their own external tool providers in their definition.
- Inline provider definitions are created and destroyed with the worker.
- Provider references are shared with the parent and are not destroyed.
- A plugin-only policy can restrict provider access to admin-trusted sources.
10. Transcripts and artifacts
Sidechain transcript
- Stored as a JSONL file in a per-session
subagents/directory. - One message per line.
- Includes assistant, user, progress, and compact-boundary messages.
- Optional subdirectories group related workers.
Output artifact
- A stable path for the parent to read worker progress.
- The parent's access depends on whether the parent has read or shell tools.
- Evicted after a grace period on terminal state.
Resume support
- The prior transcript is loaded and filtered.
- Incomplete tool calls, orphaned thinking, and whitespace-only messages are removed.
- Tool-result replacement state is reconstructed from sidechain records.
- The worktree path is validated; if it no longer exists, the parent working directory is used.
- Worker metadata is persisted across resume.
11. Work-job registry and progress
Registration
- Each worker is a task with a type-prefixed id.
- State includes status (
pending,running,completed,failed,killed), description, start and end times, and an output artifact pointer. - A cleanup handler is registered for process exit.
Foreground to background promotion
- A foreground worker can be auto-backgrounded after a threshold of elapsed time.
- A
background_signalpromise resolves when the user confirms the promotion. - The message buffer is retained during promotion.
Progress tracking
- Tool use count.
- Cumulative input and output tokens.
- Recent activity list with tool-specific descriptions.
- Optional 1–2 sentence summary generated by a periodic summarizer.
Terminal state
completed— result stored, notification enqueued.failed— error stored, notification enqueued.killed— partial result extracted, notification enqueued.- The
notifiedflag prevents duplicate notifications.
12. Termination, resume, and restart
Termination
killmarks the task as killed, aborts the controller, and triggers cleanup.kill_allstops every running worker of the current session.- For teammates, a shutdown request is sent first; a force kill uses the abort controller.
Resume
- Load the sidechain transcript.
- Resolve the worker definition from persisted metadata.
- Reconstruct the system prompt for forked workers.
- Re-register the task under the same id.
- Append the new directive to the resumed messages and continue.
Restart
- A full restart is not provided as a single operation.
- The equivalent is a fresh invocation with a new work-job id.
13. Nesting and safety
Recursive fork guard
- A forked worker cannot spawn another forked worker.
- Detection uses a synthetic marker in the forked worker's messages.
- Attempts are rejected at call time.
Teammate nesting
- A teammate cannot spawn another teammate.
- The team roster is flat.
- In-process teammates cannot spawn background workers.
Depth tracking
- Each worker increments a query-chain depth counter.
- No hard depth limit is enforced, but recursion is prevented by tool-pool and fork guards.
Isolation and safety properties
- Worktree isolation — worker filesystem changes are isolated and discarded if unused.
- Working-directory containment —
cwdand worktree paths are validated. - Abort unidirectionality — parent abort can kill child; child abort does not propagate to parent.
- Model scoping — model resolution is explicit and bounded.
- Provider scoping — worker-specific external providers are created and destroyed with the worker.
- Permission scoping — worker permission mode and tool pool are computed independently of the parent.
14. Events and notifications
Lifecycle hooks
SubagentStart— fired when the worker starts.SubagentStop— fired when the worker stops, with transcript and last message metadata.TaskCreated,TaskCompleted— task registry events.TeammateIdle— fired when a teammate finishes and is ready for new work.
Notifications
- A structured notification is enqueued for the parent on terminal state.
- Fields include work-job id, status, summary, output path, usage, and worktree path/branch if applicable.
- The
notifiedflag deduplicates.
Event consumers
- The parent agent receives notifications as system messages.
- The UI consumes task state for progress displays.
- The analytics system receives usage and completion events.
- External SDK consumers receive lifecycle and progress events.
15. OpenAgents mapping
What is already live
OpenAgents.Delegationsis the unified delegation surface.OpenAgents.Workowns durablework_jobsrows.OpenAgents.BoxandOpenAgents.ComputerAgentJobsare the two substrate authorities (IDENTITY-009).OpenAgents.Agentsprovides agent identity and grants.OpenAgents.TurnsandOpenAgents.Conversationsown the canonical turn ledger.
What is modeled but not wired
nested threadis defined indocs/taxonomy.mdbut has no schema.threadstable is proposed indocs/2026-08-23-thread-primitive-audit.md.work_jobsalready supportsdelegationandcodingkinds; a nested thread kind would be additive.
What is open in the issue tracker
This audit does not claim an issue number. The nested-thread primitive and a
thread-scoped inference_grants column are the adjacent open work described in
docs/2026-08-23-thread-primitive-audit.md.
Invariant boundaries
The following existing invariants should constrain any nested-thread implementation:
IDENTITY-009— the unified delegation surface must not widen the reach available through the underlyingboxorcomputersubstrate.WORK-001— awork_jobsrow is bounded; no job may start another SCV.TURN-001..005— every turn is an immutable provenance receipt.DATA-002— one conversation per visitor; a thread is not a conversation.
Effect-shaped contract
A new delegation Effect should own the spawn-to-completion lifecycle:
effect: thread.spawn— record the parent turn, worker type, authority snapshot, budget, and target kind.effect: thread.resume— re-attach to an existing sidechain transcript.effect: thread.cancel— abort and record the terminal reason.effect: thread.complete— record the outcome receipt and link it to the parent turn.
These Effects are not a replacement for OpenAgents.Delegations; they sit
above it, adding the nested-thread identity and transcript-link concerns that
work_jobs does not currently carry.
Tests, smokes, and receipts needed
- A test that a forked worker cannot spawn another forked worker.
- A test that a teammate cannot spawn another teammate.
- A test that worktree isolation leaves no changes when the worker made no edits.
- A smoke that a background worker outlives its parent turn and that the notification is delivered on the next parent turn.
- A receipt schema for nested-thread outcomes bound to the parent turn.
- A projection-freshness check that the UI does not show stale worker state after a node restart.
16. Gaps and recommendations
- Add a
threadstable and makeinference_grants.conversation_idnullable with a newthread_id, as recommended indocs/2026-08-23-thread-primitive-audit.md. - Extend
work_jobswith anested_threadkind or add a separatenested_threadstable that links toturn_receipts. - Define an Effect contract for thread spawn, resume, cancel, and complete.
- Store sidechain transcripts in a durable, public-safe form so they survive node restarts and can be audited.
- Implement the fork and teammate recursion guards before enabling nested thread delegation in production.
- Add a thread-scoped capability ref so a worker's tool pool and permission mode are recorded as an authority snapshot, not inferred from the parent.