---
name: harn-language
short: Harn syntax, modules, types, diagnostics, and script structure.
description: Use for Harn language syntax, typechecking, modules, imports, and idiomatic script authoring.
when_to_use: Use when writing, reviewing, or explaining Harn source code and language-level behavior.
---
# Harn language
Use this skill when writing, reviewing, or explaining `.harn` source code.
Pair it with [[harn-testing]] for fixtures and [[harn-diagnostics]] for user-facing errors.
## Start here
- Read `docs/llm/harn-quickref.md` before editing `.harn` files.
- In a Harn worktree, run `make setup` once and use
`HARN_BIN_NO_BUILD=1 ./scripts/harn_bin.sh -- <command>` for narrow CLI
checks. This resolves the lane-local binary without starting a hidden Cargo
build. Outside the repository, use the installed, pinned `harn` executable.
- Use `docs/llm/harn-triggers-quickref.md` when trigger manifests are involved.
- Read `spec/HARN_SPEC.md` as the assembled language reference.
- Default new script entrypoints to `fn main(harness: Harness) { ... }` and
route side effects through `harness.*`.
- Use `harness.fs.mkdtemp_in_workspace(prefix?)` for scratch files that must be
visible under sandbox policy; avoid composing `.harn/tmp` paths by hand.
- Use typed `std/fs` conditional replacement for shared state or artifacts
that must not overwrite a newer observation; branch on the closed receipt.
- Use bounded `std/jsonl` pages for file-backed logs and transcripts. Apply a
`SchemaContract<T>` when consumers need typed records and named rules.
- For connector credentials, read canonical ids such as
`provider/access-token` through `harness.secrets` from a package script; the
runner scopes the default provider from the nearest `harn.toml`.
- Edit the authoritative chapters under `spec/chapters/*.md` for spec changes;
never hand-edit the generated `spec/HARN_SPEC.md` or
`docs/src/language-spec.md` projections.
- Regenerate and verify every spec projection with `make sync-language-spec`.
- Check user-visible behavior with conformance fixtures under `conformance/tests/`.
- Keep examples small enough for agents to copy without hidden setup.
- Prefer existing syntax and stdlib helpers over new host-side shortcuts.
- Put platform spellings, policy rows, and other closed configuration in one
typed data-only module. Import that value into the evaluator and mechanically
project it downstream instead of duplicating lists or host branches.
## Syntax checklist
- Imports go first.
- `pipeline` is the usual entry-point construct.
- `fn` is for reusable pure or host-backed logic.
- `let` binds values; avoid mutation unless the language surface requires it.
- Use explicit type annotations at boundaries.
- Use `match` for variant-sensitive behavior.
- Use `for` for straightforward iteration.
- Use `parallel each` for bounded fan-out work.
- Use `parallel settle` when collecting settled outcomes matters.
- Use `settle_with_abort` from `std/abort` when outcomes matter *and*
branches poll or wait, so one branch's doomed verdict can stop the rest.
- Always set `max_concurrent` on broad parallel work.
- Use triple-quoted strings for long prompts in Harn source.
- Heredoc syntax is for LLM tool-call argument JSON, not general strings.
- Keep comments factual and close to non-obvious logic.
## Types and boundaries
- Treat `unknown` as the type for untrusted inputs.
- Use `SchemaContract<T>` for deterministic cross-field rules after structural
validation. Capture typed context in the rule closure; do not replace it with
an open dictionary.
- Use `ArtifactDescriptor<T>` when a producer and consumer share a durable JSON
artifact. Bind its relative name and `SchemaContract<T>` once, then reuse the
descriptor for reads and writes.
- Narrow `unknown` with `type_of`, `schema_is`, or validated helpers.
- Use `any` only as an explicit escape hatch.
- Do not erase types merely to silence the typechecker.
- Keep struct and enum names stable when they are user-facing.
- Add conformance coverage before changing type inference behavior.
- Preserve diagnostic codes and spans when refactoring typechecker logic.
- Keep imported callable declarations aligned with module graph behavior.
- Watch for strict-types behavior when changing boundary APIs.
- Update examples when public type syntax changes.
## Portable execution
- Treat portable execution as a deployment contract, not a second Harn
language. Source must use the canonical parser, typechecker, and compiler.
- Passing `harn check` is necessary but does not prove Portable Kernel v1
support. Compile the intended entry through the portable interface so
unsupported opcodes, builtins, and capability methods fail explicitly.
- Keep entry inputs and terminal values JSON-shaped: null, booleans, signed
integers, finite or tagged floats, strings, bytes, lists, and named records.
- Preserve explicit type annotations at the entry and reusable function
boundaries. The portable kernel enforces declared call contracts rather than
treating browser JSON as `any`.
- Route privileged operations through typed `harness` capabilities. Do not add
target-specific browser builtins or read ambient file, network, process,
clock, random, or model authority.
- Keep DOM, canvas, and presentation state in the browser host. Run CPU-heavy
portable behavior in a Web Worker and exchange typed values.
- Do not add a parser, evaluator, opcode table, builtin registry, or keyword
list to an adapter. Generate projections or test parity against the owning
language/runtime registry.
- Use `harn bench portable` for compiler, artifact decode, and dispatch
measurements. Use `std/timing` only for hostful application spans.
## Modules and imports
- Keep import paths explicit and readable.
- Prefer `import * as alias from "module"` when a module publishes short
member names under a collision-safe alias; do not invent receiver-method
APIs for that case.
- When callers use a namespace alias, export `event` rather than repeating the
module name in `ui_event`; the call site already reads `ui.event`.
- `pub import * as alias from "module"` re-exports the alias namespace object,
not the target's flattened members (contrast `pub import "module"`).
- Avoid relying on the current working directory in examples.
- When touching module resolution, inspect `crates/harn-modules`.
- Cross-file checks should use the same module graph as the CLI.
- Public symbols should remain discoverable by editor tooling.
- Use curated `pub import` facades for stable package surfaces; package checks
and API docs follow re-exports to the original declaration.
- Avoid duplicate import spellings for the same file.
- Prefer canonical relative paths in fixtures.
- Add a cycle regression when changing import traversal.
- Keep generated docs and tree-sitter grammar in sync after syntax changes.
- Use [[harn-orchestration]] when module behavior affects workflows.
## Prompt templates
- The template engine lives in `crates/harn-vm/src/stdlib/template.rs`.
- Do not add a second prompt-template parser.
- Keep host-call and script-call rendering behavior identical.
- Preserve pre-v2 `{{name}}` missing-identifier passthrough.
- New template constructs should fail with useful parse diagnostics.
- Update `docs/src/prompt-templating.md` for template syntax changes.
- Update `docs/llm/harn-quickref.md` when agents need the new syntax.
- Update VS Code grammar when `.harn.prompt` syntax changes.
- Add conformance fixtures under `conformance/tests/template_*`.
- Use [[harn-diagnostics]] for template parse and lint diagnostics.
## Simpler first
- Can this be ordinary Harn instead of Rust?
- Can a stdlib helper express this without a new language form?
- Can the typechecker infer it without a new annotation?
- Can a conformance fixture capture the behavior without a large harness?
- Can docs describe the rule in one sentence?
- Can the formatter preserve the style without special cases?
- Can the linter derive the rule from existing AST structure?
- Can the tree-sitter grammar remain a direct mirror of parser syntax?
- Can existing examples be updated mechanically?
- Can user-visible behavior stay backward compatible?
## Verify
- Narrow script check:
`HARN_BIN_NO_BUILD=1 ./scripts/harn_bin.sh -- check <path>`.
- Narrow lint check:
`HARN_BIN_NO_BUILD=1 ./scripts/harn_bin.sh -- lint <path>`.
- Narrow format check:
`HARN_BIN_NO_BUILD=1 ./scripts/harn_bin.sh -- fmt --check <path>` (`fmt`,
never `format`).
- Narrow conformance case:
`HARN_BIN_NO_BUILD=1 ./scripts/harn_bin.sh -- test conformance --filter <name>`.
- Parser changes: `make test ARGS='-p harn-parser <filter>'`.
- Formatter changes: `make test ARGS='-p harn-fmt <filter>'`.
- Linter changes: `make test ARGS='-p harn-lint <filter>'`.
- Syntax or keyword changes: `make conformance`.
- Harn fixture changes: `make lint-harn` and `make fmt-harn`.
- Tree-sitter changes: `(cd tree-sitter-harn && npm test)`.