Flightdeck is a multi-agent orchestration engine that coordinates AI coding agents through a task DAG (directed acyclic graph) with dependency resolution, role-based access control, and governance profiles. Agents communicate with Flightdeck exclusively via MCP (Model Context Protocol) tools, while their lifecycle is managed through ACP (Agent Client Protocol). The system persists all state in a single SQLite file, making it self-contained and easy to deploy.
flightdeck-2/
├── packages/
│ ├── shared/ Branded types, IDs, state machine transitions (shared by all packages)
│ ├── server/ Core orchestration engine, MCP server, CLI, REST API, WebSocket server
│ ├── web/ React web UI (dashboard, task board, chat)
│ ├── vscode/ VS Code extension
│ └── tui/ Terminal UI
├── pnpm-workspace.yaml
└── package.json (root scripts: test, build, cli, mcp)
packages/shared — Domain types (Task, Agent, Message, Decision), branded ID constructors (taskId(), agentId()), and the data-driven state machine (transition() function that returns { state, effects }).
packages/server — The heart of Flightdeck. Contains the facade, DAG, orchestrator, MCP server, ACP adapter, governance engine, role system, storage, CLI, REST API, and WebSocket server.
packages/web — React-based dashboard for monitoring tasks, agents, costs, and chatting with the lead agent.
packages/vscode — VS Code extension for IDE integration.
packages/tui — Terminal UI for monitoring from the command line.
┌─────────────────────────────────────────────────────────────────┐
│ User Interfaces │
│ CLI (/cli) Web UI (/web) VS Code (/vscode) TUI (/tui)│
└──────────┬──────────┬──────────────────────────────────────────┘
│ │
▼ ▼
┌──────────────────────────────┐
│ REST API + WebSocket │ ← HTTP/WS for web, CLI for terminal
│ (api/ + ws.ts) │
└──────────┬───────────────────┘
│
▼
┌──────────────────────────────┐ ┌─────────────────────────┐
│ Flightdeck Facade │────▶│ LeadManager │
│ (facade.ts) │ │ - Spawns Lead via ACP │
│ One instance per project │ │ - Steers Lead on events │
│ Wires all subsystems │ │ - Heartbeat timer │
└──────┬───────────────────────┘ └────────┬────────────────┘
│ │
│ ▼
│ ┌─────────────────────┐
│ │ AcpAdapter │
│ │ (ACP Client) │
│ │ - spawn() → process │
│ │ - steer() → prompt │
│ │ - kill() → cleanup │
│ │ Provides fs+terminal │
│ │ to agents │
│ └────────┬────────────┘
│ │ stdin/stdout ndjson
│ ▼
│ ┌─────────────────────┐
│ │ Agent Process │
│ │ (e.g. Claude Code, │
│ │ Codex CLI) │
│ │ │
│ │ MCP Client ────────────┐
│ └─────────────────────┘ │
│ │
▼ ▼
┌──────────────────┐ ┌──────────────┐ ┌─────────────────────────┐
│ TaskDAG │ │ Governance │ │ MCP Server │
│ - State machine │ │ Engine │ │ (mcp/server.ts) │
│ - Dependency │ │ - Profiles: │ │ 40+ tools exposed to │
│ resolution │ │ autonomous │ │ agents: task_*, agent_*,│
│ - Side effects │ │ collab. │ │ msg_*, memory_*, etc. │
│ - Compaction │ │ supervised │ │ Role-filtered per agent │
│ - Sub-tasks │ │ - Gates │ └────────────┬────────────┘
└────────┬─────────┘ │ - Escalation │ │
│ └──────────────┘ │
│ │
▼ ▼
┌─────────────────────────────────────────────────────────┐
│ SqliteStore (storage/SqliteStore.ts) │
│ Single file: state.sqlite │
│ Tables: tasks, agents, cost_entries, messages, threads, │
│ file_locks, activity_log, message_queue │
│ ORM: drizzle-orm + better-sqlite3 │
└─────────────────────────────────────────────────────────┘
┌────────────────────────────┐
│ Orchestrator (tick loop) │
│ Every 5 min (configurable)│
│ 1. Promote pending→ready │
│ 2. Process completions │
│ 3. Detect stalled agents │
│ 4. Auto-assign ready tasks│
│ 5. Check budget limits │
│ 6. Check spec completions │
│ 7. Compact old tasks │
│ 8. Broadcast via WS │
└────────────────────────────┘
Agents interact with Flightdeck exclusively through MCP tools (flightdeck_task_claim, flightdeck_task_submit, etc.). This means any agent runtime that supports MCP can plug into Flightdeck — no custom command protocol needed. The MCP server is injected into each agent's session at spawn time via ACP's mcpServers parameter.
Flightdeck acts as an ACP Client, while agents (Claude Code, Codex CLI, etc.) are ACP Agents. The ACP protocol handles:
- Spawning:
connection.newSession()with MCP server injection - Prompting:
connection.prompt()for steering agents with context - Session loading:
connection.loadSession()for resuming previous sessions - Capabilities: Flightdeck provides
fs(read/write files) andterminal(spawn/manage shells) capabilities to agents
The AcpAdapter includes a prompt queue system — if a prompt is in-flight, subsequent messages are queued and merged into a single follow-up prompt.
All state lives in a single state.sqlite file per project. No external database server needed. The schema uses drizzle-orm with better-sqlite3 and includes auto-migration for schema evolution. Tables: tasks, agents, cost_entries, messages, threads, file_locks, activity_log, message_queue.
Each agent role sees only the MCP tools it needs (toolPermissions.ts). Seven roles are defined:
| Role | Key Permissions |
|---|---|
| lead | Plan review/approve, decisions, escalate to human, suggestions, channel batch subscribe, memory, comms |
| director | Task CRUD, agent spawn/terminate/hibernate/wake, specs, models, cron, decisions, channel batch subscribe, comms |
| worker | Task submit/fail/cancel/resume, task comment, file locks, learnings, comms |
| reviewer | Task complete/fail, decisions, file locks, comms |
| product-thinker | Specs (read), decisions, memory, comms |
| qa-tester | Task submit/fail/resume, learnings, memory, comms |
| tech-writer | Task submit/fail, specs (read), memory, comms |
| scout | Read-only (tasks, specs, search, decisions, learnings), suggestions |
All roles (except scout) share a common comms toolset: send, read, list_channels, subscribe, unsubscribe, get_message, channel_info.
Roles are defined as Markdown files with YAML frontmatter (roles/defaults/*.md), loaded by RoleRegistry. Projects can override roles with custom definitions.
Tasks follow a strict state machine defined in packages/shared via the transition() function. Valid states:
pending → ready → running → in_review → done
↓ ↓
paused failed → ready (retry)
↓
running
ready → gated (governance hold)
any → cancelled / skipped
State transitions emit side effects (typed union SideEffect):
resolve_dependents— promote dependent tasks to readyblock_dependents— block downstream tasks on failurespawn_reviewer— auto-spawn reviewer agent for in_review tasksescalate— notify lead of issuesnotify_agent/update_dag/log_decision
The TaskDAG processes local effects (dependency resolution) and delegates external effects to the Orchestrator via an effect handler.
Three profiles control how much autonomy agents have:
autonomous— Agents auto-approve high-confidence reversible decisions; only low-confidence or irreversible decisions need reviewcollaborative— Architecture and API design decisions require human approval; implementation decisions are loggedsupervised— All decisions require human approval; task starts are gated (except reviewers)
The GovernanceEngine evaluates decisions, gates task starts, checks cost thresholds, and manages approval workflows.
-
Creation — Lead or director calls
flightdeck_declare_tasksvia MCP. Tasks are inserted into SQLite with stateready(no deps) orpending(has deps). -
Dependency Resolution — Orchestrator tick promotes
pending→readywhen all dependencies reachdone/skipped/cancelled. TheTaskDAG.resolveReady()method walks the adjacency graph. -
Assignment — Orchestrator auto-assigns ready tasks to idle agents matching the required role. If governance gates the task (supervised mode), it moves to
gatedinstead. -
Execution — Worker agent calls
flightdeck_task_claim→ state becomesrunning. Agent does work using its own tools + Flightdeck-provided fs/terminal. Callsflightdeck_task_submitwith a claim summary → state becomesin_review. -
Review — If verification is enabled, the
spawn_reviewereffect triggers a reviewer agent. Reviewer callsflightdeck_task_complete(pass) orflightdeck_task_fail(reject). If disabled, orchestrator auto-completes. -
Completion —
donestate triggersresolve_dependentseffect, which promotes downstream tasks. After a configurable TTL (default 24h), completed tasks are compacted to save context. -
Failure Handling — Failed tasks can be retried up to
maxRetries(default 3). After exhausting retries, the Lead is steered with atask_failureevent.
| Directory | Responsibility |
|---|---|
facade.ts |
High-level API wrapping all subsystems; one instance per project |
dag/ |
Task DAG with adjacency graph, dependency resolution, topological sort, compaction, sub-tasks |
orchestrator/ |
Tick loop for task promotion, auto-assignment, stall detection, budget checks, compaction |
lead/ |
Lead agent lifecycle, event-driven steering, heartbeat system, director management |
agents/ |
ACP adapter (spawn/steer/kill), agent manager, session management, model tier registry |
mcp/ |
MCP server with 40+ tools, role-based tool filtering, Zod schema validation |
governance/ |
Governance profiles, approval gates, escalation rules, cost thresholds, decision evaluation |
roles/ |
Role registry loading Markdown role definitions with YAML frontmatter permissions |
storage/ |
SQLite store (drizzle-orm), project store (filesystem), spec store, decision log, memory store, message log, report store, learnings store |
comms/ |
SQLite-backed message store for web UI chat (messages + threads) |
api/ |
REST API endpoints and WebSocket server for real-time updates |
cli/ |
Command-line interface commands |
core/ |
Re-exports from shared package |
db/ |
Drizzle schema definitions and database factory |
reporting/ |
Daily report generation from task stats and decisions |
skills/ |
Skill manager for installing/listing agent skills |
verification/ |
Cross-model review and quality gate logic |
workflow/ |
Configurable workflow engine for custom task step sequences |
Flightdeck uses MCP-as-message-broker: agents don't talk to each other directly. All coordination flows through the Flightdeck MCP server:
Agent A ──MCP──▶ Flightdeck ──MCP──▶ Agent B
│ │ │
│ task_submit() │ spawn_reviewer │ task_complete()
│ │ effect │
└─────────────────┴──────────────────┘
All state in SQLite
- DMs:
flightdeck_msg_sendwrites to message log; recipient reads viaflightdeck_channel_read - Channels: Group discussions via
flightdeck_discuss/flightdeck_channel_send - Escalations:
flightdeck_escalatewrites to theescalationschannel; Lead is steered with the escalation event - Interrupts: Lead can
flightdeck_agent_interruptto send urgent messages directly via ACPsteer()
The Lead agent is special — it receives structured steer messages from the LeadManager for events like user messages, task failures, escalations, budget warnings, and heartbeats. The Lead processes these and can spawn/terminate agents, declare tasks, and coordinate the project.
Tests live in packages/server/tests/ organized by module:
| Test Suite | What It Tests |
|---|---|
dag/dag.test.ts |
Task CRUD, dependency resolution, topological sort |
dag/effects.test.ts |
Side effect emission from state transitions |
dag/subtasks.test.ts |
Hierarchical task decomposition (FR-017) |
dag/compaction.test.ts |
Task compaction after completion (FR-015) |
dag/dag-operations.test.ts |
Batch operations, edge cases |
orchestrator/orchestrator.test.ts |
Tick loop: promotion, assignment, stall detection |
orchestrator/timer.test.ts |
Timer management |
orchestrator/compaction-retro.test.ts |
Automatic compaction and retrospective triggers |
governance/governance.test.ts |
Profile-based decision evaluation |
governance/gate.test.ts |
Task gating per governance profile |
mcp/mcp-new-tools.test.ts |
MCP tool registration and invocation |
mcp/mcp-role-filter.test.ts |
Per-role tool filtering |
mcp/mcp-errors.test.ts |
Error messages and edge cases |
mcp/mcp-chat-tools.test.ts |
Chat message and thread tools |
lead/lead-manager.test.ts |
Lead steering, heartbeat conditions, event routing |
facade/facade.test.ts |
High-level API integration |
core/ids.test.ts |
Branded ID generation |
core/state-machine.test.ts |
State transition validity and effect emission |
workflow/workflow.test.ts |
Custom workflow step sequences |
display/websocket-display.test.ts |
WebSocket broadcast format |
Tests use real SQLite (in-memory or temp file) with no mocks for storage, ensuring the full stack from MCP tool → facade → DAG → SQLite is exercised. Agent spawning and ACP communication are typically mocked at the adapter boundary.
Run all tests: pnpm test (from root or packages/server).