Architecture Finding
Type: anti-pattern / tech-debt (god object, setter-injection temporal coupling)
Affected area: src/pkg/agent/manager.go (type Manager, NewManager, the 24 Set* mutators)
Manager has grown into the process-wide god object for agent lifecycle:
- 10,167 lines in one file, 287 top-level funcs, 179 methods on
*Manager.
- The struct carries 20 func-typed callback/resolver fields (
bobAPIKeyResolver, linearCredentialResolver, explainModeDefaultResolver, kickObserver, inferenceRouteCallback, persistPauseCallback, recordPromptCallback, sandboxAuditCallback, paneCapture, visiblePaneCapture, sessionAttached, sendLiteralForAgent, sendKeysForAgent, promptDismissSleep, captureFullLogFn, clearHistoryFn, …) wired post-construction through 24 Set* methods instead of constructor parameters or interfaces.
- Only ~5 of those callback reads are nil-guarded, so correctness depends on undocumented call-order contracts between
NewManager and the Set* calls in cmd/hive wiring (temporal coupling; a missed setter is a runtime nil-deref, not a compile error).
- Concurrency is funneled through a single
m.mu used at 230 sites across unrelated concerns (credential resolution, tmux pane IO, kick delivery, pause bookkeeping), making lock-scope reasoning and future decomposition progressively harder — several fields already escaped to atomic.Pointer precisely to dodge deadlocks, as the in-file comments admit.
Impact
- Every new agent-runtime feature lands in this one file/type; merge conflicts and review blast radius grow superlinearly.
- The unguarded callback fields make startup wiring fragile: reordering
cmd/hive initialization can panic only at kick time.
- The single mutex couples slow tmux/pane IO with hot state reads; the ad-hoc
atomic.Pointer escape hatches show the lock has already stopped scaling.
- Testing requires poking unexported func fields directly (30+ test-file assignments), locking tests to implementation details.
Recommendation
Phased, behavior-preserving decomposition (each phase independently reviewable):
- Extract a
TerminalSession interface for the 8 tmux/pane fields (paneCapture, visiblePaneCapture, sessionAttached, sendLiteralForAgent, sendKeysForAgent, promptDismissSleep, captureFullLogFn, clearHistoryFn) with the current funcs as the default impl and a fake for tests (~16 prod call sites, mostly manager.go + kick_logs.go).
- Group credential/backend resolvers (
bobAPIKeyResolver, linearCredentialResolver, bobKeySourceResolver, explainModeDefaultResolver, copilot/claude token state) into a CredentialSource value passed to NewManager, eliminating their Set* mutators and the temporal coupling.
- Split observer callbacks (
kickObserver, pauseObserver, sandboxAuditCallback, recordPromptCallback, inference-route pair) into a nil-safe Hooks struct with no-op defaults.
- Only after 1–3: split
manager.go by concern into sibling files (kick delivery, pause state, sandbox, backends).
No behavior change in any phase; each is compile-and-test verifiable.
Filed by architect agent (ACMM L5 — hold-gated mode)
🐝 Hive Agent: architect | Instance: hosted-available-oke-11-placeholder-r05x | SHA: unknown
— hive: agent=architect backend=copilot model=claude-opus-4-6
Architecture Finding
Type: anti-pattern / tech-debt (god object, setter-injection temporal coupling)
Affected area:
src/pkg/agent/manager.go(typeManager,NewManager, the 24Set*mutators)Managerhas grown into the process-wide god object for agent lifecycle:*Manager.bobAPIKeyResolver,linearCredentialResolver,explainModeDefaultResolver,kickObserver,inferenceRouteCallback,persistPauseCallback,recordPromptCallback,sandboxAuditCallback,paneCapture,visiblePaneCapture,sessionAttached,sendLiteralForAgent,sendKeysForAgent,promptDismissSleep,captureFullLogFn,clearHistoryFn, …) wired post-construction through 24Set*methods instead of constructor parameters or interfaces.NewManagerand theSet*calls incmd/hivewiring (temporal coupling; a missed setter is a runtime nil-deref, not a compile error).m.muused at 230 sites across unrelated concerns (credential resolution, tmux pane IO, kick delivery, pause bookkeeping), making lock-scope reasoning and future decomposition progressively harder — several fields already escaped toatomic.Pointerprecisely to dodge deadlocks, as the in-file comments admit.Impact
cmd/hiveinitialization can panic only at kick time.atomic.Pointerescape hatches show the lock has already stopped scaling.Recommendation
Phased, behavior-preserving decomposition (each phase independently reviewable):
TerminalSessioninterface for the 8 tmux/pane fields (paneCapture,visiblePaneCapture,sessionAttached,sendLiteralForAgent,sendKeysForAgent,promptDismissSleep,captureFullLogFn,clearHistoryFn) with the current funcs as the default impl and a fake for tests (~16 prod call sites, mostlymanager.go+kick_logs.go).bobAPIKeyResolver,linearCredentialResolver,bobKeySourceResolver,explainModeDefaultResolver, copilot/claude token state) into aCredentialSourcevalue passed toNewManager, eliminating theirSet*mutators and the temporal coupling.kickObserver,pauseObserver,sandboxAuditCallback,recordPromptCallback, inference-route pair) into a nil-safeHooksstruct with no-op defaults.manager.goby concern into sibling files (kick delivery, pause state, sandbox, backends).No behavior change in any phase; each is compile-and-test verifiable.
Filed by architect agent (ACMM L5 — hold-gated mode)
🐝 Hive Agent:
architect| Instance:hosted-available-oke-11-placeholder-r05x| SHA:unknown— hive: agent=architect backend=copilot model=claude-opus-4-6