feat(symphony): bridge dispatch to Archon workflow runs (Phase 3)#4
feat(symphony): bridge dispatch to Archon workflow runs (Phase 3)#4
Conversation
Phase 3 of the archon-symphony consolidation. Replaces the Phase 2 stub
dispatchIssue (which only logged and wrote workflow_run_id=NULL rows)
with a full launch path through Archon's existing workflow executor,
isolation resolver, and provider stack.
Sequence per Symphony dispatch:
- Hard-fail when no codebase is mapped (writes a status='failed' row,
no retry — config error per CLAUDE.md fail-fast).
- Resolve workflow definition via discoverWorkflowsWithConfig against
the codebase's default_cwd (home-scoped + project-scoped + bundled).
- Create a hidden web worker conversation, resolve a real worktree via
validateAndResolveIsolation.
- Pre-create the workflow_run row through createWorkflowDeps().store,
attach its id to symphony_dispatches before launch.
- Fire executeWorkflow(...) (fire-and-forget); terminal status comes
back through getWorkflowEventEmitter() — orchestrator subscribes once
in start() and translates workflow_completed / failed / cancelled
into DB updateStatus + state mutations + retry on failure.
Server bootstrap auto-boots the Symphony service when ~/.archon/
symphony.yaml exists (silent no-op otherwise). Adds
/api/symphony/{state, dispatches, dispatches/:id, dispatch, cancel,
refresh} via OpenAPIHono with Zod-validated request/response schemas.
Service is awaited in graceful shutdown before pool close.
reconcileOnStart() reads upstream workflow_run_status for in-flight
symphony_dispatches rows so a process crash mid-dispatch doesn't
lose terminal-state writes — never marks non-terminal upstream runs
failed by timer (CLAUDE.md "No Autonomous Lifecycle Mutation"
compliance).
Verified end-to-end via the Symphony Smoke Linear project: APP-293
created in Todo, polled within ~2s, dispatched to e2e-deterministic
(bash + bun + uv/Python script DAG with conditions and trigger_rule
joins), workflow ran in a real worktree under
~/.archon/workspaces/Ddell12/Cowork/worktrees/archon/, terminal event
landed on the orchestrator, both symphony_dispatches.status='completed'
and remote_agent_workflow_runs.status='completed' written.
Test coverage: 18 new tests across dispatcher.test.ts (5),
terminal-events.test.ts (5), reconcile.test.ts (4), plus 4 new
dispatches DB helper tests. Existing dispatch-loop and multi-tracker
tests rewritten to assert the real Phase 3 lifecycle. 61 symphony
tests pass (was 41 in Phase 2). Full repo `bun run validate` clean.
Phase 3 of docs/symphoney-legacy/plans/2026-04-30-archon-symphony-consolidation.md
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
Caution Review failedPull request was closed or merged during review 📝 WalkthroughWalkthroughThis PR integrates Symphony workflow orchestration with the server, adding optional Symphony service bootstrapping, workflow bridge components that dispatch issues to an Archon workflow engine, dispatch lifecycle management (pending/running/completed/failed), and comprehensive test coverage for reconciliation and terminal event handling. Changes
Sequence DiagramsequenceDiagram
actor Client
participant Server
participant SymphonyService
participant Orchestrator
participant WorkflowBridge
participant WorkflowEngine
participant EventEmitter
Client->>Server: POST /api/symphony/dispatch
Server->>SymphonyService: (service running)
SymphonyService->>Orchestrator: dispatchIssue(key)
Orchestrator->>WorkflowBridge: dispatchToWorkflow(input)
WorkflowBridge->>WorkflowBridge: Validate state→workflow
WorkflowBridge->>WorkflowBridge: Load codebase
WorkflowBridge->>WorkflowBridge: Resolve workflow definition
WorkflowBridge->>WorkflowBridge: Insert dispatch row (pending)
WorkflowBridge->>WorkflowBridge: Pre-create workflow_run
WorkflowBridge->>WorkflowBridge: Update dispatch (running)
WorkflowBridge->>WorkflowEngine: executeWorkflow (fire & forget)
WorkflowBridge-->>Orchestrator: DispatchOutcome (launched)
Orchestrator->>Orchestrator: Record workflow_run_id mapping
Orchestrator->>Orchestrator: Add to internalState.running
Orchestrator-->>SymphonyService: dispatch registered
SymphonyService-->>Server: ok
Server-->>Client: { ok: true, dispatch_key }
Note over WorkflowEngine: Workflow executes asynchronously
WorkflowEngine->>EventEmitter: emit workflow_completed(runId)
EventEmitter->>Orchestrator: (subscribed) workflow event
Orchestrator->>Orchestrator: Lookup dispatch_key from runId
Orchestrator->>Orchestrator: Update internalState
Orchestrator->>Orchestrator: Persist status to DB
Estimated Code Review Effort🎯 4 (Complex) | ⏱️ ~60 minutes Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Review rate limit: 0/1 reviews remaining, refill in 60 minutes.Comment |
Summary
Orchestrator.dispatchIssueas a stub that wrotesymphony_dispatchesrows withworkflow_run_id = NULLand never launched any work. Symphony was just a polling loop with no value-add over upstream Archon.IWorkflowStore.createWorkflowRun, attach run id to dispatch row, fireexecuteWorkflow(...)against a real worktree resolved throughvalidateAndResolveIsolation. Subscribes togetWorkflowEventEmitter()to translate terminal events into DB + state mutations + retry. Adds/api/symphony/*REST namespace and auto-boots the service when~/.archon/symphony.yamlexists.symphoney-codex/web/directory (Phase 5). This PR is only the bridge + routes + service-host wiring.UX Journey
Before
```
Linear/GitHub Symphony (Phase 2) Archon
───────────── ───────────────── ──────
issue created ──polled──▶ Orchestrator
insertDispatch (run_id=NULL)
log: dispatch_skipped
[end of road — no work happens]
```
After
```
Linear/GitHub Symphony (Phase 3) Archon
───────────── ───────────────── ──────
issue created ──polled──▶ Orchestrator
dispatchToWorkflow ───────────▶ [+] validateAndResolveIsolation
[+] worker conversation row
attachWorkflowRun ◀────────── [+] createWorkflowRun (pre-staged)
[+] executeWorkflow (fire-and-forget)
[+ event subscription]
◀────────────────────────────── workflow_completed | failed | cancelled
updateStatus + state mutation
```
REST surface:
```
client Archon server (with symphony.yaml present)
────── ──────────────────────────────────────────
GET /api/symphony/state ──▶ orchestrator.getSnapshotView()
GET /api/symphony/dispatches ──▶ listDispatches({status?, limit?})
POST /api/symphony/dispatch ──▶ orchestrator.requestImmediateDispatch(key)
POST /api/symphony/cancel ──▶ orchestrator.requestCancel(key) + cancelWorkflowRun
POST /api/symphony/refresh ──▶ orchestrator.requestRefresh() (coalesced)
```
Architecture Diagram
Before
```
@archon/symphony
├── orchestrator/
│ ├── orchestrator.ts (dispatchIssue is a stub)
│ ├── state.ts (RunningEntry has no workflow link)
│ └── retry.ts (unused)
├── tracker/{linear,github}/
└── db/dispatches.ts (insertDispatch / updateStatus / attachWorkflowRun)
@archon/server ──no symphony wiring──
@archon/web ──no symphony surface──
```
After
```
@archon/symphony
├── orchestrator/
│ ├── orchestrator.ts [
] dispatchIssue calls bridge; subscribes to event emitter;] RunningEntry adds dispatch_id + workflow_run_id│ │ reconcileOnStart hydrates from prior process; cancel
│ │ extends to upstream cancelWorkflowRun
│ ├── state.ts [
│ └── retry.ts (now wired by terminal-event handler)
├── workflow-bridge/ [+]
│ ├── types.ts [+] BridgeDeps narrow contract for injection
│ ├── dispatcher.ts [+] dispatchToWorkflow — full launch sequence
│ └── factory.ts [+] createProductionBridge — wires real Archon plumbing
├── db/dispatches.ts [
] add listDispatches, listInFlight, getDispatchByWorkflowRunId] accepts bridge; calls reconcileOnStart before start└── service.ts [
@archon/server [
]] maybeStartSymphony() boots when symphony.yaml exists├── index.ts [
├── routes/
│ ├── api.symphony.ts [+] registerSymphonyRoutes against SymphonyServiceHandle
│ └── schemas/
│ └── symphony.schemas.ts [+] Zod schemas with .openapi() annotations
```
Connection inventory:
@archon/symphony/orchestrator@archon/symphony/workflow-bridgedispatchToWorkflow@archon/symphony/workflow-bridge/factory@archon/core/orchestratorvalidateAndResolveIsolation@archon/symphony/workflow-bridge/factory@archon/core/workflows/store-adaptercreateWorkflowDeps@archon/symphony/workflow-bridge/factory@archon/core/db/{conversations,codebases}@archon/symphony/workflow-bridge/factory@archon/workflows/executorexecuteWorkflow(..., preCreatedRun)@archon/symphony/workflow-bridge/factory@archon/workflows/workflow-discovery@archon/symphony/orchestrator@archon/workflows/event-emittergetWorkflowEventEmitter().subscribe(...)for terminal events@archon/symphony@archon/workflows@archon/server@archon/symphony/api/symphony/*@archon/server/routes/api.symphony.ts@archon/symphony/db/dispatches@archon/symphony/orchestrator@archon/symphony/db/dispatchesLabel Snapshot
Change Metadata
Linked Issue
Validation Evidence (required)
```bash
bun run validate
→ check:bundled (clean) + 11-package type-check (clean) + lint --max-warnings 0 (clean)
+ format:check (clean) + per-package test runs (all green)
bun --filter @archon/symphony test
→ 61 pass, 0 fail across 9 files (was 41 pass / 6 files in Phase 2)
bun --filter @archon/symphony exec tsc --noEmit
→ clean
```
Live e2e dogfood (2026-04-30, against Symphony Smoke Linear project)
Repointed `~/.archon/symphony.yaml` at registered codebase `Ddell12/Cowork` (id `a091411a7c31aa44e6cd6dd64299643e`, cwd `/Users/desha/Cowork`) with `state_workflow_map: { Todo: e2e-deterministic }`. Created Linear APP-293 in Todo via GraphQL.
```
[T+0.0s] symphony.service_started bridge_wired:true workflows:1
[T+0.0s] symphony.routes_registered /api/symphony/*
[T+0.0s] server_listening port:3090
[T+1.9s] symphony.dispatch_launched dispatch_id=4243863a879c8bcde70509361108a3e7
dispatch_key=linear:APP-293
workflow=e2e-deterministic
cwd=~/.archon/workspaces/Ddell12/Cowork/worktrees/archon/thread-a43dfa3b
workflow_run_id=ba375719f09b65d8b70ae12367b741c3
[T+2.0s] symphony.workflow_terminal status=completed error=null
[T+69.2s] symphony.service_stopped (SIGTERM)
```
Cross-checked at every layer:
Security Impact (required)
Compatibility / Migration
Upgrade steps:
Pull `dev`.
Edit `~/.archon/symphony.yaml` to add a `codebases:` entry per active tracker repo. Example:
```yaml
codebases:
repository: /
codebase_id: <archon codebase id from /api/codebases>
```
Restart the server. Symphony auto-boots when the config file exists.
Human Verification (required)
What was personally validated beyond CI:
Side Effects / Blast Radius (required)
Rollback Plan (required)
Risks and Mitigations
Summary by CodeRabbit
Release Notes
New Features
Tests