A codon runs without you. Once a hank starts, the agent receives your prompt, a workspace, and nothing else: no conversation, no clarification, no second chance to say what you actually meant. Everything the agent needs to do the task correctly has to be in the prompt text, and anything extra in that text costs tokens and attention without helping.
This page is about writing prompts that survive that arrangement. It covers the structure a reliable prompt tends to have, the workspace facts the agent cannot guess on its own, how to anchor file paths so the agent does not go searching, how to keep maintainer notes out of the model's context, where standing rules belong, and how to validate the wiring before you spend a run on it. The examples come from shipped fixtures, so you can check each one against a real prompt.
Give every prompt four parts#
A prompt gives a codon – one sealed agent task in the sequence – what it needs to work without us beside it. Four parts cover that need:
- Context – what the task is.
- Process – which steps to take.
- Constraints – what not to do.
- Output – what success looks like and which files to write.
Not every prompt needs all four as labeled sections. For a simple task, Process and Output are enough, folded into plain prose. For a fuller task, rename the parts to headings that fit the domain. The skeleton below is an illustrative template, not a shipped prompt:
Context: <what this task is>
Process:
1. <step>
2. <step>
Constraints: <what not to do>
Output: <success condition and file to write>
The minimal-single-provider quickstart prompt shows the brief end of that range. It anchors the input and output, gives two requirements, preserves the source deadline, and sets a short line limit – all in a few sentences:
# Summarize the meeting notes
Read the file `notes.txt` inside the `read_only_data_source` directory in your current working directory (use shell commands like `cat` — the directory may be a symlink).
Write `summary.md` in the current working directory containing:
1. A one-sentence summary of what the meeting decided.
2. Exactly three bullet points: one decision, one owner, one deadline — taken from the notes.
Keep the deadline's wording from the notes. Do not infer a month, year, calendar date, or other detail that the notes do not give.
Nothing else. Keep it under 15 lines.
The anchor-hank fixture shows the fuller end. Its prompt opens with role and context, then uses Read, Extract, and Write sections. In the excerpt below, a line containing … marks omitted source lines; it is not literal prompt or output content:
# Normalize Aster (Datalab dialect)
You are the mechanical extraction stage for supplier **Aster Metals (AST)**. Aster submitted **two** documents against RFQ NC-RFQ-0042 — extract both, faithfully, and do not decide between them. Which one wins is a judgment call for the `validate-and-repair` codon, not you.
…
Use shell commands (`cat`, `ls`) to read from `read_only_data_source/` — it may be a symlink, so avoid the native Read/LS tools on it.
…
Write a single JSON array of the two envelope objects — the original document first, then rev2 — to `./envelope-aster.json` relative to the working directory returned by `pwd`. The agent workspace is already the current directory. Do not add an execution-directory prefix or write under `read_only_data_source/`. Before finishing, run `test -s ./envelope-aster.json`.
This file is the contract for `validate-and-repair`: it must be able to load `envelope-aster.json` and see both Aster submissions independently, with nothing pre-merged or pre-decided.
When precision matters, go further and prescribe the artifact itself. Require every field, copy source values verbatim, use null for a value the source lacks, and prohibit computing, converting, or inferring values. If an item does not resolve cleanly, route it to a named ledger – a file that records the item and its reason – rather than dropping it. Continuing should never mean silently omitting an item. We work through that rule in validate and repair.
The anchor's failure-routing line and output headings make that rule visible:
You check ALL normalized envelopes (eight suppliers: AST, BCN, CDR from the dialect codons; DVR, EMB, FJR, HBR, IRS from the extracts codon) plus the intake survey against the RFQ contract and the corpus lookups, repair what you can repair deterministically, and route everything else — including one deliberately unresolvable judgment call — to a typed exception ledger. **Continue must never mean silent omission**: if a line, a part, or a whole quote doesn't cleanly resolve, it must appear in `exception-ledger.json` with a reason. It may never simply vanish from your output.
…
**`validated-records.json`** — a JSON array, one object per line that survived Steps 1–3 (the losing Aster document's lines are dropped here, since Step 1 already logged that as a conflict), with exactly these fields:
…
**`exception-ledger.json`** — a JSON array, one object per exception you emitted above, with exactly these fields:
For a task that produces a file, make writing part of success: require the agent to write the artifact's skeleton first, extend it in at least four write steps, and self-verify with wc -l before finishing. For tight-versus-loose prompt strategy and how to account for context the agent lacks, see designing codons and handoffs.
Explain the workspace before assigning work#
The four parts tell the agent what to do; they do not tell it where it is. During a run, the agent's working directory is agentRoot/, a workspace inside the managed execution directory. Task inputs are exposed there as read_only_data_source/, and prompt-named outputs land in agentRoot/. The agent knows none of this unless the prompt says so. See the runbook for output management and the execution directory for link and store mechanics.
The init-fixture shipped scaffold states the convention directly:
# Project Analysis (Haiku)
Please analyze the files in the `read_only_data_source` directory located in the current working directory and create a comprehensive analysis report.
Your analysis should include:
1. Very brief overview of the files in that directory
Please create your analysis in a file called `analysis-haiku.md` in the execution directory.
Explain the read method too. The data directory is exposed through a symlink at agentRoot/read_only_data_source/; when symlinking fails, a copy is used. The captured text prompts above use cat and ls to avoid native-tool symlink problems. Use that guidance for text when the harness provides an authorized shell, not as a universal replacement for every file-reading tool.
For an image, shell output from cat image.png is file bytes, not an image attachment the model can inspect. A native image read can expose visual content only when the selected model accepts images and the harness passes that format through as image content. Name the available image-reading tool in the prompt only after checking that route. If visual inspection is required but unavailable, the codon must report that limitation rather than claim it inspected the image. This is capability guidance, not a capture proving every image format or model route.
Keep three layers separate when writing tool instructions: the model supplies reasoning and input modalities; the harness supplies the actual tools and session behavior; the execution host supplies installed programs, credentials, filesystem access, and network access. A model name does not guarantee a browser, image reader, shell executable, or the same tools as your authoring environment. See models and harnesses.
agentRoot/ is a working directory, not a sandbox. Likewise, read_only_data_source is a convention: its symlink or copy does not enforce read-only filesystem permissions. The Claude Agent SDK route sets permissionMode: "bypassPermissions" for harness permission prompts; that does not grant operating-system privileges, change mount permissions, or override host/tool policy. Enforce isolation with the host's permissions and deployment controls, and keep prompt prohibitions as an additional instruction rather than a security boundary.
Give the agent an inventory instead of asking it to explore:
- read_only_data_source/: read-only inputs
- <current directory> (the agent workspace)/: files to create
- <utility directory>/: utility scripts
- <template path>/: template
Then add the context the agent lacks – where it sits in the sequence and where its output goes next:
Previous step: <what was produced>
This step: <what to do>
Next step: <who consumes this output>
Boundary: Your job is transformation only. Don't analyze or draw conclusions.
If a task has a held-out answer key – reference material the task must not inspect – follow designing codons and handoffs for the prohibition and its reason; use that page's anchored-line format.
Anchor every file path you name#
The #1 cause of wasted agent turns is a prompt that names a file without saying where it is. Without an anchor, the agent may wander through find, ls, or grep before it starts the actual work. Anchor both sides of the task: the reads and the writes.
Use directory-only template tokens. <%AGENT_ROOT%> means the agent's current working directory, and <%DATA_DIR%> means read_only_data_source. These tokens anchor paths; they cannot include a file's contents in the prompt. See the complete four-variable table when choosing a token.
When a required file is known to exist, say "this file always exists; do not search elsewhere." Do not ask the agent to read it back to confirm; that invites another round of reads.
Anchor writes as carefully as reads. The agent workspace is already the current directory – name the write target relative to that directory, do not add an execution-directory prefix, and say that the target is not under read_only_data_source/ when that distinction matters.
Test each sentence before keeping it#
Once a prompt draft exists, apply the "why" test to every sentence: does the agent need it to do the task? If yes, keep it; if no, cut it. Ask the inverse too: what does the agent need that isn't here? Missing instructions can do more damage than the excess.
Here is the test applied to the minimal prompt from earlier. Each instruction is marked keep when it supplies a needed input, action, constraint, or success condition:
- "Read the file
notes.txtinside theread_only_data_sourcedirectory in your current working directory" – keep: it anchors the input. - "Use shell commands like
cat– the directory may be a symlink" – keep: it selects the read method for this workspace. - "Write
summary.mdin the current working directory" – keep: it anchors the output. - "A one-sentence summary of what the meeting decided" – keep: it defines one success condition.
- "Exactly three bullet points: one decision, one owner, one deadline – taken from the notes" – keep: it defines the shape and source of the remaining output.
- "Keep the deadline's wording from the notes. Do not infer a month, year, calendar date, or other detail that the notes do not give." – keep: it prevents unsupported date detail.
- "Nothing else. Keep it under 15 lines." – keep: it limits the artifact.
Every line survives the test, which is what a tight prompt looks like. The inverse question is where this one falls short: the prompt does not specify what to do when notes.txt is missing. Treat that omission as a design gap instead of guessing a behavior.
In designing codons and handoffs, we see what happened when a prompt named the wrong supplier set: the codon followed the hardcoded enumeration, not the author's intent.
Keep author notes out of agent context#
A prompt file often serves two audiences: the model that executes it and the maintainer who edits it months later. Put a maintainer note in an HTML comment (<!-- … -->) when the agent does not need it. Hankweave strips single- and multi-line comments before sending the prompt to the model, consumes the trailing newline so blank lines do not accumulate, and applies the same stripping to system prompts. It replaces template variables first, then strips comments from the assembled content.
The file below documents a prompt without sending those notes to the LLM (the language model receiving the prompt):
# Refactoring Task
Refactor the authentication module to use JWT tokens.
<!-- CONTEXT: Migrating from sessions to JWT for mobile app.
See ENG-234 for requirements. -->
Preserve backward compatibility with /api/v1/\* endpoints.
<!-- WARNING: Don't touch OAuth flows - separate ticket (ENG-256). -->
The agent receives the prompt without those comments:
# Refactoring Task
Refactor the authentication module to use JWT tokens.
Preserve backward compatibility with /api/v1/\* endpoints.
This keeps the prompt self-documenting for maintainers without spending tokens on notes the agent does not need.
Use YAML frontmatter at the beginning of a prompt Markdown file for labeling only. Its allowed fields are name, description, tags, version, and author; it is not configuration. At prompt build, an unknown field fails the build with Invalid prompt frontmatter: followed on the next line by Unknown frontmatter field(s): <keys>. Allowed fields: name, description, tags, version, author.
Hankweave strips frontmatter before sending the prompt. With multiple files in a promptFile array, it parses and strips each file's frontmatter but retains only the first file's frontmatter. Do not confuse this labeling metadata with the hank's meta field: prompt frontmatter can surface in terminal user interface (TUI) display and event metadata, while meta identifies the hank and requires name and version. See reference/hank-json.
Put standing rules in system prompts#
Some context belongs in every run, and some belongs to one task. Put persistent context such as coding standards, style guidelines, and role framing in a system prompt. Keep task-specific instructions and information that can change between runs, such as files, in the task prompt instead.
Use the global level for context shared by every codon. At the root of hank.json, configure it with globalSystemPromptFile or globalSystemPromptText. Append codon-specific context with appendSystemPromptFile or appendSystemPromptText. The global text comes first, and the parts are joined with a blank line. When nothing is configured, no system prompt is sent.
For codon prompts, the runtime requires at least one truthy promptFile or promptText; if both are set, promptFile wins. The two codon append fields, appendSystemPromptFile and appendSystemPromptText, are mutually exclusive, as are the two hank-level global system-prompt fields. These are separate rules, not one universal XOR rule. Do not transfer the codon rule to sentinel prompt fields; use sentinel configuration for that contract. For strict file references and the remaining field contract, see reference/hank-json.
For deciding whether discovered knowledge belongs in the global system prompt, a codon system prompt, a codon prompt, a rig, or a prerequisite check, use discover before you freeze.
Wire prompt files and validate the counts#
With the prompt written, the remaining work is mechanical: reference the file correctly and confirm the wiring before running. Keep prompt files inside the hank directory – the directory containing hank.json and prompts/. In 0.10.0, promptFile names them with relative POSIX paths (using / separators). The strict path/ref contract, including rejection of absolute paths, .. escapes, and symlinks for both prompt and system-prompt file fields, belongs to reference/hank-json.
Use promptText for a short prompt. Put a longer prompt in promptFile so hank.json stays clean and the prompt remains editable and version-controlled. promptFile accepts one string or an array; an array concatenates its files in order with a blank line between them.
Run from the hank directory. Validate first, then start a headless run (without an interactive terminal) with a new execution:
bunx hankweave@0.10.0 hank.json data/ --validate
bunx hankweave@0.10.0 hank.json data/ --headless --start-new -o out
The two positional arguments are the hank path and data path. The first command checks the prompt wiring. The second starts the run and copies its outputs to out. The steps below walk through what to check at each stage.
- Validate from the fixture root. Run validation before spending a run on the task.
Check-it: the captured output includes the per-codon prompt count and the GOOD TO RUN! box.
model: haiku │ mode: fresh │ prompts: 1 (13 lines)
…
╭─ GOOD TO RUN! ─────────────────────────────────────────────────────╮
│ 1 codons • 1 prompts • 0 system prompts • 0 rigs • 1 checkpoints │
╰────────────────────────────────────────────────────────────────────╯
- Start the headless run. Use the explicit hank and data paths. The run writes the prompt-named artifact in the execution workspace;
-o outcopies it out.
Check-it: the captured output file contains one sentence followed by exactly three bullets.
# Meeting Summary
The team decided to proceed with the Calloway vendor quote for the packaging line refit based on lower total cost and included installation.
- **Decision:** Go with the Calloway quote for packaging line refit
- **Owner:** Devon
- **Deadline:** PO issued by Friday the 12th
- Inspect the run banner and copied output. The banner identifies
Source → dataandExec → ~/.hankweave-executions/<exec-id>. The execution directory contains the agent workspace,agentRoot/, where the codon's prompt-named output lands.-o outcopies outputs to a CWD-relative directory; without it, outputs stay inagentRoot/.
Check-it: the captured banner shows the source and execution paths.
╭────────────────────────────────────────────────────────────────────╮
│ Hankweave v0.10.0 │
│ darwin arm64 • node v23.8.0 │
╰────────────────────────────────────────────────────────────────────╯
Created new execution directory: ~/.hankweave-executions/<exec-id>
New execution: <exec-id>
Source → data
Exec → ~/.hankweave-executions/<exec-id>
- Break the path on purpose. Point
promptFileat a missing file and validate. Validation reports the codon and whether the path does not exist, is not a regular file, or is not readable.
Check-it: --validate exits 1 and names the codon and missing file.
• Codon 1 (write-line): promptFile "<workspace>/fixtures/scenarios/ref-violations/prompts/does-not-exist.md" does not exist
exit=1
Two details in the validation output are worth knowing before they surprise you. Validation counts content.split("\n").length for each codon's prompt file. A trailing newline produces an additional split entry, so the visible 12-line summarize.md appears as 13 lines. The system prompts number counts appendSystemPromptFile files across codons; it excludes inline append text and the global system prompt. A displayed 0 system prompts therefore does not prove that no system instructions will be sent. Check the global-setting indicator and the configured fields; troubleshooting also indexes this misleading label.
Validation warnings are not errors: an empty prompt file yields <codon>: Prompt file "…" is empty, while a prompt file over one megabyte yields … is large (X.XXMB). A missing, non-regular, or unreadable promptFile or appendSystemPromptFile fails validation with the corresponding does not exist, is not a regular file, or is not readable: … wording. For misspelled field names, use the loader's did-you-mean guidance in reference/hank-json#typos-and-suggestions.