Skip to content

Harness

Lex edited this page Aug 15, 2026 · 1 revision

Harness

The harness is the machinery between "the agent decides to run a graph" and "the graph finishes": one authoring boundary, one scheduler, one event-sourced store, and the contracts that keep every party honest. This page walks it in lifecycle order.

1. Entry: three ways in

  • /dag-flow <task> — the conversational entry. The routing guidance picks a shape for the task: plain conversation, one task child, or a workflow graph.
  • The workflow tool — eleven actions: start / extend / control (pause · resume · cancel · step · complete · replan) / status / result / list / read / guide / draft / validate.
  • draft — pass a structured graph as tool parameters; the harness renders the YAML spec, validates it, and returns the spec_path. Field-name mistakes are rejected by the tool schema at the provider boundary — they never reach the file.

One contract underneath all three: graph content lives in files, tool parameters carry only paths. A nested graph passed inline would get stringified by provider tool-call serialization — that failure mode is why the boundary is shaped this way.

2. The authoring boundary: WorkflowAuthoring

The single authority from source to prepared graph. Pipeline: parse the YAML → strict action decode → compile blocks into nodes (a review block expands into three: standards review, intent review, arbitration) → profile checks. The portable profile validates structure without touching your environment; environment additionally resolves agents, prompt assets, and models against the live catalogs. No valid prepared graph means zero events and zero persistence — a malformed graph never starts "just to see".

3. The scheduler: waves, permits, real sessions

  • The dependency graph is layered into waves; a wave runs in parallel up to the concurrency semaphore (max_concurrency, default 5).
  • A node is durably queued at admission; the child session is only created once a concurrency permit is held. A 100-node fan-out never materializes 100 sessions at once.
  • Every node is a real child session — same code path as the task tool, with its own agent, context window, and tools.
  • Model resolution per node: tier (dag.jsonc) → worker agent model → parent session model. If nothing resolves, the workflow is not created; you are asked to configure. No silent defaults.
  • Ownership: the directory stamp on the workflow row is re-read from the database on every check; adoption holds a conditional claim (one UPDATE that succeeds only while the row exists and is non-terminal); a moved session re-stamps its workflows in the same transaction.

4. The data layer: event sourcing

Every state change is a durable dag.* event. The projector writes the SQLite read model inside the publish transaction, so the read model cannot lag the log. History is event replay; there is no log table. Status transitions go through a declared state machine — invalid transitions are typed errors (HTTP 409, not 500). Terminal states are irreversible, with one sanctioned exception: extend may reopen a workflow that completed naturally at a reporting checkpoint, because a reporting leaf means a parent is awake and listening.

Ephemeral summary events (progress counts) are deliberately excluded from the durable manifest — they are recomputed, never replayed.

5. Parent-child communication

  • The parent never polls. Wake-ups are synthetic messages delivered when a report_to_parent node reaches a terminal state, or when the workflow itself terminalizes.
  • Dependency outputs flow automatically: a node's depends_on outputs become template variables and structured context in its prompt.
  • Outputs come in three shapes: plain text; structured (output_schema + submit_result); or a file — a final reply that is a single absolute path is captured as {content_ref, size, sha256, summary}, and result returns the pointer.
  • result reads in bounded pages with a cursor.

6. The quality layer: gates with teeth

  • A review block compiles into two independent reviews plus an arbitration; REJECT is a real outcome. In one real run, a fix train was rejected because a hardening commit had no falsifiable probe — the fix loop added the probe, re-ran, and passed.
  • Verification discipline: red-first probes and mutation re-proof. The regression test must fail before the fix and pass after; reverting the fix in a scratch workspace must turn it red again, and the restore must be byte-identical.
  • Deep-mode admission: a bounded Q&A round produces a fingerprinted Requirement Brief; the verdict is READY / NOT_READY / WAIVED, and a material change invalidates the fingerprint.
  • Review fingerprint binding: change the implementation and the fingerprint changes — a stale ACCEPT cannot satisfy the gate.

7. The evolution layer: revisions

Replans mark superseded nodes and bump the workflow's graph_rev. Views and terminal aggregation read the current revision only: a superseded failure never fails a workflow that succeeded on its replacement, while a live failure on the current graph stays visible until it is actually fixed. Durable history is not deleted — an agent can audit superseded nodes by id through the result store; the TUI exposes no entry to it.

8. Budgets and recovery

Four knobs: max_concurrency (5), max_node_replan_attempts (5), max_total_nodes (100), per-node timeout_ms (10 minutes, queue wait included). A deadline breach escalates: the orchestrator is woken to adjudicate (extend the deadline or cancel), and a node that keeps exceeding caps is force-cancelled.

Crash recovery is lazy, per-workflow, evidence-based. Finished sessions back-fill their captured output; ambiguous ones pause the workflow and hand disposition to the parent; provider work is never replayed — a half-finished model call is not re-sent behind your back. Failures carry a class (timeout / exec_failed / verdict_fail) that determines the repair: longer deadline, config fix, or contract restatement.


Implementation detail for each layer lives in Engine Internals.

Clone this wiki locally