Graph engineering and self-healing loops
In one sentence: every job elanous runs — shipping a code change, running a pipeline, watching a market — is one loop: observe → understand → heal, drawn as a graph that elanous can reshape while it runs.
This page explains the model. It separates what ships today from what is in progress; where a number would go stale, it gives you the command that measures it.
One loop, many domains
The loop is the same whatever the domain. What changes from one domain to another is only:
- The senses and hands you attach. A coding run observes tests and diffs and acts through a worktree; a trading run observes prices and news (placing orders is not part of the open-source core). Both plug into the same loop as observe nodes and execute nodes.
- The posture. How deep observation goes, how long the healing loop may run, how widely it grounds itself in outside sources, and which actions are allowed — all rise and fall with the situation, the way a readiness level does.
Resolution rises with difficulty
When something fails, elanous does not jump straight to a person. It raises its own resolution:
| Level | What it looks at | Example |
|---|---|---|
| L0 — signature | the failure itself: gate facts, which files are unverified, what repeated since the last round | "no failing test, but the same two files are unverified again" |
| L1 — closer observation | logs, the run ledger, the child's screen, the failing test re-run verbosely | "the test imports a module that the change deleted" |
| L2 — outside grounding | the web, developer documentation, issues and pull requests, cited | "this library changed its API in the last release" |
| Last — a person | only when the ladder is exhausted, with every triage result attached |
Each step is a node in the graph; triage decides which level comes next from what it has seen, not from a fixed rule a person set.
Posture: how hard the loop works depends on the situation
Resolution is local — one run climbing its own ladder. Posture is global — how alert the whole system should be right now. elanous borrows the military idea of a readiness level: 5 is peacetime, 1 is war.
A posture level carries a response profile. The market-watch loop that ships today already defines one:
| Posture | Observation cadence | Observation depth | Alerts |
|---|---|---|---|
| 5 | ×1 | rules only | batched |
| 4 | ×2 | second-stage check | priority |
| 3 | ×5 | cross-check | immediate |
| 2 | ×15 | deep | immediate |
| 1 | ×30 | emergency | immediate |
Posture never places an order or blocks an action on its own: at high alert, irreversible actions are escalated to a person, not refused.
The direction elanous is taking is to let posture set the frame for every loop, not only market watching: the floor and ceiling of the resolution ladder, the loop budget, and how widely the loop grounds itself outside — free search at peacetime, deep search, developer documentation and community sources at high alert.
Standard templates, reshaped while running
elanous keeps a small family of standard templates — reviewed graphs that are not edited by the machine:
| Template | For |
|---|---|
| Elanous development harness | elanous changing itself: isolated worktree, gate, unattended review, merge |
| General development | changes in any repository: plan, build, verify, review, land |
| Composite | work with execution or research in the middle: build, land, hand off, run, observe |
| Heal (shared) | called by all of the above when a step fails |
What ships today inside the development harness is one template per goal type, picked from the goal's GoalType: line or --goal-type (default implement):
| Goal type | Template | Lands as |
|---|---|---|
implement | author · plan · implement · gate · review · rework · main sync · pull request · merge | a code change |
research | investigate · (gate when code changed) · judge · landing | documents |
document | draft · review · rework · landing | documents |
operate | plan · verify · review · rework · landing | a registration or wiring change; the gate is never skipped |
The general-development and composite templates in the first table are designed; composite work today is split into pieces with elanous self orchestrate --decompose, and each piece can carry its own goal type.
A run's graph is shaped twice:
- At launch — from the template, the goal, the run contract (for example, where it runs) and past runs, elanous decides the graph for this run.
- While running — when triage meets something new, the run supervisor adds a response node (a closer look, outside grounding, a probe) at that point.
The supervisor chooses from a catalog and fills in arguments; code checks every insertion (the node exists in the catalog, its inputs are satisfied, the graph still reaches an end, the visit budget holds). It never writes a graph freely. Each run records its template plus the ordered list of changes, so a resumed run — or the same run on another machine — rebuilds the same graph.
The whole run is a graph, not only its middle
A harness run is more than plan → build → verify → land. Before the build there is intake, a launch queue, a pre-launch check and the choice of where to run; on a remote pod there is admission, image sync and credential refresh; around landing there is a merge freeze, a hold queue and cleanup; after a failure there is result parsing, salvage of partial work and healing.
All of those stages are now declared as nodes in the implement graph, grouped by phase:
| Group | Nodes | What the node decides or records |
|---|---|---|
| entry | intake · queue · pre-launch check | queued or direct · launched, waiting or refused · pass or refuse |
| dispatch | dispatch | local or remote |
| remote dispatch | pool admission · image sync · credential refresh | admitted or refused · current or synced · usable or exhausted |
| build · verify · land | author · plan · decompose · implement · gate · review · rework · main sync · pull request · merge | (the stages above, unchanged) |
| land | freeze check · hold · cleanup | open, frozen or taken · queued for a person · completed, preserved or failed |
| recover | result parse · salvage · heal · next move | why a remote run failed · partial work pushed or not · a heal case opened or folded · what to try next |
| end | stopped | where and why a run ended early |
Every one of these nodes — except image sync and next move, which are declared but not yet observed — leaves an entry and an exit record — with its outcome label — from the place in the code where the stage actually happens, and one journey key ties the stages before a run exists to the stages after it. A node that throws records error on its way out; a node whose outcome ends the run also records where the run stopped.
Branches are data: an edge reads a state value (on:) and maps each value to the next node (map:). The default graph is the full set; the environment (local or remote, freeze, queue settings) arrives as state values and simply takes a different label. Skipping a stage never deletes a node — an overlay that would make any node unreachable is refused.
How far "the graph controls the run" goes today, honestly:
| Step | What it means | Status |
|---|---|---|
| 1 | every stage declared as a node and observed at its real place in the code | 🔄 done on the main branch — ships with the next release |
| 2 | the graph, not the code, picks the next node — starting with the entry queue branch; the other branches are recorded as "edge taken", not yet steered | 🔄 the entry branch is done on the main branch (next release) · the rest is in progress |
| 2b | the same journey nodes in the research, document and operate graphs; phase groups callable as one sub-graph node | 📋 designed |
| 3 | the graph runner picks each node's recipe and enforces its visit budget | 📋 designed |
To see a run's whole journey:
elanous logs --all --include-test --event pipeline-node-entry --since 1d --json --json-data
elanous logs --all --include-test --event pipeline-node-exit --since 1d --json --json-data
Filter on data.phase == "journey" and group by data.node (and data.outcome for exits). Runs that started before the journey nodes were declared have no early-stage records — measure coverage only on newer runs.
A catalog built from parts elanous already has
The node catalog lists roles — review, verify, triage, observe logs, ground externally, approval, release, … — and maps each to the component that already does it. It marks where the same role has several implementations, so they can be consolidated rather than multiplied.
Node kinds describe what a node is allowed to do: agent (does work in a worktree), gate (runs checks), git (lands changes), judge (decides), plus observe (read-only), hitl (waits for a person without blocking) and subgraph (a whole graph as one node).
Local or remote, the same graph
A run can execute on your machine or on a remote pod. The run contract decides where, once, at the start. The graph does not change with the place — only where each node executes, and rules derived from the contract (for example, a remote run must end with a pull request so the result does not vanish with the pod). Run state and the ledger are read from one place on the host.
Workflows, tasks and intake
- A graph is the flow: order, branches, retries, pauses for approval, reshaping, healing.
- In the unified model, a workflow is one form of a graph: a graph that starts from a schedule or trigger and runs task nodes. Existing
elanous wfYAML workflows still run without a required schedule or trigger; when conversion is unsupported, they fall back to the original workflow runner. - Intake turns what you say into tasks; the task manager runs a task. Today an approved task marked
[dev]starts a harness run, and other tasks run as an agent with tools (✅ 0.2.3). See Tasks and intake.
What ships today, what is in progress
| Status (✅ in the latest release · ✅ in v0.2.5 · 🔄 in progress · 📋 designed) | |
|---|---|
| Harness runs (implement · research · document · operate templates) | ✅ ships — the orchestrator drives them; the graph declaration is checked against every pipeline step · 🔄 the stages around the pipeline (intake, dispatch, landing, recovery) are now declared and observed as nodes; the graph steers the entry branch first (next release) — see The whole run is a graph |
Graph runner with approval pauses and resume (elanous graph run · graph approve · graph run --resume) | ✅ ships |
| Run contract: local or remote (pod) | ✅ declared and resolved at run start |
| Node catalog | ✅ declared and checked (judge, execute and observe roles built from existing parts) · ✅ 0.2.3 heal roles execute through their recipes — other roles do not yet execute by role name · 🔄 the harness templates still use their own node names; mapping them to catalog role names is in progress |
| New node kinds (observe · hitl · subgraph) | ✅ accepted by the graph parser |
| Node outputs flowing to the next node · multi-way branching | ✅ 0.2.3 (each node reads the run context; a node's last-line JSON outcome picks the edge) |
| Heal template with the resolution ladder | ✅ 0.2.3: the runner walks it; the first observation step marks deleted files by itself and a real failure class closes without a human (example below) · 🔄 the deeper observation and outside-grounding steps are still being filled in |
| Posture level and alert priority for market watching | ✅ in the code — the level is computed from regime inputs and tripwires, alert priority reads it, and each computation and change is logged |
| Posture setting cadence, resolution floor/ceiling, loop budget and grounding breadth | 📋 designed — most of the response profile is not consumed yet |
Budget decision before a launch (elanous harness budget --json → proceed · next provider · wait for reset · stop, with reasons) | ✅ 0.2.3 |
Grounding sources registry (elanous grounding sources list · add · discover · status) | ✅ 0.2.3 |
| Supervisor adding nodes at launch and while running | 📋 designed |
Tasks that start harness runs (approved [dev] tasks → harness · other tasks → an agent with tools) | ✅ 0.2.3 |
| Task type → a combination of templates and workflows (run · implement · research · composite) | 📋 designed |
| Workflows as one form of graph, drawn in the same editor and canvas | ✅ ships — one canvas for task nodes and execution-stage nodes; existing workflow URLs open it; elanous wf remains available |
| One execution engine for workflows and graphs | 🔄 in progress — compatibility conversion is partial; unsupported workflows fall back to the original workflow runner |
A real walk of the heal template
A code change failed its gate with zero failing tests — the only complaint was that a file it deleted could not be verified. The same input, walked by the runner (elanous graph run graphs/heal/heal-loop.yaml --input …):
- Before the observation step could see deletions:
collect → triage (needs deeper observation) → observe-deeper (empty) → triage (needs grounding) → ground-external (empty) → triage (exhausted) → failed. The ladder climbed as designed, but each rung came back empty, so the loop ended with a person. - After:
collectchecks the working tree, marks the file as deleted, andtriageclassifies the failure as untestable:collect → triage (untestable) → acknowledge → done. No person involved, and the loop never had to climb.
That difference — the same judgment reaching a different answer because an earlier node brought back a fact — is what the observation steps are for.
To see what actually ran:
elanous logs --event pipeline-node-entry --limit 40 --all --include-test --json --json-data
elanous graph status <graph_id>
What this page deliberately does not claim
- That the in-progress rows are fully delivered today. Compatibility conversion is partial, not one unified engine.
- How often triage picks the right level. It is logged; its accuracy is not yet measured.
- Any fixed count of nodes, roles or templates. The catalog and templates are the source; count them when you need to.