Skip to content

feat(tui): bracket sub-agent delegations — return marker + sidebar breadcrumb (#3102)#3116

Open
aheritier wants to merge 2 commits into
feat/tui-delegation-spinner-readabilityfrom
feat/tui-delegation-return-marker
Open

feat(tui): bracket sub-agent delegations — return marker + sidebar breadcrumb (#3102)#3116
aheritier wants to merge 2 commits into
feat/tui-delegation-spinner-readabilityfrom
feat/tui-delegation-return-marker

Conversation

@aheritier

@aheritier aheritier commented Jun 15, 2026

Copy link
Copy Markdown
Contributor

Summary

Makes nested / sequential delegations legible. Builds on #3101.

  1. Transcript return marker. When a forwarded sub-agent (transfer_task / run_skill) hands control back, the only previous cue was the next parent badge, so a run of delegations read as one undifferentiated wall. A ↩ child → parent ─── divider (child's accent color) is now inserted when AgentSwitching(false, child, parent) fires. By design only the exit is bracketed — the existing transfer_task card remains the entry. Background agents and permanent handoffs never emit this event, so they're unaffected.
  2. Sidebar delegation breadcrumb. When delegation depth > 1, a compact root ▸ helper breadcrumb renders under the Agents title (each name in its accent color, middle elided to width), backed by an agentChain tracked in lockstep with the sidebar's sessionStack. Click-zone alignment is preserved.

The +N background agents count mentioned in the issue depends on the Phase 3 snapshot and is intentionally left as a TODO(#3103).

Screenshots

Sidebar breadcrumb while root waits on helper (it disappears once control returns):

phase2-breadcrumb

Return marker when the sub-agent hands control back:

phase2-return-marker

Tests

  • message — marker renders ↩ child → parent and is static/cacheable (not spinner-driven).
  • messagesAddDelegationReturn appends the marker.
  • page/chatAgentSwitching{false} inserts the marker; {true} / empty names do not.
  • sidebaragentChain stays balanced with sessionStack across push/pop/cancel/reset; breadcrumb renders only at depth > 1; click zones preserved.

task build / task lint clean; touched-package tests pass (same unrelated teamloader DMR failure as #3101).

Stack

Stacked on #3101; base for #3103. Tracking #3104.

Refs #3102


This is part 1 of 2 in a stack made with GitButler:

@docker-agent docker-agent 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.

Assessment: 🟢 APPROVE

Reviewed 9 changed files across the TUI delegation-return marker and sidebar breadcrumb implementation (Phase 2 of 3).

Drafter raised 5 hypotheses; verifier dismissed all 5:

# Severity File Hypothesis Verdict
1 Medium runtime_events.go L123 Missing guard for background/permanent handoffs before inserting DelegationReturn marker ✅ DISMISSED — runCollecting (background agents) explicitly does not emit AgentSwitchingEvent; only swapCurrentAgent (forwarding path) does
2 Medium sidebar.go L496 ResetStreamTracking() early-returns on sessionStack length, leaving agentChain stale ✅ DISMISSED — invariant len(agentChain) == len(sessionStack) is enforced by construction; empty stack implies empty chain
3 Medium sidebar.go L1277 ansi.Truncate fallback may corrupt ANSI escape codes ✅ DISMISSED — charmbracelet/x/ansi.Truncate is ANSI-aware by design; no escape-code corruption
4 Low sidebar.go L1367 Hard-coded tabHeaderLines=2 + skipLeadingBlock fragility ✅ DISMISSED — skipLeadingBlock handles variable-height breadcrumb blocks; constant is pre-existing
5 Low messages.go L1261 AddDelegationReturn accepts empty agent name strings ✅ DISMISSED — sole call site guards FromAgent != "" && ToAgent != "" before the call

The agentChain/sessionStack lockstep logic is sound. The breadcrumb render path correctly uses ANSI-aware width measurement and truncation throughout. The return marker is gated appropriately on the forwarding (not background) delegation path.

@aheritier aheritier marked this pull request as draft June 15, 2026 04:59
@aheritier aheritier added area/tui For features/issues/fixes related to the TUI kind/feat PR adds a new feature (maps to feat: commit prefix) labels Jun 15, 2026
@aheritier aheritier self-assigned this Jun 15, 2026
@aheritier aheritier added the status/in-progress For stuff that's being worked on label Jun 15, 2026
@aheritier aheritier added area/agent For work that has to do with the general agent loop/agentic features of the app and removed status/in-progress For stuff that's being worked on labels Jun 15, 2026
@aheritier aheritier marked this pull request as ready for review June 15, 2026 11:33

@docker-agent docker-agent 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.

Assessment: 🟢 APPROVE

The PR adds a delegation-return transcript marker and a sidebar delegation breadcrumb for nested sub-agent delegations. The implementation is clean and well-structured.

Key observations:

  • The AgentSwitching(false, ...) guard in runtime_events.go is correctly scoped — this event is only emitted from swapCurrentAgent's deferred closure, which is exclusively used for forwarded sub-agent runs (transfer_task / run_skill). No spurious markers can fire from permanent handoffs or background agents.
  • The agentChain / sessionStack pairing in sidebar.go is correctly maintained: both are pushed atomically in StreamStartedEvent, popped atomically in StreamStoppedEvent, and cleared atomically in ResetStreamTracking and StreamCancelledMsg. The invariant len(agentChain) == len(sessionStack) holds through all code paths.
  • The MessageTypeDelegationReturn render path in message.go is static/cacheable and avoids the spinner path, as stated.
  • Test coverage addresses the key edge cases: marker content, static/cacheable property, AddDelegationReturn appending behavior, AgentSwitching{true} not inserting a marker, empty-name guard, and agentChain balance across push/pop/cancel/reset.

No bugs found in the changed code.

@aheritier aheritier force-pushed the feat/tui-delegation-spinner-readability branch from cce5185 to e581cc1 Compare June 15, 2026 12:12
@aheritier aheritier force-pushed the feat/tui-delegation-return-marker branch 2 times, most recently from 3871fdb to af97d1c Compare June 15, 2026 12:36
@aheritier aheritier force-pushed the feat/tui-delegation-spinner-readability branch from a952e37 to 8ed8de7 Compare June 15, 2026 13:18
@aheritier aheritier force-pushed the feat/tui-delegation-return-marker branch from af97d1c to e19f53b Compare June 15, 2026 13:18
When a forwarded sub-agent (transfer_task / run_skill) handed control back
to its caller, the only cue was the next parent badge, so a run of
delegations read as one undifferentiated wall. Insert an explicit return
marker — "↩ child → parent ───" in the child's accent color — when
AgentSwitching(false, child, parent) fires, i.e. the deferred restore in
runForwarding. By design only the EXIT is bracketed; the existing
transfer_task card remains the entry. Background agents and permanent
handoffs never emit this event, so they are unaffected.

The marker is a static, cacheable MessageTypeDelegationReturn list item; it
is not spinner-driven.

Refs #3102
Track an agentChain alongside the sidebar's sessionStack — pushed on
StreamStarted, popped on StreamStopped, and reset on cancel and in
ResetStreamTracking — maintaining the invariant
len(agentChain) == len(sessionStack). When delegation depth exceeds 1,
render a compact breadcrumb (e.g. "root ⏵ librarian") under the Agents
title, each name in its accent color, eliding the middle to fit the width.

buildAgentClickZones skips the single-line breadcrumb block so per-agent
click rows stay aligned. The "+N background agents" count depends on the
Phase 3 snapshot and is left as a TODO (Appendix C.2).

Refs #3102
@aheritier aheritier force-pushed the feat/tui-delegation-spinner-readability branch from 8ed8de7 to 8ff2ff1 Compare June 18, 2026 08:49
@aheritier aheritier force-pushed the feat/tui-delegation-return-marker branch from e19f53b to 79ea210 Compare June 18, 2026 08:49
@dgageot

dgageot commented Jun 19, 2026

Copy link
Copy Markdown
Member

Review: bracket sub-agent delegations — return marker + sidebar breadcrumb

Nice additive change, well-scoped and well-tested. No blocking correctness or security issues — the core invariant (agentChain depth mirrors sessionStack) holds across push/pop/clear paths, and the new enum value is appended (no value shifting).

🔧 Should fix before merge

  1. run_skill scope mismatch. The description/comments suggest this covers both transfer_task and run_skill, but run_skill uses the forwarding path without SwitchCurrentAgent: true, so it never emits the AgentSwitchingEvent{Switching:false} return event → no return marker. Either adjust the wording to "agent switching / transfer_task only", or add explicit runtime support + tests for the skill path.

  2. Misleading root ⏵ root breadcrumb. Skill sub-sessions run under the same agent name, so the breadcrumb can render root ⏵ root while the transcript shows no return marker. Consider collapsing consecutive identical names, suppressing same-agent sub-sessions, or documenting that the breadcrumb tracks session nesting rather than strict agent-to-agent delegation.

💡 Polish / robustness

  1. Return marker not truncated. The rule width is clamped but the label isn't, so long agent names wrap on narrow terminals and break the single-line divider look. Use ansi.Truncate(..., "…") like the other message renderers.

  2. ResetStreamTracking asymmetry. It early-returns when sessionStack is empty, but agentChain is a separate slice. Make the reset unconditional for both slices (or return only when both are empty) to avoid brittle invariant coupling.

  3. Persistence. Return markers are live UI only and are lost on session reload. Probably intentional for an ephemeral nav cue — just confirming.

✅ Tests

Strong coverage (rendering, cacheability, event handling, chain/stack alignment, reset/cancel/load, click-zone alignment). Suggested additions:

  • Skill-path behavior (assert whether run_skill should/shouldn't emit a marker).
  • Duplicate-name breadcrumb (["root", "root"]).
  • Long-name / narrow-width return marker (no wrap, respects width).

Overall: please resolve the run_skill doc/behavior mismatch and decide on the same-agent breadcrumb before merge; the rest can be small follow-ups.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/agent For work that has to do with the general agent loop/agentic features of the app area/tui For features/issues/fixes related to the TUI kind/feat PR adds a new feature (maps to feat: commit prefix)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants