Loop-Graph Model

August 20, 2026 · View on GitHub

This document defines the shared vocabulary for designing loop-graph runs. Read it once when proposing a new node or edge; templates implement these concepts implicitly — running nodes never parse this file.

Scope: loop-graph only. Simple self-contained goals should use the host's ordinary task/goal directly; this model begins where durable graph state is useful.


The Law

No load-bearing state may live only in a node's context; it must be externalized onto a typed edge.

This is the checkable definition of drift: drift = load-bearing state that stayed in context and never made it onto an edge.

Code-review test for a new template: does this node derive X from its own prior rounds' context instead of reading it from the ledger? → bug.

Runtime test for the same law: most loop hosts hand a round the previous round's context (see the selected host reference), so a node can silently start leaning on it — and the violation only surfaces when the host truncates the chain mid-item. The fire boundary is where the Law gets tested: a node ends its turn, its timer brings it back, and whatever it can no longer reconstruct from the ledger, the directives file and ops.md was load-bearing state that never made it onto an edge. Warm context is a cache, never a store.

The convergence tracker (#7) and the milestone gate (#11) both work because they moved a computed predicate — "should I converge?", "may I advance?" — from the executor's mental arithmetic onto the state edge where a stateless re-activation reads it cold.


Nodes

A node is a specialized agent role. Each node is described by a tuple:

(prompt, model, activation, read-set, write-set, authority, stop-condition)
FieldWhat it answers
promptWhat does this node do? (the .md file)
modelWhich model tier runs it?
activationWhen does it fire? (cadence, event, manual)
read-setWhich edges + ambient context may it read?
write-setWhich edges may it write?
authorityWhat may it decide without escalation?
stop-conditionWhen does it end its loop?

Current roles

RoleModelActivationReadsWritesAuthorityStops when
Executorcheap/fastown timer; N verified rounds per fireledger, directives, ambientledgerimplementation decisionsledger reaches exit-ready, stalled, or `closed$
\text{Supervisor}\text{strong}\text{own} \text{timer}, 3–4 \times \text{the} \text{executor}'\text{s}\text{ledger}, \text{git} \text{diff}, \text{ambient}\text{directives}\text{drift} \text{corrections}, \text{acceptance}, \text{plan} \text{adjustments}\text{ledger} \text{terminal} \text{after} \text{a} \text{final} \text{audit}
\text{Scout}\text{cheap}/\text{fast}\text{on}-\text{demand} (\text{research} \text{brief} \text{in} \text{directives})\text{ledger} (\text{read}-\text{only}), \text{ambient}, \text{external}\text{findings}\text{advisory} — \text{options} + \text{tradeoffs}, \text{no} \text{plan}/\text{impl} \text{changes}\text{brief} \text{answered}, \text{cap} \text{hit}, \text{or} \text{blocked}

$authority` is the field #9 exposed as missing: "who decides X without escalating?" The executor decides how to implement; the supervisor decides whether the work meets acceptance criteria; the owner decides DDL, credentials, red-line exceptions.

Round work item

A ledger item is one independently verifiable workset. It may contain multiple related edits when they share one behavior claim, write set, and gate; the executor implements and verifies the whole set in the same round. This preserves attribution and same-round proof while avoiding artificial cold starts between coupled edits. Unrelated changes remain separate items.


Edges

An edge is a durable, typed channel between nodes. Two types:

TypeDisciplineExample
State (blackboard)single-writer, overwriteledger.md — the scoreboard
Signal (queue)single-writer, ordered, one-way; consume + cold-archivedirectives.md — supervisor → executor

Findings edge (scout → executor)

PropertyValue
TypeState (single-writer, overwrite)
Writerscout (single-writer per findings file)
Readerexecutor (read-on-reference, via blocked-on: findings#<brief-id> ledger pointer)
Disciplineread-on-reference; consume-and-retire
Filefindings.md (single) or findings/<brief-id>.md (parallel)

Why state, not signal? The scout overwrites its findings file as it refines (partial → complete). Latest-value-wins, not append-only.

Pointer convention: the executor logs blocked-on: findings#<brief-id> at the decision's ledger row, continues unblocked work, and opens the findings file only when it circles back to that row.

Retire convention: once consumed, the executor records the decision in its round log, moves the finding to archive/findings-<brief-id>.md, and removes the pointer. The executor never edits findings content — moving a spent file is custody, not authorship.

What is NOT an edge

Ambient context — read-only files that exist before and after a run: ops.md, AGENTS.md/CLAUDE.md, lint config, the templates themselves. Nobody writes them between nodes at runtime; they don't carry information along a run's timeline. They're the repo, not the graph. A host-lifecycle Timers cell in ops.md is not an edge: only the owning node writes its own cell, and only when the selected reference keeps that section.

The live signal file is a bounded queue, not the audit archive: it holds current STANDING policy plus unconsumed directives. The consumer watermark acknowledges ordered signals; before each append the single writer moves every acknowledged entry into the cold archive. Rotating against the watermark — a condition that arrives on its own — is what keeps this bounded; rotating at a fixed shard size is a rule that can wait forever while the live queue grows past the point where a reader still reads all of it.

The test: can you determine "reuse an existing edge vs create a new one" using only state + signal? Yes — #9 proved it. The proposed gates.md had signal discipline (ordered, supervisor → executor) but its content was a single flag (pass/fail per milestone) → that's a field on the state edge (ledger.md). Two types were enough.


Applying the vocabulary

When proposing a new construct (node, edge, flag), fill in the tuple / classify the edge type first. If the proposal collapses into an existing construct, it wasn't needed.

ProposalAnalysisOutcome
Auditor node (#9)Activation = "at milestone gates" needs event-bus the graph doesn't have; degrades to cadence poller = supervisor. Authority = acceptance = already supervisor's.Collapsed into a tracked flag + red line on the existing supervisor
gates.md edgeSignal discipline, but content = one flag per milestone → fits as a field on the state edgeCollapsed into Milestone gate: header in ledger.md
Scout node (#17)Activation = on-demand (not cadence/event) — distinct from supervisor. Authority = advisory only (no plan/impl changes) — distinct from executor and supervisor. Write-set = dedicated findings edge (not ledger) — new single-writer file, not a partition of an existing edge.Does not collapse — genuinely new information-flow pattern: off-critical-path research → dedicated state edge → read-on-reference consumption

The vocabulary earns its keep when filling in the tuple saves one issue → discussion → rewrite cycle.


References

  • #7 — convergence tracker (state edge field)
  • #9 — auditor proposal → gate rule
  • #11 — milestone gate implementation
  • #12 — this vocabulary's design discussion
  • #15 — worked example showing the gate in action
  • #17 — scout node proposal
  • #18 — scout node implementation (templates + findings edge + worked example)
  • #19 — executor handoff protocol (blocked-on pointer + consume/retire)