Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ build/
.vscode/
.claude/
.codex
.cursor
### Mac OS ###
.DS_Store

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -119,17 +119,23 @@ public class SubagentsMiddleware implements HarnessRuntimeMiddleware {

**`task_output`** — Retrieve the result of a background task by task_id.
- **You rarely need this.** Completed tasks are pushed back to you automatically as a `<system-reminder>` block before your next reasoning step.
- Use `task_output(block=false)` only when you need the full result and the pushed summary was truncated, or to inspect a specific task on demand.
- Avoid `block=true`; it serialises the conversation behind the task.
- Use `task_output(block=false)` when you need a specific task's latest status/result, the pushed summary was truncated, or you intentionally want to check progress while continuing other reasoning.
- Use `task_output(block=true)` only for one specific task you are ready to wait for; for multiple tasks use `wait_async_results`.

**`wait_async_results`** — Wait for background-task delivery when the next step depends on results.
- `wait_async_results(task_ids=...)` waits until the specified async subagent tasks have reached terminal state and their results can be injected.
- `wait_async_results(wait_all=true)` waits for all currently tracked non-terminal background tasks in this session. It snapshots the current set; tasks started later are not added to that wait.
- With no task ids and no `wait_all`, it keeps the legacy inbox wait behavior for any delivered async result.

**`task_cancel`** — Cancel a running background task by task_id. No effect on already-completed tasks.

**`task_list`** — List all in-flight background tasks (durable, accurate across compaction and migration). Completed tasks fall off this list after they're pushed to you.

### Background task flow
1. Spawn with `timeout_seconds=0` to fire-and-forget; the response gives you a task_id.
2. **Do not poll.** Continue with other work; when the task finishes you'll see a `<system-reminder>` containing its result.
3. If the agent has nothing useful to do, hand control back to the user — they'll prompt again when ready and the next reasoning round will surface any completions.
2. Continue with independent work. If you need fresh state, use `task_output(block=false)` for selected tasks.
3. If a later step must wait for a known group, call `wait_async_results(task_ids=...)`; if it must wait for every current background task, call `wait_async_results(wait_all=true)`.
4. If the agent has nothing useful to do, hand control back to the user — they'll prompt again when ready and the next reasoning round will surface any completions.

### Timeout promotion
When a sync spawn/send exceeds its timeout, the task is **not lost** — it is automatically promoted to a background task. You receive `status: timeout_promoted` with a `task_id`. Treat it like any async task: the result will be pushed back to you automatically as a `<system-reminder>`. Do NOT retry the same task — it is already running in the background.
Expand Down Expand Up @@ -157,8 +163,10 @@ public class SubagentsMiddleware implements HarnessRuntimeMiddleware {
4. **Reconcile** → Incorporate or synthesize the result into the main thread

### Usage patterns
- **Parallel execution**: Launch multiple subagents concurrently with timeout_seconds=0 when tasks are independent, then collect results with task_output(block=false) after a delay
- **Sync delegation**: Use default timeout for simple one-shot delegation
- **Parallel async execution**: Split work by independence/dependency. Launch independent, non-conflicting tasks with `timeout_seconds=0`; continue reasoning, then use `task_output(block=false)` for selective progress checks or `wait_async_results(task_ids=...)` / `wait_async_results(wait_all=true)` when a barrier is required
- **Parallel sync delegation**: When the toolkit executes tool calls in parallel, multiple sync `agent_spawn` / `agent_send` calls can run concurrently and the parent waits for those tool results before continuing
- **Mixed short/long work**: Wait for short prerequisite tasks first, continue reasoning with those results, and merge long-running async results later through `task_output` or `wait_async_results`
- **Sync delegation**: Use default timeout for simple one-shot delegation when one result is needed before the next reasoning step
- **Persistent session**: Spawn without a task, then use send for multi-turn interaction
- **Cancel stale work**: Use task_cancel to stop background tasks that are no longer needed
- Subagent results are NOT visible to the user — always summarize them in your response
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,8 @@ public class AgentSpawnTool {
status: accepted
task_id: %s
Use task_output(task_id='%s', block=false) to check status, \
wait_async_results(task_ids=...) to wait for a chosen group, \
wait_async_results(wait_all=true) to wait for all current background tasks, \
task_cancel(task_id='%s') to cancel, or task_list() to see all tasks. \
Do NOT call task_output immediately — the task has just started.\
""";
Expand Down Expand Up @@ -202,7 +204,10 @@ public void setGatewayBridge(SubagentGatewayBridge gatewayBridge) {
Every response starts with three lines: agent_key (pass this verbatim to \
agent_send as agent_key), agent_id (the subagent type name), and session_id \
(internal; do not use as agent_key). Sync mode returns the reply below that; \
async (timeout_seconds=0) adds task_id for task_output — task_id is NOT agent_key.\
async (timeout_seconds=0) adds task_id for task_output or wait_async_results; \
task_id is NOT agent_key. Multiple sync tool calls may run in parallel when \
the toolkit enables parallel tool execution; otherwise use async tasks for \
explicit parallelism.\
""")
public Mono<String> agentSpawn(
RuntimeContext runtimeContext,
Expand Down Expand Up @@ -444,7 +449,7 @@ public Mono<String> agentSpawn(
Send a message to an existing subagent. Use the exact string from the \
agent_key line of agent_spawn output (starts with agent:), or the label \
you set at spawn. Do not pass agent_id, session_id, or task_id here. \
timeout_seconds=0 returns task_id for task_output.\
timeout_seconds=0 returns task_id for task_output or wait_async_results.\
""")
public Mono<String> agentSend(
RuntimeContext runtimeContext,
Expand Down Expand Up @@ -977,6 +982,7 @@ private static String formatTimeoutPromoted(String taskId, long timeoutMs) {
task_id: %s
The task exceeded the %ds sync timeout but is still running in the background. \
Use task_output(task_id='%s', block=false) to check status, \
wait_async_results(task_ids=...) when this task is part of a required barrier, \
or wait — completed tasks are pushed back to you automatically. \
Do NOT retry the same task.\
""",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,10 +55,13 @@ public TaskTool(TaskRepository taskRepository) {
description =
"Retrieve the output of a background subagent task. Use when agent_spawn or"
+ " agent_send was called with timeout_seconds=0. Prefer block=false to"
+ " check status without waiting. Only use block=true (the default) when"
+ " you are ready to wait for the result. Do NOT call this immediately"
+ " after launching a task — the task status in conversation history is"
+ " stale; always call task_output or task_list to get the current state.")
+ " check status without waiting. Use block=true only for one specific task"
+ " you are ready to block on. For several async subagent tasks, prefer"
+ " wait_async_results(task_ids=...) or wait_async_results(wait_all=true)"
+ " when you need a barrier, otherwise keep reasoning and poll selected"
+ " tasks later with block=false. Do NOT call this immediately after"
+ " launching a task — the task status in conversation history is stale;"
+ " always call task_output or task_list to get the current state.")
public String taskOutput(
RuntimeContext runtimeContext,
@ToolParam(
Expand Down
Loading
Loading