Skip to content

Lead loses async results when a turn ends without a tool call (#291, #46)#428

Merged
Bill-Billion merged 1 commit into
shareAI-lab:mainfrom
Bill-Billion:feat/issue-291-s15-inbox-poller
Jun 25, 2026
Merged

Lead loses async results when a turn ends without a tool call (#291, #46)#428
Bill-Billion merged 1 commit into
shareAI-lab:mainfrom
Bill-Billion:feat/issue-291-s15-inbox-poller

Conversation

@Bill-Billion

@Bill-Billion Bill-Billion commented Jun 25, 2026

Copy link
Copy Markdown
Collaborator

Addresses #291, and the same stranding for background tasks (#46).

After the Lead spawns teammates or dispatches a background task, if the round ends with plain text (stop_reason != tool_use), agent_loop returns and the REPL falls back to a blocking input(). The async work finishes later on daemon threads, and the results (teammate inbox messages, completed background tasks) stay unconsumed until the user types again. collect_background_results() runs only in the tool-use branch, so a plain-text turn never picks up a finished background task either.

Change (only s15_agent_teams/code.py), following real Claude Code's useInboxPoller: decouple input() from turn execution.

  • input() moves to a dedicated daemon thread.
  • A poller thread wakes the Lead when its inbox has unread messages (MessageBus.peek) or a background task has completed (has_pending_background).
  • Both push to one shared event queue; the main thread runs one turn per event, draining inbox + collect_background_results() and injecting before the turn, so teammate and background results become new turns without user input.
  • Repeated wakeups are idempotent (empty drain skipped).
  • A single "[all teammates done]" marker prints once the last teammate finishes and its output is drained.

Only the Lead's main loop changes. Teammates' idle loops are untouched, since blocking a background thread is harmless and the problem is specific to the Lead, which holds the user prompt.

Verification with the real API, sending no user input between the task prompt and quit:

  • a teammate's result is delivered by the poller as a new turn, then the "[all teammates done]" marker prints;
  • a background bash job (sleep 6) is delivered by the poller ~6s later as a new turn ([wake: 0 inbox + 1 background]).

Out of scope: terminal prompt/output still interleaves (needs a TUI; real Claude Code uses Ink). Three-language README not updated.

@vercel

vercel Bot commented Jun 25, 2026

Copy link
Copy Markdown

@Bill-Billion is attempting to deploy a commit to the crazyboym's projects Team on Vercel.

A member of the Team first needs to authorize it.

@Bill-Billion Bill-Billion force-pushed the feat/issue-291-s15-inbox-poller branch from 3118ab4 to a995603 Compare June 25, 2026 15:19
…reAI-lab#291)

After the Lead spawns teammate agents, if the current round terminates with
plain text (stop_reason != tool_use), agent_loop returns and the REPL falls
back to a blocking input(). Teammates run as daemon threads and finish later,
so the results they send to the Lead's inbox stay unconsumed until the user
submits their next input.

The cause is that result delivery is bound to the turn loop, whose lifetime
ends when the model stops calling tools, while a teammate completes on its own
clock. Blocking a teammate is harmless because it runs as a background thread;
the problem is specific to the Lead, which owns the user prompt.

The fix follows real Claude Code (useInboxPoller, described in the appendix):
decouple input() from turn execution. MessageBus.peek(agent) reports whether
the inbox has unread messages without consuming them. In __main__, input()
moves to a dedicated daemon thread and an inbox poller thread peeks the Lead's
inbox every second; both push to one shared event queue. The main thread runs
one turn per event, woken by either user input or an incoming teammate message,
so teammate results become new turns without waiting for the user. Repeated
inbox wakeups are idempotent: an empty read is skipped when a prior read_inbox
already drained the messages. Only the Lead's main loop changes; the teammates'
idle loops are untouched.
@Bill-Billion Bill-Billion force-pushed the feat/issue-291-s15-inbox-poller branch from a995603 to 464a615 Compare June 25, 2026 15:27
@Bill-Billion Bill-Billion merged commit 17e2e12 into shareAI-lab:main Jun 25, 2026
3 of 5 checks passed
@Bill-Billion Bill-Billion changed the title feat(s15): event-driven inbox poller so the Lead doesn't strand teammate results (#291) Lead loses async results when a turn ends without a tool call (#291, #46) Jun 25, 2026
Bill-Billion added a commit to Bill-Billion/learn-claude-code that referenced this pull request Jun 26, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant