Skip to content

The spec

An app's harness is declared in a spec, usually harness.yaml next to the app's code. The spec selects modules and providers, sets the app's controls, and defines its tasks. It is merged with the framework defaults, any profiles it extends (such as the org baseline), and an environment overlay, then validated, before anything runs.

import harnessweaver as hw

harness = hw.load("harness.yaml")                                  # overlay from $HARNESSWEAVER_ENV
harness = hw.load("harness.yaml", env="prod", profile_dirs=[Path("/etc/acme/profiles")])
import { load } from "@harnessweaver/core";

const harness = load("harness.yaml", { env: "prod", profileDirs: ["/etc/acme/profiles"] });

Loading fails with a SpecError subclass if anything is wrong: an unknown key, provider or hook name, a broken lock or bound, a level below the required one, an expression that doesn't compile, or a hook the runner can't reach (RULES R4). Nothing is checked lazily in the middle of a run. harnessweaver spec validate loads a spec exactly the same way.

A minimal spec

apiVersion: harnessweaver/v1
kind: Harness
metadata:
  name: ticket-triage                # lowercase letters, digits, '.', '_' and '-'
  owner: support-platform
extends: ["profile://extraction"]    # optional: profiles merged before this file

models:
  provider: openai.chat
  base_url: http://localhost:4000
  api_key_env: LITELLM_API_KEY
  default: fast
  aliases: { fast: fast-chat }

tasks:
  extract:
    runner: direct
    input: app.models:Message
    output: app.models:TicketSummary
    prompt: extract@1
    budget: { max_model_calls: 3, max_usd: 0.05 }

Everything else has a safe default from the framework's defaults profile: no telemetry export and no content capture, tool manifests required, one repair for invalid output, the cel_lite policy engine allowing by default, in-memory approvals with a 24 hour timeout, and an in-memory state store (Built-in profiles).

Documents

Document kind Allowed top-level keys
App spec (harness.yaml) Harness apiVersion, kind, metadata, extends, and the sections
Profile (built-in, plugin or org) Profile The same, plus locks, bounds and require_level
Environment overlay (harness.<env>.yaml) Overlay (optional) Sections only: no metadata, extends, locks, bounds or require_level
Tool manifest (*.tool.yaml) Tool apiVersion, kind, metadata, spec (Tool manifests)
Eval dataset - suite, task, graders, thresholds, cases (Evals)

apiVersion is harnessweaver/v1 for every document. Any key that isn't listed fails loading, as does an unknown key inside a section (R4).

YAML rules

Specs are read as YAML 1.2 with the core schema, the same way in every language:

  • Only true and false are booleans: yes, no, on and off stay strings.
  • There are no implicit dates or sexagesimal numbers: 2026-01-05 and 1:30 stay strings.
  • 012 is the decimal number 12.
  • A repeated key in a map is an error.

The sections

Section What it configures Reference
metadata The app's name, owner, risk tier and description metadata
extends The profiles merged before this file Layers
plugins In-repo plugins (local) and the namespaces apps may use (allow) plugins
models The model provider, aliases, allowed aliases and prices models, Models
routing Routers: options, a cascade of stages, a default routing, Routing
context Retrieval sources and their governance context, Context and memory
memory The key-value store behind s.memory memory
guardrails Guards per stage: input, context, tool_result, output, stream guardrails, Guardrails
validation Repairs, what happens when they run out, business rules validation, Validation
tools Manifests, the trifecta rule, approval by risk, an optional judge tools, Tools
policy The policy engine and its rules policy, Policy
approvals The approval channel, timeout and default approver role approvals, Approvals
budgets Per-run limits budgets
state The state store, and whether runs are recorded for replay state, State and replay
observability Telemetry export and content capture observability, Observability
evals Eval suites per task, and online sampling evals, Evals
middleware Middleware from plugins, with bands and hooks middleware
tasks Named units of work: runner, types, prompt, model, tools, budget, approvals, guards tasks
prompts Where versioned prompts live prompts
sidecar Where harnessd is, for guards declared where: sidecar sidecar
require_level, locks, bounds Profiles only: the org's required level, locked paths, numeric bounds Layers

Sections that pick a provider

Sections and items that choose a provider with use: keep the provider's own settings next to it: guards, routing stages, context sources, middleware, runners, models, policy, approvals, state, memory, observability and tools.judge. Every key HarnessWeaver doesn't own is passed to the provider, and validated against the provider's config model when the harness loads, so a misspelled setting still fails (R4).

guardrails:
  input:
    - id: pii                  # HarnessWeaver's keys: id, use, action, mode, where, parallel,
      use: pii.regex           #   fail_mode, timeout, enabled
      action: redact
      entities: [EMAIL, PHONE] # pii.regex's own settings
      replacement: "<{entity}>"

Names in use: are <namespace>.<name> for plugin contributions (openai.chat, acme.pii). The core's own providers use bare names (direct, memory) or reserved area names (pii.regex, cel_lite, lexical).

Files next to the spec

Relative paths in a spec are resolved against the directory of the file that names them: tools.manifests globs, prompts.dir, extends file paths, and the file: setting of routing stages and context sources. The sqlite state store's path is the exception: it is relative to the working directory.

my-app/
  harness.yaml            the app spec
  harness.offline.yaml    an overlay, applied with HARNESSWEAVER_ENV=offline
  harness.prod.yaml       another overlay
  prompts/extract@1.md    versioned prompts (prompts.dir)
  tools/*.tool.yaml       tool manifests (tools.manifests)
  evals/golden.yaml       eval datasets (evals.<task>)
  .harness/profiles/      org profiles, if not on HARNESSWEAVER_PROFILE_PATH
  .harness/baselines/     eval baselines, committed

Editor support

The JSON Schemas of complete documents are in contracts/schemas/: harness.schema.json, profile.schema.json, tool-manifest.schema.json and eval-dataset.schema.json. With the YAML language server (VS Code's YAML extension, for example), point a file at one:

# yaml-language-server: $schema=https://raw.githubusercontent.com/Wise-DreamerZ/HarnessWeaver/main/contracts/schemas/harness.schema.json
apiVersion: harnessweaver/v1

Profiles and overlays are partial, so an editor may flag a profile that only tweaks part of a section; the loader is the authority.

Checking a spec

Command What it does
harnessweaver spec validate [SPEC] [-e ENV] Loads the spec with every check the app runs, and prints the layers, tasks and adoption level
harnessweaver spec explain [SPEC] [--path PREFIX] Every effective value, the layer it came from, and its locks and bounds
harnessweaver spec diff BEFORE AFTER Compares two effective specs (values, locks, level) as Markdown, for PR comments; either side can be git:<ref>:<path>
harnessweaver doctor [SPEC] Validation plus advice: the next level, reachable hooks, retry owners, pins, shadow guards, content capture
harnessweaver hooks [SPEC] [--task TASK] The middleware on every hook point, in run order

See the CLI reference.