holon 0.14.1

A headless, event-driven runtime for long-lived agents
Documentation
---
title: RFC: Task Surface Narrowing
date: 2026-04-21
status: draft
---

# RFC: Task Surface Narrowing

## Summary

This RFC proposes how Holon should narrow the public `Task` surface over time.

The central direction is:

- keep internal runtime execution records flexible
- make the public `Task` surface more specific and less overloaded
- move delegation and waiting away from `CreateTask` as long-term public
  creation semantics

In practical terms, this means Holon should gradually make public task control
mean "managed execution job, primarily command-backed execution" rather than
"any asynchronous thing the runtime can do."

`Task` is therefore an execution receipt produced by operation verbs such as
`ExecCommand` and `SpawnAgent`, not an independently creatable control-plane
object.

## Problem

Today `CreateTask` mixes several different kinds:

- `sleep_job`
- `child_agent_task`
- `command_task`

These are not peers at the same conceptual layer.

For example:

- `command_task` is about managed command execution
- `child_agent_task` is about supervised child context creation
- `sleep_job` is about delayed reactivation

When they all live under one public task entry point, several things happen:

- `Task` becomes too broad to guide the model well
- task prompt guidance has to explain multiple different mental models at once
- `CreateTask` schema becomes flat while true requirements are variant-shaped
- future tool evolution risks preserving historical names instead of clearer
  runtime semantics

## Goals

- narrow the public meaning of task-oriented tools
- reduce overload in `CreateTask`
- make `TaskList` / `TaskStatus` / `TaskOutput` / `TaskStop` center on the cases
- make `TaskList` / `TaskStatus` / `TaskOutput` / `TaskStop` center on the
  cases
  where task control is most natural
- separate task semantics from agent-plane and waiting-plane semantics

## Non-goals

- do not forbid the runtime from internally tracking non-command jobs
- do not force immediate removal of current task kinds
- do not require every migration step to be breaking

## Core Judgment

Public task semantics should be narrower than internal runtime job semantics.

That means Holon can still keep a general internal execution record if useful
for:

- persistence
- recovery
- cancellation
- audit

But the model-facing task plane should not remain the default home for every
runtime-owned asynchronous capability.

## Why Command Execution Is The Best Center For Task Control

The current `Task*` family is most natural when applied to managed command
execution:

- list running background execution
- inspect one execution handle
- fetch output
- stop the job

These are exactly the questions a command-backed task needs to answer.

They are much less natural as the primary abstraction for:

- child-agent context creation
- timer-backed waiting
- callback-backed waiting

This suggests Holon should let `command_task` become the center of gravity for
public task semantics.

## Proposed Public Direction

The intended public direction is:

- `ExecCommand` starts command execution and returns a task handle when
  execution becomes managed
- long-running command execution becomes managed task state
- `TaskList` / `TaskStatus` / `TaskOutput` / `TaskStop` primarily inspect and
  control that execution
- `TaskList` / `TaskStatus` / `TaskOutput` / `TaskStop` / `TaskInput`
  primarily inspect and control that managed execution

Meanwhile:

- child context creation moves toward the agent plane
- waiting moves toward the waiting plane

This also leaves room for one important supervised-child pattern:

- `SpawnAgent` remains an agent-plane operation
- the runtime may return a task handle as a side effect when the spawned child
  is a bounded parent-supervised execution

That handle does not make the child context itself a task.

It only means the task plane may supervise some child-agent executions when
they intentionally share the same operational semantics as other managed
execution handles.

The shared model-visible receipt shape is `TaskHandle`:

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

Both `ExecCommand` promotion and `SpawnAgent(private_child, initial_message=...)` should return
this shape under the `task_handle` field. For `ExecCommand`, the model-visible
receipt is typed by the `disposition` discriminant: direct completion uses
`disposition = completed`, while promotion uses
`disposition = promoted_to_task` and guarantees `task_handle` is present.
For `SpawnAgent(private_child)`, the handle is a parent-supervision handle and
is background by default; supervision does not imply parent scheduler blocking.
`TaskStatus`, `TaskOutput`, `TaskStop`, and `TaskInput` still accept the
contained `task_id`; the wrapper exists to make the returned execution receipt
self-describing.

This does not require `Task` to become shell-only as an internal truth. It
only requires the public surface to stop pretending that all asynchronous
runtime actions should be created the same way.

## Task Handle Versus Context Owner

This RFC depends on one hard boundary:

- `Agent` owns context
- `Task` owns a managed execution handle

That means:

- `SpawnAgent` creates an `Agent`
- `ExecCommand` starts managed command execution
- either operation may produce a task handle when the runtime wants to expose
  bounded supervision through the task plane

The task handle is therefore not the child agent itself.

It is a parent-visible supervision capability for a managed execution unit.
Receipts and task snapshots should keep that distinction explicit by exposing
both `child_agent_id` and `supervision_task_id` when the task is a supervised
private child. The shared `child_supervision` projection is the operator-facing
shape for that boundary; it is not a raw child transcript and it should not be
treated as an alternate agent identity.

## `TaskStatus` Instead Of `TaskGet`

Holon should prefer `TaskStatus` over a heavyweight generic `TaskGet`.

The reason is that task control needs a compact lifecycle snapshot more than it
needs an overloaded detail object.

`TaskStatus` should answer questions such as:

- is this task still running?
- is it terminal?
- can it accept input?
- can it be stopped?
- is output available?

This keeps the task plane readable across both:

- command-backed execution
- supervised child-agent handles

Agent-specific detail should not be forced into a task-detail object.

That should live under `AgentGet` instead.

## `TaskInput` As A Generic Supervision Input Surface

`TaskInput` should remain a generic input tool for interactive managed
execution.

That means:

- for command tasks, `TaskInput` writes stdin or PTY input
- for supervised child-agent tasks, `TaskInput` sends parent follow-up input

This is more consistent than splitting interactive task input into separate
command-only and agent-only surfaces while the operational semantics are still
the same from the parent's point of view.

## `CreateTask` As A Transitional Surface

`CreateTask` should be treated as transitional and eventually split by plane.

It can remain useful while Holon is migrating, but its long-term problems are
already clear:

- one flat object does not express variant boundaries well
- different task kinds imply different ownership models
- schema precision is weaker than the real semantics

The medium-term direction should be:

- reduce what new work goes into `CreateTask`
- migrate delegation and waiting semantics elsewhere
- leave command-backed execution as the strongest fit for the remaining public
  task plane

## Supervised Child Handles And Worktree Artifacts

`Task` should not remain the public creation word for child agents, but the
task plane may still expose a supervision handle returned as a side effect of
`SpawnAgent(private_child, initial_message=...)`.

When that child is spawned with `workspace_mode=worktree`, the runtime-created
worktree should be treated as task-owned artifact state:

- the child agent uses the worktree as its active execution projection while it
  runs
- the supervising task records the worktree path, branch, and cleanup state
- completion of the child turn does not automatically transfer ownership to the
  child agent
- cleanup responsibility belongs to task cleanup or later artifact GC
- operator-facing cleanup state is projected under `child_supervision` with
  `cleanup_owner=supervision_task`

This means `worktree_subagent_task` should not remain a runtime-created task
kind. Holon now uses a unified `child_agent_task` plus
`workspace_mode=worktree` metadata for supervised worktree-isolated child
delegation.

Holon no longer exposes a dedicated destructive worktree-discard public tool.
Manual early cleanup can use ordinary git commands inside the local
environment. Runtime-owned cleanup is best-effort and tied to task cleanup or
artifact GC.

Task-owned worktree cleanup should be tolerant of operator or agent manual
cleanup:

- if the worktree was already removed, cleanup should treat it as already
  cleared
- if branch or path state no longer matches, cleanup should record an audit
  event and avoid blocking task cleanup indefinitely
- runtime-created task branches may be deleted by runtime cleanup because they
  are ephemeral artifacts

## Migration Phases

## Phase 1: Clarify Current Semantics

Without breaking the surface yet:

- describe `command_task` as the primary task-control object
- describe `child_agent_task` as the unified supervised child-agent task
- describe `subagent_task` and `worktree_subagent_task` as legacy record names
  that map to `workspace_mode=inherit` and `workspace_mode=worktree` for
  persistence compatibility only, without scheduler blocking semantics
- describe `sleep_job` as transitional delayed-reactivation language
- describe `TaskStatus` as the preferred task metadata snapshot
- describe `TaskGet` as a removable legacy detail shape rather than the long-
  term task inspection center

## Phase 2: Narrow Task Guidance

Prompt guidance and docs should increasingly teach:

- use task control mainly for command-backed managed execution
- use `TaskInput` as the generic continuation primitive when a managed task
  explicitly needs follow-up input
- use dedicated waiting or agent-plane tools when available
- use task handles for bounded supervised execution when the runtime chooses to
  expose them
- use `TaskInput` as the generic continuation primitive when a managed task
  explicitly needs follow-up input
- use dedicated waiting or agent-plane tools when available

This reduces model confusion before public renames are complete.

## Phase 3: Split Creation Surfaces By Plane

Once replacement surfaces exist:

- agent-plane tools replace subagent task creation
- waiting-plane tools replace delayed-wait creation
- public task creation narrows toward managed execution jobs
- task inspection converges on `TaskStatus` rather than `TaskGet`
- legacy `worktree_subagent_task` records converge into unified
  `child_agent_task` metadata
- destructive worktree-discard tools stay retired in favor of task cleanup or
  ordinary git worktree management

## Phase 4: Revisit Internal Naming

Only after public migration stabilizes should Holon decide whether its
internally generalized execution record should remain named `TaskRecord` or
move to something more neutral such as `JobRecord`.

That rename should follow semantic cleanup, not lead it.

## Relationship To Tool Contract Consistency

This RFC is complementary to the contract consistency RFC.

The consistency RFC says:

- schemas should reflect real variant boundaries
- outputs should be more structured

This RFC says:

- the public task plane itself should become less overloaded

Together, they point toward:

- fewer mixed-shape public tools
- clearer plane ownership
- more predictable model behavior

## Open Questions

The following questions remain open after this RFC:

- should Holon keep a public task creation surface at all once command
  auto-promotion is strong enough, or should explicit task creation become
  rarer?
- should some non-command runtime jobs still be inspectable through `TaskList`
  for operator debugging even if they are no longer first-class model tools?
- should public task control eventually distinguish command tasks from other
  job kinds more explicitly?

## Summary

Holon should narrow the public task surface over time.

The intended end state is:

- command-backed managed execution remains the core task-control use case
- child context creation moves to the agent plane
- bounded child supervision may still use task handles
- delayed reactivation moves to the waiting plane

This gives Holon a clearer public contract without forcing premature
simplification of the internal runtime execution substrate.