apache / apache/maka

proposal(runtime): let a delegated task run without taking the session hostage — background child agents that can be redirected and stopped

Open
#3,540 2 comments 1 reaction 0 assignees View on GitHub
enhancement
Dominant language
TypeScript
Stars
5.4k
Forks
502
Avg merge
1d 2h
Merged PRs (30d)
715

Description

## Problem

Delegating to a child agent takes the session away from the user for as long as the child runs.

`agent_spawn` awaits the child to a terminal AgentRun before it returns (`packages/runtime/src/subagent-tools.ts:230` → `SessionManager.spawnChildSessionOnce`, `session-manager.ts:3068`). The parent's model loop is suspended for that whole time. What the user sees meanwhile is `ChildAgentProgressProjector` output — capped at 64 events and 8 192 characters (`child-agent-progress.ts:23-24`). For an `implementation` child doing real work that is minutes in which the user cannot ask anything, cannot redirect, and cannot steer even the parent.

The type system says this is by design rather than by oversight:

```ts
// packages/runtime/src/agent-catalog.ts:40,49
export const AGENT_INVOCATION_FOREGROUND = 'foreground';
export type AgentInvocationMode = typeof AGENT_INVOCATION_FOREGROUND; // single-member union
```

There is no `AGENT_INVOCATION_BACKGROUND` anywhere in `src/`, and all three built-in agents carry system prompts that begin "You are a foreground … child agent".

### Why this matters more than it looks

**It contradicts a principle the project has just invested in.** Mid-turn steering exists because a user must be able to intervene while work is in flight — that is the whole point of #3529 and #3530. Today that principle stops at the child boundary: the moment work is delegated, intervention becomes impossible until the child finishes. The larger and more valuable the delegated task, the longer the session is frozen, which is exactly backwards.

**Parallelism exists; asynchrony does not.** Up to five subagent tools can settle concurrently in one step (`MAX_ACTIVE_SUBAGENT_TOOLS_PER_TURN = 5`, `tool-runtime.ts:310`), so fan-out is already possible. What cannot be expressed is *starting work and continuing* — every shape available today ends with the parent waiting.

**The Agent Graph is the intended answer, and it excludes interactivity by construction.** `yield_agent_graph` sets `loopStopReason = 'graph_yield'` and ends the parent turn (`ai-sdk-backend.ts:3254`); the Host later wakes a *new* turn at a reconciliation checkpoint. That is genuinely durable and log-backed, and it is the right substrate for dependency-driven work — but "let the agent work while I keep talking to you" is not slow under it, it is inexpressible. It also costs a five-step protocol (`agent_list` → `update_agent_graph` → `yield_agent_graph` → wake → `agent_swarm_status`/`view_agent_graph` → `agent_output`), results addressed by record id rather than value, and availability only in `graph`/`swarm` orchestration mode.

**A background child that cannot be redirected or stopped is worse than none.** Both halves are missing today:

- *Redirect.* `resumeChildAgent` and `retryChildAgent` are implemented (`session-manager.ts:3637`, `:3928`), declared on the tool context (`tool-runtime.ts:249`, `:263`) and plumbed through the Host — with **zero tool consumers**. The capability exists and is simply not surfaced. The nearest model-callable thing, `update_agent_graph` with `operator_id`, queues a *new activation* serialized behind the current one (`session-manager.ts:2700`); it is Queue, not Steer.
- *Stop.* There is no `agent_stop`. A foreground child is bound to the parent tool call's abort signal, so the only cancellation is aborting the whole parent turn. Graph mode has `update_agent_graph operation=stop`; a plain spawn has nothing.

**`agent_list` lists presets, not instances.** It enumerates available `subagent_id`s and explicitly excludes execution history (`subagent-tools.ts:388`). There is no way to ask "what is running right now".

For reference, two harnesses that do expose this converge on the same small surface: spawn-in-background, send a follow-up to a named child, read partial output, stop, and list live children. The value is not the individual calls — it is that the parent stays available throughout.

## Desired outcome

A delegated task can run without taking the session hostage: the parent keeps its turn, the user can keep talking, and the running child can be inspected, redirected, and stopped by name.

Concretely, the gaps to close are asynchrony (**a**), follow-up delivery (**b**), and cancellation (**d**). Reading partial output already works — `agent_output` has no terminal-status guard and can read a live child (`subagent-tools.ts:581`) — and scheduled triggers already exist (`ScheduledTask`). A condition-watching "monitor" primitive is deliberately **not** proposed here: `ScheduledTask` plus `GoalSet` cover enough of that ground that the case is much weaker, and it deserves its own discussion if wanted.

The general question of *agent-to-agent* messaging is already open as **#2595** and is not re-litigated here. This issue is narrower and differently shaped: a supervisor delivering a follow-up instruction to a child it owns, not operators addressing each other. If #2595 lands a durable addressed-signal primitive, this should be built on it rather than beside it.

## Alternatives or workarounds

**Use the Agent Graph.** Works today and is durable, but ends the parent turn — see above. It is the right tool for dependency-driven batch work and the wrong one for "start this, stay with me".

**Keep tasks small enough that blocking is tolerable.** What users must do now, and it caps how much can usefully be delegated — the opposite of what delegation is for.

**Poll `agent_output` from the parent.** Not possible: the parent is suspended inside the `agent_spawn` call, so there is no step in which to poll.

## Implementation notes — this is not a small change

Flagging scope explicitly, because the surface area is much wider than "add a boolean to `agent_spawn`".

**Core / runtime.** `AgentInvocationMode` is a single-member union that has to grow, and every `AgentDefinition` and child system prompt asserts "foreground". Child lifetime is currently bound to the owning tool call's abort signal (`ToolRuntime.buildChildAgentContext` → `composeChildAbortSignal`, `tool-runtime.ts:330`, `:1886`); a background child needs its own ownership, its own cancellation path, and a decision about what happens when the parent turn ends or the Host restarts mid-child. Permission ceilings must not widen — a background child must not outlive the boundary that authorized it.

**Host protocol.** A live-children registry and its projection are new surface: `agent_list` would have to report instances, and output/stop/submit need operations. That means wire-schema versioning, the way the steering echo needed 4 → 5 in #3316. Multi-client behaviour has to be defined too — two attached clients observing the same running child.

**TUI.** Child progress currently renders *inside the spawning tool card*. A background child has no tool card to live in, so it needs a persistent surface of its own, plus the same discoverability and keybinding questions raised in #3538. Terminal real estate is already contended (#3421).

**Desktop.** #1457 (surface linked child sessions in the session workbar) is the natural home, and #2596 (operational topology view for Agent Graph) is adjacent. If background children land before those, each surface will invent its own representation.

**Sequencing risk.** Shipping asynchrony without stop and without visibility would leave users with invisible, unkillable work — strictly worse than today's blocking behaviour. If this is taken in slices, cancellation and listing should land with or before the background spawn, not after.

_Investigated with AI assistance (Claude Code); Maka behaviour verified against source at `f19eede03`._

Contributor guide

Open the contributing guide

Research direction

Start by reading packages/runtime/src/subagent-tools.ts, session-manager.ts, agent-catalog.ts, and tool-runtime.ts to map the current foreground lifecycle and cancellation path. Then trace the Host protocol and the TUI child-progress surface before defining the required background-child operations. Done means the parent remains interactive while a child can be listed, inspected, redirected, and stopped without exceeding its authorization boundary.

Written by the indexing model from the issue text.

Assessment

Tech stack
typescript
Domain
backend, cli, desktop
Issue type
Feature
Difficulty
5/5
Estimated time
Over a week
Activity status
Active
Clarity
Mostly clear
Newbie friendliness
35/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.