# Modules and imports

> Harn supports splitting code across files using import and top-level fn declarations.

Website: https://harnlang.com/modules.html

This page documents Harn, which is pre-1.0. Language, standard library, and CLI APIs may change. If the intended version is unclear, clarify before using this page.

---

Harn supports splitting code across files using `import` and top-level `fn` declarations.

## Importing files

```harn,ignore
import "lib/helpers.harn"
```

The extension is optional — these are equivalent:

```harn,ignore
import "lib/helpers.harn"
import "lib/helpers"
```

Import paths are resolved relative to the current file's directory.
If `main.harn` imports `"lib/helpers"`, it looks for `lib/helpers.harn`
next to `main.harn`.

## Writing a library file

Library files contain top-level `fn` declarations:

```harn
// lib/math.harn

fn double(x) {
  return x * 2
}

fn clamp(value, low, high) {
  if value < low { return low }
  if value > high { return high }
  return value
}
```

When imported, these functions become available in the importing file's scope.

## Using imported functions

```harn,ignore
import "lib/math"

pipeline default(harness: Harness, task) {
  harness.stdio.log(double(21))        // 42
  harness.stdio.log(clamp(150, 0, 100)) // 100
}
```

## Importing pipelines

Imported files can also contain pipelines, which are registered globally by name:

```harn
// lib/analysis.harn
pipeline analyze(harness: Harness, task) {
  harness.stdio.log("Analyzing: ${task}")
}
```

```harn,ignore
import "lib/analysis"

pipeline default(harness: Harness, task) {
  // the "analyze" pipeline is now registered and available
}
```

## What needs an import

Most Harn builtins — `println`, `log`, `read_file`, `write_file`, `harness.llm.call`,
`agent_loop`, `http_get`, `parallel`, `workflow_*`, `transcript_*`,
`mcp_*`, and the rest of the runtime surface — are registered globally and
require **no import statement**. You can call them directly from top-level
code or inside any pipeline.

`import "std/..."` is only needed for the Harn-written helper modules
described below (`std/text`, `std/json`, `std/math`, `std/collections`, `std/changelog`,
`std/ansi`, `std/table`, `std/diff`, `std/path`, `std/fs`, `std/os`,
`std/slug`, `std/edit`, `std/identity`, `std/disclosure`, `std/artifact/web`, `std/ui_resource`, `std/cache`,
`std/llm/envelope`, `std/llm/handlers`, `std/llm/budget`, `std/llm/prompts`, `std/vision`,
`std/context`, `std/agent_state`, `std/agents`, `std/agent/user`,
`std/agent/fact`, `std/agent/probe`, `std/agent/scratchpad`,
`std/runtime`, `std/command`, `std/gha`, `std/tui`, `std/git`,
`std/review`, `std/experiments`,
`std/project`, `std/memory`, `std/prompt_library`, `std/monitors`,
`std/postgres/query`, `std/sqlite`,
`std/net_policy`,
`std/oauth/providers`, `std/triage`, `std/worktree`, `std/checkpoint`,
`std/personas/prelude`, `std/personas/bulletins`,
`std/connectors/http`, `std/connectors/shared`, and provider-specific
`std/connectors/...` modules).
These add layered
utilities on top of the core builtins; the core builtins themselves are
always available.

## Standard library modules

Harn includes built-in modules that are compiled into the interpreter.
Import them with the `std/` prefix:

```harn,ignore
import "std/agent_state"
import "std/agents"
import "std/agent/user"
import { retry_predicate_with_backoff } from "std/async"
import "std/cache"
import "std/changelog"
import "std/collections"
import "std/connectors/http"
import "std/connectors/shared"
import "std/context"
import "std/disclosure"
import "std/edit"
import "std/identity"
import "std/artifact/web"
import "std/ui_resource"
import "std/experiments"
import "std/git"
import "std/json"
import "std/llm/budget"
import "std/llm/envelope"
import "std/llm/prompts"
import "std/math"
import "std/monitors"
import "std/net_policy"
import "std/path"
import "std/personas/bulletins"
import "std/personas/prelude"
import "std/prompt_library"
import "std/review"
import "std/slug"
import "std/text"
import "std/triage"
import "std/tui"
import "std/vision"
```

`std/oauth/providers` exports a `custom` helper, which also exists in
`std/experiments`. Use selective imports when combining those modules:

```harn
import { provider_catalog } from "std/oauth/providers"
```

### std/changelog

Pure typed changelog transformations for release harnesses:

| Function | Description |
|---|---|
| `changelog_parse_fragment(filename, body, categories)` | Validate and parse one `<id>.<category>.md` fragment |
| `changelog_order_fragments(fragments, categories)` | Order fragments by category, natural ID, and filename without integer overflow |
| `changelog_assemble_fragments(fragments, categories)` | Normalize fragment bodies and render deterministic `###` category sections |
| `changelog_parse_sections(text)` | Parse exact `##` sections outside fenced code blocks with UTF-8 byte offsets |
| `changelog_find_section(text, heading)` | Return one named section or a typed missing/duplicate-heading failure |
| `changelog_merge_unreleased(text, assembled, categories)` | Merge assembled content into `## Unreleased` while preserving authored content and newline style |

The module performs no filesystem, Git, process, versioning, or publication
work. Callers retain those policies and pass their category definitions
explicitly.

### std/async

Polling and retry helpers for closure-shaped conditions:

| Function | Description |
|---|---|
| `wait_for(timeout_ms, interval_ms, predicate)` | Poll `predicate()` until it returns a truthy value or the timeout expires |
| `retry_until(max_attempts, predicate)` | Retry `predicate()` without delay until it returns a truthy value or attempts are exhausted |
| `retry_predicate_with_backoff(max_attempts, base_ms, predicate)` | Retry `predicate()` with exponential backoff between attempts |
| `circuit_call(name, closure)` | Run `closure()` only while the named circuit breaker allows calls, recording success or failure |

### std/abort

Cooperative abort across concurrent branches: run everything and record every
outcome like `parallel settle`, but let a branch that reaches a doomed verdict
stop the siblings that have not started. See
[Cooperative abort](./concurrency.md#cooperative-abort-stdabort) for the full
semantics and its limits — it is cooperative, so a branch blocked inside one
long call is not interrupted.

| Function | Description |
|---|---|
| `abort_token(options?)` | Mint a cooperative-abort token that crosses into child tasks as a plain value |
| `abort_requested(token)` | Whether an abort has been requested — call at your branch's own safe checkpoints |
| `abort_reason(token)` | The `AbortReason` recorded when the token was tripped, else `nil` |
| `request_abort(token, reason)` | Trip the token, first writer wins; `true` when this call recorded the reason |
| `settle_with_abort(items, body, options?)` | Settle over `items` while honouring the token, with optional `max_failures` / `abort_on` policies |
| `decisive_error(outcome)` | The first failure that was not an abandonment — the cause to report |

### std/connectors/shared

Connector package helpers for common provider plumbing:

| Function | Description |
|---|---|
| `verify_hmac_signature(body, signature, secret, algorithm?, options?)` | Constant-time check for bare or `sha256=` signatures; legacy `sha1=` requires `options.allow_legacy_sha1` |
| `verify_jwt(token, jwks_url, options?)` | Verify a compact JWT against a JWKS URL, or `options.inline_jwks`, returning `{ok, claims, error}` |
| `oauth2_token_refresh(client_id, client_secret, refresh_token, token_url, options?)` | Refresh an OAuth2 access token with form-encoded `grant_type=refresh_token` |
| `rate_limit_token_bucket(state?, config?, now_ms?)` | Pure token-bucket transition for package-local quota decisions |
| `paginate_cursor(initial_url, fetch_fn, cursor_path, options?)` | Collect cursor-paginated pages from a package-supplied fetch closure |
| `connector_lifecycle(provider, state_key, reset_keys?)` | Bind `state`, `init`, `activate`, and `shutdown` operations to provider-owned runtime keys; each takes `HarnessRuntime`, with context or bindings as its second argument. Duplicate binding paths fail before state changes. |

The four `connector_http_*` helpers remain compatibility re-exports from this
module. New code should import their single implementation from
`std/connectors/http`.

### std/connectors/http

HTTP transport policy for connector packages:

| Function | Description |
|---|---|
| `connector_http_request(clock, net, method, url, options?)` | Capability-attenuated, non-throwing request wrapper with normalized retry, idempotency, error categories, and bounded failure envelopes |
| `connector_http_json(clock, net, method, url, options?)` | `connector_http_request` plus response JSON parsing; invalid JSON returns `error.category == "invalid_json"` |
| `connector_http_header(headers_or_response, name)` | Case-insensitive header lookup for response envelopes or raw header dicts |
| `connector_http_rate_limit(clock, headers_or_response)` | Extract `Retry-After`, `RateLimit-*`, and `X-RateLimit-*` metadata, including `retry_after_ms` when parseable |

### std/oauth/providers

Static OAuth provider records and factory helpers for auth orchestration:

| Function | Description |
|---|---|
| `provider_names()` | Return the ten named provider keys |
| `provider(name, overrides?)` | Return one provider record with optional endpoint/scope overrides |
| `provider_catalog(overrides?)` | Return all ten provider records, with optional per-provider overrides |
| `providers(overrides?)` | Return a namespace containing the ten records plus `github_enterprise` and `custom` factories |
| `github_enterprise(base_url, overrides?)` | Build a GitHub Enterprise Server record from an instance web base URL |
| `custom(config, overrides?)` | Build a provider record for enterprise or niche OAuth providers |

### std/triage

Normalize connector-derived inbox items into host-renderable dashboard cards:

| Function | Description |
|---|---|
| `triage_normalize(input, options?)` | Convert a TriggerEvent or provider payload into `harn.triage_event.v1`, preserving provider raw payload separately |
| `triage_dedupe_key(provider, source_kind, source_url, source_id?)` | Build a stable dedupe key from provider-neutral source provenance |
| `triage_dedupe_events(events)` | Drop duplicate triage events by stable dedupe key |
| `triage_emit(input, options?)` | Validate and append a triage event to the EventLog, returning an emit receipt |
| `triage_start_my_day(inputs, options?)` | Build a deduped Start My Day feed and optionally emit each event |

### std/monitors

Monitor waits for external state with deterministic replay records:

| Function | Description |
|---|---|
| `wait_for(options)` | Poll a source until `condition(state)` is truthy or timeout expires; push-capable sources can wake early from trigger inbox events |

See [Monitor stdlib](./stdlib/monitors.md) for the source shape and result
record.

### std/text

Text processing utilities for LLM output and code analysis:

| Function | Description |
|---|---|
| `int_to_string(value)` | Convert an integer-compatible value to a decimal string |
| `float_to_string(value)` | Convert a float-compatible value to a string |
| `parse_int_or(value, fallback)` | Parse an integer, returning `fallback` on failure |
| `parse_float_or(value, fallback)` | Parse a float, returning `fallback` on failure |
| `extract_paths(text)` | Extract file paths from text, filtering comments and validating extensions |
| `parse_cells(response)` | Parse fenced code blocks from LLM output. Returns `[{type, lang, code}]` |
| `filter_test_cells(cells, target_file?)` | Filter cells to keep code blocks and write_file calls |
| `truncate_head_tail(text, n)` | Keep first/last n lines with omission marker |
| `truncate_text(text, max_chars?, marker?)` | Keep the first `max_chars` characters and append a deterministic truncation marker |
| `truncate_middle(text, max_chars?, marker?)` | Keep both ends of a long string with an omission marker in the middle |
| `single_line_or(value, fallback?)` | Collapse whitespace into one line, returning `fallback` for blank input |
| `prefix_lines(text, prefix?)` | Prefix every line in a text block |
| `indent(text, spaces?)` | Prefix every line with a fixed number of spaces |
| `detect_compile_error(output)` | Check for compile error patterns (SyntaxError, etc.) |
| `has_got_want(output)` | Check for got/want test failure patterns |
| `format_test_errors(output)` | Extract error-relevant lines (max 20) |

These helpers are intentionally small because they sit under higher-level
reporting modules. For example, harnesses can normalize untrusted command
output before placing it into prompt context:

```harn
import { single_line_or, truncate_middle } from "std/text"

const long_output = "..."
const label = single_line_or(" cargo\n test\t-p harn-vm ", "command")
const summary = truncate_middle(long_output, 2000)
```

### std/edit

Pure helpers for agent-authored text patches:

| Function | Description |
|---|---|
| `edit_apply_node(harness.ast, params)` | AST-precise replace via a Tree-Sitter query. Splices `replacement` in for each matched node, preserving leading indentation and trailing trivia; validates the post-edit source by re-parsing. Routes through staged-fs when `session_id` is supplied. See [Edit stdlib](./stdlib/edit.md). |
| `edit_insert_at_anchor(harness.ast, params)` | AST-precise insert relative to a unique anchor node. `position` picks `before`/`after`/`first_child`/`last_child`; content is re-indented to the inferred target depth and validated by re-parsing. Routes through staged-fs when `session_id` is supplied. See [Edit stdlib](./stdlib/edit.md). |
| `edit_safe_text_patch(harness.fs, harness.random, params)` | Multi-hunk text patch with staged-fs collision rejection: reads the file through the overlay, hash-checks `expected_hash`, applies each `{old_text, new_text}` hunk through the same matcher as `edit_apply_old_new_patch`, and commits all-or-nothing through `harness.fs.safe_text_patch`. Returns `result ∈ {applied, no_op, stale_base, hunk_conflict}` plus per-call telemetry. See [Edit stdlib](./stdlib/edit.md). |
| `edit_fast_apply(harness.fs, harness.random, harness.ast, harness.llm, params)` / `fast_apply(path, intent, options?)` | Merge-model-assisted full-file apply. Reads the target file, calls the configured `merge` model role for complete updated bytes, validates and previews through `edit_dry_run`, then commits through hash-guarded `edit_safe_text_patch`. See [Edit stdlib](./stdlib/edit.md#edit_fast_apply--merge-model-assisted-full-file-apply). |
| `edit_dry_run(params)` | Render a multi-op edit plan (`apply_node`, `insert_at_anchor`, `safe_text_patch`, `rename_symbol`) as a per-file unified diff bundle without touching disk. Plan ops share a transient staged-fs session, so cumulative edits collapse to one diff per file. See [Edit stdlib](./stdlib/edit.md#edit_dry_run--preview-a-multi-op-plan). |
| `edit_apply_old_new_patch(text, old_text, new_text, options?)` | Apply one anchored old/new patch with exact, line-normalized, and structural matching; returns hashes, match kind, line span, changed regions, errors, warnings, and provenance |
| `edit_splice_lines(text, start_line, end_line_exclusive, new_text, options?)` | Replace a half-open 0-based line range and return the same patch metadata shape |
| `edit_changed_regions(before, after)` | Return deterministic line-level changed-region metadata for one contiguous diff |
| `edit_validate_changed_regions(before, after, expected_regions, options?)` | Verify that all changes fall inside expected 0-based line regions |
| `edit_strip_line_number_prefixes(text)` | Remove leading `<spaces>N<space><pipe><space>` line-number prefixes when at least 60% of non-empty lines carry them; useful as preprocessing for `old_text` pasted from a numbered file read |
| `edit_explain_whitespace_difference(needle, matched)` | Diagnose the dominant whitespace cause (tabs vs spaces, base indent, blank lines) when a fuzzy match was needed |
| `edit_check_lazy_truncation(old_content, new_content, options?)` | Detect whole-file rewrites that shrank a file below `min_keep_pct` (35%) of its original line count while still containing lazy placeholders |

Default guardrails reject empty anchors, no-op edits, whitespace-only edits,
lazy omission placeholders (including `// TODO: implement`, `// ... rest`,
`# ...`, `pass # ...`, `/* ... */`, and "unchanged" / "omitted for brevity"
phrases), ambiguous matches, and excessive patch growth.

Structural matching is conservative by default: the needle must contain at
least three non-blank lines and both the first and last anchor lines must
carry a distinctive 4+ character alphanumeric token. Callers can relax this
with `structural_require_anchored_lines: "either" | "none"`,
`structural_min_nonblank_lines: N`, or `structural_anchor_chars: N`. Pass
`strip_line_numbers: true` to apply `edit_strip_line_number_prefixes` to
`old_text` before matching. Successful line/structural matches surface a
`whitespace_explanation` field describing the dominant difference between
the needle and the matched span.

### std/artifact/web

Safe helpers for small generated HTML/CSS/JS artifacts:

| Function | Description |
|---|---|
| `web_artifact_extract(html)` | Extract `<script>`, `<style>`, and body fragments with tag-balance errors and provenance |
| `web_artifact_text_fallback(html, options?)` | Strip script/style/markup into a compact text fallback for hosts without embedded UI support |
| `web_artifact_validate(html, options?)` | Return a machine-readable validation report with fragments, warnings, errors, error codes, text fallback, hashes, and provenance |
| `web_artifact_apply_patch(html, old_text, new_text, options?)` | Compose `std/edit` patching with web artifact validation and changed-region checks |

Validation rejects unclosed core tags, obvious network calls or external
resources, forbidden host bridge calls, dangerous navigation, and inline
secret-like values via `secret_scan`. It does not parse or execute HTML.
Pass `{allow_host_bridge: true}` when the artifact is intentionally an
MCP Apps UI resource that uses `parent.postMessage` to talk to the host;
`std/ui_resource` sets this by default.

### std/ui_resource

MCP Apps-compatible UI resource records with text and structured fallbacks:

| Function | Description |
|---|---|
| `ui_resource(uri, name, html, options?: UiResourceOptions)` | Build a `UiResource` (`harn.ui_resource.v1`) record, validate HTML through `std/artifact/web`, and capture a content hash, size, requested permissions, and a CSP/sandbox policy |
| `ui_tool_meta(resource, options?: UiToolMetaOptions)` | Build a `_meta.ui` tool-declaration block (`UiToolMeta`, `harn.ui_tool_meta.v1`) with visibility, initial view, and permission/capability lists |
| `ui_tool_meta_to_mcp(meta)` | Serialize a tool-meta into the MCP Apps `_meta.ui` dict (`resourceUri`, `visibility`, etc.) for direct inclusion in `tools/list` payloads |
| `ui_text_fallback(content)` / `ui_structured_fallback(data, options?: UiStructuredFallbackOptions)` | Build `UiTextFallback` and `UiStructuredFallback` records for hosts without UI support |
| `ui_tool_result(resource, options?: UiToolResultOptions)` | Wrap a resource with mandatory text and optional `UiStructuredFallback`, using `web_artifact_text_fallback` for the default text |
| `ui_select_for_host(result, capabilities?: UiHostCapabilityInput)` | Choose between `ui_resource`, `structured_fallback`, and `text_fallback` for a host based on advertised MCP Apps capability |
| `ui_host_supports_apps(capabilities?: UiHostCapabilityInput)` / `ui_host_capabilities(input?: UiHostCapabilityInput)` | Detect whether an MCP, MCP Apps, or OpenAI Apps SDK host advertises support for the `mcp-app` profile |
| `ui_tool_call_envelope(name, params?, options?: UiToolCallOptions)` | Build the JSON-RPC `tools/call` request a sandboxed view receives from its host |
| `ui_context_update_envelope(key, value, options?: UiContextUpdateOptions)` | Build the JSON-RPC `context/update` request a view sends to its host |
| `ui_resource_csp_header(csp)` / `ui_resource_sandbox_attr(csp)` | Turn resource security settings into a header value and `<iframe sandbox>` attributes |
| `ui_tool_result_validate(result)` | Reject tool results that are missing required fields or ship a UI resource whose HTML failed validation |

Tool results always carry a non-empty text fallback so plain-text hosts
still see useful output. UI resources fail closed: `ui_tool_result` omits
`ui_resource` whenever validation finds errors (network calls, dangerous
navigation, embedded secrets, etc.) unless the caller opts in with
`allow_invalid_resource: true` for preview-only use. Pass structured fallback
data through `ui_structured_fallback(...)`; `UiToolResultOptions` expects the
typed fallback record instead of an anonymous raw payload. See
[examples/ui_resource](https://github.com/burin-labs/harn/tree/main/examples/ui_resource)
for a dashboard widget and a multi-step review form.

### std/portable

Compile and run one versioned Harn program through the native Portable Kernel:

| Function | Description |
|---|---|
| `portable.compile(source, entry, kind?)` | Compile a function or pipeline into a typed `PortableProgram`, or return exact diagnostics |
| `portable.grants(capabilities?, snapshot_key?)` | Build the exact capability ceiling and optional 32-byte snapshot key |
| `portable.start(program, input, grants?)` | Start deterministic execution and return `completed`, `suspended`, or `failed` |
| `portable.resume(program, snapshot, result, grants)` | Continue one suspended execution with its matching typed result |
| `portable.accept(request, value)` / `portable.reject(request, code, message)` | Build a matching capability result |
| `portable.run(program, input, grants, handle, options?)` | Let Harn code service capability requests until the program finishes or reaches the step limit |

Use `std/portable` when native and browser hosts must run the same artifact.
See the [portable kernel contract](./portable-kernel-reference.md) for values,
limits, grants, diagnostics, and supported Harn constructs.

### std/ui

Build interactive apps over `std/ui_resource` with checked Harn records:

```harn
import * as ui from "std/ui"
```

| Function | Description |
|---|---|
| `ui.document(title, revision, elements)` | Check ordered elements and return `harn.ui_document.v1` |
| `ui.event(raw)` | Check browser input, including canvas coordinates from `0.0` to `1.0` and snapshots |
| `ui.update(document, effects?)` | Return the structured document and optional scheduled, capture, or download effects |
| `ui.renderer_html(tool_name)` | Configure the shared browser and canvas view for one Harn event tool |
| `ui.portable_renderer_html(fallback_tool, program, state, capabilities?)` | Configure the shared view to run one Harn reducer in a host-owned browser worker |
| `ui.app_resource(uri, name, tool_name, options?)` | Package the renderer as a validated MCP Apps resource |
| `ui.portable_app_resource(uri, name, fallback_tool, program, state, capabilities?, options?)` | Package one browser reducer and its same-artifact server fallback |
| `ui.tool_metadata(resource, options?)` | Return the MCP tool metadata that opens the app |
| `ui.mcp_resource(resource, options?)` | Return the config accepted by `harness.tools.mcp_resource` |
| `ui.test.run(handle, events, options?)` | Drive event handling and scheduled effects in process without waiting for real time |

Application code owns state and decisions in Harn. Browser JavaScript exists
once inside the shared renderer. See the [`std/ui` reference](./stdlib/ui.md)
and [interactive app guide](./cookbooks/build-interactive-app.md).
Use [Run Harn app logic in the browser](./cookbooks/run-app-logic-in-browser.md)
for the worker and fallback path.

### std/llm/budget

Model-aware token budget helpers:

| Function | Description |
|---|---|
| `estimate_text_tokens(text, model?)` | Count text tokens with tiktoken for known OpenAI models, labeled tiktoken approximations for Claude/Gemini families, or a heuristic fallback |
| `estimate_text_tokens_detail(text, model?)` | Return `{tokens, encoder, source, exact, model_family, known_model_family}` for budget/debug UI |
| `token_count_encoder(model)` | Return encoder metadata for a model without counting text |

### std/llm/prompts

Prompt helpers for deterministic system prompt composition:

| Function | Description |
|---|---|
| `system_prompt_part(content, options?)` | Build a labeled, enableable fragment for the `system` list |
| `system_before(content, options?)` | Build a fragment positioned before the call/session system prompt |
| `system_after(content, options?)` | Build a fragment positioned after the call/session system prompt |
| `with_system_fragments(options, parts)` | Return `options` with normalized fragments appended to `system` |
| `system_prelude(opts)` | Build a structured system prompt from persona, constraints, tools, output contract, examples, and tone |
| `tool_use_prelude(tools, format)` | Render a deterministic tool-use prelude for non-`agent_loop` callers |

`harness.llm.call` and `agent_loop` accept one `system` option: a string or an
ordered list of `{content, title?, position?, enabled?}` fragments. For persistent
agent sessions, Harn records the composed session-level system prompt once in
transcript metadata, emits one leading fingerprint event, and sends the
synthesized provider system field on each model request without adding repeated
`system` messages to the replayable message list. A later continuation that
omits all system prompt fields reuses the stored session prompt for the provider
request without writing another transcript event.

### std/experiments

Helpers for structural prompt experiments:

| Function | Description |
|---|---|
| `prompt_order_permutation({seed?})` | Built-in experiment spec that permutes blank-line-separated sections of the latest user prompt |
| `doubled_prompt()` | Built-in experiment spec that duplicates the latest user prompt at the front and back of the message list |
| `chain_of_draft()` | Built-in experiment spec that injects a lightweight `<draft>` / final-answer scaffold |
| `inverted_system()` | Built-in experiment spec that swaps the latest user prompt with the system prompt |
| `custom(label, transform, args?)` | Build a closure-backed experiment spec from Harn |
| `latest_string_user_message(messages)` | Return `{index, message}` for the latest plain-string user message |
| `replace_message(messages, index, message)` | Return a copy of `messages` with one entry replaced |
| `prepend_message(messages, msg)` / `append_message(messages, msg)` | Convenience helpers for custom transforms |

### std/prompt_library

Reusable prompt fragments and deterministic prompt-hotspot proposals:

| Function | Description |
|---|---|
| `prompt_library(fragments?)` | Create an in-memory prompt fragment library |
| `prompt_library_load(path_or_paths)` | Load TOML `[[prompt_fragments]]` catalogs or front-matter `.harn.prompt` files |
| `prompt_library_inject(library, id, bindings?)` | Render one fragment to text |
| `prompt_library_payload(library, id, bindings?)` | Render one fragment plus cache metadata |
| `prompt_library_inject_cluster(library, filters?, bindings?)` | Render matching fragments until `max_tokens` is reached |
| `prompt_library_suggest(library, ctx?)` | Rank fragments by tags and query terms |
| `prompt_library_hotspots(conversations, options?)` | Produce tenant-scoped k-means fragment proposals |
| `prompt_library_review_queue(library, filters?)` | Return pending k-means proposals for review UIs |

See [Prompt library stdlib](./stdlib/prompt-library.md) for the fragment file
format and hotspot proposal shape.

### std/collections

Collection utilities and store helpers:

| Function | Description |
|---|---|
| `filter_nil<V>(dict<string, V>)` | Remove entries where value is nil, empty string, or "null"; preserves the value type |
| `pick_keys<V>(dict<string, V>, keys, options: PickKeysOptions = {})` | Project a dict onto a key list; pass `{drop_nil: true}` to omit nil values |
| `sum_by<T>(items, project)` | Sum non-nil numeric projection results; returns 0 for an empty or all-nil projection |
| `count_where<T>(items, predicate)` | Count matching items without materializing a filtered list |
| `store_stale(key, max_age_seconds)` | Check if a store key's timestamp is stale |
| `store_refresh(key)` | Update a store key's timestamp to now |

Typed shapes:

| Type | Description |
|---|---|
| `PickKeysOptions = {drop_nil?: bool}` | Options shape consumed by `pick_keys` |

### std/math

Extended math utilities:

| Function | Description |
|---|---|
| `clamp(value, lo, hi)` | Clamp a value between min and max |
| `lerp(a, b, t)` | Linear interpolation between a and b by t (0..1) |
| `map_range(value, in_lo, in_hi, out_lo, out_hi)` | Map a value from one range to another |
| `deg_to_rad(degrees)` | Convert degrees to radians |
| `rad_to_deg(radians)` | Convert radians to degrees |
| `sum(items)` | Sum numeric list elements, skipping nil; returns 0 for an empty or all-nil list |
| `avg(items)` | Average of a list of numbers (returns 0 for empty lists) |
| `mean(items)` | Arithmetic mean of a list of numbers |
| `median(items)` | Median of a non-empty numeric list |
| `percentile(items, p)` | R-7 percentile interpolation for `p` in `[0, 100]` |
| `argsort(items, score_fn?)` | Indices that would sort a list ascending, optionally by score |
| `top_k(items, k, score_fn?)` | Highest-scoring `k` items, descending |
| `variance(items, sample?)` | Population variance, or sample variance when `sample = true` |
| `stddev(items, sample?)` | Population standard deviation, or sample mode when `sample = true` |
| `minmax_scale(items)` | Scale a numeric list into `[0, 1]`, or all zeros for a constant list |
| `zscore(items, sample?)` | Standardize a numeric list, or all zeros for a constant list |
| `weighted_mean(items, weights)` | Weighted arithmetic mean |
| `weighted_choice(items, weights?)` | Randomly choose one item by non-negative weights |
| `softmax(items, temperature?)` | Convert numeric scores into probabilities |
| `normal_pdf(x, mean?, stddev?)` | Normal density with defaults `mean = 0`, `stddev = 1` |
| `normal_cdf(x, mean?, stddev?)` | Normal cumulative distribution with defaults `mean = 0`, `stddev = 1` |
| `normal_quantile(prob, mean?, stddev?)` | Inverse normal CDF for `0 < prob < 1` |
| `dot(a, b)` | Dot product of two equal-length numeric vectors |
| `vector_norm(v)` | Euclidean norm of a numeric vector |
| `vector_normalize(v)` | Unit-length version of a non-zero numeric vector |
| `cosine_similarity(a, b)` | Cosine similarity of two non-zero equal-length vectors |
| `euclidean_distance(a, b)` | Euclidean distance between two equal-length vectors |
| `manhattan_distance(a, b)` | Manhattan distance between two equal-length vectors |
| `chebyshev_distance(a, b)` | Chebyshev distance between two equal-length vectors |
| `covariance(xs, ys, sample?)` | Population or sample covariance between two numeric lists |
| `correlation(xs, ys, sample?)` | Pearson correlation between two numeric lists |
| `moving_avg(items, window)` | Sliding-window moving average |
| `ema(items, alpha)` | Exponential moving average over a numeric list |
| `kmeans(points, k, options?)` | Deterministic k-means over `list<list<number>>`, returns `{centroids, assignments, counts, iterations, converged, inertia}` |

```harn
import "std/math"

harness.stdio.log(clamp(150, 0, 100))         // 100
harness.stdio.log(lerp(0, 10, 0.5))           // 5
harness.stdio.log(map_range(50, 0, 100, 0, 1)) // 0.5
harness.stdio.log(sum([1, 2, 3, 4]))          // 10
harness.stdio.log(sum([1, nil, 3]))            // 4
harness.stdio.log(avg([10, 20, 30]))          // 20
harness.stdio.log(percentile([1, 2, 3, 4], 75)) // 3.25
// ["bbbb", "cc"]
harness.stdio.log(top_k(["a", "bbbb", "cc"], 2, { x -> len(x) }))
// probabilities summing to 1
harness.stdio.log(softmax([1, 2, 3]))
harness.stdio.log(cosine_similarity([1, 0], [1, 1])) // ~0.707
harness.stdio.log(moving_avg([1, 2, 3, 4, 5], 3)) // [2.0, 3.0, 4.0]

const grouped = kmeans([[0, 0], [0, 1], [10, 10], [10, 11]], 2)
// [[0.0, 0.5], [10.0, 10.5]]
harness.stdio.log(grouped.centroids)
```

### std/slug

Memorable, non-secret identifiers for runs, agents, incidents, fixtures, and
other human-facing names:

| Function | Description |
|---|---|
| `random_slug(options?)` | Random name from the built-in or caller-supplied dictionaries |
| `slug_from(value, options?)` | Deterministic name for a value using `sha256` bucket selection |
| `deterministic_slug(value, options?)` | Alias for `slug_from` |
| `slug(value?, options?)` | Random when `value` is `nil`, deterministic otherwise |
| `slugify(value, options?)` | Normalize text into a URL/file-friendly slug |
| `slug_dictionary()` | Return the default adjective/noun/verb dictionaries |
| `slug_adjectives()` / `slug_nouns()` / `slug_verbs()` | Return one default word list |

Options include `segments` (default `2`, clamped to `1..8`), `separator`
(default `"-"`), `pattern` (`"adjective"`, `"noun"`, `"verb"` entries),
`prefix`, `suffix`, `salt`, `rng`, and `dictionary`.

```harn
import "std/slug"

const run_name = random_slug({segments: 3})
const stable = slug_from(
  {repo: "harn", pr: 42}, {segments: 4, salt: "ci"},
)
const id = slugify("Agent 007 / Fast Verify")
```

### std/identity

ActorChain inspection helpers for provenance, logs, status displays, and
surface adapters:

| Function | Description |
|---|---|
| `actor_chain_report(chain)` | Validate an ActorChain dict without throwing; returns `{ok, issues, subjects, current, origin, actors, prior_actors, depth, delegated, may_act?}` |
| `actor_chain_require(chain)` | Return the report or throw a concise `std/identity` error for malformed chains |
| `actor_chain_subjects(chain)` | Return validated subjects ordered current actor first and origin last |
| `actor_chain_summary(chain)` | Return the stable summary fields from a valid chain |
| `actor_chain_format(chain, options?)` | Format `current for origin`, including prior actors by default, or use `{style: "arrow"}` for the full chain |
| `actor_chain_subject_parts(subject)` | Split `kind:id` subjects into `{kind, id}`; subjects without `:` become `{kind: "principal", id: subject}` |

```harn
import { actor_chain_format, actor_chain_report } from "std/identity"

pipeline default(harness: Harness) {
  const chain = {
    sub: "user:owner",
    scopes: ["repo:read", "repo:write"],
    act: {sub: "agent:reviewer", scopes: ["repo:read"]},
  }
  const report = actor_chain_report(chain)
  const current = report.current
  require current != nil, "chain should have a current subject"
  // "agent:reviewer"
  harness.stdio.log(current.subject)
  // "agent:reviewer for user:owner"
  harness.stdio.log(actor_chain_format(chain))
  // "agent:reviewer -> user:owner"
  harness.stdio.log(actor_chain_format(chain, {style: "arrow"}))
}
```

### std/eval/stats

Deterministic eval-meter statistics over generic row dictionaries. Rows are
identified by `name`/`case_name`, grouped by `group`, and scored from
`passes`, `trials`, `skips`, `timeouts`, `wallTimeSeconds`, and `costUsd`.
Existing ledger aliases such as `pass_rate`, `case_fingerprint`, and
`harness_config_fingerprint` are accepted when present. Paired comparisons skip
rows whose case or harness-config fingerprints are incompatible.

| Function | Description |
|---|---|
| `aggregate_trials(name, outcomes, metadata?)` | Summarize trial outcomes into a generic eval row |
| `eval_fingerprint_integrity(rows)` | Return the exact observed case-fingerprint map and require one complete, internally consistent harness generation |
| `bootstrap_mean_ci(values, resamples, alpha, seed)` | Seeded bootstrap mean CI using high-bit LCG sampling |
| `macro_pass_at_1(rows)` | Mean pass rate over decided cases with uniform case weights |
| `reliability_breakdown(rows)` | All-pass, flaky, all-fail, and no-decision case buckets |
| `pass_caret_k(rows)` / `pass_at_k(rows)` | Strict pass^k over decided cases |
| `skip_rate(rows)` / `timeout_rate(rows)` | Mean per-row skip and timeout fractions |
| `cost_per_solved(rows)` | Total realized cost divided by solved cases |
| `worst_group(rows)` | Lowest macro pass@1 group |
| `paired_case_deltas(baseline, current)` | Comparable per-case pass-rate deltas |
| `paired_delta_report(baseline, current, resamples?, seed?)` | Paired bootstrap delta with improved/regression/inconclusive status |
| `regression_gate(baseline, current, k?)` | Baseline-standard-deviation-aware regression gate |
| `routing_calibration_report(cheap, ladder, frontier)` | Over- and under-escalation routing report |

The intervals in `std/eval/stats` are fixed-sample reports. Do not repeatedly
inspect them while accumulating trials and stop when one becomes favorable.
Use `std/eval/sequential` for that workflow.

### std/eval/remote_fanout

Deterministic, transport-neutral planning and rejoin for remote evaluation.
Import it from an installed Harn release; callers do not need a source checkout.

```harn
import {
  remote_eval_fanout_plan,
  remote_eval_fanout_rejoin_receipt,
  remote_eval_fanout_trial_receipt,
} from "std/eval/remote_fanout"
```

`remote_eval_fanout_plan(options, catalog)` expands catalog groups, assigns
stable shard ids and seeds, renders the caller's argv template, validates
concurrency and artifact budgets, and binds the plan with a checksum. Transport
adapters receive that plan unchanged and return payloads keyed by its shard ids.

Use `remote_eval_fanout_trial_receipt` to bind each result to the plan and
`remote_eval_fanout_rejoin_receipt` to validate shuffled transport results.
Rejoin quarantines missing, duplicate, unknown, failed, and malformed receipts
and returns typed `readiness.ingest_ready`. It does not interpret
application-specific verdicts or mutate an external ledger.

`remote_eval_fanout_artifact_manifest` produces a checksummed remote-to-local
path rewrite map. Image construction, credentials, remote function declaration,
byte transport, and container invocation remain adapter responsibilities.

### std/eval/sequential

Anytime-valid confidence sequences and best-arm decisions for experiments that
inspect accumulating observations more than once. Confidence sequences cover
the bounded mean simultaneously at every look, so data-dependent stopping does
not consume another per-look error budget.

| Function | Description |
|---|---|
| `bounded_cs(values, delta, lo, hi)` | Anytime-valid confidence sequence for a bounded mean |
| `paired_delta_cs(baseline, current, delta)` | Sequential counterpart to `paired_delta_report`, reusing its case and fingerprint pairing |
| `default_ladder()` | Default cumulative trials-per-case schedule `[1, 2, 3, 5]` |
| `n_at_ladder_round(ladder, round)` | Cumulative trials at a one-based look |
| `incremental_ladder_trials(ladder, round)` | Fresh trials needed to reach a look |
| `savings_report(k, ladder, alive_per_round)` | Realized trial factor versus a fixed deepest-rung grid |
| `arm_decision(arms, k_frozen, delta, epsilon, n, spent, floor?)` | Frozen-family prune or terminal decision from arm confidence sequences |

The ladder schedules spend only. It never authorizes a prune or declaration.
`arm_decision` permanently divides `delta` by the number of registered arms,
requires a nonzero practical-equivalence band, and prevents pruning below its
minimum-trial floor. Its terminal outcomes distinguish a candidate `WINNER`, a
proven `BASELINE` win, a precisely measured `TIED_CONFIRMED`, and an
underpowered `UNRESOLVED` result.

`bounded_cs` evaluates support endpoints exactly; it never perturbs an endpoint
to an interior candidate mean. Exact evaluation keeps the endpoint-null capital
process valid, while retaining the endpoint in the returned interval is the
coverage behavior. Avoiding a later reset to the full support preserves the
information accumulated from an all-lower-bound or all-upper-bound stream. The
reported interval can eventually stop narrowing at its search and outward-pad
resolution.

Sequential inputs must be append-only observations. At each look, prior values
remain byte-for-byte identical and only newly observed units are appended.
Mutable per-case aggregates do not meet that contract: project each immutable
case/trial cell to a stable row before using `paired_delta_cs`, or pass the
resulting bounded delta vector directly to `bounded_cs`.

### std/eval/experiment

A closed, portable experiment contract composed over `std/schema`, versioned
artifacts, and `std/eval/sequential`. Harn owns registration, randomized-block
assignment, family-error allocation, guardrail decisions, and explicit
iterate-to-gate promotion. Hosts own arm-config validation, execution placement,
metric projection, persistence location, and notification.

| Function | Description |
|---|---|
| `validate_experiment_manifest(value, context)` | Validate the closed schema, cross-field invariants, and host-supported blocking factors once |
| `register_experiment(manifest)` | Freeze an iterate registration and both resolved case-set snapshots |
| `plan_assignments(registration, case, trial, block)` | Deterministically randomize one balanced baseline-plus-candidates block |
| `realize_assignment(plan, arm, observed_block)` | Verify and record the realized arm, host, time slot, and declared block |
| `decide_experiment(registration, input)` | Apply anytime-valid primary and guardrail decisions under one frozen family budget |
| `promote_experiment(registration, decision)` | Create a gate registration only from that registration's typed iterate winner |
| `experiment_registration_descriptor(name?)` | Typed durable descriptor for frozen registration JSON |
| `experiment_decision_descriptor(name?)` | Typed durable descriptor for decision JSON |

The manifest declares practical effect and risk, not a fixed statistical sample
size. `max_trials_per_case` and `max_spend_usd` are hard ceilings. Metric bounds
are required because anytime-valid inference cannot be honest over an
undeclared support. Percentage alarms are percentages of each paired baseline
observation; absolute alarms use metric units.

The first ladder rung is the minimum decision floor. Candidate outcomes expose
primary eliminations as `regressed_on_primary`, so a host scheduler can stop
those arms without importing execution placement into `std/eval`. A candidate
whose upper confidence bound is below the practical-equivalence band yields the
distinct terminal `BASELINE` verdict.

The spend ceiling covers the whole experiment. Promotion carries realized
iterate spend into the gate registration; it never resets the budget.
Decision input reports authoritative phase spend separately from paired
observations, so infrastructure-missing or otherwise unpaired work consumes the
ceiling without entering the confidence sequence.

Complete manifest:

```json
{
  "schema": "harn.experiment.v1",
  "experiment_id": "portable-cache-policy",
  "hypothesis": "A bounded cache policy improves success without excess cost.",
  "owner": "runtime-team",
  "baseline": {
    "id": "baseline",
    "config": {"cache": "off"},
    "complexity": 0
  },
  "candidates": [
    {
      "id": "simple",
      "config": {"cache": "on"},
      "complexity": 1
    },
    {
      "id": "replicate",
      "config": {"cache": "on"},
      "complexity": 2
    }
  ],
  "decision": {
    "delta": 0.05,
    "epsilon": 0.1,
    "ladder": [3, 5, 10, 80]
  },
  "metrics": {
    "primary": {
      "id": "success",
      "direction": "up",
      "bounds": {"lo": 0.0, "hi": 1.0}
    },
    "guardrails": [
      {
        "id": "cost",
        "direction": "down",
        "bounds": {"lo": 0.0, "hi": 5.0},
        "alarm": {"kind": "absolute", "threshold": 0.1}
      }
    ]
  },
  "assignment": {
    "mode": "randomized_block",
    "seed": "seed-42",
    "blocking_factors": ["host", "time_slot"]
  },
  "splits": {
    "iterate": {
      "id": "tune",
      "digest": "tune-v1",
      "cases": ["case-a", "case-b"]
    },
    "gate": {
      "id": "holdout",
      "digest": "holdout-v1",
      "cases": ["case-z"]
    },
    "promotion": "explicit"
  },
  "budget": {
    "max_spend_usd": 10.0,
    "max_trials_per_case": 80
  }
}
```

Arm IDs are distinct from opaque config values, so byte-identical A/A arms are
valid. Registration hashes both identities and preserves both configs. Ambient
host telemetry may optimize placement, but validity depends only on the
declared randomized blocks and realized assignment receipts.

`eval_pack_run` also supports live-verify eval cases. Set `kind: "live-verify"`
and provide `task`, `workspace` or `project`, `verify_command`,
`expected_output_paths`, and `required_output_snippets`; declare `executor` on
the manifest or case as a shell string, argv list, or `{command|argv, cwd?,
env?, timeout_seconds?}` object. Harn sends `{schema, manifest, case, trial,
trials}` to the executor on stdin. The executor returns a generic JSON outcome
with fields such as `verification`, `verificationExitCode`, `timedOut`,
`wallTimeSeconds`, `costUsd`, `producedPaths`, and `toolCallSummary`; Harn then
runs `verify_command`, checks expected paths/snippets, enforces tool budgets,
and records the merged trial outcome in the same ledger/statistics path as
replay cases.

### std/path

Path manipulation utilities:

| Function | Description |
|---|---|
| `ext(path)` | Get the file extension without the dot |
| `stem(path)` | Get the filename without extension |
| `normalize(path)` | Normalize path separators (backslash to forward slash) |
| `is_absolute(path)` | Check if a path is absolute |
| `workspace_info(path, workspace_root?)` | Classify a path at the workspace boundary |
| `workspace_normalize(path, workspace_root?)` | Normalize a path into workspace-relative form when safe |
| `workspace_canonicalize_existing(path, workspace_root?)` | Canonicalize an existing path and return it only when it remains under the canonical workspace root |
| `list_files(dir)` | List files in a directory (one level) |
| `list_dirs(dir)` | List subdirectories in a directory |

```harn
import "std/path"

harness.stdio.log(ext("main.harn"))          // "harn"
harness.stdio.log(stem("/src/main.harn"))    // "main"
harness.stdio.log(is_absolute("/usr/bin"))   // true
// "packages/app/SKILL.md"
harness.stdio.log(
  workspace_normalize("/packages/app/SKILL.md", harness.fs.cwd())
)

const files = list_files("src")
const dirs = list_dirs(".")
```

### std/vision

Deterministic OCR helpers layered on top of the runtime's `vision_ocr(...)`
builtin:

| Function | Description |
|---|---|
| `ocr(image, options?)` | Run OCR over an image path or image payload and return `StructuredText` with text, blocks, lines, tokens, source metadata, backend info, and counts |

```harn
import "std/vision"

const structured = ocr("fixtures/ui.png")
harness.stdio.log(structured.text)
harness.stdio.log(structured.lines[0]?.text)
harness.stdio.log(structured.tokens[0]?.bbox?.left)
```

### std/json

Field selection is the global [`pick(source, keys)`](pick.md)
builtin. It preserves field types and stored `nil` values and needs no import.

JSON utility patterns:

| Function | Description |
|---|---|
| `pretty(value)` | Pretty-print a value as indented JSON |
| `JsonParseFailure` | Closed error contract returned by bare `try { json_parse(text) }` when JSON is malformed or over-depth; valid JSON `null` is `Ok(nil)` |
| `stream_validator(schema)` | Incrementally validate JSON string or bytes chunks against a schema; `feed(chunk)` returns `Pending`, `Valid`, or `Invalid({reason_kind, reason, path})`, and `value()` returns the parsed JSON once valid |
| `stream_validate_create(schema)` / `stream_validate_chunk(handle, chunk)` / `stream_validate_finalize(handle)` | Standalone partial-JSON validation: each call returns a plain-dict verdict `{verdict: "pending"\|"valid"\|"invalid", reason_kind?, reason?, path?}` so streaming agents (SSE chunks, WebSocket frames) can dispatch on string verdicts without pattern-matching enum variants. `stream_validate()` returns a namespace record exposing the same trio as `create`/`chunk`/`finalize` keys |
| `merge<R1, R2>(a: {...R1}, b: {...R2})` | Shallow-merge records; fields in `b` override matching fields in `a`, preserving each field's type |
| `omit<V>(data: dict<string, V>, keys)` | Omit specific keys from a dict; preserves the value type |

```harn
import "std/json"

const parsed: Result<unknown, JsonParseFailure> = try {
  json_parse("{\"x\": 1}")
}
const data = parsed?   // {x: 1}; malformed JSON propagates Err
const schema = {
  type: "dict", required: ["x"], properties: {x: {type: "int"}},
}
const validator = stream_validator(schema)
const status = validator.feed("{\"x\": 1}")  // JsonStreamStatus.Valid
const parsed = validator.value()             // {x: 1}

// std/json/stream_validate — plain-dict verdicts for streaming agents:
const handle = stream_validate_create(schema)
// {verdict: "pending"}
const r1 = stream_validate_chunk(handle, "{\"x\":")
// {verdict: "valid"}
const r2 = stream_validate_chunk(handle, "1}")
// {verdict: "valid"}
const r3 = stream_validate_finalize(handle)
const merged = merge({a: 1}, {b: 2})    // {a: 1, b: 2}
const subset = pick({a: 1, b: 2, c: 3}, ["a", "c"])  // {a: 1, c: 3}
const rest = omit({a: 1, b: 2, c: 3}, ["b"])          // {a: 1, c: 3}
```

### std/ansi

Terminal styling helpers that follow Harn's color policy (`NO_COLOR`,
`FORCE_COLOR`, configured color mode, and TTY detection) by default:

| Function | Description |
|---|---|
| `ansi_enabled(options?)` | Return whether ANSI should be emitted for `stdout`, `stderr`, or `stdin`; accepts `{mode: "auto"/"always"/"never", enabled?, stream?}` |
| `ansi_escape(code)` | Build a Select Graphic Rendition escape sequence |
| `ansi_reset()` | Return the reset escape sequence |
| `ansi_strip(text)` | Remove CSI and OSC ANSI escape sequences |
| `ansi_visible_len(text)` | Count visible characters after stripping ANSI escapes |
| `ansi_style(text, style?, options?)` | Apply foreground/background color and common styles when enabled |
| `ansi_color(text, name, options?)` / `ansi_bg(text, name, options?)` | Apply a named foreground or background color |
| `ansi_bold(text, options?)`, `ansi_dim(text, options?)`, `ansi_underline(text, options?)` | Common text styles |
| `ansi_success(text, options?)`, `ansi_warn(text, options?)`, `ansi_error(text, options?)`, `ansi_info(text, options?)`, `ansi_muted(text, options?)` | Semantic styles for CLI status output |
| `ansi_link(label, url, options?)` | Render an OSC-8 terminal hyperlink when ANSI is enabled |
| `ansi_truncate(text, max_chars, marker?)` | Truncate by visible length after stripping ANSI escapes |

Pass `{mode: "always"}` in deterministic tests when you need to assert exact
escape output; otherwise let the auto policy decide.

```harn
import { ansi_success, ansi_strip } from "std/ansi"

const line = ansi_success("passed", {mode: "always"})
harness.stdio.log(ansi_strip(line)) // passed
```

### std/table

Deterministic plain-text and Markdown table rendering for logs, summaries, and
GitHub step output:

| Function | Description |
|---|---|
| `render_table(rows, options?)` | Render dict/list/scalar rows as a stable plain-text or Markdown table |
| `render_markdown_table(rows, options?)` | Alias for `render_table(..., {format: "markdown"})` |
| `render_kv_table(data, options?)` | Render a dict as a sorted two-column key/value table |

Columns can be inferred from the first row or supplied with
`{key, header?, align?, width?, max_width?}` entries. Cell text is single-line
normalized, ANSI-aware for visible width, and optionally capped with
`max_cell_width`.

```harn
import { render_table } from "std/table"

harness.stdio.log(render_table(
  [{name: "harn", status: "ok"}, {name: "burin", status: "queued"}],
  {columns: ["name", "status"]},
))
```

### std/diff

Line diff helpers for short texts, generated reports, release scripts, and
syntax-aware source review:

| Function | Description |
|---|---|
| `diff_lines(before, after)` | Return `{changed, insertions, deletions, old_lines, new_lines, ops}` |
| `diff_artifact(before, after, options?)` | Compute change counts and a unified diff together |
| `unified_diff(before, after, options?)` | Render a unified diff with optional `{path, from_label, to_label, context, color, color_mode}` |
| `colorize_diff(diff_text, options?)` | Apply ANSI coloring to an existing unified diff |
| `diff_summary(before, after)` | Return compact changed/insertions/deletions counts |
| `render_diff_stat(entries, options?)` | Render per-file diff stats from `{path, before, after}` or stat dicts |
| `structural_diff(ast, path_a, path_b, options?)` | Host-backed tree-sitter review diff with line-diff fallback |
| `changeset_summary(ast, files)` | Symbol-level review summary over `{path, before?, after?}` file images |

The line helpers share Harn's native Histogram engine; see the
[`std/diff` reference](stdlib/diff.md) for behavior and algorithm details. For
large file sets, call
`git diff` through `std/git` or `std/command` and use `colorize_diff` or
`render_diff_stat` for presentation.

Use `structural_diff` when a UI or agent review pass needs changed syntax-node
spans instead of a patch. It falls back to a unified line diff on unsupported
languages, parse errors, or size limits.

Use `changeset_summary` before rendering ordinary hunks when a review surface
needs to distinguish behavior-bearing symbol changes from reshaping. The
result schema is `harn.review_changeset.v1`; unsupported inputs are retained as
degraded entries, and candidate `CALLS` relations are labeled as name-matched
heuristics.

```harn
import { unified_diff } from "std/diff"

harness.stdio.log(
  unified_diff("one\ntwo", "one\nthree", {path: "example.txt"})
)
```

### std/cache

Persistent cache helpers backed by sqlite or filesystem storage:

| Function | Description |
|---|---|
| `cache_get(key, options?)` | Return `{hit: bool, value?}` for a persistent cache key |
| `cache_put(key, value, options?)` | Store a value with TTL and LRU eviction |
| `cache_clear(options?)` | Remove all entries in the configured namespace |

Options accept `store: "namespace"` or
`store: {backend: "sqlite"|"fs", namespace?, path?}`, plus `ttl`,
`ttl_seconds`, `max_age_seconds`, and `max_entries`.

### std/llm/envelope

The canonical `harness.llm.call` response contract — one rigid snake_case
envelope with all accounting owned by `usage` and a typed `outcome`
classification. Import the typed aliases when annotating call sites, and
the predicates to branch on the outcome without re-deriving it:

| Export | Kind | Description |
|---|---|---|
| `LlmResponse` | type | The full `harness.llm.call` response envelope |
| `LlmUsage` | type | Single owner of all call accounting (tokens, cost, prompt-cache, serving tier) |
| `LlmOutcome` / `LlmOutcomeKind` | type | Typed `{kind, billed}` classification and its `kind` vocabulary |
| `LlmToolCall` | type | A dispatchable tool call from the merged channel |
| `LlmStreamChunk` | type | One streamed chunk from `harness.llm.stream_call` (terminal chunk carries `stop_reason`) |
| `llm_response_is_empty(response)` | fn | True when the call committed nothing usable (`outcome.kind == "empty"`) |
| `llm_response_is_billed_empty(response)` | fn | True for the billed-noncommittal class — provider charged tokens and committed nothing usable |
| `llm_response_is_truncated(response)` | fn | True when generation was cut on an output-token limit |

See [llm_call](llm/llm_call.md#return-value) for the full envelope
field reference.

### std/llm/caller

The blessed default caller stack (the call plane's front door):

| Function | Description |
|---|---|
| `llm_caller(opts?)` | The blessed default caller stack: `with_retry(default_llm_caller(), opts?.retry ?? {})` with typed reserved-status classification and billed-empty re-dispatch |
| `default_llm_caller()` | The bottom of every middleware composition: one `harness.llm.call` folded into the canonical caller envelope |
| `LlmCallerRequest` (type) | One request through a caller stack: `{prompt, system?, opts?}` |
| `LlmCaller` (type) | A composable caller: `fn(LlmCallerRequest) -> dict` — middleware wraps one and returns another |

### std/llm/handlers

LLM call wrappers and middleware helpers:

| Function | Description |
|---|---|
| `llm_cache_key(prompt, system?, options?)` | Derive the canonical `sha256:` key for a cached LLM call |
| `with_cache(prompt, system?, options?)` | Return a cached `harness.llm.call` envelope when available, otherwise call and store the response |
| `with_circuit_breaker(handler, options?)` | Wrap a call handler with per-`(provider, model)` circuit-breaker pooling, or pass `name` to share one circuit |

`with_cache` keys `{prompt, system, provider, model, temperature, top_p,
max_tokens}` after defaults resolve. Its default store is sqlite namespace
`llm.with_cache` with TTL `10m` and LRU size 256. Calls with `tools` bypass the
cache by default; set `skip_when` to a bool or predicate closure to override
that policy.

### std/context

Structured prompt/context assembly helpers:

| Function | Description |
|---|---|
| `section(name, content, options?)` | Create a named context section |
| `context_attach(name, path, content, options?)` | Attach file/path-oriented context |
| `context(sections, options?)` | Build a context object |
| `context_artifact(input, options?)` | Normalize a host-neutral `harn.context_artifact.v1` envelope |
| `context_artifact_from_burin_digest(path, body, options?)` | Wrap a legacy `.burin/context-digests` markdown body without changing its storage path |
| `context_artifact_rank(artifact, options?)` | Score an artifact using freshness, confidence, authority, task/role/path applicability, priority, and token cost |
| `context_artifact_dedupe(artifacts, options?)` | Deduplicate artifacts by explicit source-hash overlap or canonical body key, merging provenance and metadata |
| `context_artifact_merge(left, right, options?)` | Merge two duplicate artifact envelopes |
| `context_artifact_budget(artifacts, options?)` | Rank, deduplicate, filter, and fit artifacts into token/count budgets |
| `context_artifact_select(artifacts, options?)` | Return only the selected artifacts from `context_artifact_budget` |
| `context_render_artifacts(artifacts, options?)` | Render artifacts as `markdown`, `xml`, `plain`, `compact`, or provider-capability-driven `auto` |
| `context_render_logical_section(name, title, body, options?)` | Render a logical section using the same provider capability flags as prompt templates |
| `context_render(ctx, options?)` | Render a context into prompt text |
| `context_add_artifact(ctx, artifact)` | Append a normalized context artifact to an existing context |
| `prompt_compose(task, ctx, options?)` | Compose `{prompt, system, rendered_context}` |

`context_artifact(...)` is the portable repository-context envelope for host
pipelines. It preserves the artifact body in both `body` and `text` and
normalizes metadata used by Harn and Burin-style context pipelines:

- `kind`, `scope.path` / `path`, `language`, `role`, `task`, and
  `applicability.{roles,tasks,languages,paths,tags}`
- `freshness`, `confidence`, `provenance.{source,authority,explanation,...}`,
  and `source_hashes`
- `token_estimate`, `redaction`, `sensitivity`, and arbitrary `metadata`

Rendering with `variant: "auto"` follows the active provider capability flags:
`prefers_xml_scaffolding` selects XML, `prefers_markdown_scaffolding` selects
Markdown, and otherwise the renderer falls back to plain text. Explicit
`variant: "markdown" | "xml" | "plain" | "compact"` overrides capability
selection. The Burin adapter keeps generated digest compatibility during
migration by wrapping existing `.burin/context-digests/*.md` content instead of
requiring hosts to rewrite those files.

### std/context/disclosure

Cut Markdown to an estimated token budget and render an omission note with
a recovery action. See [Context truncation](./stdlib/context-disclosure.md)
for the functions, return type, and boundary rules.

### std/context/maintenance

Portable receipts for host-owned background context jobs:

| Function | Description |
|---|---|
| `context_maintenance_dedupe_key(job_id, lifecycle_event, affected_paths?, source_id?)` | Build a stable dedupe key from job identity, lifecycle event, sorted paths, and source/session id |
| `context_maintenance_receipt(job_id, status, input?)` | Normalize a `harn.context_maintenance.job_receipt.v1` receipt |
| `context_maintenance_queue_receipt(job_id, event, options?)` | Return the standard non-blocking queued receipt for a lifecycle hook event |
| `context_maintenance_transition(receipt, status, patch?)` | Move a receipt to `running`, `succeeded`, `failed`, or `skipped` while preserving stable fields |
| `context_maintenance_replay_decision(receipt, options?)` | Return a deterministic replay include/skip decision and matching receipt update |

Use this module from lifecycle hook packages that queue refresh or librarian
jobs. See [Context maintenance hooks](./context-maintenance-hooks.md) for the
canonical file-edited, session-idle, pre-compact, post-turn, and session-end
recipes.

### std/agent_state

Durable session-scoped state helpers built on the VM-side durable-state
backend:

| Function | Description |
|---|---|
| `agent_state_init(root, options?)` | Create or reopen a session-scoped durable state handle |
| `agent_state_resume(root, session_id, options?)` | Reopen an existing durable state session |
| `agent_state_write(handle, key, content)` | Atomically persist text content under a relative key |
| `agent_state_read(handle, key)` | Read a key, returning `nil` when it is absent |
| `agent_state_list(handle)` | Recursively list keys in deterministic order |
| `agent_state_delete(handle, key)` | Delete a key |
| `agent_state_handoff(handle, summary)` | Write a structured JSON handoff envelope to the reserved handoff key |
| `agent_state_handoff_key()` | Return the reserved handoff key name |

See [Agent state](./agent-state.md) for the handle format, conflict
policies, and backend details.

### std/memory

Append-only durable memory helpers for observations that should be recalled by
later runs:

| Function | Description |
|---|---|
| `harness.memory.store(namespace, key, value, tags?, options?)` | Append a memory observation and return a `memory_record` dict |
| `harness.memory.recall(namespace, query, k?, options?)` | Return active records ranked by deterministic BM25-style lexical recall |
| `harness.memory.summarize(namespace, window?, options?)` | Return an extractive summary dict for a recent or query-filtered slice |
| `harness.memory.forget(namespace, predicate, options?)` | Append a soft-delete tombstone for matching records |

The default backend writes JSONL events under `.harn/memory/<namespace>/`.
Pass `{root: "path"}` in options to choose a different memory root. Forgetting
is a tombstone operation, so the event log remains auditable.

See [Memory](./memory.md) for record shape, predicate options, and replay
notes.

### std/postgres

Postgres persistence helpers for durable tenant state, event logs, receipts,
claims, and audit records:

| Function | Description |
|---|---|
| `harness.postgres.pool(source, options?)` | Open a pooled Postgres connection from a URL, `env:NAME`, `secret:namespace/name`, or source dict |
| `harness.postgres.connect(source, options?)` | Open a single-connection pool |
| `pg_query(handle, sql, params?)` | Run a parameterized query and return rows as dictionaries |
| `pg_query_one(handle, sql, params?)` | Return the first row, or `nil` when no rows match |
| `pg_execute(handle, sql, params?)` | Run a statement and return `{rows_affected}` |
| `pg_transaction(pool, callback, options?)` | Run a closure with a scoped transaction handle, committing on success and rolling back on error |
| `pg_close(pool)` | Close a pool handle |
| `pg_stmt_cache_clear(pool)` | Clear prepared-statement caches on idle primary and replica connections |
| `pg_mock_pool(fixtures)` | Create fixture-backed Postgres test handle |
| `pg_mock_calls(mock)` | Inspect mock SQL calls |

See [Postgres](./postgres.md) for parameter binding, transaction settings,
RLS examples, pool options, and migration boundaries.

### std/postgres/query

Harn-native query ergonomics over `std/postgres`. This module keeps raw SQL as
the source of truth while making SQL templates, named query records, and
repeated projection fragments easier to review:

| Function | Description |
|---|---|
| `named(name, mode, sql, params?)` | Build a serializable query record with `name`, `mode`, `sql`, and `params` |
| `sql(template, values?, options?)` | Build a `{sql, params}` record by replacing `{name}` placeholders with `$n` bind parameters |
| `named_sql(name, mode, template, values?, options?)` | Build a named query record from a SQL template |
| `one(handle, query)` | Run a query record through `pg_query_one` |
| `many(handle, query)` | Run a query record through `pg_query` |
| `exec(handle, query)` | Run a query record through `pg_execute` |
| `run(handle, named_query)` | Dispatch a named query by `mode` (`one`, `many`, or `exec`) |
| `identifier(name)` | Validate a static SQL identifier for projection helpers |
| `quote_identifier(name)` | Quote one SQL identifier part with PostgreSQL double-quote escaping |
| `ident(name)` | Build a quoted identifier fragment for `sql(...)` |
| `ident_path(parts)` | Build a dotted quoted identifier fragment for `sql(...)` |
| `unsafe_sql(fragment)` | Mark a source-controlled SQL fragment for insertion into `sql(...)` |
| `columns(parts)` | Join projection fragments/strings into one `{projection}` fragment |
| `select_clause(parts)` | Render `SELECT ...` from projection parts as a fragment |
| `uuid_text(name)` | Fragment `name::text AS name` |
| `nullable_uuid_text(name)` | Fragment UUID/`::text` projection that preserves nulls |
| `timestamptz_json(name)` | Fragment `to_json(name)#>>'{}' AS name` |
| `nullable_timestamptz_json(name)` | Fragment timestamp projection that preserves nulls |

In SQL templates, ordinary `{name}` placeholders are always bound as params;
use `{{` and `}}` for literal braces. SQL structure must be explicit through
`ident(...)`, `ident_path(...)`, or `unsafe_sql(...)`; `unsafe_sql(...)` must
only wrap source-controlled fragments.

### std/sqlite

SQLite persistence helpers for local state, offline demos, event-log
inspection, and deterministic fixtures:

| Function | Description |
|---|---|
| `harness.sqlite.open(path, options?)` | Open `:memory:` or a file-backed SQLite database |
| `sqlite_query(handle, sql, params?)` | Run a parameterized query and return rows as dictionaries |
| `sqlite_query_one(handle, sql, params?)` | Return the first row, or `nil` when no rows match |
| `sqlite_execute(handle, sql, params?)` | Run a statement and return `{rows_affected}` |
| `sqlite_transaction(db, callback, options?)` | Run a closure with a scoped transaction handle, committing on success and rolling back on error |
| `sqlite_migrate(db, options)` | Apply pending `.sql` migration files |
| `sqlite_mock_db(fixtures)` | Create fixture-backed SQLite test handle |
| `sqlite_mock_calls(mock)` | Inspect mock SQL calls |

See [SQLite](./sqlite.md) for file-open safety, SQLite type affinity,
transactions, migrations, mocks, and the comparison with `std/postgres`.

### std/io

Terminal-oriented helpers for scripts that need direct operator input without
shelling out to `bash`:

| Function | Description |
|---|---|
| `is_tty(fd?)` | Return whether fd `0`, `1`, or `2` is attached to a terminal; defaults to stdin |
| `harness.stdio.read_line(opts?)` | Read one line from stdin and return `{ok, value?, status?, error?}`; supports `prompt`, `timeout_ms`, `trim`, `echo`, and `raw` options |
| `read_password(prompt?, timeout_ms?)` | Convenience wrapper around `read_line` with terminal echo disabled |
| `write_stderr(text)` | Write text to stderr without appending a newline |

`read_line` statuses are `ok`, `eof`, `timeout`, `interrupt`, and `error`.
Prompts are written to stderr with ANSI sequences preserved.

### std/runtime

Generic host/runtime helpers that are useful across many hosts:

| Function | Description |
|---|---|
| `runtime_task()` | Return the current runtime task string |
| `runtime_pipeline_input()` | Return structured pipeline input from the host |
| `runtime_prompt_content()` | Return the active ACP session prompt as typed, normalized text/image/audio/PDF content blocks |
| `runtime_dry_run()` | Return whether the current run is dry-run only |
| `runtime_approved_plan()` | Return the host-approved plan text when available |
| `runtime_state_paths_under(root)` | Return the conventional state and worktree roots beneath a caller-selected project root without consulting ambient path overrides |
| `process_run(argv, options?)` | Execute a process through argv-mode `process.exec`; prefer this for programmatic commands |
| `process_shell(command, options?)` | Execute an explicit shell command through `process.exec` using the host default shell unless options provide `shell` or `shell_id` |
| `process_result_text(result)` | Return stdout, stderr, combined output, or inline output from a command-runner result |
| `process_result_success(result)` | Return explicit command success when present, otherwise derive success from `status` and `exit_code` |
| `shell_quote(value)` | Quote a value as one POSIX shell argument for unavoidable shell-mode command composition |
| `interaction_ask(question)` | Ask the host/user a question through the typed interaction contract |
| `interaction_ask_with_kind(question, kind)` | Ask the host/user a question with an explicit interaction kind |
| `record_run_metadata(run, workflow_name)` | Persist normalized workflow run metadata through the runtime contract |

### std/fs

File-system convenience helpers built on the globally available host file
primitives. These remove the repeated parent-directory, parse/fallback, and
relative-path boilerplate that release scripts and harnesses tend to carry:

| Function | Description |
|---|---|
| `ensure_parent_dir(path)` | Create the parent directory for a file path when needed |
| `harness.fs.read_lines_page_result(path, options?)` | Read a bounded page of complete UTF-8 lines and return a byte-and-line cursor or typed filesystem failure |
| `read_json(path)` | Read required JSON, throwing `StructuredReadFailure` for absent, unreadable, malformed, or over-depth input |
| `read_json_result(path)` | Read JSON as `Result<unknown, StructuredReadFailure>` without erasing failure kind, path, or parser location |
| `read_json_typed<T>(path, schema: Schema<T>, apply_defaults?) -> T` | Read required JSON and validate it against a schema, throwing the typed read or schema failure |
| `read_json_typed_result<T>(path, schema: Schema<T>, apply_defaults?)` | Read and schema-validate JSON as `TypedReadResult<T>`, whose error is `TypedReadFailure` |
| `read_json_contract<T>(path, contract: SchemaContract<T>) -> T` | Read required JSON through structural validation and named rules |
| `read_json_contract_result<T>(path, contract: SchemaContract<T>)` | Preserve read, schema, rule-violation, and broken-rule failures in one typed result |
| `write_json(path, value, options?)` | Write JSON with optional `{pretty, trailing_newline, ensure_parent}` |
| `read_yaml(path, fallback?)` / `write_yaml(path, value, options?)` | YAML file helpers |
| `read_toml(path, fallback?)` / `write_toml(path, value, options?)` | TOML file helpers |
| `write_lines(path, lines, options?)` | Write a list of lines as one text file |
| `append_line(path, line)` | Append exactly one line with a newline terminator |
| `touch(path)` | Create an empty file if it does not exist |
| `find_files(root, pattern, options?)` | Glob below `root`; pass `{relative: true}` for root-relative paths |
| `relative_path(root, path)` | Return a slash-normalized path relative to `root` when possible |
| `is_file(path)` / `is_dir(path)` | Return type-aware existence checks |
| `file_size(path)` | Return file size in bytes, or `nil` when unavailable |
| `search_evidence(roots, patterns, options?)` | Walk each labeled root once, match every labeled literal with one multi-pattern matcher, and return deterministic path-relative hits plus per-root settlement and truncation receipts |
| `search_evidence_background(roots, patterns, options?)` | Run the same search through the cancellable long-running operation lifecycle |

Evidence search's optional case-insensitive mode folds ASCII letters.

`StructuredReadFailure` contains `{kind, format, path, detail, line?, column?}`. Its closed
`kind` union is
`"absent" | "unreadable" | "malformed" | "recursion_limit"`; only callers that
explicitly treat absence as optional should default that case.
`SchemaValidationFailure` contains `{kind: "schema_invalid", path, detail,
issues}`. Contract reads add `ContractValidationFailure`, whose kind is
`schema_invalid`, `rule_failed`, or `rule_error`. It retains the path, format,
and typed validation issues. `TypedReadFailure` is the owner alias for all read,
schema, and rule failures.

```harn
import { read_json, relative_path, write_json } from "std/fs"

const path = path_join(harness.fs.temp_dir(), "report/data.json")
write_json(path, {status: "ok"}, {pretty: true})
harness.stdio.log(read_json(path).status)
harness.stdio.log(relative_path(harness.fs.temp_dir(), path))
```

Evidence results expose caller labels rather than root paths, so persisted
reports can stay stable and avoid machine-local checkout paths. Hits are sorted
by root ID, pattern ID, relative path, line, and column regardless of `threads`.
One missing or unreadable root yields a partial receipt without discarding other
roots. `max_matches` is global; `max_matches_per_root` prevents one root from
consuming unbounded memory before deterministic global truncation.

### std/jsonl

Use bounded pages for logs, transcripts, and other large JSONL files. Static
page cursors contain an exact byte offset and next physical line number. For an
actively growing file, use the append reader: its cursor additionally owns file
identity, observed size, optional caller generation, and incomplete-tail
semantics so callers can resume without rebuilding filesystem heuristics.

| Function | Description |
|---|---|
| `read_jsonl_page_result(path, options?)` | Read at most `max_records` physical lines and `max_bytes`, preserving malformed rows as per-record issues |
| `read_jsonl_append_page_result(path, options?)` | Incrementally read only newline-committed records, retaining a partial tail and reporting typed cursor resets |
| `read_jsonl_page(path, options?)` | Throwing filesystem-failure form of the bounded raw reader |
| `read_jsonl_contract_page_result<T>(path, contract, options?)` | Apply structural schema validation and named rules to each parsed record, preserving all record failures |
| `read_jsonl_contract_page<T>(path, contract, options?)` | Throwing filesystem-failure form of the bounded contract reader |
| `fold_jsonl_file(path, initial, reducer, options?)` | Fold a file without materializing its complete contents |
| `read_jsonl(path, options?)` | Compatibility helper that materializes records while reading the file in bounded pages |
| `parse_jsonl(text, options?)` / `fold_jsonl(text, initial, reducer, options?)` | Process JSONL already held in memory |
| `write_jsonl(path, items)` / `append_jsonl(path, item)` | Replace or append JSONL output |

Contract pages distinguish malformed JSON, invalid structure, violated rules,
and broken rule implementations as `malformed`, `schema_invalid`,
`rule_failed`, and `rule_error`. Record issues retain the source line, byte
offset, and raw text. A line larger than `max_bytes` fails the page with
`file_too_large`; it is never returned partially.

```harn
import { read_jsonl_contract_page_result } from "std/jsonl"
import { schema_contract } from "std/schema"

const contract = schema_contract(schema_of(Event), [])
let cursor = {offset: 0, line: 1}
while true {
  const page = unwrap(
    read_jsonl_contract_page_result(
      "events.jsonl", contract, {cursor: cursor},
    ),
  )
  for record in page.records {
    handle(record.value)
  }
  if page.done {
    break
  }
  cursor = page.next_cursor
}
```

### std/run_artifacts

Directory-backed run artifact helpers for harness-local outputs. The default
root is `harness.fs.runtime_paths().run_root`, so `HARN_RUN_DIR` and the active runtime
root keep working; pass `{root}` when a host owns a different artifact store.

These helpers do not define a second run-record schema. Keep using
`workflow_result_persist`, `run_record_save`, `run_record_load`, and portal run
records for canonical workflow history and portal inspection. Use artifact
directories for local harness files such as facts, audits, reviews, agent
results, and transcript sidecars. When a run needs to leave the machine, export
a portable session bundle instead of treating the raw artifact directory as the
support boundary.

| Function | Description |
|---|---|
| `run_artifacts_open(kind, options?)` | Create or resolve `.harn-runs/<kind>/<run_id>` under `{root?, namespace?, run_id?}` |
| `run_artifacts_from_dir(kind, dir)` | Reconstruct the basic run artifact shape for recovery/chat/review flows without writing |
| `run_artifacts_list(kind, options?)` | List recent run directories newest-first with `{root?, namespace?, limit?}` |
| `run_artifact_path(run, name)` | Resolve a relative artifact path inside `run.dir`, rejecting absolute paths and `..` traversal |
| `artifact_descriptor<T>(name, contract)` | Bind one traversal-safe artifact name to its `SchemaContract<T>` |
| `run_artifact_write_json<T>(run, descriptor, value, options?)` | Validate and conditionally replace descriptor-bound JSON, returning the file receipt |
| `run_artifact_write_json_result<T>(run, descriptor, value, options?)` | Preserve validation and filesystem failures without mutating on invalid or stale input |
| `run_artifact_read_json<T>(run, descriptor)` | Read required JSON through the descriptor's structural schema and validation rules |
| `run_artifact_read_json_result<T>(run, descriptor)` | Preserve absence, malformed JSON, schema, rule, and broken-rule failures |
| `run_artifact_write_json_raw` / `run_artifact_write_json_raw_result` | Low-level untyped JSON write escape hatch |
| `run_artifact_read_json_raw` / `run_artifact_read_json_raw_result` | Low-level untyped JSON read escape hatch |
| `run_artifact_write_text(run, name, text, options?)` | Write text with parent-directory and trailing-newline behavior |
| `run_artifact_read_text(run, name, fallback?)` | Read text with a fallback for missing or unreadable files |
| `run_artifact_transcript_dir(run, name?)` | Return a transcript sidecar directory such as `agent-llm` or `chat-llm` |
| `run_artifact_transcript_path(run, name?)` | Return `<transcript-dir>/llm_transcript.jsonl` inside the run directory |

`run_artifacts_open` and `run_artifacts_from_dir` return `RunArtifactsRun`.
`run_artifacts_list` returns `list<RunArtifactsRun>`. The nested
`RunArtifactPaths` shape contains the standard local artifact names: `facts`,
`audit`, `review`, `agent_result`, `agent_trace`, and
`agent_llm_transcript`. `ArtifactWriteOptions.replace` accepts the
`std/fs.FileReplaceOptions` lease and durability policy. Descriptor writes
validate before entering that conditional-replacement boundary.

```harn
import {
  artifact_descriptor,
  run_artifact_read_json,
  run_artifact_transcript_path,
  run_artifact_write_json,
  run_artifacts_from_dir,
  run_artifacts_open,
} from "std/run_artifacts"
import { schema_contract } from "std/schema"

type EvaluationFacts = {status: "complete", checks: int}

const run = run_artifacts_open("eval", {run_id: "smoke-001"})
const facts_artifact = artifact_descriptor(
  "facts.json",
  schema_contract(schema_of(EvaluationFacts), []),
)
const written = run_artifact_write_json(
  run,
  facts_artifact,
  {status: "complete", checks: 7},
  {pretty: true},
)
const facts: EvaluationFacts = run_artifact_read_json(run, facts_artifact)

const transcript = run_artifact_transcript_path(run)
const reopened = run_artifacts_from_dir("eval", run.dir)
harness.stdio.log(written.status + ":" + facts.status)
harness.stdio.log(reopened.paths.facts)
harness.stdio.log(transcript)
```

### std/artifacts/typed

Versioned typed contracts and bounded readers for durable artifacts. Layers a
single explicit concept — a schema `version` — on top of `std/schema` contracts,
`std/run_artifacts` descriptors, and `std/jsonl` page reads, so harnesses stop
treating durable results as permissive dicts. Every read collapses to one
`ArtifactReadFailure` whose `kind` keeps the six failure modes distinct:
`missing`, `malformed`, `version_mismatch`, `schema_invalid`, `rule_failed`,
`rule_error`.

| Function | Description |
|---|---|
| `versioned_contract<T>(id, version, contract, options?)` | Bind a `SchemaContract<T>` to a stable id + version with `{supported?, version_field?, absent_version?}` |
| `versioned_descriptor<T>(name, versioned)` | Bind a traversal-safe artifact name to a versioned contract (reuses `artifact_descriptor`) |
| `check_versioned<T>(value, versioned)` | Version-select then structurally validate a value without touching the filesystem |
| `read_versioned_json_result<T>(path, versioned)` | Read one versioned JSON artifact, preserving source bytes on `raw` |
| `discriminated_spec(discriminant, families)` | Build a tagged-event decode spec from a discriminant field + per-family versioned contracts |
| `decode_event(value, spec)` | Decode one record into a recognized typed event or an explicit `unknown` envelope |
| `read_typed_events_page_result(path, spec, options?)` | Read one bounded page of tagged JSONL events, decoding each record |

Unknown future event families flow through the `unknown` envelope
(`{recognized: false, tag, value}`) — a distinct typed value that names the
unrecognized discriminant and preserves the whole record, never a raw-dict
fallback. Artifacts written before typing carry no version field; set
`absent_version` so they read as their original (legacy) version rather than
failing `version_mismatch`.

### std/agent/artifacts

Typed contracts, descriptors, and readers for the durable artifacts a Harn
agent run leaves on disk: `agent-result.json`, the
`agent-llm/llm_transcript.jsonl` observability event stream, and the transcript
context derived from it. Types mirror the runtime's serialized shapes
field-for-field, typing deterministic runtime facts (ids, timestamps, stop
reasons, token/cost counts) distinctly from provider/model-authored prose
(assistant text, thinking, the system prompt). Each type's fields are annotated
GUARANTEED / OPTIONAL / MODEL / REDACTED / UNSTABLE in the module source.

| Function | Description |
|---|---|
| `agent_result_contract()` / `agent_result_descriptor(name?)` | Versioned contract + descriptor over the legacy-compatible `AgentResultArtifact` shape for `agent-result.json` |
| `read_agent_result_result(path)` | Read a durable agent-result artifact, keeping all six failure kinds distinct |
| `transcript_event_spec()` | Discriminated decode spec for every stable `llm_transcript.jsonl` event family |
| `read_transcript_events_page_result(path, options?)` | Read one bounded page of typed transcript events |
| `transcript_context_contract()` / `read_transcript_context_result(path, options?)` | Contract + bounded fold reconstructing provider/model/system context |
| `agent_transcript_path(run, name?)` | Resolve the standard `agent-llm/llm_transcript.jsonl` path for a run |

Stable event families: `system_prompt`, `context_manifest`, `tool_schemas`, `routing_decision`,
`provider_call_request`, `provider_call_response`, `resolved_dispatch`. Legacy
transcripts and results (no `schema_version`) read as version 1; the current
typed contract is version 2.

### std/os

Environment and host diagnostic helpers:

| Function | Description |
|---|---|
| `os_info()` | Return platform, arch, cwd, home/temp dirs, user/host, pid, runtime paths, and TTY status |
| `env_bool(name, fallback?)` | Read a boolean environment variable using common CLI spellings |
| `env_int(name, fallback?)` | Read an integer environment variable |
| `env_list(name, separator?)` | Split a path/list environment variable and drop blanks |
| `require_env(name)` | Return a required env var or throw a clear error |
| `which(binary)` | Resolve an executable on `PATH`, returning `nil` when absent |
| `command_exists(binary)` | Return whether an executable is visible on `PATH` |

Use `std/os` for script-local concerns. Use `std/runtime` when the information
comes from the Harn host contract rather than the ambient operating system.

### std/command

Deterministic command-runner helpers for Harn scripts and harnesses. These use
the same hostlib command runner and artifact reader substrate as model-facing
host tools, but return script-friendly step records with retry bookkeeping and
compact recovery context:

| Function | Description |
|---|---|
| `command_run(harness.tools, spec, options?)` | Run an argv-first command through `harness.tools.run_command` and return normalized success, status, output, artifact, and timing fields |
| `command_json(harness.agent, harness.tools, harness.clock, spec, options?)` | Run a JSON-emitting command and parse stdout by default; use `allow_empty: true` for probe-style nil output or `result: "record"` for structured failures |
| `command_json_step(harness.agent, harness.tools, harness.clock, name, spec, options?)` | Run a normal command step, preserving retry/classify/recovery hooks, and add `json` on success or `parse_error`/`error` on malformed output |
| `command_try(harness.agent, harness.tools, harness.clock, attempts, options?)` | Try ordered equivalent probes until one returns an ok value, recording `attempts`, `fallback_index`, and `fallback_total` |
| `command_wait_for_output(harness.tools, locator, pattern, options?)` | Wait without polling until selected background stdout, stderr, or combined output matches a literal or regex, the process exits, or `timeout_ms` expires |
| `command_output_range(harness.tools, locator, options?)` | Range-read a command output artifact by command result, `command_id`, `handle_id`, or artifact path |
| `command_output_tail(harness.tools, locator, options?)` | Read the last bytes of a command output artifact without calculating offsets |
| `command_step(harness.tools, harness.clock, name, spec, options?)` | Run one named command and return a normalized step record with artifacts, tail text, optional classification, optional recovery hint, and attempts |
| `command_step_with_retry(name, spec, options?)` | Explicit alias for `command_step` with a retry policy in options |
| `command_steps_append(steps, name, spec, options?)` | Run a step, append it to a step list, and return `{steps, step, success, status, exit_code}` |
| `command_steps_failed(steps, options?)` | Return whether any step failed and was not caller-marked recovered |
| `command_last_failed_step(steps, options?)` | Return the last unrecovered failed step, or `nil` |
| `command_step_ref(step, options?)` | Return compact agent/recovery context with command identity, status, artifacts, classification, recovery hint, and capped tail |
| `argv_label(argv)` | Render argv parts as a stable space-separated label for logs |
| `shell_command_from_argv(argv)` | Render argv as safe shell text, unwrapping shell wrappers such as `["bash", "-lc", "cmd"]` to the script payload |
| `shell_command_from_value(value)` | Normalize string, argv-list, or dict-shaped provider command values into shell text using `argv`, `command`, or `cmd` fields |
| `command_output_text(result, stream?)` | Extract stdout, stderr, combined output, tail, or failure tail from a command result |
| `command_failure_text(result, options?)` | Render a compact failure block with status, exit code, effective cwd, and capped stdout/stderr |
| `command_result_ok(stdout?, extra?)` | Build a normalized success result for tests and harness adapters |
| `command_result_fail(exit_code?, stderr?, extra?)` | Build a normalized failure result for tests and harness adapters |

If the OS cannot start the requested program, `command_run` throws
`{error: "io_error", kind, message, category: "environment", operation:
"process_spawn", requested_cwd, cwd}`. `requested_cwd` is `nil` when the
caller omitted it; `cwd` is always the normalized effective directory where
the spawn was attempted. A missing executable has `kind: "not_found"` in both
sandboxed and unsandboxed execution. Other spawn failures and ordinary nonzero
exit results remain distinct.

`CommandResult` names the common normalized result fields,
`CommandOutputMatch` names the matched range and latest command state, and
`CommandStepReceipt` names the portable command-step fields used by durable
audit and recovery artifacts. Host-backed `CommandResult.cwd` records the
canonical effective directory even when the caller did not pass `cwd`;
structured `command_json(harness.agent, harness.tools, harness.clock, ..., {result: "record"})` failures carry the same
field. All preserve additional adapter-owned fields.

Readiness checks start with `{background: true}`, wait on
`command_wait_for_output`, and use `defer` with `command_cancel` so the process
group has one clear owner:

```harn,ignore
import {
  command_cancel, command_run, command_wait_for_output,
} from "std/command"

const server = command_run(
  harness.tools, ["my-server"], {background: true},
)
defer { command_cancel(server, {wait_result_ms: 5000}) }
const ready = command_wait_for_output(
  harness.tools, server, "ready", {timeout_ms: 10000},
)
if !ready.matched { throw "server exited or timed out before readiness" }
```

Set `source` to `stdout`, `stderr`, or `combined` (the default), `regex: true`
for a regular expression, and `from_offset` to resume from a prior
`next_offset`. Match ranges are byte offsets in the selected source. Matching
does not stop the command.

`spec` is either an argv list, such as `["git", "status", "--short"]`, or a
dict with `argv`, `cwd`, `env`, `env_mode`, `stdin`, `timeout_ms`, `capture`,
and `max_inline_bytes`. Shell execution is enabled only when the spec
explicitly sets `{mode: "shell", command: "..."}`. Shell mode uses the host
default shell unless the spec or options provide `shell` or `shell_id`.

Retry and classification stay generic. Harnesses provide domain-specific
closures instead of teaching stdlib about a release, repository, package
manager, or host:

```harn,ignore
import { command_step } from "std/command"

const step = command_step(
  harness.tools, harness.clock, "verify package",
  ["cargo", "test", "-p", "harn-vm"], {
  cwd: repo_root,
  capture: {max_inline_bytes: 12000},
  tail_bytes: 8000,
  retry: {
    max_attempts: 2,
    delay_ms: 0,
    should_retry: { step, _attempt -> return step.exit_code == 101 },
  },
  classify: { step ->
    if contains(step.failure_tail ?? "", "permission denied") {
      return {kind: "permission", retryable: false}
    }
    return nil
  },
  recovery_hint: { step ->
    if step?.classification?.kind == "permission" {
      return "Check credentials or filesystem permissions, then rerun"
        + " the same step."
    }
    return nil
  },
})
```

Use `command_json` when the command's stdout is expected to be one JSON
document. Non-zero exits, empty output, and malformed JSON throw by default; set
`result: "record"` to receive `{ok:false,error,step}` instead. The JSON layer
delegates retries and recovery metadata to `command_json_step`:

```harn,ignore
import { command_json, command_json_step } from "std/command"

const repo = command_json(
  harness.agent, harness.tools, harness.clock,
  ["gh", "api", "repos/burin-labs/harn"], {
  timeout_ms: 10000,
  capture: {max_inline_bytes: 65536},
})

const probe = command_json_step(
  harness.agent, harness.tools, harness.clock, "read repo metadata",
  ["gh", "api", "repos/burin-labs/harn"], {
  retry: {max_attempts: 2, delay_ms: 0},
})
```

Use `command_try` only for ordered equivalent probes, such as a structured
connector followed by a CLI JSON fallback. It does not retry, route workflows, or
know about providers:

```harn,ignore
import { command_json, command_try } from "std/command"

const repo = command_try(harness.agent, harness.tools, harness.clock,
  [
    {
      source: "connector",
      run: fn() { return repos_get("burin-labs", "harn") },
    },
    {source: "cli", run: fn() {
      return command_json(
        harness.agent, harness.tools, harness.clock,
        ["gh", "api", "repos/burin-labs/harn"],
      )
    }},
  ],
  {
    normalize: { value, source ->
      return {source: source, name: value.name}
    }
  },
)
```

### std/gha

GitHub Actions workflow command helpers. The render-only functions are useful
in any terminal; the write helpers append to the file paths GitHub exposes in
`$GITHUB_OUTPUT`, `$GITHUB_ENV`, and `$GITHUB_STEP_SUMMARY`, or to an explicit
path supplied by tests:

| Function | Description |
|---|---|
| `gha_escape_data(value)` | Escape `%`, CR, and LF for workflow command data |
| `gha_escape_property(value)` | Escape workflow command property text |
| `gha_annotation(kind, message, options?)` | Build a `::notice`, `::warning`, or `::error` annotation line |
| `gha_notice(message, options?)` / `gha_warning(message, options?)` / `gha_error(message, options?)` | Print an annotation to stdout |
| `gha_env_block(name, value, delimiter?)` | Build a multiline-safe environment/output block |
| `gha_write_output(name, value, path?)` | Append one value to `$GITHUB_OUTPUT` or `path`; returns `false` outside Actions when no path is available |
| `gha_write_env(name, value, path?)` | Append one value to `$GITHUB_ENV` or `path` |
| `gha_append_summary(markdown, path?)` | Append Markdown to `$GITHUB_STEP_SUMMARY` or `path`, ensuring a trailing newline |

```harn
import { gha_annotation, gha_write_output } from "std/gha"

harness.stdio.log(gha_annotation("warning", "line1\nline2", {
  file: "src/main.rs",
  line: 7,
  title: "Heads up",
}))
gha_write_output("release_tag", "v1.2.3")
```

### std/tui

Terminal presentation and picker helpers for interactive scripts:

| Function | Description |
|---|---|
| `page(opts)` | Show a text or markdown artifact through `$PAGER` when stdout is a TTY; otherwise print the full artifact and footer. Returns `{ok, paged, error?}` |
| `select_from(items, opts?)` | Show a picker over `items` and return `{ok, value, status}`. Auto-detects `fzf` then `gum choose` and falls back to a numbered `read_line` menu when neither is available or stdout is not a TTY |
| `terminal_width(default_width?)` | Return the current terminal width, falling back to `default_width` or 80 |
| `rule(char?, width?)` | Return `char` repeated to `width`, or to the terminal width when width is omitted |
| `clear()` | Write the ANSI clear-screen sequence to stdout |

`page` accepts `{title?, body, format?, no_pager?, footer?}`. `format` may be
`"text"` or `"markdown"`; markdown currently passes through raw so callers can
choose their own renderer before paging. In pager mode Harn respects `$PAGER`,
adds `-R -F -X` for `less`, falls back to printing when the pager is missing,
and treats `$PAGER=cat` as print-only.

```harn
import { page } from "std/tui"

const result = page({
  title: "Release audit",
  body: audit_markdown,
  format: "markdown",
})
if !result.ok {
  harness.stdio.log(result.error ?? "pager failed")
}
```

`select_from` accepts:

- `prompt` — header shown above the menu / passed to fzf's `--prompt`.
- `display` — `fn(item) -> string` rendering each row. Default reads
  `label` / `title` / `name` / `headline` on dicts and falls through
  to `to_string`.
- `preview` — `fn(item) -> string` rendered into fzf's preview pane.
  The numbered fallback ignores it.
- `multi` — `true` returns a list of items; `false` (default) returns
  one. Numbered fallback accepts comma-separated 1-based indices.
- `default_index` — 0-based index to use when the operator hits Enter
  on an empty line. The numbered menu marks it with a leading `*`.
- `cancel_value` — value returned in `value` when the operator
  cancels (Ctrl+C, Esc, `q`, `/exit`). Defaults to `nil`.
- `prefer_external` — `"auto"` (default), `"fzf"`, `"gum"`, or
  `"none"`. Use `"none"` in scripted tests to force the numbered
  fallback so `mock_stdin` drives the run.
- `header` — extra banner shown above the menu / passed via
  `--header`.

Return shape: `{ok: bool, value: any | list<any>, status: string,
error?: string}`. `status` is `"selected"`, `"cancelled"`, `"eof"`,
or `"error"`. On errors the `error` field carries a one-line message
describing what went wrong; the value is `opts.cancel_value` (or
`nil`).

```harn,ignore
import { select_from } from "std/tui"

const runs = [
  {id: "r-001", label: "r-001 — 2 PRs queued"},
  {id: "r-002", label: "r-002 — verification flake"},
]
const pick = select_from(runs, {prompt: "Pick a run", default_index: 0})
if pick.ok {
  harness.stdio.log("operator chose " + pick.value.id)
}
```

### std/git

Local filesystem git helpers for scripts and narrow agent tool registries. The
module keeps existing receipt-producing `git.*` builtins available through
function wrappers, and adds Harn-level argv-mode helpers for common checkout
operations that should not require a generic shell or `run_command` tool.

| Function | Description |
|---|---|
| `git_run(args, options?)` | Run one argv-mode local git command; `args` omit the leading `git` and inherit git-safe env removal |
| `git_status(repo?)` | Return structured porcelain status using the receipt-producing `git.status` builtin |
| `git_discover(path?)` | Return repository root/git-dir metadata using the receipt-producing discover builtin |
| `git_diff(repo?, selector?)` | Return diff text using the receipt-producing `git.diff` builtin |
| `git_tag_list(repo?, options?)` | Return filtered local tags in structured `data.tags` |
| `git_describe(repo?, options?)` | Return parsed tag, distance, SHA, and dirty state |
| `git_ls_remote(remote, options?)` | Return structured remote ref/OID entries without caller-side stdout parsing |
| `git_current_branch(repo?, options?)` | Return `{success, branch, detached, ...}` for the current checkout |
| `git_log(repo?, options?)` | Return recent commit log output with optional `rev`, `rev_range`, `max_count`, `oneline`, and `paths` |
| `git_switch(branch, repo?, options?)` | Switch to a branch/ref, with optional `create`, `force_create`, `detach`, or `discard_changes` |
| `git_pull_ff_only(repo?, remote?, branch?, options?)` | Run `git pull --ff-only` with optional quiet/prune flags |
| `git_fetch(remote?, repo?, refspecs?)` | Fetch from an existing remote using the receipt-producing `git.fetch` builtin |
| `git_checkout_sync(options)` | Apply an exact-ref checkout transaction with explicit dirty-tree policy and a typed aggregate receipt |
| `git_checkout_plan(options)` | Return the canonical labeled argv plan consumed by execution and fixture runners |
| `git_tool_catalog(options?)` | Return searchable metadata for available git operations |
| `git_find_tool(query, options?)` | Rank git operations for a natural-language query with a deterministic lexical scorer |
| `git_run_tool(operation, args?, options?)` | Dispatch one catalogued git operation by id; mutating operations require `include_mutations: true` |
| `git_tools(registry?, options?)` | Build an agent tool registry from selected granular git helpers |
| `git_toolbox_tools(registry?, options?)` | Build a compact `find_git_tool` / `run_git_tool` registry for small/local models |

Receipt-producing helpers export their canonical contracts, including
`GitReceipt`, the operation-specific `GitStatusReceipt`,
`GitLsRemoteReceipt`, `GitFetchReceipt`, `GitPushReceipt`, worktree receipts,
and their typed `data` records. The receipt `schema` and each specialized
`operation`/`action` literal are stable discriminants. Core audit fields and
operation data are stable for durable orchestration; `command_policy`,
`approval`, captured output text, and new status/category values are diagnostic
and may grow additively. Compose adapters with these aliases instead of copying
their record shapes or widening them to `dict`.

`git_tools(...)` defaults to read-only helpers (`git_status`,
`git_current_branch`, `git_log`, `git_diff`, `git_branch_list`, and
`git_remote_list`). Mutating helpers such as `git_switch` and
`git_pull_ff_only` are only added when requested through `enabled_tools`, so
harnesses can expose just the git operations a model should be allowed to call.
Pass `defer_loading`, `namespace`, or `tool_config` to make the generated tools
participate in the existing Tool Vault / `tool_search` flow.

Automation that must normalize a checkout should use `git_checkout_sync`
instead of maintaining separate command labels and execution branches. The
transaction fetches only the requested base ref, creates a missing local base
from that fetched remote-tracking ref, fast-forwards without a second broad
fetch, and verifies the final branch and clean-tree postconditions.

```harn,ignore
import { git_checkout_sync } from "std/git"

const receipt = git_checkout_sync({
  repo: repo_root,
  remote: "origin",
  base_branch: "main",
  dirty_policy: "stash",
  stash_name: "release-preflight-" + run_id,
})
if !receipt.success {
  throw receipt.failure_kind + ": " + receipt.recovery
}
```

Use `dirty_policy: "refuse"` when mutation must stop on local evidence,
`"preserve"` for cleanup that should leave a dirty checkout untouched, or
`"stash"` with a stable name to continue while retaining manual recovery.
Stashes are deliberately not popped automatically: a later failure cannot
silently mix preserved changes into a different checkout state. Tests can pass
`runner: fn(step) { ... }`; the callback receives the same `kind`, `label`, and
`argv` values used by production.

```harn,ignore
import { git_tools } from "std/git"

const tools = git_tools(nil, {
  repo: repo_root,
  enabled_tools: ["git_status", "git_log", "git_switch"],
  names: {git_log: "release_git_log"},
})
```

For local models that do better with a tiny stable tool surface, use the
two-tool toolbox:

```harn,ignore
const tools = git_toolbox_tools(nil, {
  repo: repo_root,
  include_mutations: true,
})
```

### std/connectors/github

Typed facade for the package-backed GitHub connector. Provider-specific HTTP,
GraphQL, token rotation, and optional `gh` auth fallback live in the
`harn-github-connector` package; this module keeps scripts on stable Harn helper
names and normalized result envelopes.

| Function | Description |
|---|---|
| `github_slug_from_remote(url)` | Parse common GitHub SSH/HTTPS remote URLs into `owner/repo`, or `nil` when the URL is not GitHub |
| `github_repo(repo, name?)` | Normalize an `owner/repo` slug, GitHub remote URL, repo dict, or owner+repo pair |
| `pr_view(repo, pull_number, options?)` | Return the connector's canonical typed PR view |
| `pr_checks(repo, pull_number, options?)` | Return normalized check runs and aggregate state |
| `pr_edit(repo, pull_number, edits, options?)` | Update the closed PR edit field set through the typed connector method |
| `release_view(repo, tag?, options?)` | Return a typed release envelope by exact tag, or the latest release |
| `workflow_dispatch(repo, workflow_id, ref?, inputs?, options?)` | Dispatch a `workflow_dispatch` workflow without shelling out |
| `workflow_runs(repo, query?, options?)` | List repository or workflow-scoped Actions runs with query filters separate from transport options |
| `workflow_run(repo, run_id, options?)` | Fetch one Actions run |
| `workflow_run_jobs(repo, run_id, query?, options?)` | Fetch one page of jobs for an exact Actions run |
| `workflow_run_cancel(repo, run_id, options?)` | Request cancellation of an exact Actions run |
| `github_actions_exact_proof(request, runs, jobs_page)` | Reduce complete run/job evidence into one fail-closed exact-SHA success proof |
| `read_file_at_ref(repo, path, ref?, options?)` | Read decoded repository file text at a ref |
| `latest_release(repo, options?)` | Return a stable latest-release envelope with `tag_name` and `asset_names` |
| `release_assets(repo, release_id?, options?)` | Return a stable release-assets envelope |
| `enable_auto_merge(repo, pull_number, options?)` | Enable PR auto-merge and return `{ok, state, strategy, ...}` |
| `close_pr(repo, pull_number, comment?, options?)` | Optionally comment, then close a PR through the issues endpoint |
| `api_call(path, method, body?, options?)` | Raw GitHub REST escape hatch when no typed helper exists |

The pure-Harn connector package also exports matching owner/repo helper names
such as `actions_workflow_dispatch(...)`, `repos_get_text(...)`,
`github_latest_release(...)`, and `github_close_pr(...)`; the stdlib facade
includes those aliases so release scripts can move between stdlib and package
imports without changing call sites.

### std/review

Typed review helpers that pair with the global `self_review(...)` builtin:

| Function | Description |
|---|---|
| `review_rubrics()` | Return the built-in rubric library as a dict keyed by preset name |
| `review_rubric(name)` | Return one rubric preset body, or `nil` when the preset is unknown |

Type aliases:

- `ReviewFinding`
- `ReviewRound`
- `ReviewResult`

### std/llm/ensemble

LLM ensemble helpers:

| Function | Description |
|---|---|
| `debate(opts)` | Run a multi-debater LLM debate. Pass `adaptive_stop: true` to stop after two consecutive stable rounds when each debater's consecutive-round drift is below `stability_threshold` (default `0.15`) and emit `debate_stability_short_circuit` on `llm.ensemble.debate` |

### std/project

Project metadata helpers plus deterministic project evidence scanning:

| Function | Description |
|---|---|
| `metadata_namespace(harness.project, dir, namespace)` | Read resolved metadata for a namespace, defaulting to `{}` |
| `metadata_local_namespace(harness.project, dir, namespace)` | Read only the namespace data stored directly on a directory |
| `project_inventory(harness.project, namespace?)` | Return `{entries, status}` for metadata-backed project state |
| `project_root_package(harness.fs, harness.project)` | Infer the repository's root package/module name from common manifests |
| `harness.project.fingerprint(path?)` | Return the normalized shallow repo profile used by higher-level personas |
| `project_context_profile(harness.project, path, options?)` | Resolve project signals into prompt fragments, skills, tool groups, MCP preset candidates, and token-delta metadata |
| `project_scan(harness.project, path, options?)` | Scan a directory for deterministic L0/L1 evidence |
| `project_enrich(harness.project, path, options)` | Run caller-owned L2 enrichment over bounded project context with schema validation and caching |
| `project_scan_tree(harness.project, path, options?)` | Walk subdirectories and return a `{rel_path: evidence}` map |
| `project_enrich(harness.project, path, options?)` | Run a structured per-directory L2 enrichment with caller-owned prompt/schema |
| `project_deep_scan(harness.project, harness.clock, path, options?)` | Build or refresh a cached per-directory evidence tree backed by metadata namespaces |
| `project_deep_scan_status(harness.project, namespace, path?)` | Return the last deep-scan status for a namespace/scope |
| `project_catalog(harness.project)` | Return the built-in anchor/lockfile catalog used by `project_scan(harness.project, ...)` |
| `project_scan_paths(harness.project, path, options?)` | Return only the keys from `project_scan_tree(harness.project, ...)` |
| `project_stale(harness.project, namespace?)` | Return the stale summary from `metadata_status(...)` |
| `project_stale_dirs(harness.project, namespace?)` | Return the tier1+tier2 stale directory list |
| `project_requires_refresh(harness.project, namespace?)` | Return `true` when stale or missing hashes require refresh |

Host-specific editor, git, diagnostics, learning, and filesystem/edit helpers
should live in host-side `.harn` libraries whose public APIs accept nominal
Harness handles. The host implements those registered capabilities; ordinary
libraries do not call the privileged `host_call(...)` wire.

### std/agents

Workflow helpers built on transcripts and `agent_loop`:

| Function | Description |
|---|---|
| `workflow(config)` | Create a workflow config |
| `action_graph(raw, options?)` | Normalize planner output into a canonical action-graph envelope |
| `action_graph_batches(graph, completed?)` | Compute dependency-ready action batches grouped by phase and tool class |
| `action_graph_render(graph)` | Render a human-readable markdown summary of an action graph |
| `action_graph_flow(graph, config?)` | Convert an action graph into a typed workflow graph |
| `action_graph_run(task, graph, config?, overrides?)` | Execute an action graph through the shared workflow runtime |
| `task_run(task, flow, overrides?)` | Run an act/verify/repair workflow |
| `workflow_result_text(result)` | Extract a visible text result from an LLM call, workflow wrapper, or ad hoc payload |
| `workflow_result_run(obs, task, workflow_name, result, artifacts?, options?)` | Normalize an ad hoc result into a reusable run record with the active execution identity |
| `workflow_result_persist(fs, obs, task, workflow_name, result, artifacts?, options?)` | Persist an ad hoc result as an execution-identified run record without going through `workflow_execute` |
| `handoff_artifact(value)` | Wrap a typed handoff payload as a normal workflow artifact without transferring raw transcript history |
| `workflow_session(prev)` | Normalize a task result or transcript into a reusable session object |
| `workflow_session_new(metadata?)` | Create a new empty workflow session |
| `workflow_session_restore(run_or_path)` | Restore a session from a run record or persisted run path |
| `workflow_session_fork(prev)` | Fork a session transcript and mark it `forked` |
| `workflow_session_archive(prev)` | Archive a session transcript |
| `workflow_session_resume(prev)` | Resume an archived session transcript |
| `workflow_session_compact(prev, options?)` | Summarize/compact a session transcript in place |
| `workflow_session_reset(prev, carry_summary)` | Reset a session transcript, optionally carrying summary, while preserving `workflow_id` |
| `continue_as_new(prev, options?)` | Advance workflow generation and return a reset session that keeps the same `workflow_id` |
| `workflow_session_persist(prev, path?)` | Persist the session run record and attach the saved path |
| `workflow_continue(prev, task, flow, overrides?)` | Continue from an existing transcript |
| `workflow_compact(prev, options?)` | Summarize and compact a transcript |
| `workflow_reset(prev, carry_summary)` | Reset or summarize-then-reset a workflow transcript |
| `worker_request(worker)` | Return a worker handle's immutable original request payload |
| `worker_result(worker)` | Return a worker handle/result payload or worker-result artifact payload |
| `worker_provenance(worker)` | Return normalized worker provenance fields |
| `worker_research_questions(worker)` | Return the worker's canonical `research_questions` list |
| `worker_action_items(worker)` | Return the worker's canonical `action_items` list |
| `worker_workflow_stages(worker)` | Return the worker's canonical `workflow_stages` list |
| `worker_verification_steps(worker)` | Return the worker's canonical `verification_steps` list |

`workflow_session(...)` returns a normalized session dict that includes the
current transcript, message count, summary, persisted run metadata,
`workflow_id` when one is available, and a `usage` object when the source run
captured LLM totals:
`{input_tokens, output_tokens, total_duration_ms, call_count}`.

For background or delegated execution, use the worker lifecycle builtins
(`spawn_agent`, `send_input`, `resume_agent`, `wait_agent`, `close_agent`, `list_agents`)
directly from the runtime, or the `worker_*` helpers above when you need the
normalized request/provenance views.

### std/workflow/patterns

Small deterministic workflow recipes:

| Function | Description |
|---|---|
| `workflow_self_verifying_graph(config?)` | Build an `act -> verify` graph |
| `workflow_command_verify_graph(config?)` | Build an `implement -> verify -> repair -> verify` graph |
| `workflow_verification_only_graph(config?)` | Build a graph with only a verifier node |
| `workflow_failover(config)` | Run typed failover over opaque route handles with caller-owned evaluation/classification callbacks |

### std/agent/contracts

Typed results and producer-owned terminal outcomes for the agent plane:

| Type | Description |
|---|---|
| `AgentResult` | Result of `agent_loop` and `HarnessAgent.session_finalize`, including LLM/tool summaries and the terminal outcome |
| `AgentTerminalOutcome` | Stable terminal decision with the precise `kind` plus canonical `lifecycle_state` and `run_record_status` projections |
| `AgentTerminalKind` | Closed natural, policy, cancellation, error, suspension, and unknown vocabulary; `policy_no_progress` identifies text-only nudge exhaustion and `policy_thrash` identifies a repeated-action stall hard stop |

Consumers branch on `AgentResult.terminal.kind`. The transport `status`,
`stop_reason`, and raw payload fields remain available for diagnostics but do
not own completion classification. Persistence adapters store
`AgentResult.terminal.run_record_status` instead of interpreting `kind` again.

### std/agent/options (agent specification and model-option resolution)

Model-option resolution helpers (moved here from the removed
`std/agent/stack` in 0.10 — see [Migrating to 0.10](./migrations/v0.10.md)):

`AgentSpec` is the flat public loop contract. Its six named components are
`AgentModelSpec`, `AgentExecutionSpec`, `AgentCapabilitySpec`,
`AgentLifecycleSpec`, `AgentContextSpec`, and `AgentObservabilitySpec`; use a
component type when an interface should accept only that part of the spec.

| Function | Description |
|---|---|
| `agent_model_options(config?)` | Resolve explicit options, role/env provider-model overrides, model-aware option packs, tool format, and capability cleanup |
| `agent_sanitize_model_options(options?, policy?)` | Strip unsupported reasoning and prompt-cache fields before provider dispatch; `{mode: "healthcheck"}` also drops deliberate-reasoning knobs for probes |

### std/agent/stream

Private-span filtering and terminal envelopes for streaming chat UIs:

| Function | Description |
|---|---|
| `agent_private_text_filter(text, config?)` | Strip complete or unterminated private tagged spans from a full text value |
| `agent_private_stream_state(config?)` | Create state for split-safe streaming private-span filtering |
| `agent_private_stream_delta(state?, delta?, config?)` | Fold one provider delta and return a safe `visible_delta` |
| `agent_private_stream_finish(state?, config?)` | Flush the final safe suffix and report withheld/unterminated private state |
| `agent_stream_call(prompt, system?, options?)` | Wrap `harness.llm.stream_call` with private filtering, callbacks, and terminal status envelopes |

### std/agent/progress

Agent progress events for hosts that render live agent status:

| Function | Description |
|---|---|
| `agent_progress(input)` | Emit a `progress_reported` event for the current agent session; `input` requires `message` or `entries`, with optional `replace` and `metadata` |
| `agent_progress_entry_schema()` | Return the schema for one normalized progress entry |
| `agent_progress_event_report(value, apply_defaults?)` | Validate a captured `progress_reported` event and return a structured schema report |
| `agent_progress_event_schema()` | Return the schema for captured `progress_reported` events |
| `agent_progress_event_value(value, apply_defaults?)` | Validate a captured progress event and return it, throwing on failure |
| `agent_progress_payload_report(value, apply_defaults?)` | Validate a normalized progress payload and return a structured schema report |
| `agent_progress_payload_schema()` | Return the schema for normalized progress payloads |
| `agent_progress_payload_value(value, apply_defaults?)` | Validate a normalized progress payload and return it, throwing on failure |
| `agent_progress_tool(registry?, options?)` | Add a handler-backed progress tool to a registry; options may set `name`, `description`, and `system_prompt_nudge` |
| `agent_progress_tool_config_normalize(config?)` | Validate progress-tool config and apply the default name and description |
| `agent_progress_tool_config_report(value, apply_defaults?)` | Validate progress-tool config and return a structured schema report |
| `agent_progress_tool_config_schema()` | Return the schema for progress-tool config dictionaries |
| `agent_progress_tool_config_value(value, apply_defaults?)` | Validate progress-tool config and return it, throwing on failure |

### std/agent/scratchpad

Live, session-local working memory for `agent_loop`:

| Function | Description |
|---|---|
| `agent_scratchpad_options(opts?)` | Normalize the public `agent_loop(harness, ..., {scratchpad})` option |
| `agent_scratchpad_init(session, opts)` | Initialize the session scratchpad from the task or `scratchpad.initial` |
| `agent_scratchpad_recitation_fragment(session, opts)` | Return the prompt-tail `_system_fragments` entry that recites the current scratchpad |
| `agent_scratchpad_reorganize(session, opts, iteration, context?)` | Run the structured reorganization pass, validate source refs, and persist the compacted scratchpad |
| `agent_scratchpad_reorganize_if_due(session, opts, iteration_index, context?)` | Apply the configured reorganization cadence after a completed turn |

### std/agent/user

Simulated-user helpers for eval harnesses:

| Function | Description |
|---|---|
| `agentic_user(task_or_config, behavior?, tools?, model?, options?)` | Return an answerer that uses an LLM, optionally with read tools, to stand in for the harness user |
| `scripted_user(script, options?)` | Return a deterministic fixture answerer with string or `{match, reply/action}` script entries |
| `fixture_user(script, options?)` | Alias for `scripted_user(...)` |
| `simulated_user_respond(answerer, payload?)` | Ask an answerer for `{action, message?, reason?}` |
| `user_tools(answerer, registry?, options?)` | Add an `ask_user` tool backed by a simulated answerer |
| `simulated_user_post_turn(answerer, options?)` | Build a `post_turn_callback` that answers plain-text clarification questions |
| `simulated_user_status(answerer)` | Return public state such as reply and LLM-call counts |
| `simulated_user_read_tools(registry?, options?)` | Alias for read-only host research tools appropriate for an agentic simulated user |

### std/agent/fact

Typed fact envelopes over `std/memory` for cross-session assertions:

| Function | Description |
|---|---|
| `fact(input, options?)` | Normalize and validate a `harn.fact.v1` envelope with kind, claim, evidence, confidence, provenance, optional `valid_until`, and `asserted_at` |
| `fact_id(kind, claim, evidence?, provenance?)` | Build a stable fact id from the normalized assertion fields |
| `fact_key(fact)` | Return the reserved `fact:<kind>:<id>` memory key |
| `fact_tags(fact, tags?)` | Return canonical fact memory tags, generic and kind-scoped evidence tags, and caller tags |
| `store_fact(input, options?)` | Store a typed fact as `MemoryRecord.value`; `options.namespace` or `options.scope` selects the memory namespace |
| `recall_facts(query, kind?, min_confidence?, scope?)` | Recall facts by memory query, kind, and minimum confidence |
| `invalidate_facts(harness.memory, predicate, scope?)` | Append memory tombstones for matching facts by id, key, kind, claim/query, tags, or evidence |

### std/agent/probe

Run a small snippet and persist the verified outcome as a `harn.fact.v1`
Observation so future sessions recall the answer instead of re-guessing.
MVP supports `eval` (shell or `harn run`) and `typecheck` (`harn check
--json`); `test` and `inspect` are reserved and currently return an
`unknown` outcome.

| Function | Description |
|---|---|
| `probe(kind, body, options?)` | Run a snippet of the given kind, capture stdout/stderr/exit code, and auto-record the outcome as an Observation fact |
| `probe_eval(body, options?)` | Convenience for `probe("eval", ...)` — shell by default, `options.lang = "harn"` runs the body via `harn run` |
| `probe_typecheck(body, options?)` | Convenience for `probe("typecheck", ...)` — writes the fragment to a temp file and invokes `harn check --json` |

### std/handoffs

Harn-owned route policy for typed handoff artifacts:

| Function | Description |
|---|---|
| `handoff_route_select(handoff, routes?, context?)` | Select the first matching handoff route decision from explicit routes or loaded `[[handoff_routes]]` |
| `handoff_routed(payload, routes?, context?)` | Compose and normalize a handoff with the selected target and route decision embedded |
| `handoff_dispatch(handoff, decision?, options?)` | Persist the selected route, enqueue a target-specific dispatch record in the EventLog, and optionally call a local dispatcher hook |

### std/worktree

Helpers for isolated git worktree execution built on explicit
`HarnessProcess` authority:

| Function | Description |
|---|---|
| `worktree_default_path(repo, name)` | Return the default Harn-owned worktree path beneath `repo` |
| `worktree_create(harness.process, repo, name, base_ref, path?)` | Create or reset a worktree branch at a target path |
| `worktree_remove(harness.process, repo, path, force)` | Remove a worktree from the parent repo |
| `worktree_status(harness.process, path)` | Run `git status --short --branch` in the worktree |
| `worktree_diff(harness.process, path, base_ref?)` | Render diff output for the worktree |
| `worktree_shell(harness.process, path, script)` | Run an arbitrary shell command inside the worktree |

### std/personas/bulletins

Transparent profile bulletin envelopes for durable persona facts. See
[Profile bulletins](./personas/profile-bulletins.md) for the full envelope and
review semantics.

| Function | Description |
|---|---|
| `bulletin_propose(input, options?)` | Build a `harn.profile_bulletin.v1` proposal with stable id, evidence, source, and privacy fields |
| `bulletin_id(scope, scope_key, subject, assertion, persona?)` | Compute the stable bulletin id for matching/dedupe |
| `bulletin_validate(bulletin)` | Throw if the envelope is missing required fields or has out-of-range confidence |
| `bulletin_emit(input, options?)` | Append a `proposed` bulletin to `personas.bulletins.proposed` and return an emit receipt |
| `bulletin_decide(bulletin, action, options?)` | Build a typed `harn.profile_bulletin_decision.v1` envelope without emitting |
| `bulletin_emit_decision(decision, options?)` | Append a decision envelope to `personas.bulletins.decisions` |
| `bulletin_accept` / `bulletin_reject` / `bulletin_expire` / `bulletin_supersede` | Decide-and-emit shorthands |
| `bulletin_apply_decisions(bulletins, decisions)` | Project the latest decision per id onto bulletins |
| `bulletin_partition(bulletins)` | Group bulletins by status |
| `bulletin_active(bulletins, now?)` | Return `accepted` bulletins still within their TTL |
| `bulletin_render_for_prompt(bulletins, options?)` | Render an audit-friendly prompt block distinguishing accepted facts from proposals |
| `bulletin_dedupe(bulletins)` | Drop duplicate bulletins by stable id |

### std/personas/prelude

Reusable orchestration primitives for durable persona workflows. See
[Persona prelude](./personas/prelude.md) for the complete API.

| Function | Description |
|---|---|
| `verify_then_act(verifier, actor, options?)` | Run an actor only after an ok-shaped verifier result |
| `bounded_loop(state_init, step_fn, options?)` | Run a state loop with iteration, duration, and progress bounds |
| `cheap_classify_then_escalate(input, cheap_model, escalate_model, escalation_predicate, options?)` | Use a cheap classifier first and escalate ambiguous or failed cases |
| `parallel_sweep_with_circuit_breaker(items, step_fn, options?)` | Process a bounded parallel sweep and stop scheduling after too many failures |
| `with_audit_receipt(step_fn, options?)` | Wrap a step so success and failure both produce a receipt envelope |
| `with_approval_gate(approval_kind, step_fn, options?)` | Require a replayed or HITL approval before running a step |

### Selective imports

Import specific functions from any module:

```harn
import { extract_paths, parse_cells } from "std/text"
import std::personas::prelude::{verify_then_act}
```

### Namespace imports

Bind a module's public surface under a single alias without flattening
members into the caller scope:

```harn,ignore
import * as text from "std/text"

pipeline default(harness: Harness) {
  __io_println(text.truncate_middle(long_text, 80))
}
```

- Only the alias is introduced locally (`text` above). Member names like
  `truncate_middle` are **not** available as bare identifiers.
- Access exports with `alias.member` / `alias.member(...)`. This is a
  namespace object (a closed dict with a `"_namespace"` marker), not a
  receiver-method call on an arbitrary value — unknown members fail at
  check time with the module path and nearby export suggestions.
- Selective, wildcard, and namespace imports may appear together in the
  same file.

For a private namespace used only through statically named members, the
compiler records those members in import bytecode and cached module metadata.
The runtime still loads the complete target module and runs its initialization
exactly once; it narrows only the namespace dict bound in the importing module.
Returning, passing, indexing, mutating, or publicly re-exporting the namespace
keeps the complete dict. This optimization does not change source behavior.

`pub import * as ns from "module"` re-exports the **alias namespace
object** on this module's public surface. It does **not** flatten the
target's members into the facade (contrast `pub import "module"`).

Namespace imports are not receiver methods on handles or class-style
APIs. They are a compile-time module binding: the alias names a closed
export set from the module graph. Prefer a structural `harn codemod`
rule when migrating a family of long prefixed calls
(`run_artifact_read_json_contract_result(...)`) to shorter names under a
namespace import once a module publishes those shorter exports.

### Exporting type aliases

`pub type` exports a type alias so importers can use it in annotations
and schema-as-type positions alongside the functions that produce it:

```harn,ignore
// targets.harn
pub type SmartTarget = {name: string, score: int}

pub fn pick(name: string) -> SmartTarget {
  return {name: name, score: 1}
}
```

```harn,ignore
import { SmartTarget, pick } from "./targets"

fn describe(t: SmartTarget) -> string {
  return t.name
}
```

Type annotations are erased from ordinary values, but an imported public alias
materializes a schema when used with `schema_of`, a schema guard, or a
schema-valued option. Reflection resolves nested imports and behaves the same
for filesystem and embedded standard-library modules. A type alias without
`pub` stays module-private; importing it is an error.

### Public re-exports

A facade module can re-publish symbols from other modules as part of its
own public surface by prefixing any import with `pub`:

```harn,ignore
// Facade that exposes a curated public API while the implementation
// lives in shard files.
pub import { enrich_source_batch, enrich_source_dir } from "enrich-source"
pub import { enrich_test_batch, enrich_test_dir } from "enrich-test"
pub import "shared"
```

- `pub import "module"` re-exports every public name from the target
  module — the wildcard form.
- `pub import { name } from "module"` re-exports only the listed names.
- `pub import * as alias from "module"` re-exports the namespace alias
  object itself (not the flattened members).

Re-exports compose: a facade can re-export from another facade and the
chain is followed transitively. `harn check` flags re-export conflicts
when two `pub import`s contribute the same name from different sources,
or when a re-exported name collides with a local `pub` declaration.
Editor go-to-definition follows re-export chains to the originating
declaration.

Re-exported functions keep their full parameter and return-type contracts.
Callers can pass a structurally valid record without importing the function's
signature-only aliases. A private alias remains private; the checker uses it
without adding its name to the facade's public API. See
[Type annotations](spec/language/19-type-annotations.md) for closed-record
argument rules.

Plain `import` (without `pub`) remains private — the imported names are
visible only inside the importing file.

### Public structs and enums

Public structs and enums cross the same import boundary as public functions:
the checker and runtime use one declaration contract, so a name accepted by
`harn check` is also available when the importing module executes.

```harn,ignore
// shapes.harn
pub struct Point {
  x: int
  y: int
}

pub enum Outcome {
  Found(point: Point)
  Missing
}
```

```harn,ignore
import { Outcome, Point } from "./shapes"

pipeline default(harness: Harness) {
  const point = Point {x: 3, y: 4}
  const outcome = Outcome.Found(point)
  harness.stdio.log(outcome.variant)
}
```

An imported public struct binds its constructor. An imported public enum binds
a namespace whose members construct the corresponding variants, including
zero-field variants. Private payload types may remain private to the defining
module; only the enum itself needs to be public. Public type aliases and
interfaces remain type-only imports, except that schema-capable aliases may
also be used in schema expression positions.

## Package-root prompt assets

`harness.fs.render_prompt(...)`, the `template.render` host
capability, and `{{ include "..." }}` directives accept two
package-root forms in addition to plain source-relative paths. They
exist to keep prompt-asset references stable across pipeline file
moves — a refactor that relocates the caller no longer breaks the
asset path.

```harn,ignore
harness.fs.render_prompt("@/prompts/tool-examples.harn.prompt", bindings)
harness.fs.render_prompt("@partials/tool-examples.harn.prompt", bindings)
```

Resolution rules:

- **`@/<rel>`** — resolves from the calling file's project root (the
  nearest `harn.toml` ancestor). The resulting absolute path is the
  same regardless of how deep the caller sits in the workspace.
- **`@<alias>/<rel>`** — resolves from a `[asset_roots]` entry in the
  project's `harn.toml`:

  ```toml
  [asset_roots]
  partials = "pipelines/partials"
  prompts  = "pipelines"
  ```

Both forms reject `..` segments and absolute targets so a
package-rooted asset can never escape the project root. Plain (non-`@`)
paths keep the legacy source-relative behavior unchanged — back-compat
is exact.

`{{ include "@/..." }}` is honored inside `.harn.prompt` files too,
so a deeply-included partial can pull in its sibling fragments by the
same stable name regardless of which entry pipeline rendered it.

Stdlib prompt assets use `std/...harn.prompt` paths:

```harn,ignore
harness.fs.render_prompt(
  "std/agent/prompts/tool_contract_text.harn.prompt", {},
)
```

These assets are embedded alongside stdlib modules, cache by stable asset id
and content hash, and use `std://...` template URIs in prompt provenance.
They are the default home for reusable model-facing stdlib prompt prose.

`harn check` resolves `@`-paths during preflight and fails the run
when:

- the calling file has no `harn.toml` ancestor;
- an `@<alias>/...` reference targets an alias that isn't defined in
  `[asset_roots]`;
- the resolved file does not exist.

`harn contracts bundle` records every resolved `@`-path under
`prompt_assets`, so packagers don't need to maintain a separate file
list. The Harn LSP's go-to-definition jumps straight from a literal
`harness.fs.render_prompt("@/...")` argument to the target prompt file.

## Import behavior

Import paths resolve in this order:

1. `std/<module>` from the embedded stdlib
2. Relative to the importing file, with implicit `.harn`
3. Installed packages in the nearest ancestor's leased current generation
4. Package manifest `[exports]` aliases
5. Package directories with `lib.harn`

Packages can publish stable module entry points in `harn.toml`:

```toml
[exports]
capabilities = "runtime/capabilities.harn"
providers = "runtime/providers.harn"
```

With that manifest, `import "acme/capabilities"` resolves to the declared file
inside the current generation's `packages/acme/`. Nested package modules use
the same leased packages root to import siblings without brittle relative
paths.

`harn add`, `harn install`, and `harn lock` prepare an immutable generation
from `harn.lock` and publish it through `.harn/package-current.toml`. Git dependencies must specify `tag`,
`rev`, or `branch`; Harn resolves them to commits, records content hashes, caches
them under the user cache directory, and copies them back into the
workspace as needed. Package dependencies are flattened into the same
generation packages root, so a connector package can import an SDK package
declared in its own `harn.toml` without requiring a sibling checkout.
Directory path dependencies are live-linked when possible and are meant
for local development; git-installed packages cannot publish transitive
path dependencies.

Use registry names for discoverable first-party and community packages:

```bash
harn package search notion
harn package info @burin/notion-sdk
harn add @burin/notion-sdk@1.2.3
```

Registry-name installs resolve through the package index and then write
the same git dependency table as a direct GitHub install. Direct GitHub
refs remain the right choice for private repos, unreleased commits,
temporary pins, and local dogfood before a package is added to the
shared index.

Registry-backed manifest entries can stay semantic instead of lowering to a
git table by using `version`:

```toml
[registry]
url = "./harn-package-index.toml"

[dependencies]
notion-sdk-harn = { version = "^1.2" }
notion = { version = ">=1.2,<2.0", registry_name = "@burin/notion-sdk", package = "notion-sdk-harn" }
```

`harn install` selects the highest unyanked semver version that matches the
range, clones the git tag/rev/branch recorded by the registry index, and pins
the resolved commit plus content hash in `harn.lock`. Frozen/offline installs
reuse that lock entry and the local package cache without querying the
registry again.

Canonical bootstrap for first-party packages:

```bash
cargo install harn-cli
harn init connector-app
cd connector-app
harn add github.com/burin-labs/harn-openapi@v1.2.3
harn add github.com/burin-labs/notion-sdk-harn@v1.2.3
harn add github.com/burin-labs/notion-connector-harn@v1.2.3
harn install --frozen
harn check main.harn
```

Equivalent manifest entries:

```toml
[dependencies]
harn-openapi = { git = "https://github.com/burin-labs/harn-openapi", tag = "v1.2.3" }
notion-sdk-harn = { git = "https://github.com/burin-labs/notion-sdk-harn", tag = "v1.2.3" }
notion-connector-harn = { git = "https://github.com/burin-labs/notion-connector-harn", tag = "v1.2.3" }
```

Installed package code is importable, but package manifests do not
automatically inject host runtime configuration. Runtime tables such as
`[llm]`, `[capabilities]`, `[[hooks]]`, and `[[triggers]]` only come
from the root project's `harn.toml` by default.

1. The imported file is parsed and executed
2. Pipelines in the imported file are registered by name
3. Non-pipeline top-level statements (fn declarations, let bindings) are executed, making their values available
4. Circular imports are detected and skipped (each file is imported at most once)
5. The working directory is temporarily changed to the imported file's directory, so nested imports resolve correctly
6. Source-relative builtins like `harness.fs.render_prompt(...)` inside imported functions resolve
   paths relative to the imported module's directory, not the entry pipeline

## Static cross-module checking

`harn check`, `harn run`, `harn bench`, and the Harn LSP all build a
**module graph** from the entry file that follows `import` statements
transitively, so they share one consistent view of what names are
visible in each module.

When every import in a file resolves, the typechecker treats a call to
an unknown name as an **error** (not a lint warning):

```text
error: call target `helpr` is not defined or imported
```

Resolution is conservative: if any import in the file fails to resolve
(missing file, parse error, nonexistent package), the stricter
cross-module check is turned off for that file and only the normal
builtin/local-declaration check applies. That way one broken import
does not produce a flood of follow-on undefined-name errors.

Go-to-definition in the LSP uses the same graph, so navigation works
across any chain of imports — not just direct ones.

## Import collision detection

If two wildcard imports export a function with the same name, Harn will
report an error at both runtime and during `harn check` preflight:

```text
Import collision: 'helper' is already defined when importing lib/b.harn.
Use selective imports to disambiguate: import { helper } from "..."
```

To resolve collisions, use selective imports to import only the names
you need from each module:

```harn,ignore
import { parse_output } from "lib/a"
import { format_result } from "lib/b"
```

## Pipeline inheritance

Pipelines can extend other pipelines:

```harn
pipeline base(harness: Harness, task) {
  harness.stdio.log("Step 1: setup")
  harness.stdio.log("Step 2: execute")
  harness.stdio.log("Step 3: cleanup")
}

pipeline custom(harness: Harness, task) extends base {
  override setup() {
    harness.stdio.log("Custom setup")
  }
}
```

If the child pipeline has `override` declarations, the parent's body runs
with the overrides applied. If the child has no overrides, the child's body
replaces the parent's entirely.

## Organizing a project

A typical project structure:

```text
my-project/
  main.harn
  lib/
    context.harn      # shared context-gathering functions
    agent.harn        # shared agent utility functions
    helpers.harn      # general-purpose utilities
```

```harn,ignore
// main.harn
import "lib/context"
import "lib/agent"
import "lib/helpers"

pipeline default(harness: Harness, task, project) {
  const ctx = gather_context(task, project)
  const result = run_agent(ctx)
  finalize(result)
}
```

---

## Read next

- [Pick fields from a record](https://harnlang.com/pick.md)
- [Concurrency](https://harnlang.com/concurrency.md)
