feat(tui): bracket sub-agent delegations — return marker + sidebar breadcrumb (#3102)#3116
Conversation
docker-agent
left a comment
There was a problem hiding this comment.
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.
docker-agent
left a comment
There was a problem hiding this comment.
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 inruntime_events.gois correctly scoped — this event is only emitted fromswapCurrentAgent'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/sessionStackpairing insidebar.gois correctly maintained: both are pushed atomically inStreamStartedEvent, popped atomically inStreamStoppedEvent, and cleared atomically inResetStreamTrackingandStreamCancelledMsg. The invariantlen(agentChain) == len(sessionStack)holds through all code paths. - The
MessageTypeDelegationReturnrender path inmessage.gois static/cacheable and avoids the spinner path, as stated. - Test coverage addresses the key edge cases: marker content, static/cacheable property,
AddDelegationReturnappending behavior,AgentSwitching{true}not inserting a marker, empty-name guard, andagentChainbalance across push/pop/cancel/reset.
No bugs found in the changed code.
cce5185 to
e581cc1
Compare
3871fdb to
af97d1c
Compare
a952e37 to
8ed8de7
Compare
af97d1c to
e19f53b
Compare
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
8ed8de7 to
8ff2ff1
Compare
e19f53b to
79ea210
Compare
Review: bracket sub-agent delegations — return marker + sidebar breadcrumbNice additive change, well-scoped and well-tested. No blocking correctness or security issues — the core invariant ( 🔧 Should fix before merge
💡 Polish / robustness
✅ TestsStrong coverage (rendering, cacheability, event handling, chain/stack alignment, reset/cancel/load, click-zone alignment). Suggested additions:
Overall: please resolve the |
Summary
Makes nested / sequential delegations legible. Builds on #3101.
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 whenAgentSwitching(false, child, parent)fires. By design only the exit is bracketed — the existingtransfer_taskcard remains the entry. Background agents and permanent handoffs never emit this event, so they're unaffected.root ▸ helperbreadcrumb renders under the Agents title (each name in its accent color, middle elided to width), backed by anagentChaintracked in lockstep with the sidebar'ssessionStack. Click-zone alignment is preserved.Screenshots
Sidebar breadcrumb while
rootwaits onhelper(it disappears once control returns):Return marker when the sub-agent hands control back:
Tests
message— marker renders↩ child → parentand is static/cacheable (not spinner-driven).messages—AddDelegationReturnappends the marker.page/chat—AgentSwitching{false}inserts the marker;{true}/ empty names do not.sidebar—agentChainstays balanced withsessionStackacross push/pop/cancel/reset; breadcrumb renders only at depth > 1; click zones preserved.task build/task lintclean; touched-package tests pass (same unrelatedteamloaderDMR 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: