Compose hanks without losing their boundaries

A single hank handles a single bounded job well. Real work rarely stays that small. Sooner or later you will want one hank to launch another as a separate process, a loop that rebuilds an implementation against a fixed test suite, a skill written for interactive agents turned into declarative codons, or shared scaffolding reused across several hanks. Each of these moves crosses a boundary, and each boundary has rules: what passes through it, what stays sealed, and what fails loudly when you get it wrong.

This page works through those four composition patterns. Each section gives the mechanics, a checked fixture or capture to read against, and a short check you can run to confirm the pattern behaved as expected in your own execution.

When one hank should call another#

When we already have a hank for a bounded task, we can call it from another hank rather than fold its steps into the parent. A command operation in the parent's rig launches the child as a separate process, and the child remains a black box to the parent: it keeps its own state, journal, and checkpoints. The command requires run. Its workingDirectory is project by default (the agent workspace, agentRoot) or can be lastCopied. The schema row below records that contract; see rig concepts and hank JSON for the rest of it.

Scroll to explore the table →
fieldtypedefaultrequiredconstraintsdescription
typestringyes= commandType of setup operation
commandobjectyes
allowFailurebooleanfalsenoIf true, failure of this operation won't fail the codon (default: false). Recommended for rig setup in loop codons where operations might fail in some iterations (e.g., running commands that might not succeed initially).

Before invoking the child, stage it inside the agent workspace. The child hank and every file its command needs must be reachable from the workspace, so a copy operation prepares the directory and a command operation then runs the staged file. The fragment below shows that ordering: make a directory, copy a script into it, run it.

Captured 2026-09-06T13:17:40+00:00 · hankweave@0.10.0 · valid-fragment (hank.schema.json).

JSON
      "rigSetup": [
        {
          "type": "command",
          "command": {
            "run": "mkdir -p pipeline"
          }
        },
        {
          "type": "copy",
          "copy": {
            "from": "rigs/preflight.ts",
            "to": "pipeline/preflight.ts"
          }
        },
        {
          "type": "command",
          "command": {
            "run": "bun pipeline/preflight.ts"
          }
        }
      ],

The launch itself uses the binary directly; there is no run subcommand. Three flags do most of the work for a child invocation:

Output
bunx hankweave@0.10.0 child/hank.json child/data --headless --start-new -o ./child-out

Use --headless for unattended mode, since a child cannot answer prompts. Use --start-new when unchanged data must be processed again: without it, an unchanged completed child can resume silently, exit 0, and do no work. Use -o to receive the child's configured exports. The child runs in its own ~/.hankweave-executions/<id>/ directory with separate state, journal, and checkpoints (sealed recovery states). Only files selected by the child's outputFiles and copied to the configured output directory return to the parent workspace, so a child without outputFiles exports nothing through -o.

FIG. 1 Process boundary and file handoff between parent and child hanks
Read the diagram as text
Output
+-- parent execution (agentRoot) ------------------------------------+
|                                                                    |
|  +-------------------------------+    +----------------------------+
|  | copy rig stages the child's   |--->| command rig runs           |
|  | files                         |    | bunx hankweave             |
|  +-------------------------------+    +-------------+--------------+
|                                                     | spawns the process
|  +-------------------------------+                  |             |
|  | parent workspace receives     |<------------+    |             |
|  | child-out                     |  copied     |    |             |
|  +-------------------------------+  via -o     |    |             |
+---------------------------------------------- | ----|-------------+
                                                |     v
+-- child execution (its own execution directory) ------------------+
|                                                                    |
|  +-------------------------------+   +----------------------------+|
|  | child process                 |---| sealed: state, journal,    ||
|  | (--headless --start-new)      |   | checkpoints                ||
|  +---------------+---------------+   +----------------------------+|
|                  v                                                 |
|  +-------------------------------+                                 |
|  | outputFiles exported via -o   |---------------------------------+
|  +-------------------------------+                                 |
+--------------------------------------------------------------------+

The checked parent-child fixture puts these pieces together. It stages child before running it; the child declares outputFiles: [{copy: ["child-out.txt"]}]; and the parent command uses -o child-run, checks that child-run/child-out.txt is non-empty, then copies the file into the parent workspace. Read the rig below with that chain in mind: the copy operation stages, and the long command operation launches, verifies, and retrieves.

Captured 2026-09-04 · hankweave@0.10.0 · parent-child rig command.

JSON
        {
          "type": "copy",
          "copy": {
            "from": "child",
            "to": "child"
          }
        },
        {
          "type": "command",
          "command": {
            "run": "cd child && bunx hankweave@0.10.0 hank.json data/ --headless --start-new --max-cost 0.3 -o child-run > child-run.log 2>&1 && printf '\\nchild exit: 0\\n' >> child-run.log && test -s child-run/child-out.txt && cp child-run/child-out.txt ../child-out.txt && tail -3 child-run.log > ../child-run-tail.txt"
          }
        }

The capture records the parent's own managed execution (parent-success, exit 0) and a clean child exit with COMPOSE exported via -o. The child runs as its own managed execution because each launched hankweave process auto-creates one. In this fixture the child inherits ambient provider keys from the parent process: neither the child hank nor the rig supplies a key. A default child also avoids the nested-execution guard, which requires an explicit execution path containing both /.hankweave-executions/ and /data.

Failure crosses the boundary in a specific way. A non-zero child exit fails the rig operation, the parent codon fails in preparing before agent tokens are spent, and the rig.setup.failed event carries failureType and, when present, exitCode. With allowFailure: true, the operation logs a warning and the codon continues instead. The event rows below show exactly what the journal records in each case.

Scroll to explore the table →
idcategoryjournaledsentinelRoutedpayloadFieldsreceipts
rig.setup.completedagentic-backbonetruetruecodonId, rigType, commandCount, durationMs, createdCheckpointschemas/event-schemas.ts:597, schemas/event-schemas.ts:990, schemas/event-schemas.ts:1242, hankweave-runtime.ts:2160
rig.setup.failedagentic-backbonetruetruecodonId, failureType, exitCode?, commandIndex?, ignoredschemas/event-schemas.ts:602, schemas/event-schemas.ts:991, schemas/event-schemas.ts:1243, hankweave-runtime.ts:1791

Check-it: After a successful call, inspect the parent's journal for rig.setup.completed and verify the exported file in the parent workspace. The two captures below show both outcomes from the fixture. The first is the success run; the second is the supplied failure variant, where the failing child makes the command rig fail with exit code 1 and rig.setup.failed while the parent codon fails in preparing. The fixture manifest attributes the child failure to a missing child prompt.

Captured 2026-09-04 · hankweave@0.10.0 · observed parent-child success run.

Output
exit=0

Captured 2026-09-04 · hankweave@0.10.0 · observed child failure.

Output
[<ts>] [ERROR] Rig setup failed at item 2 ({"type":"command","command":{"run":"cd child && bunx hankweave@0.10.0 hank.json data/ --headless --start-new --max-cost 0.3 -o child-run > child-run.log 2>&1 && printf '\\nchild exit: 0\\n' >> child-run.log && test -s child-run/child-out.txt && cp child-run/child-out.txt ../child-out.txt && tail -3 child-run.log > ../child-run-tail.txt","workingDirectory":"project"},"allowFailure":false}): Command failed with exit code 1
exit=1

A child can also live outside the parent entirely. Pass one positional remote hank beginning with https://, http://, or git@; Hankweave clones it into a local cache and reports either ✓ Cloned to cache or Using cached version. See the CLI reference for the remote grammar.

At campaign scale, the docs forge uses this same process boundary for concurrent shard hanks. It also uses a hank as another hank's data: one gate-packet hank included its own hank.json, system prompt, and codon prompts in its data directory. Treat those as separate executions and inputs, not a shared session.

Build to a fixed spec with a discovered implementation#

Sometimes we know exactly what the output must look like but need an agent to discover the implementation. The Clausetta pattern handles this: fix the specification and tests first, then research, run a build loop and an evaluation loop, and document the result. The spec stays fixed while repeated evaluation guides the implementation.

Give the agent the documentation, the fixed spec as its system prompt, and an evaluation suite. In Clausetta, the spec is a 1,400-line definition of the exact output format, and a rig runs the evaluation suite between build iterations. Each iteration gets fresh context, with the new context told to inspect the current implementation and continue from it. The pseudocode below shows the shape of one cycle before we look at the hank that implements it.

Output
// Pseudocode, not the implementation.
build loop:
  give the iteration fresh context
  read the current implementation and fixed specification
  build or continue the implementation
  let the rig execute the fixed evaluation suite
  if the termination condition is met: terminate
  otherwise: iterate

Use continuationMode: "fresh" for the fresh-eyes choice. <%EXECUTION_DIR%> is a silent alias of canonical <%AGENT_ROOT%>; prefer <%AGENT_ROOT%> in new hanks, and see hank JSON for template variables. In the checked fixture, the evaluation command sits inside the implement codon's rigSetup and the surrounding loop supplies the termination condition, so the evaluation is part of each cycle rather than a separate evaluation codon. Running a fixed test suite in a rig beats asking an agent to improvise the check.

This pattern works when the specification remains the source of truth, each iteration sees current state with fresh eyes, the evaluation suite gives an objective pass or fail, and files preserve documentation across iterations. A reported production comparison put (Agentic Build × Automated Review) × 5 + Agentic Review ahead of (Agentic Build + Agentic Review) × 4; comics-hank v2 was reported at one-tenth the cost with better output. That is a named reported result, not a promise about a new hank.

Clausetta ships as learning/examples/clausetta in the runtime repository. The checked counterpart is examples/connector-build-test. The excerpt below shows its build loop and evaluation rig: note the iterationLimit termination, the loop budget, the fresh continuation mode, and the bun test rig command with allowFailure: true so a failing test run feeds the next iteration instead of killing the codon.

Captured 2026-09-03T07:25Z · hankweave@0.10.0 · connector-build-test build loop and evaluation rig.

JSON
      "type": "loop",
      "id": "build",
      "name": "Build the connector until the tests pass",
      "terminateOn": {
        "type": "iterationLimit",
        "limit": 3
      },
      "budget": {
        "maxDollars": 0.6,
        "maxTimeSeconds": 600,
        "onExceeded": "complete"
      },
      "codons": [
        {
          "id": "implement",
          "name": "Write the connector implementation",
          "model": "haiku",
          "continuationMode": "fresh",
          "promptFile": "prompts/build.md",
          "rigSetup": [
            {
              "type": "copy",
              "copy": {
                "from": "spec.md",
                "to": "spec.md"
              }
            },
            {
              "type": "copy",
              "copy": {
                "from": "tests",
                "to": "tests"
              }
            },
            {
              "type": "command",
              "command": {
                "run": "bun test 2>&1 | tee test-output.txt"
              },
              "allowFailure": true
            }
          ],
          "checkpointedFiles": ["connector.ts"],
          "onFailure": "retry",
          "retryConfig": {
            "maxAttempts": 2,
            "delayMs": 1000,
            "maxDelayMs": 60000
          },
          "budget": {
            "maxDollars": 0.2,
            "maxTimeSeconds": 300,
            "onExceeded": "complete"

Check-it: The connector fixture runs bun test in the evaluation rig between fresh build iterations. The loop terminates at its declared iterationLimit, with the evaluation-pass artifact on disk; run and cost details belong to the linked example page.

Import a skill by extracting, never by pasting#

A skill is written for interactive use: an agent decides when to invoke it and improvises. A hank is declarative: its codons and operations are predetermined. That mismatch means a skill cannot be pasted into a hank wholesale. The usable parts have to be extracted and re-expressed as a recipe.

Use this four-way map:

  1. Turn scripts into rig commands.
  2. Turn relevant instructions into prompt sections.
  3. Turn references/-style documents into pre-compiled context files.
  4. Turn a multi-step workflow into codon structure.

Discard trigger conditions, interactive instructions, UI-specific logic, and "when to use this skill" metadata; a hank has explicit codons instead of an invocation decision. Skills commonly live in Claude Code's CLAUDE.md and .claude/commands/*.md, Codex's ~/.codex/skills/{name}/SKILL.md and AGENTS.md, or Cursor's .cursor/rules/*.md and ~/.cursor/skills-cursor/{name}/SKILL.md. Read these as source material, not as Hankweave inputs.

Read the whole skill before extracting. Test every script against sample data, check dates, and question unverified endpoints or instructions. Prefer extracting three useful lines over importing three pages, and verify the scripts before wiring them into a codon; see rig verification.

The worked example, hankweave-skill.md, is maintained as a front door plus a catalogue and per-concern section files, with bun assemble.ts assembling the completed sections instead of one hand-edited blob. The excerpt below is that front door; notice how much of it is navigation and maintenance guidance rather than agent instruction.

Assembled 2026-09-01 · illustrative source excerpt.

Output
<!-- hankweave skill · assembled 2026-09-01 · 5 sections · filter: ruled,living -->

# How to be a good agentic programmer

*Split into section files 2026-08-30 (Hrishi's design): this file is the front door — the definition and the fit test. What-is-where lives in `catalogue.md`. Everything else lives in `sections/`, one file per concern, loaded when needed. `catalogue.md` says what is where and how done it is; `bun assemble.ts` builds the skill from the done sections as one clean document; `--all` includes the staged stubs.*

For this skill, we can extract fit-test and write/run-shape material into prompt sections and map its section-file layout to per-codon promptFile entries. It ships no scripts, so most of the skill need not enter the hank, and assemble.ts is a maintenance command in the source header, not behavior we are extracting. Keep the capabilities you need explicit as rig code, prompt instructions, and workspace templates so you can inspect and debug them.

Once the hank is composed, validate it. --validate hashes the data source, resolves and validates configuration, and performs harness, credential, and catalog checks. It disables registry provider health checks and does not execute codons or make a model-generation self-test. It can add $schema to the hank and write a temporary log, so do not describe it as universally offline or no-write. Runtime startup is separate: it may run provider health checks such as generateText("Hi", maxOutputTokens: 16) on available registry providers, and those probes may be billable and sit outside a tracked-codon capture total.

The acceptance marker is the GOOD TO RUN! box:

Captured validation output from the minimal single-provider fixture.

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

Check-it: Confirm the GOOD TO RUN! box appears after composing the hank. Do not confuse this configuration check with runtime-startup provider calls.

Share scaffolding without leaving the hank directory#

When several hanks need the same project skeleton, keep package.json, tsconfig.json, and src scaffolding in a templates directory inside the hank, then copy-rig that template into the agent workspace before the codon that needs it.

The location matters because of the path rules. A copy rig's from must be a portable relative POSIX path inside the parent hank directory; absolute paths, .. escapes, and symlinks are rejected. Put shared scaffolding inside the hank or use a remote hank rather than an old ../templates-style path. See the hank JSON path rules.

Loops add a second decision: install once, or start fresh each iteration. If a loop reuses dependencies without changing them, add a setup codon before the loop and install once there, leaving rigSetup off the loop codons. Files written into the agent workspace persist across codons in one execution, and the rig-setup checkpoint seals that prepared state. Keep the full setup contract in rigs.

For independent template variants, do the opposite: copy a fresh template in each iteration and set archiveOnSuccess to the file glob (a file-matching pattern) current-project/**. A loop codon's archive path follows this layout:

Output
<execution>/rigArchive/<loopId>-<iteration>/<codonId>-<iteration>/<path>

The runtime turns an ID such as edit#0 into edit-0, so the checked fixture contains rigArchive/revise-0/edit-0/current-project/history.txt and rigArchive/revise-1/edit-1/current-project/history.txt. From a command running in agentRoot, copy-back starts at the sibling path ../rigArchive/. Loop-level termination archives use rigArchive/<loopId>-loop/<path>; non-loop codons use rigArchive/<codonId>/<path>. See execution directory for the archive contract.

Those archive paths make sense once the ID scheme is clear. A loop's declared id is its loopId. A codon inside that loop receives a runtime ID of <codonId>#<iteration>, with the iteration suffix starting at zero–for example, review#0.

Check-it: For an install-once loop, verify that the next iteration starts with dependencies present and its loop codon has no rigSetup. For template variants, inspect both iteration suffixes and the codon directory in rigArchive.