All tools for one agent to interact with others, organized into four domains: planning, execution, entities, and communication.
This page explains each tool, which sessions may operate which tasks, and how two budgets limit a chain.
Design document: docs/reference/design/runtime/agent-collaboration.md.
For eight reference implementations compared across the same eight dimensions, see Agent collaboration: eight implementations compared.
The domains describe different resources, not disjoint tool namespaces. The agent tool selects or creates a running branch and submits work; job queries inspect admitted work; execution controls manage its lifecycle.
A manually maintained plan list. It records intentions only, corresponds to nothing currently running, and does not run an agent when edited.
A queued, running, or terminal job has an execution_id, state, and result. list_jobs and job_output query it; cancellation uses the canonical execution command, not a separate model tool.
Here an agent means a running conversation branch. agent() creates a branch or continues one; list_agents lists branches and archive_agent archives them. A saved AgentSpec is a separate reusable configuration selected by agent_id.
send_message continues a branch without creating a new one; the actual turn still requires Job admission. read_conversation reads local history without executing it. Target-level owner/project ACL is not implemented for that history read.
todo_* edits plans; agent creates or continues conversation branches and submits execution; list_jobs/job_output inspect jobs already admitted; execution.cancel controls an existing execution through the execution command protocol. It is not a callable job_stop tool. Cancelling work does not archive the branch or delete its saved configuration.execution_id. The current job_output(job_id=...) argument, details.job_id, and attach.job_id retain their compatibility names. Use the returned execution ID as that argument; do not introduce task_id aliases.
Create a branch, dispatch to a branch, send a message, or query. Creation in either mode and dispatch to an existing branch durably admit a Job. Background creation and existing-branch dispatch return an execution_id immediately; foreground creation waits for that Job and returns its final result. A synchronous return does not remove the internal execution identity or bypass admission.
agent; the only difference is whether to= is present:
without to=, create a new branch and immediately execute prompt; with to=, submit work to an existing branch. The diagram’s Job record applies to foreground creation, background creation, and existing-branch dispatch. Foreground creation waits for completion; the asynchronous forms return an execution handle.
Message uses send_message without creating a branch. Direct delivery starts an admitted Job and returns delivery_id=job_id; a busy target first returns an inbox receipt, which is not yet execution admission. Query uses four read-only tools.Both continue an existing branch through Job admission. The target contract automatically returns nonempty completion results for both; an additional explicit send_message reply is optional. Automatic completion return is not yet connected to the current canonical completion paths.
| Guarantee | Message send_message | Task agent() / agent(to=) |
|---|---|---|
| Who may initiate | An enabled sender, to a resolvable unarchived branch | An enabled caller may submit; output ownership and execution-control authority constrain the resulting execution_id |
| Has an execution ID | Direct delivery returns delivery_id=job_id. A busy-target inbox receipt precedes Job creation; the actual turn still requires admission | All forms retain an internal execution identity. Background creation and existing-branch dispatch return execution_id; foreground creation waits and returns the final result |
| Result contract | Target: return nonempty completion results automatically; an additional explicit send_message reply is optional. Completion integration is pending | The turn produces a terminal result, error, or cancellation. The same nonempty completion return applies; its integration is pending |
| Can it be cancelled | A recorded message cannot be withdrawn by cancelling its admitted execution. The execution is subject to canonical cancellation authority; an inbox receipt is not an execution ID | The execution command execution.cancel requests cancellation using execution_id and expected_version; queued work is withdrawn, running work enters cancellation |
| Does cancellation cascade | Once admitted, the Job follows its execution-parent cancellation relationship | Yes: stopping a task stops all tasks it spawned |
| Can you operate someone else’s task | Knowing a delivery or execution ID grants no control; admitted execution queries and controls retain their authorization checks | job_output permits the target execution session, caller session, or qualifying ancestor relationship. Canonical control separately validates owner/project/session/action authority and the expected status version |
| Is concurrent dispatch safe | A receipt acknowledges delivery or queueing, not successful completion; errors remain possible | Yes, with records: a busy target queues the task and is not interrupted |
archive_agent allows local sessions with that tool capability to archive other branches without a creator-only check. This is a current shared-local trust boundary. Target lifecycle operations must enforce the same scoped authorization as other branch operations; preserving data is not an exemption from authorization.
Message count increases on creation, send_message, and agent(to=...) delivery. The target automatic result follow-up rebinds the completed job’s count without another increment; the helper defines this behavior but is not connected to the canonical completion paths. Generation depth increases only on new-branch creation; job admission, queue capacity, and live capacity are separate limits.
Both support 0, disabling that individual limit; resource admission remains independently enforced.
# Change from the CLI; effective immediately openprogram config set agent.max_spawn_depth 1 # Default: the main agent creates workers, and workers complete their own work openprogram config set agent.max_spawn_depth 2 # Workers may create one more generation; the third generation is rejected openprogram config set agent.max_spawn_depth 0 # Unlimited depth openprogram config set agent.max_messages 8 # Default: at most 8 hops per chain openprogram config set agent.max_messages 0 # Unlimited messages; agents may keep exchanging messages openprogram config set agent.max_spawn_fanout 8 # Default: at most 8 agents created per turn openprogram config set agent.max_spawn_fanout 0 # Unlimited agents created in one turn
~/.openprogram/config.json file’s agent section.
Setting these three limits to 0 does not disable resource admission. max_jobs_per_session counts admitted jobs, max_queued_per_session bounds queued work, and max_live_per_session bounds active capacity; see resource governance. Foreground creation, background creation, and dispatch to an existing branch all durably admit a Job. The foreground path calls run_agent_turn, which calls runner.spawn_job(wait=True) and waits for the Job; synchronous waiting does not exempt it from admission or resource limits.to=Addressing the initiator’s current branch creates a direct cycle and is rejected immediately.
Branches remain permanently in the session DAG for forks, replay, and transcript reading. Archiving does not delete a branch; it writes an archived marker in that target branch’s shared metadata. This is global within the local session store, not a per-caller hidden flag. Every sender sees the same archived state.
The diagram’s manual archive is implemented. Asynchronous archive_when_done is a target completion behavior: its helper is not connected to the current canonical or borrowed-claim completion paths. The synchronous tool’s local archive path is already called.
Three common workflows using different combinations of the same tools.
There are five independent tasks. Write the plan, dispatch each item, then update the plan.
blocked_byMark dependencies.execution_id without occupying my turn.job_output(job_id=...). Use the UI or canonical execution command surface for cancellation; no job_stop model tool is registered.An agent has already done part of the work, so no new agent needs to start from scratch. Check the agent list, then choose a message or a managed task.
to="SID:HEAD", turn count, approximate word count, and a tail preview.execution_id and supports cancellation. Its terminal outcome may be a result, error, or cancellation.Find out how previous work was completed without running an agent or loading the entire history into context.
-10 means the last ten turns.list_agentsThe full SID:HEAD string it provides is also accepted when pasted.The reference implementation comparison records design context; it does not define the current OpenProgram API. External tool names are not compatibility aliases.
| OpenProgram interface | Contract |
|---|---|
agent | Create and execute a branch, or dispatch to an existing branch using to. Saved AgentSpec creation is a different operation. |
list_jobs | List current-session jobs with status and resource view. |
job_output(job_id, block, timeout) | Read or wait for an execution’s result. block=true; timeout is in milliseconds, default 30000, maximum 600000. |
execution.cancel | Execution command, not a model tool. The current Web UI submits an execution ID, expected status version, and command ID through the canonical control endpoint. |
todo_* | Maintain the plan; editing a todo does not execute it. |
list_agents, archive_agent | List and archive conversation branches; do not manage the saved AgentSpec registry. |
send_message, read_conversation | Message delivery continues a branch and admits an execution when the turn starts; transcript reading starts no execution. send_message and agent(to=...) share the nonempty completion-return target; extra explicit replies are optional. |
For a call at message A in source session S that creates a branch in T, the attach node is persisted in S with predecessor=A. Its extra.attach.session_id=T and extra.attach.head_id=H reference the target. The outer stream event data.session_id=S selects the receiving conversation; card_id remains stable across running and terminal updates. There is no persisted attach.caller_session_id field.
| Operation | Session used | Locator |
|---|---|---|
| Load or update the card | S, from the containing message store or stream envelope | Attach node ID / card_id |
| Place the card at its call site | S | Persisted predecessor; conversation mapping resolves the caller. Live tool_call_id associates the call, with legacy ordered fallback. |
| Inspect the referenced branch | T = attach.session_id | attach.head_id; missing while a background branch is still running |
| Read execution status or cancel | Execution authority, independent of card placement | job_id compatibility field; canonical execution_id and status_version for cancellation |
The current conversation mapper attaches cards to caller messages in S. AssistantBubble separates same-session and external cards; AttachCard reads T for navigation. A cross-session target is not a local DAG sequence node in S. Render a reference to T and resolve H in T; never search S for H or move the card to T because attach.session_id is T.
Current limitation: the full AttachCard’s cross-session Open action navigates to session T without explicitly selecting H, while the execution strip issues checkout with T and H. Target behavior requires both surfaces to open the same referenced branch and to show an unavailable target without relocating or removing the source card. This navigation parity is planned, not implemented by this documentation change.
Verified current behavior: tool inventory, identifier compatibility, synchronous/asynchronous Job admission, message delivery versus inbox receipts, attach fields, and shared manual branch archive. Completion helpers define automatic return and asynchronous auto-archive, but the inspected canonical and borrowed-claim completion paths do not call them. Their integration, scoped lifecycle authorization, history target-level owner/project ACL, output reads after message-budget exhaustion, and cross-session navigation parity remain implementation requirements.