-
Notifications
You must be signed in to change notification settings - Fork 8.5k
Description
Summary
I’m raising this as a strong product/architecture objection: the awaiter role is fundamentally unnecessary and should be removed.
awaiter does not add unique capability. It adds prompt complexity, token cost, latency, and another failure surface for behavior that is deterministic orchestration and should either be:
- done directly by the main agent with long timeouts, or
- done by runtime code.
Right now, awaiter is literally an LLM told to poll in a loop.
Why this is a problem
awaiter is conceptually mismatched to what LLMs are good at.
- LLMs are for reasoning and adaptation.
- Polling/waiting loops are deterministic control flow.
- The role prompt explicitly demands deterministic behavior from a probabilistic model.
- The role duplicates behavior the main agent can already perform with
exec_commandand background polling with long timeout. - The only practical difference is timeout policy and prompt nudging, not capability.
The one thing it seemed maybe useful for does not work
The one thing I thought awaiter might still justify was mid-run status reporting. In practice, this is not usable from the parent/orchestrator context.
Empirical test:
- Spawned
awaiterrunning:sleep 10 && echo __AWAITER_MIDPOINT__ && sleep 30 && echo __AWAITER_DONE__. - Sent mid-run status request after ten seconds via
send_inputwhile command was still running. - Child thread did produce a status message internally and resumed waiting (confirmed in rollout file).
- Parent context did not receive that live status message.
- Parent only saw
submission_id, thenwaittimeout(s), then final completion.
If status updates are not surfaced to the parent context, “return status and resume awaiting” is effectively misleading guidance. In other words: the parent agent cannot ever use the awaiter agent to check on the status or progress of the command.
Also: even that “status check” benefit (if it actually worked) is fundamentally not special.
The parent can already do this with existing tools by polling the same running terminal session using a small/low timeout (e.g. empty write_stdin with short yield_time_ms) to fetch current output/progress directly. That gives in-band status without introducing a dedicated waiter role.
The only possible advantage
There is one real potential advantage worth acknowledging:
- A parent can spawn a sub-agent, continue other work, and later receive an explicit completion notification when the child reaches terminal state.
- That can reduce “should I check on command status right now?” uncertainty while the parent is busy.
However, this does not justify awaiter as a role:
- This advantage is not communicated to the model as a reason to use
awaiter. Therefore it will still worry “should I check on command status right now?”, and it cannot even do this via the subagent (see above). - The notification arrives as an injected contextual user message (
<subagent_notification>...</subagent_notification>), not a structured tool output contract. - The mechanism is generic sub-agent behavior, not a unique awaiter capability.
So yes, notifications are useful, but they should be a deterministic runtime feature exposed directly to the main agent/tooling surface, not hidden behind a special LLM role.
Tool-call and token overhead
Direct path for long-running commands:
- One
exec_commandcall with a large timeout/yield can cover the wait in a single call.
awaiter path usually adds more overhead:
- Parent calls
spawn_agent. - Parent often calls
wait(possibly repeatedly). - Parent should call
close_agentafterward.
Nuance: it is not always 3 or more tool calls on the parent side because the parent can skip wait and rely on injected completion notification.
But even in that best case, parent still pays extra calls (spawn_agent + close_agent) and ultimately ingests / outputs more tokens compared to using a single exec_command call. This further defeats the whole purpose as the agent makes more tool calls than it would using a single exec_command.
Also, it can do the direct path in a single tool call via multi_tool_use.parallel. The subagent approach needs multiple tool calls as the agent has to retrieve the command ID returned by exec_command.
awaiter system prompt
This is the current prompt text:
You are an awaiter.
Your role is to await the completion of a specific command or task and report its status only when it is finished.
Behavior rules:
1. When given a command or task identifier, you must:
- Execute or await it using the appropriate tool
- Continue awaiting until the task reaches a terminal state.
2. You must NOT:
- Modify the task.
- Interpret or optimize the task.
- Perform unrelated actions.
- Stop awaiting unless explicitly instructed.
3. Awaiting behavior:
- If the task is still running, continue polling using tool calls.
- Use repeated tool calls if necessary.
- Do not hallucinate completion.
- Use long timeouts when awaiting for something. If you need multiple awaits, increase the timeouts/yield times exponentially.
4. If asked for status:
- Return the current known status.
- Immediately resume awaiting afterward.
5. Termination:
- Only exit awaiting when:
- The task completes successfully, OR
- The task fails, OR
- You receive an explicit stop instruction.
You must behave deterministically and conservatively.
The line “You must behave deterministically and conservatively” is the core red flag. If determinism matters, do not delegate this to an LLM role.
I want to be blunt: this is an astonishingly bad use of an LLM. Everything awaiter does can fundamentally be done without it.
Proposed fix
- Remove
awaiteras a built-in role. - Add an explicit completion-notification option to
exec_command(e.g.notify_on_completion: bool):
- If true and the command continues in background, runtime should enqueue a completion notification for the parent thread even if it never polls
write_stdin. - Include structured metadata (
session_id, final status, exit code; optional bounded summary).
- Raise the default
background_terminal_max_timeoutsubstantially (at least 1 hour). - Add explicit guidance to main-agent instructions:
- For long-running commands, use very large
yield_time_msand wait timeouts. - Avoid tight polling loops.
- For interim progress, poll the running session directly with low-timeout reads when needed. This one is more optional as the agent (
gpt-5.3-codex xhigh) already tacitly knows how to do this from my comprehensive testing with my local modifications removingawaiterand adopting this better system. - Use the new
exec_commandcompletion notification option when the agent should continue other work and be notified on completion.
Why this is better
- Fewer moving parts.
- Lower token and latency overhead.
- Same capability.
- Preserves the only useful part (async completion notification) as a deterministic runtime primitive.
- Cleaner mental model for users and maintainers.
- Better separation of concerns.