refactor(agents): route dispatch through AgentDispatchPort (#519) #533
No reviewers
Labels
No labels
area:agents
area:dashboard
area:database
area:design
area:design-review
area:flows
area:infra
area:meta
area:security
area:sessions
area:webhook
area:workdir
security
type:bug
type:chore
type:meta
type:user-story
No milestone
No project
No assignees
2 participants
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
charles/claude-hooks!533
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "boss/519"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Closes #519
Introduce
AgentDispatchPort(infrastructure/agents/) with two adapters:DefaultAgentDispatchwrapsrunAgentTaskfor container-mode workers,HostContainerLifecyclewrapsrunForemanTurnfor the foreman singleton, and aHostAwareAgentDispatchcomposite picks between them offconfig.host_mode. The worker registry now dispatches through the port factory and no longer importsrunAgentTask/runForemanTurn/ forgejo config helpers inline.runAgentTaskis@internal; an import-guard test enforces that no production code outsideinfrastructure/agents/imports it directly.Test plan
bun x tsc --noEmitclean across all four workspacesbiome check .clean (only 2 pre-existing infos in unrelated files)buildAgentEnv > FORGE_TYPE env wiring (MF-4)tests are pre-existing onmainand unrelatedagent-dispatch-port.test.tscovers routing, the test seam, and the import-guardPort wiring is correct and all ACs from #519 are satisfied.
runAgentTaskis now@internal, import-guard test confirms the only production callsite isdefault-agent-dispatch.ts, andregistry.tsdispatch goes cleanly throughgetAgentDispatchPort().dispatch().HostAwareAgentDispatchrouting byhost_modeis correct; theas ForemanTaskRequestcast inHostContainerLifecycleis safe given the routing invariant. CI green, 5 new tests cover routing + seam + guard.Rebase abandoned — PR design invalidated by parallel work on
main.Two unresolvable architectural collisions surfaced during
git rebase origin/main:AgentDispatchPortalready exists onmainatapps/server/src/infrastructure/agent/agent-dispatch-port.ts(singularagent/) with incompatible semantics:dispatch()returns{ taskId: string }(enqueue), whereas this PR defines it underinfrastructure/agents/returningPromise<TaskResult>(inline await of the SDK call). Same name, different layer.HostContainerLifecycleexists onmainas aContainerLifecyclePort(low-level:ensureReady/buildExecEnv/buildExecArgs/release). This PR introduces a class with the same name as anAgentDispatchPortwrappingrunForemanTurn. Add/add conflict with no merge that preserves both meanings.mainhas landedb398770(AgentDispatchPort),a92edf1(ContainerLifecyclePort + Docker/host adapters),ef7b74e(McpRegistryPort),f05eee6(ClaudePort migration),adcdbee(per-call forge-token override) — splitting the same goal across four finer-grained ports. Per the comment indefault-agent-dispatch.ts: "Production wiring connects this torunAgentTaskonce the full AgentDispatchPort migration is complete in M25." Main is mid-stream on the same refactor with a different design.Recommend closing this PR and re-opening the registry-side wiring as part of the M25 series on top of main's port set.
Stopping rebase — PR is fully superseded by
main.git diff origin/main..origin/boss/519is empty: the AgentDispatchPort work shipped via the follow-on stack already onmain(b398770domain interface,a92edf1ContainerLifecyclePort,ef7b74eMcpRegistryPort,f05eee6ClaudeAgentPort migration / #529,adcdbeeper-call forge-token override). Rebasinge61193contomainwould drop to an empty commit.Recommend closing this PR — no force-push performed.
Rebase aborted — design invalidated by
main.Since this PR was opened, the M25 series has landed on main (#519's sibling commits —
b398770,a92edf1,ef7b74e,f05eee6,adcdbee) and introduced three names that collide with this PR:AgentDispatchPort— main has two:domain/ports/agent-dispatch-port.ts(high-level domain port withdispatch/steer/kill/status) andinfrastructure/agent/agent-dispatch-port.ts(low-level token-resolution port withMcpRegistryPort+ForgePortFactory). This PR'sinfrastructure/agents/agent-dispatch-port.tsis a third, differently-shaped port (dispatch(opts) → Promise<TaskResult>wrappingrunAgentTask/runForemanTurn).DefaultAgentDispatch— main's (infrastructure/agent/default-agent-dispatch.ts) resolves a forge token and delegates to an injectedAgentRunner. This PR's wrapsrunAgentTaskdirectly.HostContainerLifecycle— exact same path (infrastructure/agents/host-container-lifecycle.ts). Main's implementsContainerLifecyclePort(no-op container lifecycle for host-mode agents). This PR's implementsAgentDispatchPortwrappingrunForemanTurn. Add/add conflict on identical filename, different concept.The original goal — "registry should not import
runAgentTask/runForemanTurndirectly" — is still valid (main'sregistry.tsline 290 still has the inlinehost_modebranch +runForemanTurncall). But this PR's implementation needs to be rebuilt on top of main's existingAgentDispatchPort/ContainerLifecyclePort/McpRegistryPortrather than introducing a parallel third port.Recommend: close and re-cut against current main using the existing M25 ports. Happy to take the redesigned issue if reopened.
Rebase on
origin/mainaborted — design invalidated by main.AgentDispatchPort: main has it atdomain/ports/agent-dispatch-port.tsas a high-level domain port (AgentDispatchRequest/AgentDispatchHandleshape, evolved byadcdbeefor per-call forge-token override). This PR addsinfrastructure/agents/agent-dispatch-port.tswith an unrelated low-level shape (config/abort/onSteer*callbacks). Same name, different layer, incompatible contracts.infrastructure/agents/host-container-lifecycle.ts: add/add conflict. Main has it as aContainerLifecyclePortadapter (a92edf1, with companion test). The PR adds a different file at the same path implementing the (now-obsolete) low-levelAgentDispatchPort.domain/dispatch/registry.ts: main still callsrunAgentTask/runForemanTurninline — the wiring step the PR aimed at is unfinished, but the port main intends to wire is the higher-level domain port, not the one this PR defines.Suggested path forward: close this PR and re-cut against
domain/ports/agent-dispatch-port.tsonceContainerLifecyclePort+McpRegistryPortadapters are in place — the registry-level wiring belongs in that follow-up.Rebase aborted — PR design invalidated by main.
AgentDispatchPortalready exists on main atapps/server/src/domain/ports/agent-dispatch-port.ts(b398770) with an incompatible shape: returnsAgentDispatchHandlefor async tracking, separatesteer/kill/statusmethods, domain-pure types. This PR introduces a parallel port atinfrastructure/agents/returningPromise<TaskResult>synchronously.infrastructure/agents/host-container-lifecycle.tsis an add/add collision: main's class implementsContainerLifecyclePort(ensureReady/buildExecEnv/buildExecArgs/release); this PR's class implementsAgentDispatchPort(singledispatchmethod). Same path, same name, different abstraction.onSDKMessagecallback conflicts with main's NF-9 migration toonTaskEvent/TaskEvent(SDKMessage no longer leaks past the runner).Re-doing the wiring on top of main's existing
AgentDispatchPort+ContainerLifecyclePortis a fresh implementation, not a rebase. Recommend closing this PR and opening a new one that wires the registry through the existingdomain/ports/agent-dispatch-port.ts.Aborted rebase on
origin/main— design invalidated.Since this PR was authored, main landed an alternative decomposition along the same seam:
b398770/1fadd4b—AgentDispatchPortdomain interface atdomain/ports/agent-dispatch-port.ts(different shape:dispatch → AgentDispatchHandle, plussteer/kill/status).a92edf1—ContainerLifecyclePort+DockerContainerLifecycle/HostContainerLifecycleadapters.ef7b74e—McpRegistryPort+DefaultMcpRegistry.f05eee6—ClaudeAgentPortmigration in agent-runner + foreman.adcdbee— per-call forge-token override on the canonical port.This PR's
infrastructure/agents/agent-dispatch-port.ts(typeddispatch → TaskResult) clashes with main's domain-levelAgentDispatchPort, and this PR'sHostContainerLifecycle(anAgentDispatchPort) clashes with main'sHostContainerLifecycle(aContainerLifecyclePort). Same names, incompatible shapes. The rebase produced two unresolvable conflicts (registry.ts,host-container-lifecycle.ts) that can't be reconciled without rewriting the PR against the new port topology.Closing-of-#519 is now on main's roadmap as the
DefaultAgentDispatchadapter that orchestrates the three decomposed ports — the doc-comment indomain/ports/agent-dispatch-port.tscalls it out as a separate follow-up. Recommend closing this PR and reopening #519 against the new design.e61193c61481ee636e89