Remove `awaiter`

Resolved 💬 9 comments Opened Feb 26, 2026 by Chriss4123 Closed May 10, 2026
💡 Likely answer: A maintainer (darlingm, contributor) responded on this thread — see the highlighted reply below.

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:

  1. done directly by the main agent with long timeouts, or
  2. 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_command and 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:

  1. Spawned awaiter running: sleep 10 && echo __AWAITER_MIDPOINT__ && sleep 30 && echo __AWAITER_DONE__.
  2. Sent mid-run status request after ten seconds via send_input while command was still running.
  3. Child thread did produce a status message internally and resumed waiting (confirmed in rollout file).
  4. Parent context did not receive that live status message.
  5. Parent only saw submission_id, then wait timeout(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_command call 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_agent afterward.

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

  1. Remove awaiter as a built-in role.
  2. 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).
  1. Raise the default background_terminal_max_timeout substantially (at least 1 hour).
  2. Add explicit guidance to main-agent instructions:
  • For long-running commands, use very large yield_time_ms and 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 removing awaiter and adopting this better system.
  • Use the new exec_command completion 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.

View original on GitHub ↗

9 Comments

darlingm contributor · 4 months ago

(I'm not a dev.) I'll be curious to see the official response. Here's my initial thoughts though. It could be that OpenAI saw enough times where they wanted to give further instructions about running tasks, due to cases where the model did something wrong. The system prompt you quoted allows a lot more control over how tasks are ran without polluting the parent agent's context.

awaiter does have fixed overhead for the parent agent to create/work with it, wait for it, and give it instructions. But, if those instructions were in the parent agent, given each time a task was ran, it would pollute its context heavily, I think more than the overhead of a subagent -- but maybe I'm wrong there.

That said, you might be able to effectively disable it. (Not tested.) Roles appear that they can be overridden. config.toml under [agents.<name>] can have description and config_file. The code appears to give the user's config preference over the built-in. If you set the description to something like "This agent does nothing; do not use it.", I think Codex CLI would give the model that description instead of the one quoted below, and it would be extremely unlikely to use it. You might need a dummy config file (see awaiter.toml reference above.) In itself, it would be unfortunate to pollute the parent agent's context with the don't use this command, but it might disable it.

That said, I think it would be great for a user to be able to easily disable built-in roles without this "hack". I'm sure OpenAI will be adding a lot more roles in the future, and it would be great to give advanced users the ability to tweak this and manage their context a lot more.

For curiosity, the built-in description for awaiter is (codex-rs/core/src/agent/role.rs:193):

Use an awaiter agent EVERY TIME you must run a command that might take some time.
This includes, but not only:

  • testing
  • monitoring of a long running process
  • explicit ask to wait for something

When YOU wait for the awaiter agent to be done, use the largest possible timeout.

Chriss4123 contributor · 4 months ago

@darlingm I see why you think that because I thought that initially too.

  1. awaiter does not avoid parent-context pollution.

The parent already gets awaiter guidance in its own tool schema/instructions. The spawn_agent tool description includes built-in role descriptions (including awaiter), so the description text is already in parent context. Yes, technically the developer prompt is not included. But the parents context is still polluted by convoluted and unneeded awaiter-related guidance.

  1. It also doesn’t avoid instruction duplication.

Spawned sub-agents inherit the parent config/instructions, then add role-specific overrides. What instructions does this agent have that the parent agent does not tacitly have? This is a weak argument as nothing special is conveyed to this agent that the parent agent doesn't already know. The subagents system prompt is LITERALLY: "Endlessly poll the command until it finishes". Is that useful? No.

  1. Overriding [agents.awaiter] is only a local workaround and does not disable the role.

You can override its description/config, but not remove the built-in role itself. So this doesn’t provide a clean, explicit “disable built-in role” control.

The optimal design is this. Remove awaiter, add deterministic async completion signaling directly to exec_command (e.g. notify_on_completion), and raise background timeout defaults.

Smrtnyk · 4 months ago

i can confirm that it does not add to more pleasant experience

Chriss4123 contributor · 4 months ago

I'd love to open a PR to fix this issue, as I've been fixing this exact issue and iterating and testing ever since this was commited to the repository (I run my own custom Codex with so many issues and nits fixed as this repository does not welcome pull requests anymore, even for past contributors like myself).

If a Codex developer would like to grant me permission, I'll open the PR to fix this issue.

darlingm contributor · 4 months ago
@darlingm I see why you think that because I thought that initially too. ...

I don't have an opinion on your proposed alternative. Just giving my initial thoughts of status quo vs awaiter. Your proposed change isn't an area of Codex that I've look at to have an informed opinion.

  1. Agreed that there's parent-context pollution. I was just saying it might be less. I mentioned that there's overhead in the parent of the description text, but it keeps the longer system prompt out of it. My point was if they have determined that they need to add all the extra language when running a task, that it seems like there's a smaller context cost doing that in a subagent. Although, I now see the system prompt for awaiter is 235 tokens, and its description is 64. It felt like the system prompt would be a lot higher. In practice, I'm wondering if the 64 will grow with agent-related overhead close to or exceeding the 235.
  2. For your question about what instructions the subagent has that the parent doesn't, yeah, I just meant the 235 token system prompt being replaced by the 64 plus sub-agent orchestration overhead.
  3. Totally agreed. I included "That said, I think it would be great for a user to be able to easily disable built-in roles without this 'hack", noting it will become even more important to be able to do so as OpenAI adds more roles in the future.Regarding your proposed alternative, I don't have an opin
Chriss4123 contributor · 4 months ago

@darlingm I 100% agree we should be able to disable subagents. I have that feature implemented in my own local modifications of Codex and I have explorer hard disabled as it's over-invoked and useless for me.

I feel like Codex is different to Claude Code in that it's decently opionated, but for stuff like this, we need to be able to customize it as a default.

jif-oai contributor · 4 months ago

Sub-agent is an experimental feature. You actively decided to enable it. We are still trying to figure out the best default agents to use. We will take your feedback and iterate on this

darlingm contributor · 4 months ago

For anyone who didn't notice, v0.107.0 says it temporarily removed awaiter.

etraut-openai contributor · 2 months ago

This feature request hasn't received any upvotes in many months, so I'm going to close it.