---
title: RFC: Agent Control Plane Model
date: 2026-04-21
status: draft
---

# RFC: Agent Control Plane Model

## Summary

This RFC defines Holon's top-level runtime and control-plane model.

The central direction is:

- `Agent` is the primary runtime object
- `WorkItem`, `Task`, and `Waiting` are distinct control-plane objects with
  narrower responsibilities
- creating another execution context belongs to the agent plane, not the task
  plane
- workspace or worktree isolation is an execution projection property, not a
  separate first-class noun
- task-created worktrees are supervised artifacts owned by task lifecycle, not
  by child agent identity

This RFC is the parent model for related plane-specific RFCs:

- delegation belongs to the agent plane
- managed command execution belongs to the task plane
- blocked and resumable state belongs to the waiting plane

## Problem

Holon's current model still carries overlapping concepts for context creation
and execution control.

Today the system has several adjacent ideas:

- public session creation
- internal `child_agent_task` supervision
- historical `subagent_task` and `worktree_subagent_task` migration records
- command-backed background tasks
- sleep, callback, and wake mechanisms

These capabilities are all useful, but they do not belong to the same layer.

The result is conceptual overload:

- `Task` is asked to represent command execution, child context creation, and
  waiting-like behavior
- `session` and `subagent` both describe new execution contexts
- worktree isolation is partly expressed as a task kind rather than as a
  property of execution context
- worktree artifact lifecycle must stay separated from child-agent execution
  state
- prompt and tool guidance have to explain several overlapping abstractions at
  once

Because Holon has not yet shipped a stable public contract, this is the right
time to simplify the model directly rather than preserve historical surface
shapes.

## Core Judgment

Holon should unify around `Agent` as the primary context-owning runtime
primitive.

Everything else should become narrower:

- `WorkItem` expresses what meaningful work exists
- `Task` expresses managed execution, primarily command-backed execution
- `Waiting` expresses why work is blocked and what may reactivate it

The important long-term distinction is not `session` versus `subagent`.

The important distinctions are:

- root versus child
- public versus private
- parent-supervised versus self-owned
- fresh versus forked context derivation
- inherited versus isolated workspace projection

## Canonical Runtime Objects

Holon should standardize on four primary control-plane objects.

## 1. `Agent`

`Agent` is the only object that owns an execution context.

An agent owns:

- queue
- lifecycle state
- prompt context and local summaries
- brief or user-facing history
- workspace projection
- ingress policy
- current work focus

This means:

- one agent owns one context
- current public sessions are root self-owned agents
- delegated subagents are child agents with bounded lifecycle modes

Holon should keep one runtime object here.

It should not create separate runtime types for:

- session agents
- child agents
- long-lived named agents
- future collaborative agents

Those should be different profiles of the same `Agent` object.

## 2. `WorkItem`

`WorkItem` is the unit of meaningful work identity.

A work item owns:

- title or objective statement
- plan and progress state
- acceptance or boundary notes
- blocked, active, or done status

A work item does not own:

- prompt context
- shell execution state
- waiting capability lifecycle

Its job is to anchor what the agent is trying to accomplish.

## 3. `Task`

`Task` is the unit of managed execution.

Its primary long-term use is:

- command-backed execution
- supervised execution inspection
- output retrieval
- stop and continuation control

A task does not own an independent reasoning context.

That is why delegated child work should not remain a task-shaped public
creation concept.

However, a task may still serve as a supervision handle for a child agent when
the runtime intentionally returns one.

## 4. `Waiting`

`Waiting` is the unit of blocked or resumable state.

It answers:

- what is this agent waiting on?
- which work item is blocked?
- what future condition should reactivate execution?

Waiting does not own prompt context and should not be treated as a task
substitute.

## Ownership Rules

The runtime should preserve these ownership boundaries strictly.

- only `Agent` owns context
- only `WorkItem` owns work identity
- only `Task` owns managed execution lifecycle
- only `Waiting` owns blocked or resumable state

These rules matter because they prevent several common sources of confusion:

- a shell command pretending to be a delegated agent
- a waiting record pretending to be a background task
- a work item pretending to be a runtime scheduler

## Agent Profile Model

Holon should use one `Agent` definition with profile-based variation.

In other words:

- child agent is not a separate runtime type
- long-lived private child is not a separate runtime type
- public named agent is not a separate runtime type

They are different combinations of agent profile fields.

The detailed first-pass profile contract now lives in:

- `agent-profile-model.md`

This control-plane RFC keeps the higher-level rule:

- the runtime should have one `Agent` object model
- public tools may expose a smaller set of stable spawn modes that map onto a
  richer internal agent profile

## Control Planes

The public control surface should be split into four planes.

## 1. Agent Plane

The agent plane owns context creation and agent lifecycle.

Its responsibilities are:

- create another execution context
- define parent-child relationships
- define public or private visibility
- define supervision and cleanup ownership
- define result routing and addressability

The central expansion primitive should be:

- `SpawnAgent`

This should be the only public way to create another reasoning context.

The central inspection primitive should be:

- `AgentGet`
  - Returns an `AgentGetResult` envelope carrying the current `AgentSummary`.

`AgentGet` should read agent-plane state for the context-owning agent:

- identity and visibility
- lifecycle / closure posture
- active work focus and waiting state
- visible child-agent lineage where policy allows

`AgentGet` is distinct from task-plane inspection. `TaskStatus` should inspect a
managed execution handle, while `AgentGet` should inspect the agent that owns
the broader context.

The agent plane also owns the current-run intervention surface.

`current_run_id` identifies the provider/tool turn currently executing inside
the agent. Operator abort is a control-plane mutation, not an enqueued message.
It should:

- cancel only the current provider/tool future;
- preserve the agent, work item, ledger, and workspace state;
- reject an optional stale `run_id` guard if the observed run has changed;
- record an aborted terminal turn with reason `operator_aborted`;
- record the dequeued message outcome as aborted rather than processed;
- pause the agent by default after the abort so it does not immediately repeat
  the same path.

For bounded parent-supervised delegation, `SpawnAgent` should always return the
new `agent_id` and return a task-plane handle when the parent keeps a
supervising execution record for that child.

Under this model:

- "start a new session" becomes root-agent creation
- "delegate bounded work" becomes child-agent creation

## 2. Work Plane

The work plane owns work identity and plan continuity.

Its responsibilities are:

- create and update work items
- mark active, blocked, or done work
- keep plan and boundary state attached to the right work item

The work plane should answer:

- what is the agent currently trying to achieve?
- what remains open?
- what is blocked versus active?

## 3. Task Plane

The task plane owns managed execution.

Its responsibilities are:

- start command execution
- inspect running execution
- accept later input for interactive execution
- read output
- stop execution
- later continue interactive execution

The task plane should narrow toward command-backed execution as its center of
gravity.

This means:

- `ExecCommand` is the startup primitive
- `TaskList`, `TaskStatus`, `TaskOutput`, `TaskStop`, and later `TaskInput`
  inspect and control managed execution state
- delegated child context creation should move out of task language

The task plane may still supervise other managed execution handles when they
share the same operational semantics.

The clearest non-command example is:

- a bounded child agent spawned through `SpawnAgent` and returned to the parent
  as a supervision handle

## 4. Waiting Plane

The waiting plane owns blocked work and future reactivation.

Its responsibilities are:

- create waiting intents
- represent timer-backed and callback-backed waiting
- cancel obsolete waits
- reactivate the right work when future conditions arrive

Waiting should usually be anchored to a work item, not left as detached
runtime residue.

## Lifecycle Axes

Holon should describe agent lifecycle through explicit orthogonal axes rather
than overloaded product nouns.

## Root versus child

- root agents are primary entry points
- child agents are spawned from another agent and preserve provenance

## Public versus private

- public agents may receive external ingress
- private agents only receive parent-directed work or runtime-controlled input

## Parent-supervised versus self-owned

- parent-supervised agents remain inside a parent-owned supervision contract
- self-owned agents expose their own operator-facing lifecycle surface

This distinction is separate from visibility in the general model, even though
the first-pass presets intentionally couple:

- `private_child` with parent supervision
- `public_named` with self-owned lifecycle

Restart behavior should follow supervision semantics, not a separate lifetime
label.

That means:

- a supervised child must remain restart-safe while its supervision handle is
  still live
- child cleanup should happen when the supervision contract reaches terminal
  state or the parent runtime explicitly cleans up the child
- daemon restart may interrupt execution, but it must not silently erase a live
  supervised child handle

## Fresh versus forked

- fresh agents start from explicit handoff only
- forked agents start from an inherited parent context view

This distinction should be modeled explicitly.

It is an execution-context derivation choice, not a separate runtime type.

These axes are more stable and more expressive than preserving `session`
versus `subagent` as parallel primary abstractions.

## Fork Context Derivation

`fork` should not mean "copy the parent's raw transcript."

It should mean:

- the runtime derives a bounded inherited context view
- the parent agent may provide explicit handoff
- the child starts from the combination of both

The default contract should be:

- `fork` = runtime-derived context + handoff
- `fresh` = no inherited parent conversation view + handoff

If a parent wants to fully redefine the child's starting context, it should use
`fresh` rather than trying to override `fork`.

### Runtime responsibility

The runtime should own the base derivation logic for `fork`.

This is important because the runtime has the structured state needed to make a
stable cut:

- active work item
- active work plan
- waiting and callback state
- recent turn memory
- durable working memory
- finalized episode memory

The runtime should therefore build the inherited child context from structured
runtime state first, not from naive transcript replay.

### Parent-agent responsibility

The parent agent should still participate, but through explicit handoff rather
than through full manual context reconstruction.

Handoff is where the parent expresses:

- what the child is being asked to do
- what constraints matter most
- what output form or return contract is expected
- which details deserve extra emphasis beyond the default inherited context

This gives the parent useful steering power without making `fork` semantics
unpredictable.

### Default fork anchor: active work item

When a parent has an active work item, `fork` should anchor inheritance on that
active work item by default.

This means the child should usually inherit:

- the full current snapshot of the active work item
- the full current snapshot of the active work plan
- waiting or callback state attached to that active work item
- the small hot-turn tail most relevant to that active work item
- finalized episode summaries associated with that active work item when needed

This does **not** mean the runtime should blindly copy every historical turn
tagged to that work item.

The default inherited view should remain a bounded projection centered on the
active work item, not a work-item-filtered transcript dump.

### Non-active work should stay out by default

History and state from non-active work items should normally stay out of the
forked child context.

They should only enter when one of the following is true:

- the handoff explicitly references them
- they contain constraints or decisions the child must obey
- they carry unresolved dependencies that still block the active work item
- they share the same delivery target or parent work chain in a way the runtime
  can identify structurally

This keeps delegated children focused and reduces contamination from unrelated
long-session history.

### Cache and stability implications

This rule also improves prompt stability.

The inherited fork prefix should be composed mostly of structured durable
projections that do not change every turn, while:

- hot tail remains small and volatile
- handoff remains explicit and task-shaped

That is better than rebuilding a large transcript-shaped blob on every fork,
and it leaves room for prompt-prefix cache reuse.

## Provenance Versus Supervision

Holon should not overload one `parent_agent_id` field with every possible
meaning.

There are at least two different relationships:

- provenance: which agent created or derived this one?
- supervision: which agent currently owns control, result routing, or cleanup?

These should be modeled separately.

Useful distinctions include:

- `lineage_parent_agent_id` for origin and audit
- `supervisor_agent_id` for current control responsibility

This matters most once an agent becomes long-lived or externally reachable.

A public named agent may still preserve lineage provenance, while no longer
remaining under strong parent supervision.

## Workspace Projection Model

Workspace or worktree isolation should not be modeled as a separate control
plane object.

It should be modeled as an execution projection property owned by the agent,
and consumed by execution started under that agent.

Useful projection modes include:

- `inherit`
- `worktree`
- `explicit_root`

This means:

- a worktree-isolated delegated run is a child agent with
  `workspace_mode=worktree`
- a task usually executes within its owning agent's workspace projection
- task tools should not become the long-term home for workspace-context
  creation semantics
- a task-created worktree is artifact state owned by the supervising task
  lifecycle
- a child agent may hold that worktree as its active execution projection while
  running, but the child agent should not be the artifact owner

This keeps workspace isolation explicit without creating another overlapping
kind such as "workspace task."

The historical `worktree_subagent_task` shape has converged into a unified
supervised `child_agent_task` handle with `workspace_mode=worktree` metadata.
Cleanup is driven by task cleanup or future artifact GC rather than by
model-facing workspace switching.

## Routing And Result Rules

Message and result routing must stay explicit.

### External ingress

Direct operator ingress should target a public self-owned agent unless an
explicit future policy says otherwise.

Private child agents should not receive direct operator ingress.

### Delegated child results

By default:

- private child agents report status and results to the parent
- child-local reasoning history stays local unless summarized back explicitly

Parent agents remain responsible for synthesis, prioritization, and final
user-facing reasoning.

Child results should be treated as bounded outputs or observations, not as a
transfer of overall understanding.

## Spawn Result Contract

`SpawnAgent` should always return the created `agent_id`.

In the first stable model, private child creation should also return
`task_handle`.

`SpawnAgent` should expose one caller-provided spawn text field:
`initial_message`. For `private_child`, `initial_message` is required, is
delivered as the child's first delegation message, and is the source for the
stable parent-visible task label. For `public_named`, `initial_message` is
optional bootstrap input and must not imply parent-supervised task provenance.
The spawn surface should not expose separate `summary`, `task_summary`,
`prompt`, or `work_item` fields.

The important rule is:

- `agent_id` identifies the context-owning runtime object
- `task_handle` is a structured `TaskHandle` execution receipt that identifies
  the parent's managed supervision handle

This keeps the control planes distinct:

- the agent plane creates and identifies the child context
- the task plane may supervise that child when the runtime chooses to expose a
  bounded handle

`TaskHandle` is shared by any operation that produces asynchronous execution
state:

```rust
pub struct TaskHandle {
    pub task_id: String,
    pub task_kind: String,
    pub status: TaskStatus,
    pub initial_output: Option<String>,
}
```

The handle is not an independently creatable control-plane object. It is an
execution receipt returned as a side effect of an operation verb such as
`SpawnAgent` or `ExecCommand`.

### When a task handle should exist

A `task_handle` is appropriate when the spawned child is:

- private
- bounded
- parent-supervised
- still part of the parent's managed lifecycle

This is the most natural shape for delegated work that still behaves like a
managed execution unit from the parent's perspective.

In the first-pass profile model, this is not just a common case. It is the
default rule:

- private spawned agents return `task_handle`

### When a task handle should not exist

A `task_handle` should normally be absent when the spawned agent is:

- public
- operator-addressable
- not under strong parent supervision
- intended to remain open as its own long-lived runtime object

In those cases, the runtime should return only `agent_id` and rely on
agent-plane addressing rather than a parent-scoped supervision handle.

In the first-pass profile model, this means:

- public named agents return `agent_id` without `task_handle`

### Task-handle capabilities for supervised children

When a child agent is returned with a `task_handle`, the parent should use the
task plane for bounded supervision:

- `TaskStatus` for handle-level lifecycle state using `task_handle.task_id`
- `TaskInput` for supervisor follow-up input
- `TaskOutput` for progress and result output
- `TaskStop` for cancellation or termination of the supervised execution

This should not replace future mailbox-style agent communication.

It only defines the bounded supervision path for parent-controlled child work.

### Relationship to `AgentGet`

`AgentGet` and task-handle inspection should answer different questions.

- `AgentGet` should inspect the agent as a context-owning runtime object
- `TaskStatus` should inspect the parent-visible supervision handle

For example:

- `AgentGet` may expose profile, visibility, work focus, and waiting state
- `TaskStatus` should stay focused on lifecycle and control metadata for the
  managed execution handle

This avoids collapsing agent identity and task supervision into one overloaded
detail surface.

### Long-lived child agents

If Holon later introduces long-lived named child agents, they should be treated
as addressable agents, not as upgraded tasks.

If such an agent becomes public or externally reachable, Holon should weaken or
remove strong parent-supervision semantics by default while preserving lineage
metadata.

## Default Child Boundary

The initial Holon model should optimize for bounded private child agents, not
for open-ended multi-agent collaboration.

The default bounded-child contract should therefore be:

- direct operator ingress is not allowed
- child-to-child direct messaging is not part of the initial model
- recursive child spawning is disabled by default
- shared persistent memory writes are disabled by default
- external side effects remain governed by runtime policy and profile
  capability boundaries

This keeps delegation useful without prematurely turning bounded child work
into a public team model.

## Bounded Children Versus Collaborative Agents

Holon should distinguish between:

- bounded child agents
- long-lived private child agents
- public collaborative agents

These should still be profiles of one `Agent` object, not different runtime
types.

The important distinction is not the noun. The important distinction is the
profile.

A useful first-phase mental model is:

- bounded private child: private, parent-only ingress, rejoin-parent
- long-lived private child: private, machine-only or parent-only ingress,
  restart-safe while supervised, still belongs to the parent's supervision tree
- public named agent: public ingress, self-owned lifecycle, lineage preserved
  for audit only

Holon does not need public collaborative agents to validate the core agent
plane model.

The first stable milestone should optimize for bounded private child agents,
with long-lived private children added only when delegated objectives
truly require them.

## Tool Surface Direction

The intended public direction is:

- agent plane: create and manage contexts
- work plane: express meaningful work identity
- task plane: manage command execution
- waiting plane: manage blocked and resumable state

This RFC therefore sets the parent model for these related RFCs:

- [Agent Delegation Tool Plane](./agent-delegation-tool-plane.md)
- [Task Surface Narrowing](./task-surface-narrowing.md)
- [Command Tool Family](./command-tool-family.md)
- [Waiting Plane And Reactivation](./waiting-plane-and-reactivation.md)

Those RFCs should stay narrower than this one.

They refine one plane each.

This RFC defines the shared object model and plane boundaries underneath them.

## Simplification Decision

Because Holon has not yet shipped a stable public API, the preferred direction
is direct simplification rather than compatibility layering.

That means the preferred direction is not:

- keep `session`, `subagent`, and workspace-task concepts as parallel primary
  nouns

It is:

- make `Agent` the primary runtime primitive
- treat root sessions as the product-facing form of root agents
- treat delegated subagents as child-agent lifecycle modes
- treat worktree isolation as a projection mode
- keep `Task` focused on managed execution

## Open Questions

The following questions remain open after this RFC:

- should Holon expose long-lived named child agents early, or keep the public
  agent plane bounded-only for longer?
- should worktree-isolated child creation become a parameter on `SpawnAgent`
  or a specialized convenience tool built on the same model?
- should long-lived private child agents be exposed early as an explicit spawn
  profile for workflows such as PR shepherding, CI follow-up, or webhook-driven
  continuation?
- how much waiting state should appear by default in prompt context versus
  on-demand inspection?
- should the product continue using the word `session` as a user-facing term
  even after the runtime fully migrates to `Agent`?

## Summary

Holon should adopt one top-level control-plane model:

- `Agent` owns context
- `WorkItem` owns work identity
- `Task` owns managed execution
- `Waiting` owns blocked and resumable state

From that model:

- delegation belongs to the agent plane
- command execution belongs to the task plane
- waiting belongs to the waiting plane
- workspace or worktree isolation is a projection property, not a separate
  runtime noun

This gives Holon a clearer foundation for context isolation, delegation,
worktree-backed execution, and future multi-agent growth.
