Plane Architecture
Audience: Tier 2/3 (team adopter, framework contributor).
This chapter explains AI-DLC's three-plane architecture — the separation between control, data, and management concerns — and the compile boundary that joins them. Cross-link to Sensor System (control-plane sensor manifests), Rule System (control-plane rule files), and State Machine (data-plane lifecycle). This is the design substrate; sensor and rule manifests are the source files the substrate operates on.
1. The problem
v0.5.0 introduces rules, sensors, and a learning loop that captures observations during workflow runs and writes them somewhere they fire automatically next time. The underlying model has real complexity: per-stage imports of multiple kinds, universal-default rules attached by filename, sensor manifests, a hook chain. Customers shouldn't have to learn all of it to use the framework, and the framework shouldn't pay lookup costs in hot paths.
The right framing is not "make the design simpler." It's "borrow the discipline that already exists for systems like this." Modern networking's three-plane architecture is the closest analog, and the lessons transfer almost directly.
2. The networking analog
A modern router (or SDN controller) splits its work into three planes:
- Management plane — SNMP, syslog, dashboards, NETCONF, gRPC, CLI. Configuration in, observability out. Human cadence (configure once, query occasionally).
- Control plane — BGP, OSPF, IS-IS, route computation. Given the topology, decide which path each packet should take. Topology cadence (seconds to minutes).
- Data plane — flow tables, ACLs, ASIC forwarding. Forward this packet now, in nanoseconds, by table lookup. Line rate.
Five lessons worth stealing:
Filter evaluation never happens at packet rate
OSPF computes shortest paths once when topology changes. The result is installed into the data plane's flow tables. Every subsequent packet is a table lookup — nanoseconds, deterministic, no filter logic. Smart work happens at topology-change time, not at packet time.
Plane interfaces are explicit
OpenFlow, P4, NETCONF, gRPC. The control plane doesn't reach into the data plane's memory; it sends a structured message that says "install this flow." The data plane confirms. This makes failure modes diagnosable — a refused install is a clear signal, not a silent miss.
Failures isolate cleanly
If the control plane crashes, the data plane keeps forwarding using the last-installed rules. If the data plane fails, the control plane notices via heartbeat and re-routes around the failure. Neither plane bringing the other down is a load-bearing property.
Telemetry closes the loop
The data plane reports flow stats, drops, latency. The control plane consumes telemetry and decides: re-route around congestion, scale a service, mark a peer down. The control plane gets smarter because the data plane reports back.
Three planes, three cadences
Management plane runs at human cadence. Control plane runs at topology cadence. Data plane runs at line rate. Each plane is allowed to be slow if its job permits.
3. Mapping to AI-DLC
The mapping is closer than it sounds. (<record>/ below = the active intent's
record dir, aidlc/spaces/<space>/intents/<YYMMDD>-<label>/; the audit trail is a
dir of per-clone shards under <record>/audit/.)
| Networking | AI-DLC analog |
|---|---|
| Control plane (BGP, OSPF, route computation) | Stage definitions, rules, sensors — the schema of what should run |
| Data plane (packet forwarding, flow tables) | Stage executions, Bolts, agent invocations — the actual runs |
| Management plane (SNMP, dashboards, CLI) | /aidlc --doctor, designer (future), audit queries, CLAUDE.md |
| Routing protocol (BGP / OSPF) | Compile: aidlc-graph.ts compile reads stage frontmatter + rules + sensors, resolves each stage's pull imports against the source registries, emits the graph |
| FIB (forwarding information base) loaded into ASIC | stage-graph.json with per-stage rules_in_context + sensors_applicable resolved at compile time |
| OpenFlow / NETCONF (interface) | stage-graph.json — the explicit interface between compile and orchestrator |
| Telemetry (NetFlow, sFlow) | Audit log, sensor firings, memory.md entries → the intent's runtime-graph.json |
| Reactive flow install (PACKET_IN) | Learning loop: data plane reports an observation → user confirms → file write into rules/sensors plus a frontmatter edit on the originating stage when a new sensor binding is captured |
| Proactive route install (BGP advertisement) | Framework PR: ships new stages/rules/sensors before any workflow runs |
| Topology change → recompute routes | Workflow start → compile reads current source files; subsequent recomputes triggered by next workflow |
| Pre-installed FIB stable across packet flight | Learning-loop writes during a workflow don't affect the in-flight compiled view; they apply at next workflow start (see §5) |
| Line-rate forwarding by table lookup | Orchestrator + dispatcher read pre-resolved fields off graph nodes; no resolution walks at runtime |
The plane labels in stage-graph.json and the runtime read patterns
mirror the FIB/route-table/CLI separation. Configuration enters at the
management plane; the control plane compiles it, and the data plane
executes by lookup.
4. The compile boundary
The load-bearing insight: networking doesn't put filter evaluation or inheritance walks at packet rate because the work is too expensive there. The control plane computes once, installs into the FIB, the data plane forwards by lookup. AI-DLC follows the same pattern: compile once at workflow start, read the resolved view throughout the workflow, recompile at the next workflow start.
What compiles, what stays on disk
| State | Lifecycle | Source on disk | Compiled into | Read by |
|---|---|---|---|---|
| Stage DAG, scope routing, artifact production | Framework-versioned (changes via framework PR) | Stage frontmatter (.claude/aidlc-common/stages/*.md) |
stage-graph.json |
Orchestrator, doctor, designer |
| Rules (prose, prescriptive) | Mutable; framework PR or learning-loop writes | aidlc/spaces/<active-space>/memory/<scope>.md (filename-derived; org/team/project attach to every stage) |
stage-graph.json per-node rules_in_context |
Orchestrator (resolved view); Claude Code auto-load reads source for in-context prose |
| Sensors (manifests, verification checks) | Mutable; framework PR or learning-loop writes (manifest authored once; stages import by id) | .claude/sensors/aidlc-<id>.md |
stage-graph.json per-node sensors_applicable |
Dispatcher reads resolved list at stage entry; PostToolUse fires from it |
| Workflow execution telemetry | Per-workflow, accumulating | audit/ shards · memory.md · Bolt forks |
<record>/runtime-graph.json |
Doctor, gate ritual, future cross-workflow observer |
| Per-stage observation log | Per-stage-run | <record>/<phase>/<stage>/memory.md |
(no compile — read directly) | Gate ritual at this stage's gate |
Two compiled artefacts, both per-workflow. stage-graph.json carries
the resolved control-plane view: stage DAG, scope routing, artifact
production, plus per-stage rule and sensor lists with inheritance
pre-resolved. runtime-graph.json is recompiled — a full event-sourced walk of the audit
log — on every transition-class audit event. Source files on disk are the
authoring surface; compiled graphs are what runtime reads.
One compile, at workflow start
The compile reads stage frontmatter, walks aidlc/spaces/<active-space>/memory/ and
.claude/sensors/, attaches universal-default rules by filename
(org.md, team.md, project.md apply to every
stage), then looks up each stage's pull imports against the source
registries:
- The stage's
phase: <name>field attaches the matchingphases/<name>.mdrule (one rule per stage). See Rule System. - The stage's
sensors: [<id>, ...]list resolves each id against.claude/sensors/. Unknown ids fail the compile loud — a stage can't silently fail to fire something it imports. See Sensor System.
The compile emits stage-graph.json with the answers baked into each
stage node. Throughout the workflow, the orchestrator and dispatcher
read those pre-resolved fields. Learning-loop writes during the
workflow update source files but don't affect the in-flight compiled
view — the user already corrected the orchestrator in-stage; the rule
is for next time. The next workflow's compile picks them up. Same
shape as BGP not recomputing routes mid-packet-flight.
Locked and atomic
Two failure modes the compile must address from day one. This
implementation's per-Bolt worktrees isolate state for parallel agents
most of the time, but data/stage-graph.json is repo-shared, not
worktree-scoped — and the user can launch /aidlc in two terminals
against the same checkout — so the compile needs defence regardless.
- Concurrent compiles would race writes to
data/stage-graph.json. The compile runs underwithAuditLockfrom v0.4.0 (seelib.ts) so concurrent invocations serialise — the second waits for the first to finish, then runs against fresh source state. - Crash mid-write. A compile interrupted mid-write must not leave
consumers reading invalid JSON. The compile writes to a temp file,
validates the output, then renames atomically via POSIX
rename(2). Readers see either the previous compile's output or the new one — never a half-written file.
Both patterns already live in the codebase for state-and-audit work; the compile inherits them.
Two graphs, two lifecycles
The two compiled artefacts have different consumers and different update cadences:
stage-graph.json— control plane. Compiled at workflow start. Stable across the entire workflow's lifetime. Read by the orchestrator (DAG topology, scope routing), the dispatcher (per-stagesensors_applicable); Claude Code's auto-load consumes the source rule files in parallel for prose context.runtime-graph.json— data plane. Per-workflow artefact at<record>/runtime-graph.json. Recompiled (full event-sourced walk of the audit log) on every transition-class audit event. Aggregates execution telemetry: which stages ran, which Bolts forked, which sensors fired, which memory.md files exist. Read by the gate ritual (to surface candidates), doctor (for execution health), and future cross-workflow observers. See Runtime Graph for the schema, compile lifecycle, and recovery model.
Mixing them violates the cacheability boundary. stage-graph.json
wants to be cached for the lifetime of the workflow;
runtime-graph.json mutates per-event. The same artefact would mean
re-issuing the entire control plane every time the data plane reports
a sensor fired.
Why rules and sensors flow through the same compile
The compile is symmetric across all control-plane inputs. Stage
frontmatter, rule files, and sensor files are all read by
aidlc-graph.ts at workflow start; all contribute to the resolved
per-stage view. Networking does this — BGP and OSPF feed the same FIB;
ACLs and policies feed the same flow tables. One compiled view,
multiple sources.
Concretely, each stage node gains two fields:
{
"slug": "requirements-analysis",
"phase": "inception",
"sensors": ["required-sections", "upstream-coverage"],
"rules_in_context": [
{"path": "aidlc/spaces/default/memory/org.md", "scope": "org"},
{"path": "aidlc/spaces/default/memory/team.md", "scope": "team"},
{"path": "aidlc/spaces/default/memory/project.md", "scope": "project"},
{"path": "aidlc/spaces/default/memory/phases/inception.md", "scope": "phase"}
],
"sensors_applicable": [
{"id": "required-sections", "path": ".claude/sensors/aidlc-required-sections.md"},
{"id": "upstream-coverage", "path": ".claude/sensors/aidlc-upstream-coverage.md"}
]
}
matches (when present in the resolved sensor entry) is a sensor-side
capability filter — "this sensor analyses files matching this glob"
— that the compile snapshots verbatim from the manifest into the
stage's resolved entry. The PostToolUse hook reads it at fire time
without re-opening the manifest.
Two consequences:
- The dispatcher does no resolution walks at runtime. Stage entry
reads
sensors_applicableoff the node — already looked up, already attached. PostToolUse fires from this pre-resolved list. - Doctor and designer query one resolved view. "What rules apply
to this stage?" "Which sensors fire when I edit this file?" Both
answer from
stage-graph.jsondirectly, no filesystem walks.
The networking lesson, applied
The control plane (compile) does the slow, clever work once; the data
plane (orchestrator + dispatcher reads) then runs fast on the resolved
view. Compile at workflow start resolves all per-stage imports —
every sensors: id looked up, every phase: attached, the
universal-default rules picked up by filename. Throughout the workflow,
runtime reads pre-resolved fields off graph nodes. BGP doesn't
recompute routes mid-packet-flight; AI-DLC doesn't recompile
mid-workflow. Learning-loop writes during a workflow update source
files; they enter the compiled view at the next workflow start.
5. Recovery as an emergent property
The data plane just described — runtime-graph.json, audit log,
state docs, memory.md, artefact tree — has a property it didn't pay
for. A fresh harness session, started after compaction or a clean
restart, can read these five sources together and reconstruct enough
of the workflow's state to pick up where the previous session left
off. Recovery isn't a feature added to v0.5.0; it's an emergent
property of the design's data discipline.
Five sources, one picture
| Source | Records what | Read order |
|---|---|---|
Artefact tree (<record>/<phase>/<stage>/*.md) |
The decisions themselves, in finished form | First |
memory.md per stage |
What got noticed during the decision-making | Second |
Audit log (<record>/audit/ shards) |
When each decision happened, who approved | Third |
State docs (<record>/aidlc-state.md, per-stage state) |
Where in the workflow we are right now | Fourth |
runtime-graph.json |
Cross-stage summary (durations, sensor firings, learnings counts) | Fifth |
The artefacts go first because they're the durable record of what was actually agreed. The other four sources are about the journey — what was considered, when it happened, what's pending, what the patterns look like across stages. A human picking up someone else's half-finished work reads the same way: outputs first, notes second, timeline third, current cursor fourth, summary view last.
What recovery reconstructs cleanly
Decisions, outputs, in-stage context, timeline, current position, and which Bolts forked with what outcomes. A fresh session reading the five sources knows what the workflow has produced, what corrections the user has made, what gates have been approved, what's still pending, and roughly how the previous run was going.
What recovery can't reconstruct
The previous session's conversational rhythm — the user's mid-stage
Q&A pattern, the LLM's working hypotheses before they got committed
to memory.md, partial tool calls interrupted mid-flight. The
conversation buffer is gone with the session. This is a fundamental
property of LLM sessions, not a fixable design property. Recovery's
job is to give the new session enough to re-orient and continue, not
to recreate the previous session's exact mental state.
The consistency constraint
Five sources, written by different code paths, all have to stay
reasonably consistent for recovery to work. The audit log is canonical
(append-only, source of truth for "what happened"); the other four
are consistent with it because their writes are gated by the same
withAuditLock primitive from v0.4.0. A fresh session reading any of
the five against the audit log can detect drift and reconcile.
What this means for the framework
The framework already practises a small version of this: today's
aidlc-state.md carries a Scope field that is written at intent
birth and read on session-resume, so the workflow's scope survives
context compaction without the orchestrator having to re-derive it. The
generalisation is that every part of the data plane that records
something durable becomes part of the recovery surface, not just the
state file. The deeper principle is that the data plane is structured
for recovery: because every durable record is part of the recovery
surface, a resume path is a small addition on top of substrate that
already exists, rather than a bolt-on.
Next Steps
This chapter is the lens; the rest of the Developer Reference is the mechanics seen through it.
- The control plane in action — how the orchestrator drives the compiled stage graph. See Orchestrator.
- Control-plane inputs — the source files the compile resolves into each stage node: Sensor System (manifests) and Rule System (rule files).
- The data-plane artefact — the schema, compile lifecycle, and
recovery model of
runtime-graph.json. See Runtime Graph. - The user-facing view — control/data/management framed for someone running a workflow rather than building the framework. See Rules and the Learning Loop.