One sentence
Nodes are AgentProfiles, edges are typed and carry optimizable prompt directives, a loop is just a cyclic graph — and every existing orchestration family (driverAgent, supervisorAgent, runLoop/loopDispatch, agent-eval's AgentDriver + multishot, VB's hand-rolled shot loop) becomes an instance of this one primitive instead of a sibling implementation.
Why now — measured, not aesthetic
A real 8-round driven run (VB, arc-smart-wallet-product, glm-5.2, 2026-08-01) produced the evidence:
- Rounds 5–7 received a 241-char generic fallback instead of steering (rounds 1–4: ~1,700 chars). The anti-Goodhart filter stripped the driver's instruction and silently substituted boilerplate. In graph terms: the analysis edge worked (the driver's diagnosis was sharp — "the worker is stuck in a self-verification loop"), the delegation edge was severed, and nothing observed the edge. Half the run was unsteered and no artifact said so.
- The driver was structurally incapable of investigating: a single
callLlmJson, no tools, no turns, one-sentence system prompt — while driverAgent (with observe_agent, run_analyst, spawn-by-profile, conserved budgets) sat unused in the installed runtime.
- The driver's prompt was assembled by functions (
buildWorkerDriverSystemPrompt et al.), so the only GEPA-optimizable surface was a one-sentence registry file while ~200 lines of doctrine sat hardcoded in TypeScript. A role expressed as code is a role that can never improve.
The primitive
type NodeId = string
interface GraphNode {
id: NodeId
profile: AgentProfile // the ONLY way a node is described. No role-builder functions.
}
type GraphEdge =
// Work flows down. The delegation directive is DATA -> versionable, sweepable, optimizable.
| { kind: 'delegates'; from: NodeId; to: NodeId; directive: PromptHandle }
// Findings flow anywhere: an analyst lens over N nodes' traces, delivered to ONE node,
// wrapped in a directive telling the recipient what to do with the analysis.
| { kind: 'analyzes'; analyst: string; over: NodeId[]; to: NodeId; directive: PromptHandle }
interface AgentGraph {
nodes: GraphNode[]
edges: GraphEdge[]
deliverable: DeliverableSpec // termination is mandatory, not optional
budget: Budget // conserved pool across the whole graph
}
runGraph(graph, opts): Promise<GraphResult>
Driver↔worker is the two-node cyclic instance. "Agent 3 analyzes outputs of 1 and 2 and reports to 1 only" is one edge, not a framework:
{ kind: 'analyzes', analyst: 'convergence', over: ['w1', 'w2'], to: 'driver', directive: h('convergence-report/v0') }
Acceptance test for the whole design: an agent can author a working topology as plain data in ≤20 LOC (code-mode teachable). If a scheduler, DAG validator, builder API, or DSL appears, we have rebuilt ADC's workflow engine — which stays on the paid platform and is explicitly not this.
Semantics that are load-bearing (each one paid for in incidents)
- Edge delivery is observable. Every traversal appends to an edge ledger:
delivered | stripped | fell-back | empty, with byte counts. The 241-char incident is the motivating case — an unobservable edge cannot be trusted and its directive cannot be optimized. (Generalizes VB's new steering-integrity.jsonl.)
- Oracles are environment, not nodes. Graders/verifiers must not be addressable in the graph — an edge to them leaks the rubric (VB's rubric leak needed a 5-site chokepoint redaction precisely because the grader's vocabulary reached the driver). "Everything is a profile" has exactly one exception and this is it. Analysts may read the environment's verdicts; nodes may not read the environment's internals.
- Conserved budget or no cycles. A cyclic graph without conserved-pool semantics never terminates. Keep
driverAgent's existing model: per-child reservation from one pool (perWorker, maxLiveWorkers), deliverable as the only success exit, loopUntil semantics for bounded retry.
- Directives live in the prompt registry (
prompts/<surface>/v<n>.md pattern), one surface per edge. That is what makes every edge a GEPA/DSPy optimization target with the downstream node's outcome delta as feedback — createDspyRlmTraceEngine (agent-eval ≥0.140) is the intended engine. The GEPA→DSPy migration must be finished first (see deletions).
What this subsumes — the deletion/absorption ledger
At least four sibling orchestration families exist across two packages plus VB. Post-graph, each is either an instance (thin wrapper allowed during migration) or deleted:
agent-runtime/kernel (245 exports today):
driverAgent → the star-topology graph instance. Its analyzeOnSettle hardwires findings→spawning-driver; generalizing that destination to to: NodeId + directive IS the analyzes-edge. Keep the internals, retire the bespoke surface.
supervisorAgent, supervise, superviseSurface, createSupervisor → a supervisor is a driver profile with a different directive. Candidate for instance-of-graph; audit for anything genuinely unique (span recording stays).
runLoop, loopUntil, loopDispatch, loopCampaignDispatch, routerToolLoop — five loop entry points. runGraph + deliverable should leave at most runLoop (the executor) + loopUntil (the combinator); the dispatch pair looks like campaign-layer sugar that belongs beside campaigns, not in kernel.
dumbDriver, naiveDriver → these are directives (information-poor steering policies), not drivers. Re-express as registry prompts on a delegates-edge; delete the functions. (VB independently duplicates them as buildDumbContinuationPrompt/buildNaiveContinuationPrompt — same knowledge, third copy.)
runTree, pipeline, replaySpawnTree, worktreeFanout → audit: tree = acyclic graph; if runTree is not expressible as runGraph we have two graph runtimes.
agent-eval:
AgentDriver (a SECOND driver, persona-plays-a-user) → a driver profile whose directive is the persona. Instance, then delete the class.
buildDriverSystemPrompt, buildWorkerDriverSystemPrompt, decideNextUserTurn → delete the functions, keep what they know: the harness capability brief and the "never write a thin steer" contract become seed data in the initial directive prompts, which GEPA then optimizes away from. Role-as-function is the anti-pattern this issue exists to end.
./multishot (runMultishot, runMultishotMatrix, MultishotDriverEmptyError, computeCellComposite, runJudge) → a third loop family, added while VB forked its own. The matrix/judge/composite parts are eval-layer and stay; the loop should be a graph instance. MultishotDriverEmptyError becomes an edge-ledger empty event.
PairwiseSteeringOptimizer + the GEPA remnants → confirm createDspyRlmTraceEngine fully supersedes; delete what it replaced (AxGepaSteeringOptimizer is already gone in 0.140.1; VB's gepa-reflective-proposer.ts targets the deleted class and is dead code walking).
VB (blueprint-agent scripts/experiments) — the proof-of-subsumption consumer:
lib/sandbox-driver/shot-loop.ts + lib/shot-reviewer.ts + shot-reviewer/ (~2,000+ lines) → two profiles + a 2-node graph. The doctrine in context.ts (200 hardcoded lines) becomes the driver directive's seed prompt. VB keeps ONLY: realness gate, search arms, verticals (per its own charter §5/§6).
VB_USE_RUNTIME_CHAT half-migration (transport only, default off) → subsumed by the above.
Ship plan
- P0 — spec + skeleton:
AgentGraph types, runGraph executing the 2-node cyclic case by delegating to driverAgent internals; edge ledger; directives resolved via prompt registry. No deletions yet.
- P1 — proof on a real cell: VB's
arc-smart-wallet-product run replayed as a graph (same worker profile, driver doctrine as directive seed) side-by-side vs the legacy shot loop. Replacement is measured, not asserted: pass-rate, realness, edge-ledger completeness, tokens.
- P2 — analysts as edges: generalize
analyzeOnSettle destination; route a real TraceAnalyst's findings to a named node with a directive; edge-directive GEPA/DSPy pass on the driver edge.
- P3 — absorption: supervisor/multishot/AgentDriver become instances; deletion ledger executed package by package with a discriminating test per deletion (a green suite that cannot tell the old path is gone is not evidence — proven twice this week).
Open questions
runTree vs runGraph — one runtime or two? (Audit pending.)
- Where does the edge ledger persist — per-run artifacts (VB pattern) or the OTLP trace itself (
agent-trace-contract now has span links with agent.link.kind; steered_by is already specced)? Leaning OTLP: one trace carries topology + delivery.
- Budget semantics for
analyzes edges (analyst spend attribution — the analyzing node, the observed node, or the graph?).
- Does
DeliverableSpec suffice as the only termination, or do cyclic graphs need per-edge traversal caps as a backstop? (Leaning: yes, cap per edge, fail loud.)
Living issue — updated as the VB substrate-reconciliation survey and the kernel audit land. Evidence trail: VB run artifacts oc-glm52@generic-arc-smart-wallet-product-r0 (prompt-shot-N.txt, reviewer-memory.jsonl, steering-integrity work), agent-trace-contract@1.0.2 span-link semantics.
One sentence
Nodes are
AgentProfiles, edges are typed and carry optimizable prompt directives, a loop is just a cyclic graph — and every existing orchestration family (driverAgent,supervisorAgent,runLoop/loopDispatch, agent-eval'sAgentDriver+multishot, VB's hand-rolled shot loop) becomes an instance of this one primitive instead of a sibling implementation.Why now — measured, not aesthetic
A real 8-round driven run (VB,
arc-smart-wallet-product, glm-5.2, 2026-08-01) produced the evidence:callLlmJson, no tools, no turns, one-sentence system prompt — whiledriverAgent(withobserve_agent,run_analyst, spawn-by-profile, conserved budgets) sat unused in the installed runtime.buildWorkerDriverSystemPromptet al.), so the only GEPA-optimizable surface was a one-sentence registry file while ~200 lines of doctrine sat hardcoded in TypeScript. A role expressed as code is a role that can never improve.The primitive
Driver↔worker is the two-node cyclic instance. "Agent 3 analyzes outputs of 1 and 2 and reports to 1 only" is one edge, not a framework:
Acceptance test for the whole design: an agent can author a working topology as plain data in ≤20 LOC (code-mode teachable). If a scheduler, DAG validator, builder API, or DSL appears, we have rebuilt ADC's workflow engine — which stays on the paid platform and is explicitly not this.
Semantics that are load-bearing (each one paid for in incidents)
delivered | stripped | fell-back | empty, with byte counts. The 241-char incident is the motivating case — an unobservable edge cannot be trusted and its directive cannot be optimized. (Generalizes VB's newsteering-integrity.jsonl.)driverAgent's existing model: per-child reservation from one pool (perWorker,maxLiveWorkers),deliverableas the only success exit,loopUntilsemantics for bounded retry.prompts/<surface>/v<n>.mdpattern), one surface per edge. That is what makes every edge a GEPA/DSPy optimization target with the downstream node's outcome delta as feedback —createDspyRlmTraceEngine(agent-eval ≥0.140) is the intended engine. The GEPA→DSPy migration must be finished first (see deletions).What this subsumes — the deletion/absorption ledger
At least four sibling orchestration families exist across two packages plus VB. Post-graph, each is either an instance (thin wrapper allowed during migration) or deleted:
agent-runtime/kernel (245 exports today):
driverAgent→ the star-topology graph instance. ItsanalyzeOnSettlehardwires findings→spawning-driver; generalizing that destination toto: NodeId+ directive IS the analyzes-edge. Keep the internals, retire the bespoke surface.supervisorAgent,supervise,superviseSurface,createSupervisor→ a supervisor is a driver profile with a different directive. Candidate for instance-of-graph; audit for anything genuinely unique (span recording stays).runLoop,loopUntil,loopDispatch,loopCampaignDispatch,routerToolLoop— five loop entry points.runGraph+deliverableshould leave at mostrunLoop(the executor) +loopUntil(the combinator); the dispatch pair looks like campaign-layer sugar that belongs beside campaigns, not in kernel.dumbDriver,naiveDriver→ these are directives (information-poor steering policies), not drivers. Re-express as registry prompts on a delegates-edge; delete the functions. (VB independently duplicates them asbuildDumbContinuationPrompt/buildNaiveContinuationPrompt— same knowledge, third copy.)runTree,pipeline,replaySpawnTree,worktreeFanout→ audit: tree = acyclic graph; ifrunTreeis not expressible asrunGraphwe have two graph runtimes.agent-eval:
AgentDriver(a SECOND driver, persona-plays-a-user) → a driver profile whose directive is the persona. Instance, then delete the class.buildDriverSystemPrompt,buildWorkerDriverSystemPrompt,decideNextUserTurn→ delete the functions, keep what they know: the harness capability brief and the "never write a thin steer" contract become seed data in the initial directive prompts, which GEPA then optimizes away from. Role-as-function is the anti-pattern this issue exists to end../multishot(runMultishot,runMultishotMatrix,MultishotDriverEmptyError,computeCellComposite,runJudge) → a third loop family, added while VB forked its own. The matrix/judge/composite parts are eval-layer and stay; the loop should be a graph instance.MultishotDriverEmptyErrorbecomes an edge-ledgeremptyevent.PairwiseSteeringOptimizer+ the GEPA remnants → confirmcreateDspyRlmTraceEnginefully supersedes; delete what it replaced (AxGepaSteeringOptimizeris already gone in 0.140.1; VB'sgepa-reflective-proposer.tstargets the deleted class and is dead code walking).VB (blueprint-agent
scripts/experiments) — the proof-of-subsumption consumer:lib/sandbox-driver/shot-loop.ts+lib/shot-reviewer.ts+shot-reviewer/(~2,000+ lines) → two profiles + a 2-node graph. The doctrine incontext.ts(200 hardcoded lines) becomes the driver directive's seed prompt. VB keeps ONLY: realness gate, search arms, verticals (per its own charter §5/§6).VB_USE_RUNTIME_CHAThalf-migration (transport only, default off) → subsumed by the above.Ship plan
AgentGraphtypes,runGraphexecuting the 2-node cyclic case by delegating todriverAgentinternals; edge ledger; directives resolved via prompt registry. No deletions yet.arc-smart-wallet-productrun replayed as a graph (same worker profile, driver doctrine as directive seed) side-by-side vs the legacy shot loop. Replacement is measured, not asserted: pass-rate, realness, edge-ledger completeness, tokens.analyzeOnSettledestination; route a real TraceAnalyst's findings to a named node with a directive; edge-directive GEPA/DSPy pass on the driver edge.Open questions
runTreevsrunGraph— one runtime or two? (Audit pending.)agent-trace-contractnow has span links withagent.link.kind;steered_byis already specced)? Leaning OTLP: one trace carries topology + delivery.analyzesedges (analyst spend attribution — the analyzing node, the observed node, or the graph?).DeliverableSpecsuffice as the only termination, or do cyclic graphs need per-edge traversal caps as a backstop? (Leaning: yes, cap per edge, fail loud.)Living issue — updated as the VB substrate-reconciliation survey and the kernel audit land. Evidence trail: VB run artifacts
oc-glm52@generic-arc-smart-wallet-product-r0(prompt-shot-N.txt, reviewer-memory.jsonl, steering-integrity work), agent-trace-contract@1.0.2 span-link semantics.