#23166: fix(agents): restore subagent announce chain from #22223
agents
maintainer
size: XL
Cluster:
Agent Messaging Enhancements
## Summary
Restores the exact subagent announce logic from PR #22223 (commit fe57bea08) which was overwritten by subsequent commits f555835b0 and 8178ea472.
## Problem
Two commits landed after #22223 merged and rewrote `subagent-announce.ts`, re-introducing the broken `send` path that bypasses the parent LLM:
- f555835b0 (Shadow): "Channels: add thread-aware model overrides" (+387/-144 lines)
- 8178ea472 (Onur): "feat: thread-bound subagents on Discord" (+279/-40 lines)
This caused:
- Raw "✅ Subagent X finished" messages appearing in channels instead of the parent LLM processing results
- Cron subagent announces routing to dead isolated sessions
- Nested subagent chains breaking at the top level
- Intermediate subagent responses leaking to channels before children complete
## Fix
Restored `src/agents/subagent-announce.ts` and `src/agents/subagent-registry.ts` to exactly match the working state from fe57bea08. Updated callers/tests to match the restored interface.
All subagent completions route through `method: "agent"` only. No `send` path.
## Testing verified
- Simple subagent ✓
- Nested subagent (depth 2) ✓
- Depth 3 chain ✓
- Parallel children (parent waits for both) ✓
- Cron targeted ✓
- Cron untargeted (legacy, no to/channel) ✓
- No leaked "✅ Subagent main finished" messages
## Related
- Fixes regression from #22099
- Restores behavior from #22223
- Closes #22099
<!-- greptile_comment -->
<h3>Greptile Summary</h3>
This PR reverts `subagent-announce.ts` and `subagent-registry.ts` to the working state from PR #22223 (commit `fe57bea08`), undoing changes introduced by two subsequent commits that re-introduced a broken `send` delivery path bypassing the parent LLM.
**Key changes:**
- Removes the `method: "send"` direct delivery path from `subagent-announce.ts`, ensuring all subagent completions route through `method: "agent"` only
- Removes `spawnMode` tracking from `SubagentRunRecord` in the registry (session-mode vs run-mode distinction for announce routing)
- Removes `subagent_delivery_target` hook integration and `resolveSubagentCompletionOrigin` from the announce flow
- Removes lifecycle hook emission (`emitSubagentEndedHookOnce`, `completeSubagentRun`) from the registry — the `subagent_ended` hook is no longer explicitly emitted from the registry module
- Inlines helper functions previously extracted into `subagent-registry-cleanup.ts`, `subagent-registry-completion.ts`, `subagent-registry-queries.ts`, and `subagent-registry-state.ts`
- Switches from `isDeliverableMessageChannel` to `isInternalMessageChannel` (semantically equivalent)
- Updates all test files to match the simplified interface
**Notable side-effects:**
- `emitLifecycleHooks: false` was removed from two `sessions.delete` calls (sweeper and post-announce cleanup), which changes behavior: lifecycle hooks will now fire during these deletions
- The inline `SubagentRunRecord` type in `subagent-registry.ts` now diverges from the version in `subagent-registry.types.ts` (which still includes `spawnMode`, `endedReason`, `endedHookEmittedAt`) — the store module still references the old type and sets `spawnMode` on disk-loaded records
<h3>Confidence Score: 3/5</h3>
- This PR restores known-working logic but introduces two behavioral side-effects (lifecycle hook emission on delete) that should be verified before merging.
- The core revert logic (removing the `send` path, routing all completions through `method: "agent"`) is well-motivated and addresses a real regression. However, the removal of `emitLifecycleHooks: false` from two `sessions.delete` calls changes behavior in a way that could cause duplicate farewell messages or unexpected hook side-effects for channel plugins. Additionally, the `SubagentRunRecord` type now diverges between `subagent-registry.ts` and `subagent-registry.types.ts`/`subagent-registry.store.ts`, creating maintenance risk. The extracted helper modules (`subagent-registry-cleanup.ts`, `subagent-registry-completion.ts`, etc.) are left orphaned without being deleted.
- Pay close attention to `src/agents/subagent-registry.ts` (lifecycle hook behavior change in sweeper) and `src/agents/subagent-announce.ts` (lifecycle hook behavior change in post-announce cleanup)
<sub>Last reviewed commit: cb9b0a5</sub>
<!-- greptile_other_comments_section -->
<!-- /greptile_comment -->
Most Similar PRs
#13105: fix: debounce subagent lifecycle events to prevent premature announ...
by mcaxtr · 2026-02-10
82.5%
#18468: fix(agents): prevent infinite retry loops in sub-agent completion a...
by BinHPdev · 2026-02-16
81.6%
#20268: feat(hooks): emit subagent:complete internal hook event
by AytuncYildizli · 2026-02-18
81.1%
#7584: Tests: align subagent announce wait expectations
by justinhuangcode · 2026-02-03
80.7%
#22982: fix: prevent stale threadId from routing subagent announces to wron...
by unboxed-ai · 2026-02-21
80.3%
#22719: fix(agents): make subagent announce timeout configurable (restore 6...
by Valadon · 2026-02-21
80.1%
#23229: fix(subagent-announce): gate queued delivery on channel deliverabil...
by mcinteerj · 2026-02-22
80.0%
#22407: fix: allow agent turn after subagent completion message delivery
by noodleprincss-ai · 2026-02-21
79.9%
#8471: fix(subagent): add defensive checks for undefined string fields
by adam-smeth · 2026-02-04
79.5%
#13303: feat(subagents): replace silent boolean with announce enum ('user'|...
by ivalsaraj · 2026-02-10
78.7%