6.8 KiB
Milestone B DagCoordinator Enqueue-Only Adapter Design
Related tasks: FN-4491, FN-4490, FN-4487, FN-4471, governance gate FN-4359.
See also: Schema migration plan · Implementation checklist · ADR v1
Governance gate
Per AGENTS.md reliability governance policy:
"Reliability mechanism changes are currently under freeze pending FN-4359 governance hardening; treat new reliability-layer behavior changes as blocked unless explicitly approved in task scope."
This design is architecture-only. Milestone B implementation requires freeze lift or explicit carve-out.
Adapter contract
ADR traceability: derived from ADR-0001 Decision (enqueue-only scheduling boundary) and Consequences #4 (no merge-path changes).
Proposed file: packages/engine/src/dag-coordinator.ts.
DagCoordinator is enqueue-only:
- evaluates node readiness from DAG tables.
- creates/unblocks tasks through
TaskStorepublic APIs. - triggers wakeups through
HeartbeatTriggerSchedulersurface.
DagCoordinator MUST NOT:
- call scheduler internals in
packages/engine/src/scheduler.ts. - mutate
AgentSemaphoredirectly (packages/engine/src/concurrency.ts). - bypass checkout leasing / 409 conflict semantics.
Proposed methods:
startDagRun(spec: DagRunSpec): Promise<{ runId: string }>onTaskMoved(event: { taskId: string; from: string; to: string; status?: string }): Promise<void>onTaskUpdated(event: { taskId: string; status?: string; column?: string }): Promise<void>cancelDagRun(runId: string, reason: string): Promise<void>tick(runId?: string): Promise<void>(optional bounded reconciliation pass)
Event sources
Existing observable events from current engine/task store surfaces:
task:createdtask:movedtask:updated
Evidence in source scan:
packages/engine/src/scheduler.tssubscribes totask:created,task:moved,task:updated.packages/engine/src/executor.tssubscribes totask:moved,task:updated.packages/engine/src/project-manager.tsforwards runtimetask:created,task:moved,task:updated.
No dedicated task:failed event was confirmed in existing public event vocabulary; failure detection should currently derive from task:updated status/column state.
Required engine seams (additive only)
- Additive coordinator wiring point in project engine bootstrap to register coordinator listeners without changing scheduler dispatch semantics.
- Optional narrow helper on task store/runtime event payloads for explicit terminal reason mapping (if current
task:updatedpayload lacks enough fidelity). - Feature-flag check seam (
experimentalDagCoordinator) at wiring boundary so default behavior is unchanged.
All seams must be additive and behavior-preserving while FN-4359 freeze is active.
Concurrency and event-loop safety
- Coordinator logic must be async and non-blocking.
- No
execSyncin coordinator paths (aligns with AGENTS.md Engine Process Rules). - Any external command path (if ever needed) must use async
exec/promisify(exec)with timeout. - Readiness evaluation should use bounded batches to avoid monopolizing the single Node event loop.
Merge-path integration (explicit invariant)
The coordinator does not change merger behavior. It does not alter:
- post-squash audit,
- file-scope invariant,
- gitignored-path guard,
- one-task-one-branch execution/merge mapping.
Failure handling map (from FN-4490 contract)
- Per-node failure → mark
dag_node.status=failed; emitdag:node:fail; evaluate run abort/continue policy. - Edge failure/dependency unsatisfied → keep downstream
blocked/pending; emitdag:node:blockedwith reason. - Partial-DAG abort → set run
aborted; mark remaining non-terminal nodesskipped/blocked; emitdag:run:abort. - Whole-DAG cancel → mark run
cancelled; mark non-terminal nodescancelled; emitdag:run:abortwith cancel reason. - Retry-exhausted → defer to existing executor retry exhaustion path (incl. FN-4398
retriesBurnedsemantics); coordinator records terminal node outcome, no custom retry engine. - Governance-blocked (e.g., FN-3973/FN-4488 policy denial) → emit
dag:node:blockedwith policy reason; do not retry automatically.
Observability and audit
Logger prefix: [dag-coordinator] (aligned with existing subsystem prefix style in packages/engine/src/logger.ts).
Minimum events (FN-4490 contract):
dag:run:startdag:node:enqueuedag:node:completedag:node:faildag:run:completedag:run:abort- additive:
dag:node:blocked
Each event payload should include at least: runId, dagRunId, nodeId, taskId?, status, reasonCode?, timestamp.
Run-audit linkage:
- database-domain audit entries for DAG row transitions.
- git-domain unchanged (normal task flow only).
- filesystem-domain only for normal task artifacts, not DAG-specific side channels.
Multi-project scope
ADR traceability: aligns with ADR-0001 Consequences #3 (single-project prototype scope).
Decision: per-project coordinator instance.
Rationale (from docs/multi-project.md):
- task/state persistence is project-local in
.fusion/fusion.db. - central DB manages registry/global coordination, not per-project task execution state.
- one coordinator per project runtime keeps ownership boundaries consistent with existing runtime model.
Test strategy for Milestone B implementor
- Unit tests (
packages/engine/src/__tests__/) for readiness evaluation, enqueue behavior, and event emission. - Integration tests with real SQLite project DB (same init/migration path), mock heartbeat scheduler trigger points, and fixture DAG specs.
- Use existing executor helper conventions in
packages/engine/src/__tests__/executor-test-helpers.tswhen synthesizing worktree/task execution preconditions. - Add/update interaction tests in
packages/engine/src/__tests__/reliability-interactions/for scheduler/executor/self-healing/restart-recovery adjacency.
Out of scope
- Dashboard DAG UX/product surfaces (deferred to FN-4492 / Milestone C).
- Cross-project or cross-node DAG execution.
- Time-travel/replay engine.
- Autoscaling/capacity orchestration redesign.
Open questions
- Should coordinator reconcile missed events solely via periodic
tick()or rely on exhaustive event subscriptions plus startup scan? - Where should DAG run initiation be invoked from (API route, workflow step hook, mission loop) for minimal coupling?
- What is the minimal policy reason-code taxonomy for
dag:node:blockedto support diagnostics without new reliability behavior? - Should node completion depend strictly on task column transitions (
done) or include additional status fields for retry-exhausted terminal mapping?