You're reading the 0.10.0 archive.

Push a hank toward completion

A codon that finishes is not always a codon that finished the work. An agent can stop early, write less than you expected, or leave an open-ended task half-explored, and the run will still report success. This page is about the four ways hankweave lets you push a hank further: extending a codon to its context boundary, nudging it with a follow-up prompt, looping until the context fills, and wiring a sentinel sweep so an observer's notes feed the agent's next pass. Each shape costs something - tokens, complexity, or both - so we start with how to choose, then walk through each pattern, and end with how to run and verify the result.

Choosing how hard to push for completeness#

A codon is one agent task in a hank. When we want it to take another pass, we have three options: extend it with exhaustWithPrompt, add an Any More? follow-up codon, or use a Sentinel Sweep. Use an extension for open-ended, single-agent exploration when each pass does not need a different structure. For work where each iteration needs multiple distinct steps, choose a loop instead of an extension. Use a sweep when you need review-loop behavior without adding codons. A nudge is the lightest option when one more prompt may be enough.

For the field contracts, see codon concepts, loop termination, and sentinel configuration and output paths. Before wiring a sweep, also read the one-way sentinel flow: the observer writes its own report, and the working agent reads it.

Three terms come up throughout the page. A session is one agent conversation inside a codon, identified by a SessionId. continue-previous reuses the previous codon's session; fresh starts a new one. A run spans RunStarted through RunCompleted, while the execution directory contains agentRoot/ and .hankweave/ state. These distinctions matter because each completion pattern operates at a different level: exhaustWithPrompt extends the current codon's session to its context boundary; it does not extend the whole run. The setting is one line of JSON, but the prompt must tell the agent what "more" means.

Check-it: For each codon, decide whether you need per-iteration structure, a second perspective, or an open-ended continuation. Pick a loop, sweep, extension, or nudge from those answers before adding continuation work.

Making one codon work to the context boundary#

The simplest pressure shape is exhaustWithPrompt. Set it when one codon should continue in the same session. After each successful completion, the harness - the runtime that drives a run - sends that prompt unchanged again. Each extension is one additional completion round.

Three fields control the behavior:

Scroll to explore the table →
fieldtypedefaultrequiredconstraintsdescription
exhaustWithPromptstringnoPrompt to send when extending codon until context exhaustion. When set, the codon will automatically continue with this prompt after each successful completion until context is exhausted.
maxExtensionsinteger100no> 0Maximum number of extensions before forcing completion. Default: 100. Safety valve to prevent infinite extension loops.
autoCompactbooleannoWhether the harness may auto-compact (summarize/trim) the session when the context window fills. Default: false — compaction is disabled, the provider's context-overflow error surfaces instead, plain codons fail at the…

The defaults change what "done" means. With autoCompact off, its default, a plain codon fails at the provider's context-overflow error. An exhaustWithPrompt codon treats reaching that same boundary as successful completion. The runtime can also stop at maxExtensions, whose default is 100, so use a deliberately small limit when you are testing or containing spend.

An extension opens only when all of these conditions hold: the configuration is present; the codon is not interrupted; it is under maxExtensions; its exit code is 0; a result message arrived; there is no error result or classified failure; the budget is still available; and the context has not yet been exceeded. Context exhaustion closes the extension codon successfully instead of opening another round.

Each extension emits one codon.extended event. It is journaled, routed to sentinels, and carries the codon id and name, extension number, prompt, cumulative tokens, and cumulative cost. The running state file (state.json) records extensionCount.

Scroll to explore the table →
idcategoryjournaledsentinelRoutedpayloadFieldsreceipts
codon.extendedserver-statetruetruecodonId, codonName, extensionNumber, exhaustWithPrompt, cumulativeTokens, cumulativeCostschemas/event-schemas.ts:565, schemas/event-schemas.ts:962, schemas/event-schemas.ts:1236, hankweave-runtime.ts:2946

Extensions change how often the agent runs, not how often the run seals state. On the normal completion path, an extended codon seals one checkpoint at its end, of type completed or error; it does not seal one checkpoint per extension. A normally completed codon emits one codon.completed event and runs outputFiles once. The post-seal failure described next is an observed exception to that path. Do not put continuationMode: "continue-previous" on the codon after an exhaustWithPrompt codon: the previous context is exhausted, so hankweave rejects that arrangement at load time. Use fresh for the next codon.

Check-it: After a run, inspect the journal for codon.extended and its increasing extensionNumber; inspect the state for extensionCount; then confirm that the codon has one final checkpoint rather than one checkpoint per extension.

When an extension fails after the work is done#

Extensions have one failure mode worth knowing before you rely on them. Treat a failure during an extension as a whole-run failure even if the codon's checkpoint already sealed with its artifact complete. The run exits 1, and resume wants to re-run the sealed codon. Use resume, rollback, and retry for recovery; do not infer success from a checkpoint or copied output.

The normal captured lifecycle is: the codon runs and may emit extension events; one CheckpointCreated transition seals it; codon.completed is emitted; and outputFiles.copy runs when -o or outputDirectory is set. Without an output directory option, outputs remain in agentRoot/. The post-seal extension failure is an observed exception to that successful lifecycle, not an extra checkpointing stage.

The documentation project stopped using exhaustWithPrompt after this failure pattern appeared and moved completeness pressure into prompt-tail self-audits. If you keep extensions, pair them with a small maxExtensions and a codon budget. Every extension re-sends the accumulated context, so the cost grows with the continued session.

Check-it: When diagnosing a failed run, distinguish a normal single-checkpoint completion from the observed post-seal extension failure: the latter is still exit 1 and still requires the linked resume procedure.

Nudging instead of extending#

If an internal extension loop is more machinery than the task needs, use the Any More? pattern for a light follow-up. Add an ordinary follow-up codon with continuationMode: "continue-previous" and the whole prompt "Any more left to do?". We are asking the agent for another judgment about completeness, not independently checking that judgment.

JSON
{
  "continuationMode": "continue-previous",
  "promptText": "Any more left to do?"
}

The previous ordinary codon must have completed successfully, and the follow-up must use the same model. Different models cannot share a session. Do not use this nudge after an exhaustWithPrompt codon, whose context is exhausted.

A nudge asks the agent to look again, but it cannot make the agent look carefully. If a codon writes files and could finish silently, put a self-audit in its prompt: write a skeleton first, make at least four incremental write steps, verify the result, and run wc -l before finishing. Without that discipline, a one-codon hank can report success while writing nothing and sealing an empty checkpoint.

Unlike an extension, this nudge is an ordinary codon. It seals its own checkpoint and rolls back independently.

Check-it: Confirm that the nudge follows a successful ordinary codon and uses the same model. If the prior codon exhausted its context, replace the nudge with a fresh codon or another completion shape.

Looping until the context is full#

Choose a loop when "keep going" needs a runtime termination rule rather than a fixed count. Set terminateOn to { "type": "contextExceeded" }; the loop iterates until the runtime signals context exhaustion, and exhaustion is success.

JSON
{
  "terminateOn": {
    "type": "contextExceeded"
  }
}

Set every codon in that loop to continuationMode: "continue-previous". A fresh codon resets context on every iteration, so the loop would continue forever; hankweave rejects that configuration while loading the hank. The loop stops on the provider's real context boundary: the same signal that fails a plain codon is the loop's success condition. See loops for the rest of the termination contract.

Budget for accumulation: a continue-previous loop pays for all prior context again on every iteration. By iteration five, output from iteration one is being paid for four more times.

Check-it: Validate the hank after setting the loop. Every codon in a contextExceeded loop must continue the previous session, and the loop must stop on the real context boundary rather than resetting context.

Sweeping: sentinels that feed the agent's next pass#

The patterns so far all ask the same agent to continue its own work. A Sentinel Sweep adds a second perspective without adding codons. A sentinel observes the working codon and writes findings through an output path containing /, such as ./sentinel-notes/issues.md, so the agent can read them on its next pass. Pair that path with an exhaustWithPrompt that tells the agent to read the notes and fix what was caught. Keep the flow one-way: the sentinel writes; the agent reads and does not edit the sentinel's file.

Where the notes land depends on the output path. A bare output filename resolves to <exec>/.hankweave/sentinels/outputs/<sentinelId>/<file> in the managed directory. A path containing /, including ./x.md, resolves against agentRoot, where the codon's agent can read it. The full path and sandbox rules belong to sentinel configuration.

Scroll to explore the table →
fieldtypedefaultrequiredconstraintsdescription
formatenumnotext | jsonl

Use output.format: "text" or output.format: "jsonl"; "json" is not a valid value.

Sentinel prompts are Eta templates. Their it value contains up to 1000 events, the codon's id, name, description, and startTime, and world.currentTime. Include <%= it.events %> when the sentinel needs event data; without that interpolation, the model receives no event data. The checked observer uses <%= JSON.stringify(it.events, null, 1) %>.

Model ids behave differently for sentinels than for codons, and the difference bites at runtime. Use a full provider-qualified model id for a sweep sentinel. A model string without / is not checked against the registry at load and fails at its first model call with No LLM provider available. A full model id is checked at load and, when unavailable, is skipped with an info-level log. In the checked failure capture, the short sentinel model string haiku passes validation with GOOD TO RUN! and sentinels: 1, then throws No LLM provider available at every trigger. The anchor therefore uses anthropic/claude-haiku-4-5.

The anchor's quality-observer demonstrates the observer half. It triggers on codon.completed for validate-and-repair; its prompt receives completion-event fields and explicitly says that artifact existence, row coverage, and correctness cannot be verified from that input. Its bare quality-observer.log output therefore goes to the managed sentinel directory, not beside the codon's files.

JSON
{
  "id": "quality-observer",
  "name": "Quality Observer",
  "description": "Observes the validate-and-repair completion event. Reports only status and budget fields present in that event; cannot read or verify output artifacts.",
  "trigger": {
    "type": "event",
    "on": [
      "codon.completed"
    ],
    "conditions": [
      {
        "operator": "equals",
        "path": "codonId",
        "value": "validate-and-repair"
      }
    ]
  },
  "execution": {
    "strategy": "immediate"
  },
  "model": "anthropic/claude-haiku-4-5",
  "userPromptText": "The codon `validate-and-repair` completed. Here are the triggering completion events:\n\n<%= JSON.stringify(it.events, null, 1) %>\n\nSummarize only status, failure reasons and budget fields actually present. These completion events do not provide file contents or file.updated events. State explicitly that artifact existence, row coverage and correctness cannot be verified from this input. Never treat missing file-update events as proof of missing files. Observe and report only.",
  "output": {
    "format": "text",
    "file": "quality-observer.log"
  }
}
JSON
      "sentinels": [
        {
          "sentinelConfig": "sentinels/quality-observer.json"
        }
      ],

The checked sentinel-sweep fixture wires an event-conditioned observer per codon. It demonstrates the observer half only; it has no exhaustWithPrompt, no slash-containing agent-root output, and no codon.extended events.

JSON
{
  "$schema": "https://unpkg.com/hankweave@0.10.0/schemas/hank.schema.json",
  "meta": {
    "name": "sentinel-sweep demo",
    "version": "1.0.0"
  },
  "hank": [
    {
      "id": "step-one",
      "name": "write part one",
      "model": "haiku",
      "continuationMode": "fresh",
      "promptText": "Write the single word 'alpha' to part-one.txt. Nothing else.",
      "checkpointedFiles": [
        "part-one.txt"
      ]
    },
    {
      "id": "step-two",
      "name": "write part two",
      "model": "haiku",
      "continuationMode": "fresh",
      "promptText": "Write the single word 'beta' to part-two.txt. Nothing else.",
      "checkpointedFiles": [
        "part-two.txt"
      ],
      "sentinels": [
        {
          "sentinelConfig": "sentinels/sweep-observer.json"
        }
      ]
    }
  ]
}

To close the loop, let's use sentinel-sweep-exhaust: one digest codon with exhaustWithPrompt and maxExtensions: 2, an observer sentinel triggered by codon.started and codon.extended, and sentinel-notes/observer.log as a slash-containing output path. The prompt reads ./sentinel-notes/, and the capture shows the digest growing across passes.

FIG. 1 One-way feedback flow in a sentinel sweep
Read the diagram as text
Output
+-- agentRoot/ -----------------------------------------------------+
|                                                                   |
|  +-----------------------------------------+   folds the notes    |
|  | digest codon, extended with             |-------------------+  |
|  | exhaustWithPrompt                       |                   v  |
|  +--------+------------------------^-------+   +-----------------+|
|           |                        |           | notes-digest.md ||
|           | emits codon.extended   | read on   +-----------------+|
|           |                        | the next pass                |
|           |            +-----------+------------------+           |
|           |            | sentinel-notes/observer.log  |           |
|           |            +--------------^---------------+           |
+-----------|---------------------------|---------------------------+
            v                           | writes findings
   +--------------------+               |
   | observer sentinel  |---------------+
   +--------------------+
JSON
{
  "$schema": "https://unpkg.com/hankweave@0.10.0/schemas/hank.schema.json",
  "meta": { "name": "sentinel-sweep-exhaust", "version": "1.0.0", "description": "One codon that extends itself with exhaustWithPrompt and folds in the notes a sentinel writes into its workspace." },
  "hank": [
    {
      "id": "digest",
      "name": "Digest the observer's notes",
      "model": "deepseek-v4-flash",
      "continuationMode": "fresh",
      "promptFile": "./prompts/digest.md",
      "exhaustWithPrompt": "Look in ./sentinel-notes/ again. Fold any note you have not digested yet into notes-digest.md as a new bullet (quote the note's line). If there is nothing new, append the single line 'no new notes' and stop.",
      "maxExtensions": 2,
      "checkpointedFiles": ["notes-digest.md", "sentinel-notes/**"],
      "sentinels": [ { "sentinelConfig": "./sentinels/observer.json" } ]
    }
  ]
}

Check-it: For an event observer, verify the completion log only for the status and budget fields that the event contains; use an artifact checker or a codon to verify files. For the loop-closing shape, look for sentinel-notes/observer.log under agentRoot/ and have the agent read it without editing it.

Running and verifying a completion pattern#

Whichever shape you chose, the verification steps are the same. Work from the directory containing the hank. Validate the configuration first:

⌁ Terminal
bunx hankweave@0.10.0 hank.json data/ --validate

--validate hashes the inputs, resolves and validates configuration, and performs harness setup and credential/catalog checks. It does not execute codons or make a model-generation self-test, and its registry initialization disables provider health checks. It may add $schema to the hank and write a temporary log. Runtime startup is separate: it can run provider health checks such as generateText("Hi", maxOutputTokens: 16) for available registry providers, and those calls may be billable outside tracked codon cost.

A successful validation looks like this:

Output

╭────────────────────────────────────────────────────────────────────╮
│  Hankweave v0.10.0                                                 │
│  darwin arm64 • node v23.8.0                                       │
╰────────────────────────────────────────────────────────────────────╯

Calculating data signature for validation...

> Validating configuration: <workspace>/fixtures/minimal-single-provider/hank.json

  Data source:    <workspace>/fixtures/minimal-single-provider/data
  Execution path: ~/.hankweave-executions/validation-<id>

✓ Configuration is valid!

╭──────────────────────────────────────────────────────────────────────────────╮
│  Minimal single provider v1.0.0                                              │
│  1 codon • 0 loops                                                           │
╰──────────────────────────────────────────────────────────────────────────────╯

└─ [1] summarize-notes (Summarize the notes)
      model: haiku │ mode: fresh │ prompts: 1 (13 lines)
      checkpointedGlobs: 1

╭─ GOOD TO RUN! ─────────────────────────────────────────────────────╮
│  1 codons • 1 prompts • 0 system prompts • 0 rigs • 1 checkpoints  │
╰────────────────────────────────────────────────────────────────────╯

Run it:  hankweave hank.json <data_path>

Environment Variables:

  From System (HANKWEAVE_ prefixed):
    - CAPTURE_VERSION: 0.10.0
    - TELEMETRY: 0

exit=0

Run the hank explicitly; a bare hankweave invocation launches the welcome wizard instead of this hank:

⌁ Terminal
bunx hankweave@0.10.0 hank.json data/ --headless --start-new -o out

The copied outputs are in out/; inspect ~/.hankweave-executions/<id>/agentRoot/ for the run's agent files. A slash-containing sentinel output appears below agentRoot/; a bare-named sentinel log appears under .hankweave/sentinels/outputs/<sentinelId>/; the journal carries codon.extended; and state.json carries extensionCount. The observer log and the execution tree below show what a healthy sweep leaves behind: the sentinel's stated limits, and both the digest and the notes file under agentRoot/.

Output
**Status:** Success
**Cost:** $<cost>
**Duration:** 197.8 seconds
**Exit Status:** Success
...
- **Artifact existence cannot be verified** — no file.updated events provided
- **Row coverage cannot be verified** — no file contents or update events provided
- **Correctness cannot be verified** — no file contents or update events provided
Output
./notes-digest.md
./sentinel-notes/observer.log

Check-it: The captured extension records show incrementing extensionNumber values 1 and 2; the execution tree contains both notes-digest.md and sentinel-notes/observer.log.

Recognize two failure signatures. An extension failure can leave a sealed checkpoint and complete artifact while the run exits 1 and resume offers to re-run the sealed codon. Separately, a short, unqualified sentinel model can fail at trigger time with No LLM provider available while codons run normally. Use a full provider-qualified sentinel model. If a skipped sentinel must fail its codon, set failCodonIfNotLoaded: true; its default is false. An outputFiles beforeCopy or copy failure forces RunFailed and shutdown with exit 1.

Check-it: Name both failure signatures: exit 1 after a sealed checkpoint, and the sentinel-side No LLM provider available error.

Choose the pressure shape#

For each codon, decide whether another pass needs its own structure, another perspective, and enough value to justify the extra spend. Choose no completion pattern for bounded, structured work; a nudge or extension for an open-ended survey; a loop for repeated multi-step work; and a sweep when the agent should read an observer's notes on its next pass. The sentinel-sweep-exhaust fixture closes that feedback loop; the anchor's quality-observer demonstrates observation only.