Plan Engine — the dev-workflow graph
The Plan Engine is a live Postgres graph that is the daily-driver source of truth for plans and tasks. It is editable and actionable from two clients — Claude (via MCP) and the admin UI — and is kept bidirectionally synced with GitHub issues/PRs and Vercel deploys.
It is the dev-workflow graph and must never mix with tenant/client data (see Product SoR and the two-database split in Platform map).
One graph, three views, actionable from two clients. The graph is the canonical end-state source of truth; GitHub stays a synced mirror (its elimination is deferred).
Where it lives
| Piece | Location |
|---|---|
| Schema + migrations | @tedos/db (packages/db) — Drizzle/Postgres, in a dedicated plan schema |
| API | The Plan API in @tedos/api, prefix /plan/* |
| MCP | 9 plan_* tools wrapping the same service (one engine, two clients) |
| Admin | /plan (editable tree), /roadmap (timeline), /work (where-we-stand) |
Data model — 5 tables (plan schema)
| Table | Purpose |
|---|---|
plan_node | The tree — kind (effort · sub · milestone · roadmap · note), status, role, priority, timeline_*, outcome_label (positive · negative · gold) |
plan_dependency | The DAG — depends_on / blocks, with a cycle-guard on insert |
plan_version | Append-only history — every write appends a row (before / after jsonb) |
plan_link | External work — issue / pr / deploy / branch from github / vercel; the issue#↔node map |
sync_event | Idempotency / loop-prevention — unique(source, payload_hash) dedupes the bidirectional sync |
The schema is also the training-trace dataset schema: plan_version is the trajectory store,
and outcome_label carries the auto/gold reward signal (see ADR-006 in
Decisions).
API + MCP surface
- Plan API (
apps/api, Fastify v5, validated with@fastify/type-provider-zod): CRUD for nodes / dependencies / links, plustree,roadmap, andhistoryprojections. Operator-gated. Every write appends aplan_versionrow by construction. - MCP server (
apps/api/src/mcp): 9plan_*tools wrapping the same service layer, so Claude and the admin hit one engine, two clients — no second implementation.
Sync bridge + loop-prevention
Bidirectional, so the graph (SoT) and GitHub (mirror) never drift:
- graph → GitHub (mirror): pg-boss jobs propagate node create/update/status to GitHub issues
- Projects v2 status.
- GitHub → graph + Vercel → graph: HMAC-verified ingress webhooks update the graph; Vercel
deploys land as
plan_link(deploy). - Reconciliation: last-write-wins with a drift sentinel;
sync_eventdedupes; loop-prevention via origin tags + echo-skip (a change the bridge itself made is not re-emitted).
Admin views
| Route | What |
|---|---|
/plan | Editable plan tree + node-detail editor (create/link issue, start, status) |
/roadmap | The timeline projection of the same graph — aligned by construction, not a doc |
/work | The "where we stand" surface, repointed from live GitHub GraphQL to the graph |
Deploy topology
Deployed to the Fly app tedos-api (the same app as the product API) as two process groups:
web (the Fastify API: /plan/*, webhooks, MCP) and worker (the pg-boss queue draining the
graph→GitHub mirror jobs). See Deploy topology.
Why a graph instead of files
Plans used to be MDX files read from disk, editable only via an IDE + PR. The Plan Engine moves
them to an editable, actionable graph: plan → subplans (parent + dependsOn + timeline), with
the roadmap as a projection that stays aligned by construction. The decision records are ADR-004
(plans → graph) and ADR-005 (tasks → graph, GitHub mirrored) — see Decisions.