🌐 US-Proxy
class="logged-out env-production page-responsive" style="word-wrap: break-word;" >
Skip to content

feat: improve sub-agent orchestration tools - #26673

Merged
mafredri merged 10 commits into
mainfrom
mathias/codagt-512-improve-sub-agent-orchestration-tools-list_agents-rename
Jun 26, 2026
Merged

feat: improve sub-agent orchestration tools#26673
mafredri merged 10 commits into
mainfrom
mathias/codagt-512-improve-sub-agent-orchestration-tools-list_agents-rename

Conversation

@mafredri

@mafredri mafredri commented Jun 24, 2026

Copy link
Copy Markdown
Member

The orchestration tools (spawn_agent, wait_agent, message_agent, close_agent) work, but they tell the orchestrator the wrong story, so it abandons work that is still running. This is measured: in the personal-agent chat snapshots ~23% of wait_agent calls time out and ~10% of delegated agents are never cleanly collected. A retry-disciplined harness orphans ~0% with the same descriptions, which puts the fix at the tool response and the missing guidance, not the description alone.

The behavior change: the orchestrator is now taught that sub-agents are persistent, reusable, interruptible workers, not one-shot disposable calls. An agent can be created but not removed; interrupt_agent stops its current work and leaves it waiting. It can be messaged at any time: a queued message (default) is delivered after current work, an interrupting message stops current work and delivers immediately. Agents linger after completing and can be reused via message_agent when their existing context is useful for a follow-up task. If the orchestrator loses track of them after a compaction, list_agents recovers the fleet.

The mechanism:

  • wait_agent returns an informational payload on timeout (status, timed_out) and a structured payload on error status (last_error, report) instead of a bare error that reads as failure. On timeout, if the agent completed during the gap between the last poll and the timer, the normal completion payload is returned instead (via checkSubagentCompletion, avoiding a TOCTOU on stale status).
  • list_agents (new, root-only, paginated, most-recently-active first, archived excluded) lists spawned agents so the fleet survives a compaction.
  • close_agent is renamed to interrupt_agent (response returns interrupted instead of terminated); a hidden ToolNameAliases alias keeps old histories dispatching. The interrupted field is true only when an actual interrupt occurred, not when the agent was already idle.
  • message_agent explains queue-by-default and interrupt: true; the interrupt flag stops current work but does not jump the message ahead of already-queued messages.
  • spawn_agent says that agents persist and can be reused, and that spawned agents are the orchestrator's responsibility (don't abandon a working one).
  • The system prompt adds that error status is often recoverable and that list_agents recovers the fleet. These are precision overlays; the tool descriptions are load-bearing on their own.
  • Frontend: list_agents renders through a dedicated ListAgentsTool component (like ListTemplatesTool) with an expandable agent list and links to each chat. close_agent maps to the interrupt action; no separate close action exists in the frontend.

Dogfood validation (6 tests on a dev instance):

  • Error recovery: an agent that ran false handled the failure gracefully. message_agent resumed it successfully. (Triggering a real chat-level error requires a provider failure; unit tests cover this path.)
  • Timeout recovery: wait_agent with a 2s timeout returned timed_out: true on a long-running agent. A subsequent wait returned the completed report. No contradictory timed_out with terminal status observed.
  • Pagination: 12 agents spawned; list_agents with limit=5 returned correct total, returned, and has_more across three pages (5/5/4).
  • interrupt_agent on a running agent: returned interrupted: true, status: interrupting. The agent transitioned running to interrupting to waiting.
  • close_agent alias: not advertised in the tool list (by design). The hidden alias dispatches old-history calls to interrupt_agent at the executeSingleTool level.
  • list_agents in plan mode: available (backend allowlist includes it; unit tests verify).

Implements CODAGT-512.

🤖 This PR was created with the help of Coder Agents, and will be reviewed by a human. 🏂🏻

@linear-code

linear-code Bot commented Jun 24, 2026

Copy link
Copy Markdown

CODAGT-512

@mafredri
mafredri force-pushed the mathias/codagt-512-improve-sub-agent-orchestration-tools-list_agents-rename branch 13 times, most recently from cd8be4f to 0d3ef4b Compare June 25, 2026 11:38
@mafredri

Copy link
Copy Markdown
Member Author

/coder-agents-review

@coder-agents-review

coder-agents-review Bot commented Jun 25, 2026

Copy link
Copy Markdown
Contributor

Chat: Review posted | View chat
Requested: 2026-06-25 17:36 UTC by @mafredri
Spend: $237.19 / $200.00

Review history
  • R1 (2026-06-25): 16 reviewers, 3 Nit, 1 Note, 7 P3, COMMENT. Review
  • R2 (2026-06-25): 16 reviewers, 3 Nit, 1 Note, 1 P2, 7 P3, COMMENT. Review
  • R3 (2026-06-25): 17 reviewers, 5 Nit, 3 Note, 1 P2, 12 P3, 1 P4, REQUEST_CHANGES. Review

deep-review v0.9.0 | Round 3 | 1ae96fc..3a5a48f

Last posted: Round 3, 22 findings (1 P2, 12 P3, 1 P4, 5 Nit, 3 Note), REQUEST_CHANGES. Review

Finding inventory

Finding Inventory

Findings

# Sev Status Location Summary Round Reviewer Posted
CRF-1 P3 Author fixed (ad94452) chatprompt.go:905 "Keep in sync" comment now misleading after divergence from subagent.go tool list R1 Netero Yes
CRF-2 Nit Author fixed (ad94452) AgentChatPage.stories.tsx:758 Double-hyphen -- as prose separator in comment R1 Netero Yes
CRF-3 Note Author accepted R2 (legacy-only UI branch retained) Tool.tsx:530 Frontend timeout detection dead code for new-format wait_agent timeouts R1 Netero Yes
CRF-4 P3 Author fixed (ad94452) subagent.go:928 list_agents offset pagination unstable under concurrent mutations R1 Hisoka Yes
CRF-5 P3 Author fixed (ad94452) subagent.go:730 Timeout response can carry timed_out:true alongside status:"completed"/"waiting" (race) R1 Hisoka Yes
CRF-6 P3 Author fixed (ad94452) subagent.go:876 interrupt_agent response claims "interrupted":true when agent was already idle R1 Mafuuu Yes
CRF-7 P3 Author fixed (ad94452) subagent.go:1373 closeSubagent method not renamed alongside close_agent -> interrupt_agent R1 Gon P3, Leorio Nit Yes
CRF-8 P3 Author fixed (ad94452) subagent.go:919 list_agents surfaces raw DB error without operation context R1 Chopper Yes
CRF-9 P3 Author fixed (ad94452) subagent_internal_test.go:3704 TestWaitAgentTimeoutReturnsInformationalPayload uses 1-second wall clock instead of mock clock R1 Komugi P3, Bisky Note Yes
CRF-10 Nit Author fixed (ad94452) subagent.go:929 sort.SliceStable -> slices.SortStableFunc (Go 1.21+) R1 Ging-Go Yes
CRF-11 Nit Author fixed (ad94452) subagent.go:744 var errStatus; xerrors.As -> errors.AsType (Go 1.26+) R1 Ging-Go Yes
CRF-12 Nit Dropped by orchestrator (outside diff, pre-existing) SubagentTool.tsx:60 Doc comment says "three label variants" but handles five R1 Leorio No
CRF-13 Nit Dropped by orchestrator (defensive one-liner, minimal risk) subagent.go:1553 subagentLastErrorMessage raw JSON fallback for empty ChatError.Message R1 Hisoka No
CRF-14 Note Dropped by orchestrator (unlikely to arise, model handles unknown statuses) subagent.go:888 list_agents description mentions "completed" as idle but it is a legacy state R1 Mafuuu No
CRF-15 Note Dropped by orchestrator (intentional design choice, transient split) chatloop.go:1224 Metrics record under original alias name instead of resolved canonical name R1 Hisoka, Mafuuu, Meruem No
CRF-16 P2 Author fixed error half (3a5a48f); panel re-raised R3 (interrupting half never closed) subagent.go:1450 Timeout/poll classify error/interrupting as normal completion payloads R2 Komugi P2, Mafu-san P3, Mafuuu P3, Meruem P3, Pariston P2, Melody P3, Chopper P3 Yes
CRF-17 P2 Dropped by orchestrator (contract now documents best-effort pagination, one approved fix from R1) subagent.go:947 list_agents mutable updated_at pagination can still skip agents R2 Komugi No
CRF-18 P3 Dropped by orchestrator (message_agent interrupt branch pre-existed this PR) subagent.go:833 message_agent interrupt flag lacks direct tool test R2 Bisky No
CRF-19 P3 Open subagent.go:832 message_agent returns "interrupted":args.Interrupt (echoes request, not actual outcome); sibling of CRF-6 R3 Mafuuu P3, Chopper P3 Yes
CRF-20 P3 Open SubagentTool.tsx:50 Dead list action in subagent label machinery (SUBAGENT_VERBS.list type-required but unused; renderSubagentLabel case "list" purely dead) R3 Gon P3, Mafuuu Nit, Robin Note, Melody Note Yes
CRF-21 P3 Open subagent.go:758 CRF-16 timeout-gap completion branch (handleSubagentDone) has no regression test R3 Bisky P3, Kite P3 Yes
CRF-22 P3 Open subagent.go:743 Timeout !done path does a second GetChatByID; transition between reads reintroduces timed_out:true + terminal status (CRF-5 class, smaller window) R3 Hisoka Yes
CRF-23 P4 Open prompt.go:136 block injected into child subagent prompts that lack message_agent/list_agents (class shared with planning block) R3 Kite Yes
CRF-24 P3 Open Tool.stories.tsx:1101 ListAgentsCompleted story asserts only header; agent rows and View-agent links never render or get checked R3 Bisky Yes
CRF-25 Nit Open ToolIcon.tsx:118 Stray unrelated blank-line insertions (ToolIcon.tsx:118, Tool.tsx:502) R3 Nami Yes
CRF-26 Nit Open subagent.go:1384 90*time.Second recording-stop timeout is an inline magic literal beside named-constant block R3 Gon Yes
CRF-27 Note Open subagent_catalog.go:307 interrupt_agent description promises "transitions to waiting" but requires-action case returns status:"running" R3 Leorio Yes
CRF-28 Note Open Tool.stories.tsx:1100 ListAgentsTool error state has no Storybook story (site/AGENTS.md wants failure-path coverage) R3 Chopper Yes
CRF-29 P3 Dropped by orchestrator (empirically disproved: isSubagentToolName("list_agents") returns BotIcon via early return at ToolIcon.tsx:83, confirmed by Melody) ToolIcon.tsx:132 list_agents falls through to wrench icon R3 Gon P3, Nami Nit, Chopper Note No
CRF-30 Note Dropped by orchestrator (same class as CRF-14; model handles unknown statuses; low impact) subagent.go:887 list_agents legend omits paused/requires_action (live, not legacy) R3 Melody, Leorio No
CRF-31 Note Dropped by orchestrator (3-line body, different layers; CRF-13 already dropped) subagent.go:1359 subagentLastErrorMessage duplicates chat.LastError decode from stream_loop.go R3 Robin No
CRF-32 Nit Dropped by orchestrator (trivial; single reviewer; call site names the variable) subagent.go:1404 interruptSubagent unnamed bool return R3 Gon No

Contested and acknowledged

CRF-3 (Note, Tool.tsx:530) - frontend timeout detection dead code for new-format wait_agent timeouts

  • Finding: wait_agent timeouts now return non-error payloads with timed_out: true, so the frontend's error-branch timeout detection no longer runs for new-format timeout responses.
  • Author defense: No action needed because the branch still handles legacy timeout responses in chat history. New-format timeout responses intentionally bypass UI error rendering, and the LLM consumes timed_out for retry behavior.
  • Author accepted (R2): The response scope is bounded to legacy rendering compatibility, and no UI timeout indicator is required for the new non-error payload.

CRF-16 (P2, subagent.go:1450) - interrupting half re-raised R3

  • Finding: CRF-16 (R2) named two unpaired statuses, error and interrupting. The R3 fix routed the timeout-gap path through checkSubagentCompletion/handleSubagentDone and added the structured payload for error, but interrupting was not addressed.
  • Author action (R3 reply PRRC_kwDOGkVX1s7PIn-N): Describes the fix for error and waiting/completed via waitAgentSuccessResponse; does not mention interrupting.
  • Panel re-raise (R3): checkSubagentCompletion returns done=true for every status except pending/running, and handleSubagentDone special-cases only error, so interrupting is classified as a completion in both the poll loop and the timeout-gap path. waitAgentSuccessResponse returns status:"interrupting" with the partial pre-interrupt report and, on the timeout path, stops the recording. This lands on the PR's own documented interrupt-then-wait flow. Pariston P2; Meruem/Melody/Chopper/Mafu-san P3. Fix: exclude ChatStatusInterrupting from the done predicate in checkSubagentCompletion.

Law analysis

Round 3: effective +1061 -129 (27 files), 449 production / 612 test. Head 3a5a48f. Verdict: Don't split. Enforcement: Advisory. One reviewable idea (disposable to persistent recoverable subagents); concerns overlap on a single chatd subagent lifecycle surface; high test density; no generated cascade.

Round log

Round 1

Panel. Netero + 16 panel reviewers. 6 P3, 4 Nit, 1 Note posted. 4 dropped. Reviewed against 1ae96fc..0d3ef4b.

Round 2

Churn guard proceeded: 10 addressed, 1 acknowledged, 0 silent. Netero found no new findings. Panel found 1 new P2. Dropped 2. Reviewed against 1ae96fc..ad94452.

Round 3

Churn guard classified CRF-16 addressed, but the panel re-raised its interrupting half (5 reviewers: Pariston P2, Meruem/Melody/Chopper/Mafu-san P3). Law: Don't split (advisory). Netero: no new findings. New: 5 P3, 1 P4, 2 Nit, 2 Note. Dropped 4 (CRF-29 wrench icon empirically disproved; CRF-30/31/32 low-value). Reviewed against ad94452..3a5a48f.

About deep-review

CRF = Coder Review Finding (P0-P4, Nit, Note)

Reviewer Focus
Bisky tests
Chopper ops/errors
Churn-guard change verification
Ging language modernization
Gon naming
Hisoka edge cases
Killua perf
Kite change integrity
Knov contracts
Knuckle SQL
Komugi flake/determinism
Kurapika security
Law decomposition
Leorio docs
Luffy product
Mafu-san process
Mafuuu contracts
Melody dispatch/pairing
Meruem structural
Nami frontend
Netero mechanical checks
Pariston premise testing
Pen-botter product gaps
Razor verification
Robin duplication
Ryosuke Go arch
Takumi concurrency
Zoro shape

🤖 Managed by Coder Agents.

@coder-agents-review coder-agents-review Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well-measured change. The diagnosis (tool errors cause orchestrator abandonment, not task descriptions) is backed by a controlled comparison, and the fix is proportional: structured payloads instead of bare errors, better naming to reinforce persistence, fleet recovery via list_agents, and two-sentence system prompt guidance. Test density is 66% (544 test LOC / 280 production LOC). The backward-compat alias system is particularly clean: old history dispatches correctly, results preserve the original name, and the frontend handles both names.

Severity summary: 6 P3, 4 Nit, 1 Note.

"The old pattern's safety argument died when the preconditions changed." (Pariston, noting why the fix is at the response format, not just descriptions)

Generated by Coder Agents.


coderd/x/chatd/subagent.go:1373

P3 [CRF-7] closeSubagent is the sole holdout from the close_agent -> interrupt_agent rename:

interrupt_agent (tool)  -> interruptAgentArgs (struct)
  -> closeSubagent (method)          <-- still "close"
  -> InterruptChat (underlying API)

A reader of the interrupt_agent handler at line 864 sees p.closeSubagent(...) and must decide whether "close" and "interrupt" mean different things. One caller, one definition; rename to interruptSubagent.

(Gon P3, Leorio Nit)

🤖

site/src/pages/AgentsPage/components/ChatElements/tools/Tool.tsx:530

Note [CRF-3] With the backend change, wait_agent on timeout now returns a non-error payload (timed_out: true), so subagentIsError is false and this timeout detection branch never executes for new-format responses. The timed_out field is consumed by the LLM to retry, not by the UI. The old error-format detection still works for pre-existing timeout responses in chat history, so backward compat is maintained. No action needed unless a visual timeout indicator is wanted for new-format responses.

(Netero)

🤖

🤖 This review was automatically generated with Coder Agents.

Comment thread coderd/x/chatd/chatprompt/chatprompt.go Outdated
Comment thread site/src/pages/AgentsPage/AgentChatPage.stories.tsx Outdated
Comment thread coderd/x/chatd/subagent.go Outdated
Comment thread coderd/x/chatd/subagent.go Outdated
Comment thread coderd/x/chatd/subagent.go Outdated
Comment thread coderd/x/chatd/subagent.go
Comment thread coderd/x/chatd/subagent_internal_test.go
Comment thread coderd/x/chatd/subagent.go Outdated
Comment thread coderd/x/chatd/subagent.go Outdated
@mafredri
mafredri force-pushed the mathias/codagt-512-improve-sub-agent-orchestration-tools-list_agents-rename branch from 0d3ef4b to ad94452 Compare June 25, 2026 12:59

Copy link
Copy Markdown
Member Author

In reply to CRF-3 (Note, site/src/pages/AgentsPage/components/ChatElements/tools/Tool.tsx:530): #26673 (review)

No action needed, as the reviewer notes. The old error-format detection branch in Tool.tsx still handles legacy timeout responses in chat history. New-format timeout responses (non-error with timed_out: true) correctly bypass the error branch, and the LLM consumes the timed_out field to retry. No code change made.


In reply to CRF-7 (P3, coderd/x/chatd/subagent.go:1373): #26673 (review)

Renamed closeSubagent to interruptSubagent. The method signature now returns (database.Chat, bool, error) where the bool indicates whether an actual interrupt occurred. One definition, one call site in the interrupt_agent handler.

🤖 Posted using /amend-review skill via Coder Agents.

@mafredri

Copy link
Copy Markdown
Member Author

/coder-agents-review model:gpt-5.5 thinking:high

@coder-agents-review coder-agents-review Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Round 2 addressed the first-pass issues well: the rename is cleaner, list_agents now documents best-effort ordering, raw DB errors are wrapped, and the wall-clock timeout test now uses a mock clock. One new issue remains in the timeout recovery path.

Severity summary: 1 P2.

"The live axis is scheduling between the timeout timer and the worker status update."

🤖 This review was automatically generated with Coder Agents.

Comment thread coderd/x/chatd/subagent.go Outdated
@mafredri
mafredri force-pushed the mathias/codagt-512-improve-sub-agent-orchestration-tools-list_agents-rename branch 2 times, most recently from c49f4e4 to 083f40d Compare June 25, 2026 15:06
@mafredri
mafredri requested a review from Copilot June 25, 2026 15:40
@mafredri

Copy link
Copy Markdown
Member Author

@codex review

@mafredri
mafredri requested a review from DanielleMaywood June 25, 2026 15:41
@mafredri
mafredri force-pushed the mathias/codagt-512-improve-sub-agent-orchestration-tools-list_agents-rename branch from 083f40d to c8aec22 Compare June 25, 2026 15:44

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 083f40d8a6

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread coderd/x/chatd/subagent.go

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR updates chatd sub-agent orchestration semantics and UI rendering so orchestrators stop treating delegated agents as one-shot calls. It adds a new list_agents tool, renames close_agent to interrupt_agent with backward-compatible aliasing, and adjusts wait_agent to return structured, recoverable payloads (including on timeout and error status).

Changes:

  • Backend: wait_agent now returns non-error JSON on timeout (timed_out: true) and structured JSON on error-status completion; adds list_agents; renames close_agent to interrupt_agent with tool name alias dispatch.
  • Frontend: Updates tool rendering, labels, and visibility rules for interrupt_agent, legacy close_agent, and new list_agents, including new ListAgentsTool UI.
  • Prompt/testing: Adds <subagent-orchestration> guidance and expands Go + frontend tests/stories for the new behaviors.

Reviewed changes

Copilot reviewed 25 out of 25 changed files in this pull request and generated 7 comments.

Show a summary per file
File Description
site/src/pages/AgentsPage/components/ChatElements/tools/utils.test.ts Adds coverage for status mapping behavior around interrupted.
site/src/pages/AgentsPage/components/ChatElements/tools/toolVisibility.ts Updates lifecycle action handling for interrupt.
site/src/pages/AgentsPage/components/ChatElements/tools/toolVisibility.test.ts Tests visibility rules for legacy close_agent, interrupt_agent, and list_agents.
site/src/pages/AgentsPage/components/ChatElements/tools/ToolLabel.tsx Updates subagent label verbs for interrupt and list.
site/src/pages/AgentsPage/components/ChatElements/tools/ToolIcon.tsx Adds an icon case for list_agents (but see review comment).
site/src/pages/AgentsPage/components/ChatElements/tools/Tool.tsx Adds ListAgentsRenderer, routes list_agents away from SubagentRenderer, and tweaks reporting logic.
site/src/pages/AgentsPage/components/ChatElements/tools/Tool.stories.tsx Updates stories from close_agent to interrupt_agent and adds list_agents stories.
site/src/pages/AgentsPage/components/ChatElements/tools/SubagentTool.tsx Updates verb set to include interrupt and list.
site/src/pages/AgentsPage/components/ChatElements/tools/subagentDescriptor.ts Adds interrupt and list actions and maps legacy tool names.
site/src/pages/AgentsPage/components/ChatElements/tools/ListAgentsTool.tsx New UI component to render list_agents output with links to chats.
site/src/pages/AgentsPage/components/ChatConversation/messageParsing.test.ts Extends parsing tests for interrupt_agent and list_agents descriptor behavior.
site/src/pages/AgentsPage/AgentChatPage.stories.tsx Updates example transcripts to use interrupt_agent.
coderd/x/chatd/subagent.go Implements list_agents, interrupt_agent, wait_agent structured timeout/error payloads, and tool-name aliasing.
coderd/x/chatd/subagent_internal_test.go Adds/updates tests for timeout payload, error-status payload, and list_agents behavior.
coderd/x/chatd/subagent_catalog.go Updates subagent tool guidance text (plan mode tool list, persistence guidance).
coderd/x/chatd/recording_internal_test.go Updates timeout behavior expectation: wait_agent timeout is no longer a tool error.
coderd/x/chatd/prompt.go Adds <subagent-orchestration> system prompt section.
coderd/x/chatd/instruction_internal_test.go Verifies the new orchestration prompt block is present.
coderd/x/chatd/generation.go Plumbs tool-name alias map into tool execution.
coderd/x/chatd/chatprompt/chatprompt.go Updates lifecycle-tool detection to include interrupt_agent and legacy close_agent.
coderd/x/chatd/chatloop/chatloop.go Adds alias-aware dispatch (ToolNameAliases) when executing tools.
coderd/x/chatd/chatloop/chatloop_run_internal_test.go Tests tool alias resolution behavior.
coderd/x/chatd/chatd.go Updates plan-mode allow/deny lists and explore tool gating for new/renamed tools.
coderd/x/chatd/chatd_test.go Updates expected tool lists to include interrupt_agent and list_agents.
coderd/x/chatd/chatd_internal_test.go Updates active tool sets for turns to include interrupt_agent and list_agents.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread site/src/pages/AgentsPage/components/ChatElements/tools/ListAgentsTool.tsx Outdated
Comment thread site/src/pages/AgentsPage/components/ChatElements/tools/ToolIcon.tsx Outdated
Comment thread coderd/x/chatd/subagent.go
Comment thread coderd/x/chatd/subagent.go Outdated
Comment on lines +920 to +928
children := make([]database.Chat, 0, len(rows))
for _, row := range rows {
children = append(children, row.Chat)
}
// Sort most recently active first with a stable id
// tiebreak. The shared query's own ordering is not
// altered because it backs the chats sidebar; paginate
// here instead.
slices.SortStableFunc(children, func(a, b database.Chat) int {

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Valid observation, but the in-memory sort is the intended design. The shared GetChildChatsByParentIDs query backs the chats sidebar and its ordering was deliberately not altered. Subagent counts per chat are small in practice. Adding DB-level pagination would require a new query or modifying the shared one, which is out of scope. The handler now sorts rows directly (no intermediary array) and uses min() for clamping, which addresses the code cleanliness concern without changing the data access pattern.

🤖 Posted using /amend-review skill via Coder Agents.

Comment thread coderd/x/chatd/subagent.go Outdated
Comment thread site/src/pages/AgentsPage/components/ChatElements/tools/Tool.tsx Outdated
Comment thread site/src/pages/AgentsPage/components/ChatElements/tools/Tool.tsx Outdated
@mafredri
mafredri force-pushed the mathias/codagt-512-improve-sub-agent-orchestration-tools-list_agents-rename branch from c8aec22 to ad0a318 Compare June 25, 2026 15:52
The orchestration tools work, but they tell the orchestrator the wrong
story, so it acts on the misframing. wait_agent says "final response"
and returns a bare error on timeout, which reads as failure rather than
"still working." spawn_agent frames a spawn-wait-done lifecycle, so the
orchestrator never learns agents persist and can be reused. close_agent
sounds like it destroys when it only interrupts, message_agent hides its
queue and interrupt behavior, and there is no way to list spawned agents
to recover the fleet after a compaction.

This is measured, not hypothesized: in the personal-agent chat snapshots
~23% of wait_agent calls time out and ~10% of delegated agents are never
cleanly collected. A retry-disciplined harness orphans ~0% with the same
descriptions, so the give-up is decided against the tool response and the
missing hygiene guidance, not against the description.

The fixes land where the decision is made. wait_agent returns an
informational timeout payload and a recoverable-aware error payload
instead of a bare error. A new list_agents tool recovers the fleet.
close_agent becomes interrupt_agent, with a hidden alias so old histories
still dispatch. The descriptions and a <subagent-orchestration> section
in the system prompt teach persistence, queuing, and not abandoning a
working agent.

Implements CODAGT-512.
@mafredri
mafredri force-pushed the mathias/codagt-512-improve-sub-agent-orchestration-tools-list_agents-rename branch from ad0a318 to afaa14d Compare June 25, 2026 15:55
mafredri added 2 commits June 25, 2026 16:20
…only, clean up

Round 3 review fixes:
- SubagentRenderer checks rec.timed_out from the structured
  payload, not just error strings, so non-error timeout responses
  render with the clock icon and timeout verb.
- list_agents handler rejects delegated chats via ParentChatID.Valid
  guard, matching the root-only contract.
- list_agents sorts rows directly, no intermediary children array;
  clamps with min() instead of if-chains.
- ListAgentsTool shows 'Listed 0 agents' for completed empty lists,
  not 'Listing agents'.
- Remove dead list_agents case from ToolIcon switch (unreachable via
  isSubagentToolName early return).
- interrupt_agent description says 'transitions to waiting' instead
  of 'stops and waits', matching the async transition behavior.
- Add SubagentWaitTimedOutStructured and ListAgentsEmpty stories.
- Add DelegatedChatRejected subtest to TestListAgents.
…with min/max

The ListAgentsTool label showed total instead of the page size, so a
paginated call with 3 of 15 agents displayed 'Listed 15 agents'.
The label now shows 'Listed N of M agents' using agents.length for
the page count. The limit clamping replaced three if-blocks with
min(max(limit, 1), maxListAgentsLimit) and updated the story assertion
to match.
@mafredri
mafredri requested a review from Copilot June 25, 2026 16:45
@mafredri

Copy link
Copy Markdown
Member Author

@codex review

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 25 out of 25 changed files in this pull request and generated 2 comments.

Comment thread site/src/pages/AgentsPage/components/ChatElements/tools/Tool.tsx

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: d5cbc1cba0

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread coderd/x/chatd/subagent.go
…ack, docs

ListAgentsTool label always uses 'Listed N of M agents' format, dropping
the single-agent special case that hid the total on paginated calls.
ListAgentsRenderer falls back to the plain string result when the
backend returns a text error, so the UI shows the actual error instead
of a generic fallback.
Docs updated to replace close_agent with interrupt_agent and add
list_agents to the tool tables in index.md and architecture.md.
@github-actions

Copy link
Copy Markdown

Docs preview

📖 View docs preview for docs/ai-coder/agents/architecture.md

@mafredri

Copy link
Copy Markdown
Member Author

/coder-agents-review set-spend-limit:200

@mafredri
mafredri requested a review from kylecarbs June 25, 2026 17:50
Comment on lines +263 to +266
// ToolNameAliases maps a non-advertised tool name to the canonical
// tool it dispatches to. Used for backward compatibility when a tool
// is renamed but old chat histories still reference the old name.
ToolNameAliases map[string]string

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need to do this? I guess we do so we don't break existing chats, but I feel like models would discover this change anyways with their new tools?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Honestly, I did not try. I went the "better safe than sorry approach". I did consider it and I think they should probably discover it after one failed call of missing tool. Or they might give up ¯\_(ツ)_/¯. If you want I'll remove the back-compat. FE will still need it unless we migrate the data though.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Owner already replied in-thread: the back-compat alias is the "better safe than sorry" approach, and the frontend needs it unless chat history data is migrated.

🤖 Posted using /amend-review skill via Coder Agents.

Comment on lines +644 to +646
"Returns the agent's response and status. A timeout is not "+
"a failure: the agent is still running. Call wait_agent again "+
"or use list_agents to check its status.",

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤌🤌🤌

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not a finding. Emoji reaction to the wait_agent description, no actionable content.

🤖 Posted using /amend-review skill via Coder Agents.

@coder-agents-review coder-agents-review Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Round 3 is a strong pass on prior feedback. Every R1/R2 finding is addressed, and several were over-delivered: CRF-3 (accepted in R2) was actually fixed so the UI now honors the structured timed_out payload, the docs were updated for the rename and list_agents, list_agents got a root-only guard with a matching test, and the new ListAgentsTool component cleanly mirrors ListTemplatesTool. Determinism and alias wiring verified clean.

The re-review surfaces one genuine regression and a cluster of new issues from this round's refactor and new component. The interrupting half of CRF-16 was never closed: the fix paired error but left interrupting classified as a completion in both the poll loop and the timeout-gap path, and it now sits on this PR's own documented interrupt-then-wait flow (Pariston P2; Meruem, Melody, Chopper, Mafu-san P3). That re-raise is why this is REQUEST_CHANGES.

One item needs a human decision rather than an agent call: the orphan fix is entirely advisory (payload shape, descriptions, system prompt) with no backend reaping of children. The product analysis frames a backend cascade-cancel as a separate safety net. Please either file a tracking issue or explicitly accept the advisory-only scope; it should not be left implicit.

Note: three reviewers flagged list_agents rendering a wrench icon. That is incorrect; isSubagentToolName("list_agents") returns BotIcon via the early return in ToolIcon, so it renders a bot. The only residue is a stray blank line, folded into the nit below. Not raising the icon claim.

Severity summary: 1 P2 (re-raised), 5 P3, 1 P4, 2 Nit, 2 Note.

"I went looking for a fight on the lifecycle surface. The alias dispatch holds. One thread still moves when I pull it." (Hisoka)


coderd/x/chatd/subagent.go:832

P3 [CRF-19] message_agent returns "interrupted": args.Interrupt, echoing the request flag instead of whether an interrupt actually happened. This is the unfixed sibling of CRF-6, which corrected exactly this for interrupt_agent (now derived from interruptSubagent's real transition).

SendMessageBusyBehaviorInterrupt only interrupts an active run. When the target is idle (waiting), nothing is interrupted, yet the response still says interrupted: true. This round also rewrote the description to promise interrupt: true "stop[s] the agent's current work," sharpening the contract the response breaks. The orchestrator is told it halted in-progress work that never existed.

Fix: derive interrupted from the target's pre-send status (active vs idle), mirroring interrupt_agent. (Mafuuu P3, Chopper P3)

🤖

🤖 This review was automatically generated with Coder Agents.

Comment thread coderd/x/chatd/subagent.go
Comment thread coderd/x/chatd/subagent.go Outdated
Comment thread coderd/x/chatd/prompt.go Outdated
Comment thread site/src/pages/AgentsPage/components/ChatElements/tools/Tool.stories.tsx Outdated
Comment thread site/src/pages/AgentsPage/components/ChatElements/tools/ToolIcon.tsx Outdated
Comment thread coderd/x/chatd/subagent.go Outdated
Comment thread coderd/x/chatd/subagent_catalog.go
…d prompt, clean up dead list action

checkSubagentCompletion now treats interrupting as not-done so the
agent settles into waiting or error before classification. This fixes
both the poll loop and the timeout-gap path returning stale output for
a transient interrupting status.

checkSubagentCompletion returns the chat on the not-done path instead
of an empty Chat{}, eliminating a second unsynchronized GetChatByID in
the timeout handler.

message_agent derives interrupted from the target's pre-send status
instead of echoing args.Interrupt.

The subagent-orchestration prompt block is extracted to a const and
stripped from delegated child chats that cannot call list_agents or
message_agent.

list_agents is removed from actionByToolName and list from
SubagentAction, eliminating dead verb entries and the
name !== list_agents carve-out in Tool.tsx dispatch.

ListAgentsCompleted story now expands and asserts agent rows.
ListAgentsError story covers the failure path. Timeout-gap completion
test drives the error-in-gap branch.

Recording stop timeout extracted to named constant. Stray blank lines
removed. interrupt_agent description tightened.

Copy link
Copy Markdown
Member Author

In reply to CRF-19 (P3, coderd/x/chatd/subagent.go:832): #26673 (review)

message_agent now derives interrupted from the target's pre-send status (targetChatInfo.Status == running || pending) instead of echoing args.Interrupt. When the target is idle (waiting), nothing is interrupted, so the response says interrupted: false.

🤖 Posted using /amend-review skill via Coder Agents.

mafredri added 3 commits June 25, 2026 19:28
list_agents is no longer in actionByToolName, so
getSubagentDescriptor returns null for it. The test now
asserts null instead of action: "list".
Removing list_agents from actionByToolName made
isSubagentToolName("list_agents") return false, so the early
return in ToolIcon no longer fires. The tool fell through to
the default case and rendered a WrenchIcon instead of a
BotIcon. Add case "list_agents" back to the switch.
Removing list_agents from actionByToolName broke icon rendering
(fell through to WrenchIcon) and classification. list_agents is a
subagent tool registered in subagentTools(). Restored list in
SubagentAction, list_agents in actionByToolName, verb entries,
ToolLabel case, and the name !== list_agents dispatch carve-out.
Added a comment in subagentDescriptor.ts explaining that list
renders through ListAgentsRenderer, not SubagentRenderer.
@mafredri
mafredri marked this pull request as ready for review June 26, 2026 08:44
mafredri added 2 commits June 26, 2026 09:17
Restore the 'Keep in sync with coderd/x/chatd/subagent.go' comment on
isSubagentLifecycleToolName; the replacement had removed the
maintenance coupling.

Add missing interrupted field to interrupt_agent results in
AgentChatPage.stories.tsx (2 locations).

Remove duplicate ListTemplates stories comment block in
Tool.stories.tsx.

Add comment in checkSubagentCompletion explaining why interrupting
is treated as not-done (transient status that settles to waiting or
running).

Fix interrupt_agent description: 'may briefly read interrupting
before transitioning to waiting, or running if there are queued
messages'. Removes vague 'worker finalizes' terminology.

Update docs to match: 'transitions to waiting or running if there
are queued messages'.

Fix ListAgentsTool doc comment to say 'Listed N of M agents'
instead of 'Listed N agents'.
Remove 'settles into waiting' and 'the worker finalizes' from
interrupt_agent description. The agent can transition to waiting
or running depending on queued messages, so the description now
says 'may briefly read interrupting before transitioning to
waiting, or running if there are queued messages'.
@mafredri
mafredri merged commit 59fcc9c into main Jun 26, 2026
34 of 37 checks passed
@mafredri
mafredri deleted the mathias/codagt-512-improve-sub-agent-orchestration-tools-list_agents-rename branch June 26, 2026 10:41
@github-actions github-actions Bot locked and limited conversation to collaborators Jun 26, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants