# Codons: why work is split into sealed units When you hand a coding agent a large task, the natural instinct is to let one session run until the work is done. Hankweave takes the opposite position: it splits the work into codons, sealed agent tasks that run in a fixed order, each with its own session, its own environment setup, and a checkpoint that records what it left behind. This page explains why that boundary exists, what exactly a codon seals, and the decisions you make at each boundary: whether the next codon inherits the conversation, what happens on failure, how a codon can use its whole context window, and what survives for the next stage. By the end you should be able to look at a `hank.json` file and explain not just what each codon does, but why it ends where it does. ## Why not one long agent session? A long agent session appears to preserve context, so why introduce boundaries at all? The answer starts with the file that defines the run. A hank is a JSON file whose `hank` array places its codons in a fixed order. A codon is one sealed agent task in that sequence; it gives the session a deliberate end. With `continuationMode: "fresh"`, each codon starts a new agent session. That is the default for most cases, and all four codons in the shipped init fixture use it. Context created inside a session disappears when that session ends unless the codon explicitly writes it to files. That boundary is what makes the work inspectable: instead of trusting one uninterrupted conversation for the whole run, you can look at what one bounded session actually left for the next. The mechanism for that inspection is the checkpoint. The files named by `checkpointedFiles` are watched for changes, streamed to Hankweave's interactive WebSocket client–the TUI (terminal interface)–and tracked in Hankweave's git-based checkpoint system. Any attached read-only behavior is enforced by client-side guards; the server handshake itself is not a read-only mode. The patterns are resolved using applicable `.gitignore` rules inside the agent workspace, and the linked data-source tree is excluded from checkpointing. A checkpoint – the sealed record of tracked files at a codon boundary – lives in a dedicated git repository inside the execution directory: its git directory is `.hankweave/checkpoints/.hankweavecheckpoints` (named `.hankweavecheckpoints` instead of `.git`, so Git does not treat the execution environment as a submodule), and the agent workspace (`agentRoot`) is its work tree. Inspect it with `git --git-dir=.hankweave/checkpoints/.hankweavecheckpoints log`; there is no `.git` there, so a plain `git log` would silently answer from a parent repository. Gitignore patterns filter the tracked set; they do not widen it. The checkpoint repository belongs to the execution directory, not the operator's repository. For the ordered checkpoint-restore procedure, see [checkpoints](/0.10.0/files/concepts/checkpoints) and [resume, rollback, and retry](/0.10.0/files/operate/resume-rollback-and-retry). The overall shape of one codon is a pipeline: prompt, model, and checkpointed files go in, one agent session runs, a sealed checkpoint comes out, and the next codon starts fresh. ```text // Pseudocode, not the implementation. prompt + model + checkpointedFiles → one agent session → sealed checkpoint → next codon starts fresh ``` Codon boundaries also shrink the trust window: you are not trusting one agent for two hours; you are trusting ten agents for fifteen minutes each with explicit handoffs. The durations describe the trust model, not a runtime guarantee. Sealed boundaries give you somewhere to recover from. A failed execution normally invokes `rollbackToLastSuccess` on startup, with `autostart` controlling whether it continues; if there is no checkpoint to roll back to, startup falls back to a fresh run. The TUI/WebSocket rollback controls let an operator choose a recovery point. A codon can therefore be rerun from a selected checkpoint, bounding the recovery point to the codon's sealed artifacts. There is no CLI checkpoint-restore flag in 0.10.0; the exact rollback procedure belongs to [checkpoints](/0.10.0/files/concepts/checkpoints) and [resume, rollback, and retry](/0.10.0/files/operate/resume-rollback-and-retry). While a codon runs, it moves through a defined set of states. The two excerpts below come from the pinned source: the first is the full `CodonStatus` union, the second lists which states each non-terminal state can transition to. ```ts export type CodonStatus = | "preparing" // Rig setup running (copy files, run commands) | "starting" // Spawning Claude process | "initializing" // Process started, waiting for session ID | "running" // Claude is working (have session ID) | "completing-sentinels" // Completing sentinel work (draining queues) | "completed" // Success - terminal state | "failed" // Failed - terminal state | "skipped"; // User skipped - terminal state ``` ```ts preparing: ["starting", "failed", "skipped"], starting: ["initializing", "failed", "skipped"], initializing: ["running", "completed", "failed", "skipped"], running: ["completing-sentinels", "completed", "failed", "skipped"], "completing-sentinels": ["completed", "failed", "skipped"], ``` Read the union top to bottom and it traces the normal flow: `preparing` while rig setup runs, `starting` and `initializing` while the agent process spawns, `running` while the agent works, `completing-sentinels` while sentinel queues drain, and finally `completed`. The transition table shows the ways off that path: every non-terminal state can leave for `failed` or `skipped`, which are terminal alongside `completed`. ## What exactly gets sealed into a codon The states above describe how a codon behaves at runtime; its configuration determines what it is. That configuration lives in the `hank` array, the immutable logic sequence. It must contain at least one entry, and its order is fixed when the hank loads. Each entry is either a codon (the default `type`) or a loop (`type: "loop"`) with required fields `type`, `id`, `name`, `terminateOn`, and `codons`. A codon requires `id`, `name`, `model`, and `continuationMode`. Its configuration must contain at least one truthy `promptFile` or `promptText`. Supplying both is accepted: the prompt builder reads `promptFile` first and falls back to `promptText`. Prefer one prompt field when authoring, but do not infer runtime rejection from the schema descriptions, which call the pair mutually exclusive. The system-prompt pairs are different: a codon's `appendSystemPromptFile`/`appendSystemPromptText` and the hank root's `globalSystemPromptFile`/`globalSystemPromptText` reject supplying both. The contract requires `continuationMode`; “default for most cases” describes the usual value, `fresh`, rather than an omitted field. The full field set, drawn from the schema, is below. Most fields are optional; the required four plus a prompt are the minimum viable codon. | field | type | default | required | constraints | description | | ------------------------ | ------------------------- | ------- | -------- | ------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `type` | `string` | `codon` | no | = `codon` | Type discriminator - optional, defaults to 'codon' | | `id` | `string` | | yes | minLength 1 | Unique identifier for this codon (e.g., 'codon-1', 'data-analysis') | | `name` | `string` | | yes | minLength 1 | Human-readable name displayed in UI and logs | | `promptFile` | `string \| array` | | no | | Path to a file containing the prompt (mutually exclusive with promptText). Must be a relative path inside the hank directory using '/' separators; absolute paths, '..' escapes, and symlinks are rejected. | | `promptText` | `string` | | no | | Inline prompt text (mutually exclusive with promptFile) | | `appendSystemPromptFile` | `string \| array` | | no | | Path to a file containing system prompt to append (mutually exclusive with appendSystemPromptText). Must be a relative path inside the hank directory using '/' separators; absolute paths, '..' escapes, and symlinks are… | | `appendSystemPromptText` | `string` | | no | | Inline system prompt text to append (mutually exclusive with appendSystemPromptFile) | | `model` | `string` | | yes | minLength 1 | Model to use for this codon. Can be a Claude model ('sonnet', 'opus'), Gemini model ('gemini-2.0-flash-exp', 'flash'), or any other model supported by the configured shim. | | `continuationMode` | `enum` | | yes | `fresh` \| `continue-previous` | How this codon should handle continuation from previous codons. 'fresh': Start a new session (default for most cases). 'continue-previous': Continue from the previous codon's session, maintaining context and conversatio… | | `rigSetup` | `array` | | no | | Rig setup operations to run before codon starts. Each operation must complete successfully for codon to start. | | `description` | `string` | | no | | Optional description shown to users about what this codon does | | `checkpointedFiles` | `array` | | no | | Glob patterns for files to checkpoint during codon execution. These files will be: watched for changes and streamed to the client, tracked in the git-based checkpoint system, and resolved using gitignore rules for consi… | | `env` | `object` | | no | | Optional environment variables to set for the Claude process | | `outputFiles` | `array` | | no | | Optional output copy steps to run after codon completion: files to copy out from a completed codon, with optional pre-copy commands. | | `sentinels` | `array` | | no | | Sentinels to run during this codon. Sentinels are parallel observation agents that process the event stream. Each entry is a wrapper object with sentinelConfig (portable sentinel configuration, file or inline) and setti… | | `archiveOnSuccess` | `array` | | no | | Paths to archive after successful completion. These files/directories are moved to rigArchive/ after the codon completes successfully. Paths are relative to the agent workspace (agentRoot/). Archived files can be restor… | | `onFailure` | `enum` | | no | `abort` \| `retry` \| `ignore` | How to handle codon failure. 'abort' (default): Use existing failure behavior (server stays active for retriable errors, shuts down for non-retriable). 'retry': Automatically retry up to maxAttempts times if the error i… | | `retryConfig` | `object` | | no | | Configuration for retry behavior. Only used when onFailure is 'retry'. Delays grow exponentially from delayMs and are capped at maxDelayMs; when the provider supplies a Retry-After hint, that value is used instead of th… | | `exhaustWithPrompt` | `string` | | no | | Prompt 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. | | `maxExtensions` | `integer` | `100` | no | > 0 | Maximum number of extensions before forcing completion. Default: 100. Safety valve to prevent infinite extension loops. | | `autoCompact` | `boolean` | | no | | Whether 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… | | `shimIdleTimeout` | `integer` | | no | > 0; max 1800 | Max seconds between agent events before the session aborts (idle timeout). Overrides hank-level and runtime defaults. If unset, falls back to hank override, runtime config, or the built-in default (180s for Anthropic mo… | | `budget` | `object` | | no | | | One constraint in that table deserves emphasis because it is a common authoring mistake: a `promptFile` must be a relative path inside the hank directory and use `/` separators. Absolute paths, `..` escapes, and symbolic links are rejected. See the [hank JSON reference](/0.10.0/files/reference/hank-json) for the complete path contract. With the field set in mind, here is a real codon object from the shipped init fixture – the minimum viable shape plus checkpointing and an output copy step: ```json { "id": "analyze-pi", "name": "Analyze Project (Pi)", "model": "pi/anthropic/claude-haiku-4-5", "continuationMode": "fresh", "promptFile": "./prompts/analyze-pi.md", "checkpointedFiles": ["analysis-pi.md"], "outputFiles": [ { "copy": ["analysis-pi.md"] } ] } ``` Notice what this codon does not need: no `rigSetup`, no failure policy, no budget. It declares a prompt, a model, a fresh session, one tracked file, and one file to copy out. That is enough to seal a unit of work. One thing no configuration can seal is the model's wording. The model's text is stochastic: identical input and configuration can produce different output. We make the surrounding control flow, checkpointing, and evidence capture deterministic so we can inspect the work a run actually did. That does not promise repeatable model wording. Before the codon starts, its optional `rigSetup` array prepares the environment. It may contain `copy` operations or `command` operations. A command can specify `workingDirectory` as `project` or `lastCopied`: `project` runs in the execution-directory agent root; `lastCopied` runs in the target path of the most recent preceding `copy` operation, falling back to the agent root when none preceded it. Each operation must complete successfully before the codon starts unless that operation sets `allowFailure: true`; the prepared environment is the rig. The rig **executes** setup operations, the codon **runs**, and the checkpoint **seals** the tracked result afterward. The rig is explicit setup, not a claim that the agent's model output is deterministic. See [rigs](/0.10.0/files/concepts/rigs) for the setup contract. The lifecycle diagram above uses four conceptual phases – pending, rig, run, seal – and it is worth being precise about how they map to the `CodonStatus` enum you saw earlier, because they are not the same four things. `pending` precedes `preparing`; `rig` corresponds to `preparing`; `run` covers `starting`, `initializing`, `running`, and `completing-sentinels`; and `seal` follows the terminal `completed` state. The two figures below show the same four phases in ASCII and mermaid form. ![codon lifecycle](/content-assets/cf45dff5691c48c0/diagrams/concepts-codons/1.png) codon lifecycle
Diagram as text ```text pending ──→ rig ──→ run ──→ seal waits prepares agent checkpoint its turn environment session seals after it ```
## When should a codon continue the conversation? Everything so far has assumed `fresh` sessions, where files are the handoff. Sometimes the conversation itself is the handoff, and that is what `continuationMode` controls. A fresh session is the context firewall: it starts without the previous conversation and sees the current prompt plus the files available to it. `continue-previous` continues the previous codon's session with its full conversation history, and it requires that the previous codon completed successfully. Choose it when the conversation itself is part of the handoff; choose `fresh` when the files are the handoff. | field | type | default | required | constraints | description | | ------------------ | ------ | ------- | -------- | ------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `continuationMode` | `enum` | | yes | `fresh` \| `continue-previous` | How this codon should handle continuation from previous codons. 'fresh': Start a new session (default for most cases). 'continue-previous': Continue from the previous codon's session, maintaining context and conversatio… | The choice comes with edge cases, and they are not all treated equally. The first codon may be configured with `continue-previous`, but it produces a load warning rather than a hard error. The warning text is: `: First codon has continuationMode "continue-previous" but there's no previous codon` At startup, Hankweave prints it under the `! Configuration warnings:` block, after the hank structure rendering and before the server starts. By contrast, `continue-previous` with a model different from the previous codon is a hard validation error: different models cannot share a session ID. A codon can continue only from a previous codon that uses the same model; when the model changes, configure `fresh`. Continuing after a loop configured with `terminateOn: "contextExceeded"`, or after a codon with `exhaustWithPrompt`, errors at load because there is no usable context to continue. Continuing from a codon that checkpoints no files produces a warning because its work may not survive the handoff. Conversation continuity also affects how you read codons' two identifiers in logs: 1. The **config ID** is the ID written in `hank.json`. 2. The **runtime ID** is the ID used during execution. Outside a loop, they are the same. Inside a loop, the runtime ID carries the iteration suffix, such as `normalize-next#0`. This distinction matters when a log or rollback target names one iteration rather than the configuration entry. A loop is a repeating sequence of codons, and prompt composition has its own design rules. For prompt structure, template variables, and system-prompt choices, continue to [authoring prompts](/0.10.0/files/author/prompts) and the [hank JSON reference](/0.10.0/files/reference/hank-json). ## What happens when a codon fails? Continuation decides what a successful codon hands forward; failure policy decides what an unsuccessful one leaves behind. It answers a narrow question: what should happen after this codon fails? The codon **retries** or **aborts** according to policy; `ignore` lets the sequence continue. `onFailure` is the string enum `abort`, `retry`, or `ignore`, and its default is `abort`. The separate `retryConfig` object holds the retry settings. | field | type | default | required | constraints | description | | ------------- | -------- | ------- | -------- | ------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `onFailure` | `enum` | | no | `abort` \| `retry` \| `ignore` | How to handle codon failure. 'abort' (default): Use existing failure behavior (server stays active for retriable errors, shuts down for non-retriable). 'retry': Automatically retry up to maxAttempts times if the error i… | | `retryConfig` | `object` | | no | | Configuration for retry behavior. Only used when onFailure is 'retry'. Delays grow exponentially from delayMs and are capped at maxDelayMs; when the provider supplies a Retry-After hint, that value is used instead of th… | The default, `abort`, behaves differently depending on whether the failure can be retried and whether anyone is watching. Under `abort`, a retriable failure keeps the Hankweave WebSocket server active for a retry, while a non-retriable failure shuts the server down. In `--headless` mode, no interactive client is present to keep that retry available, so a retriable failure under `abort` fails fast instead. An idle-timeout abort is retriable, so `onFailure: "retry"` applies to it. A `retryConfig` without `onFailure: "retry"` is a configuration error. Which failures count as retriable is decided by a shared classifier. At the provider request level, connection resets, network and DNS failures, timeouts, idle-timeout aborts, HTTP 429 rate limits, and HTTP 5xx or overloaded responses are retriable. Authentication, invalid requests, billing, credit or quota exhaustion, and usage-limit caps are permanent. The shared classifier checks error text and status codes in order and uses the first matching rule, and both harnesses use it. An unrecognized error after a session has been established is retriable by default: one mistaken retry costs one bounded attempt, while one mistaken permanent classification could end the run. A process that fails before establishing a session is treated as a non-retriable local setup or process error instead. Two terms in that taxonomy are worth pinning down. The server is Hankweave's WebSocket server, whose default interactive interface is the TUI. Codon sessions use one of two agent harnesses (the runners for those sessions): the Claude Agent SDK for Anthropic models or the embedded Pi SDK for other providers. Their idle-timeout defaults differ: 180 seconds for the Claude SDK and 120 seconds for the Pi SDK. The failure taxonomy, `failureReason` values, and exit codes belong to [errors and exit codes](/0.10.0/files/reference/errors-and-exit-codes), not this concepts page. The built anchor fixture (7 codons) supplies a concrete policy contrast: `survey-and-extracts` retries with `onFailure: "retry"` and `retryConfig`, while `validate-and-repair` uses `onFailure: "abort"`. The relevant fragments: ```json { "id": "survey-and-extracts", "name": "Prepare native exports and review raw intake", "model": "haiku", "continuationMode": "fresh", "promptFile": "./prompts/survey-and-extracts.md", … "onFailure": "retry", "retryConfig": { "maxAttempts": 3, "delayMs": 20000, "maxDelayMs": 120000 }, … }, { "id": "validate-and-repair", "name": "Validate & Repair Envelopes", … "onFailure": "abort", … }, ``` The retrying codon is an intake step where a transient provider error is worth absorbing; the validation step aborts so a human can look at what went wrong. When you do choose `retry`, `retryConfig` bounds the work. `maxAttempts` accepts 1–10 attempts and defaults to 3 attempts. `delayMs` accepts 0–60000 milliseconds and defaults to 1000 milliseconds. `maxDelayMs` accepts 0–600000 milliseconds and defaults to 60000 milliseconds. The wait grows exponentially as `delayMs × 2^attempts`, capped at `maxDelayMs`; a provider-supplied `Retry-After` replaces the computed wait and is capped as well. Two caveats complete the picture. Retry counters exist only in memory: a server restart during a retry loses the counter and leaves the codon failed, and an ordinary restart then follows the failed-execution rollback path described above. And `ignore` needs a dependency check: if a later codon needs files that the failed codon should have created, continuing can make the later failure harder to explain. ## How a codon uses its whole context window Failure policy handles codons that stop early. The opposite case also exists: some tasks need one codon to keep asking the same session for another completion. Set `exhaustWithPrompt` and Hankweave re-sends that prompt unchanged after each successful completion. One extension is one additional completion round. The codon continues until context is exhausted, or until `maxExtensions` forces completion. `maxExtensions` defaults to 100 extensions and is the safety valve against an infinite extension loop. | field | type | default | required | constraints | description | | ------------------- | --------- | ------- | -------- | ----------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `exhaustWithPrompt` | `string` | | no | | Prompt 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. | | `maxExtensions` | `integer` | `100` | no | > 0 | Maximum number of extensions before forcing completion. Default: 100. Safety valve to prevent infinite extension loops. | | `autoCompact` | `boolean` | | no | | Whether 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… | Extensions can consume additional model work. Use them when the task needs continued context, not as an unbounded substitute for sizing a codon. What happens at the context boundary itself depends on `autoCompact`, which defaults to false. With the default, the provider's context-overflow error surfaces: a plain codon fails at the window, while a loop configured to terminate on `contextExceeded` terminates at the real boundary. With `autoCompact: true`, the harness compacts at the boundary, retaining a summary and trimming earlier turns. It emits a `compact_boundary` event–the `contextExceeded` signal–and continues on the compacted session. This differs from the default: with `autoCompact: false`, context overflow supplies the `contextExceeded` boundary, so a loop configured for that signal terminates there. The event is visible in the run event stream and is catalogued by [reference/events](/0.10.0/files/reference/events). > **VersionNote:** In 0.8.0, compaction changed from the default behavior to opt-in. A codon that relied on silent auto-compaction now reaches the provider's context boundary unless it sets `autoCompact: true`. ## What survives when a codon finishes? A codon boundary is useful only when the next stage can find the work. Several mechanisms decide what survives, and the first is the one introduced at the start of this page: `checkpointedFiles` supplies the tracked set. Its patterns are watched, streamed to the client, and resolved with applicable `.gitignore` rules inside `agentRoot`; the linked data-source tree is excluded. Hankweave connects the agent workspace to the source data directory with symbolic links by default. A symbolic link is a filesystem entry that points to another path; when creating the link fails, Hankweave falls back to copying. Checkpointing tracks files in place; `outputFiles` copies them out. After a codon completes successfully, `outputFiles` runs only when an output directory is configured. Its matching file patterns are copied from `agentRoot` to that directory. A configured `beforeCopy` entry is a shell command that runs in `agentRoot` before those patterns are copied; the output hook does not receive a `lastCopied` path. If an `outputFiles` copy or its `beforeCopy` command fails, the run fails. With no `-o` output target, both the hook and export are skipped; there is no default output directory. For the execution-directory layout, see [Execution directory](/0.10.0/files/reference/execution-directory). Output-directory procedures belong to the [runbook](/0.10.0/files/operate/runbook). Environment variables are the third kind of thing that crosses the boundary, and they have their own pass-through rules. For the agent environment, each system variable named `HANKWEAVE_` is passed through as ``, except variables under `HANKWEAVE_RUNTIME_` and `HANKWEAVE_SENTINEL_`; a literal value of `unset` removes the target variable. Codon-level `env` values overlay and override that pass-through. Keep secrets in system environment variables with the `HANKWEAVE_` prefix rather than in `hank.json`. > **Pitfall:** Values placed in `env` are part of `hank.json` and can reach logs and checkpoints; use system variables with the `HANKWEAVE_` prefix for secrets. Finally, a codon can carry limits on what it may consume before it finishes. A codon-level `budget` can carry `maxDollars`, `maxTimeSeconds`, `maxOutputTokens`, `maxContextTokens`, and `onExceeded: "complete" | "fail"`. Its `onExceeded` policy overrides the hank-level policy at `overrides.budget.onExceeded`. Hank and loop budget containers also carry `allocation` and `shares`. When a budget cap trips, its cost rules belong to [budgets](/0.10.0/files/concepts/budgets). | field | type | default | required | constraints | description | | ------------------ | --------- | ------- | -------- | -------------------- | ---------------------------------------------------------------------------------------------------------------------------- | | `maxDollars` | `number` | | no | > 0 | Max cost in USD. Execution stopped when exceeded. | | `maxTimeSeconds` | `number` | | no | > 0 | Max wall-clock time in seconds. | | `maxOutputTokens` | `integer` | | no | > 0 | Max output tokens. | | `maxContextTokens` | `integer` | | no | > 0 | Max context window tokens (high-water mark of input+output per turn). Useful for capping context growth independent of cost. | | `onExceeded` | `enum` | | no | `complete` \| `fail` | Override hank-level onExceeded for this codon. | ## How small should a codon be? The mechanics above give you the pieces; sizing is the judgment call that assembles them. Start with a single purpose. A `fresh` continuation mode creates a context firewall; `continue-previous` accumulates conversation when that continuity is the handoff. We work through those sizing decisions in [designing codons and handoffs](/0.10.0/files/author/designing-codons-and-handoffs). As prompt authors, we hold context the model lacks: why the task matters, what happened before, and what “good” looks like. Each unstated assumption is a possible failure point. At a codon boundary, we decide what to put in the prompt and what to leave in a file. Three corrections catch common boundary mistakes: * **Don't do this:** configure `continue-previous` for a first codon and treat the warning as a previous session. **Instead:** use `fresh` when there is no previous codon. * **Don't do this:** change models while continuing a session. **Instead:** use `fresh` at a model boundary. * **Don't do this:** put unrelated analysis, repair, tests, and documentation into one giant codon. **Instead:** split them into single-purpose codons with explicit file handoffs. Use the [BOUNDARY DRILL](/0.10.0/files/author/designing-codons-and-handoffs) when deciding whether two stages need different models, different failure policies, or an artifact a person might inspect. Then make the boundary explicit in the prompt and the tracked files.