harness.md Frontmatter Reference

harness.md is the root artifact of every AI Harness project. It is a Markdown file with a YAML frontmatter block: the frontmatter declares the runtime configuration; the body becomes the system prompt.

This page is the exhaustive reference for every field the loader recognizes, the type and default for each, and a worked example for the non-obvious ones.

Versioning note. Every field documented on this page is part of the stable harness configuration surface under SemVer. Fields marked experimental may change; new optional fields may be added in minor releases without breaking existing files.

File shape

---
# YAML frontmatter — runtime configuration
model:
  provider: copilot
  name: gpt-4o
context:
  max_history: 50
delegation:
  max_depth: 2
---

# Markdown body — becomes the system prompt

You are a careful assistant. ...

Rules enforced by the loader (config.LoadMarkdown in config/markdown.go):

  1. The file must start with a --- delimiter line.
  2. The frontmatter must be closed by a second --- on its own line.
  3. The body after the closing delimiter is the system prompt. If empty, no system prompt is set from this file.
  4. Frontmatter is parsed as YAML. Unknown top-level keys are ignored silently — typos in field names produce no error. Use harness validate to confirm the runtime sees what you expect.

harness.md may also be supplied as plain harness.yaml / harness.yml for environments where Markdown is awkward; the schema is identical and no system prompt is read from the file.

Top-level fields

FieldTypeDefaultRequired
modelModelsee belowno
models[Model]emptyno
contextContextsee belowno
tools[Tool]emptyno
tools_policyToolsPolicyno policyno
hooks[Hook]emptyno
delegationDelegationsee belowno
metaMetadisabledno
serveServenoneno
networkNetworkunrestrictedno

The minimal valid frontmatter is an empty block — defaults will fill in a working gpt-4o profile against the GitHub Copilot endpoint, provided GITHUB_TOKEN is set in the environment.


model

The primary completion model. Exactly one model block is active per turn; if models is also set, it becomes a routing table (see models).

model:
  provider: copilot
  name: gpt-4o
  max_tokens: 4096
  temperature: 0.3
  base_url: https://api.githubcopilot.com
  api_key_env: GH_TOKEN
  retry:
    max_retries: 3
    initial_backoff_ms: 250
    max_backoff_ms: 8000
    multiplier: 2.0
FieldTypeDefaultNotes
namestringgpt-4oProvider-specific model identifier. Must be non-empty after defaults.
providerstringopenaiOne of openai, copilot. Drives default base_url selection.
max_tokensint4096Per-completion cap. Must be > 0.
temperaturefloat0.7Must be in [0.0, 2.0].
base_urlstringderived from providerOverride for proxies / Azure OpenAI / local gateways. copilothttps://api.githubcopilot.com; openaihttps://api.openai.com/v1.
api_key_envstringGITHUB_TOKENName of the env var that holds the API key. The harness never reads keys from frontmatter directly.
retryRetryharness defaultsPer-model retry policy for completion errors.

retry

Retry policy applied to model completion calls (not tool calls). All fields optional; absent fields fall back to harness-level defaults.

FieldTypeConstraintNotes
max_retriesint>= 00 disables retries entirely.
initial_backoff_msint>= 0First sleep before retry #1.
max_backoff_msint>= 0Upper bound on the backoff after multiplier expansion.
multiplierfloat>= 0Geometric growth factor between retries.

Retry kicks in for transient completion errors and finish_reason=length truncation (see PR #121). finish_reason=content_filter is a hard error and is not retried.


models

An optional list of additional model profiles available at runtime.

models:
  - name: gpt-4o
    provider: copilot
    api_key_env: GH_TOKEN
    retry:
      max_retries: 3
  - name: gpt-4o-mini
    provider: copilot
    api_key_env: GH_TOKEN

Each entry has the same schema as model. The first entry is the default at boot; sub-agents and tools may switch profiles by name. When models is empty, the single model block is the only profile.


context

Context-window management.

context:
  max_history: 50
  max_tokens: 64000
  system_prompt: ""
FieldTypeDefaultNotes
max_historyint50Max turns retained in the rolling history before compaction.
max_tokensint128000Soft budget for the assembled prompt. Compaction kicks in before this is exceeded.
system_promptstring""Inline system prompt. Overridden by the Markdown body if the file has one (preferred path).

Setting system_prompt in frontmatter is supported for .yaml configs and for tests; in .md files prefer writing the prompt as the body.


tools

Inline tool definitions. Each entry registers one tool with a single harness.md-resident Starlark script. Most projects keep tools as separate artifacts in .harness/tools/<name>.md instead — see Tool Artifact Schema — but the inline form remains supported for small examples and tests.

tools:
  - name: echo
    description: Echo a message back.
    timeout_ms: 1000
    parameters:
      message:
        type: string
        description: What to echo
        required: true
    script: |
      def run(message):
          return message
FieldTypeRequiredNotes
namestringyesUnique within the harness. Duplicates fail validation.
descriptionstringnoSurfaced to the model in the tool listing.
parametersmap[string]ParamnoTool argument schema.
timeout_msintnoMust be >= 0. 0 means harness default.
scriptstringnoStarlark source. Required if the tool has no other handler.

param

FieldTypeDefaultNotes
typestringOne of string, int, bool, object, array.
descriptionstring""Surfaced to the model.
requiredboolfalseValidation: missing required params produce a tool error before the script runs.

tools_policy

Declarative governance over which registered tools the agent may invoke. Patterns are shell-style globs evaluated against tool names (e.g. fs.*, delegate*, web_fetch).

tools_policy:
  mode: allowlist
  allow:
    - "fs.read"
    - "fs.list"
    - "web_fetch"
    - "delegate*"
  deny:
    - "fs.remove"
    - "exec"
FieldTypeDefaultNotes
modestringinferredallowlist or denylist. When omitted: a non-empty allowallowlist, else denylist.
allow[string]emptyPatterns the agent may call.
deny[string]emptyPatterns the agent may not call. Deny always wins over allow.

Policy is enforced at the registry level: a denied call never reaches the tool's Starlark script, and the OTel span is marked tool.policy=denied. See the Governance & Policy concept page.


hooks

Inline hook registrations. As with tools, most projects ship hooks as separate artifacts in .harness/hooks/<name>.md (see Hook Artifact Schema); the inline form is for small examples and tests.

hooks:
  - event: tool.pre
    handler: audit_pre
    when: 'payload["name"] == "fs.read"'
    priority: 100
    script: |
      def handle(event, payload):
          metrics.incr("audit.read")
          return {"action": "allow"}
FieldTypeRequiredNotes
eventstringyesMust be a recognized event name. Validation rejects unknown events.
handlerstringyesStable identifier for traces and logs. Inline hooks may reuse the handler name only once.
whenstringnoStarlark expression evaluated against the event payload before the hook runs.
priorityintnoLower numbers run first. Default 0.
scriptstringyes*Starlark source. Optional only if the hook references an existing handler by name.

Recognized event names (full list in Hook Artifact Schema):

  • tool.pre, tool.post
  • completion.pre, completion.post
  • delegate.pre, delegate.post
  • agent.start, agent.turn, agent.stop

delegation

Sub-agent delegation budget.

delegation:
  max_depth: 2
  max_concurrent: 4
  iterations_per_depth: [12, 6]
FieldTypeDefaultNotes
max_depthint1Maximum sub-agent depth. 0 disables delegation entirely.
max_concurrentint1Cap on simultaneous in-flight delegations across the whole tree.
iterations_per_depth[int]nonePer-depth turn budget. [12, 6] ⇒ root agent gets 12 turns, depth-1 sub-agents get 6.

When iterations_per_depth has fewer entries than max_depth, the last entry is reused for deeper levels.


meta

Configuration for the meta.* Starlark built-ins (self-augmenting agents). All fields are required when meta is present.

meta:
  enabled: true
  max_tools: 20
  max_hooks: 20
  max_agents: 5
  max_call_depth: 2
FieldTypeNotes
enabledboolMaster switch. When false, every meta.* call returns an error.
max_toolsintCap on dynamically registered tools across a single run.
max_hooksintCap on dynamically registered hooks across a single run.
max_agentsintCap on dynamically registered agents across a single run.
max_call_depthintMaximum nesting depth for meta.* calls (prevents recursive self-augmentation).

Dynamically registered tools are still subject to tools_policymeta.register_tool cannot bypass governance.


serve

Declarative configuration for harness serve. Replaces the repeated --source / --telegram-* CLI flags. Secrets are never embedded — each source references an env var via token_env.

serve:
  sources:
    - type: stdin
    - type: telegram
      token_env: TELEGRAM_BOT_TOKEN
      poll_timeout_seconds: 25
      chat_allowlist: [7729308746]
      offset_path: ./.harness/state/telegram-offset.json
    - type: meshwire
      token_env: MESHWIRE_TOKEN
      mesh_id: family-mesh
      agent_id: harness-bot
      sender_allowlist: [peer-reviewer]
      poll_timeout_seconds: 30
      base_url: https://meshwire.io

serve.sources must contain at least one entry. Duplicate types are not supported in v1. Unknown type values produce a validation error so a stale binary running newer config fails loudly instead of silently dropping sources.

Per-source fields

type: stdin

No required fields. Reads prompts from standard input; emits replies to standard output. Equivalent to harness run but participates in the multi-source dispatch loop.

type: telegram

FieldTypeRequiredConstraintNotes
token_envstringyesnon-emptyEnv var holding the Bot API token.
chat_allowlist[int64]yesnon-emptyTelegram chat IDs allowed to invoke the harness.
poll_timeout_secondsintno0..50Long-poll timeout. 0 ⇒ source default.
offset_pathstringnoFile path for durable update_id persistence.

type: meshwire

FieldTypeRequiredConstraintNotes
token_envstringyesnon-emptyEnv var holding the MeshWire auth token.
mesh_idstringyesnon-emptyMeshWire mesh this harness joins.
agent_idstringyesnon-emptyThis harness's agent_id within the mesh.
sender_allowlist[string]yesnon-emptyPeer agent_ids whose messages this harness will accept.
poll_timeout_secondsintno0..60Long-poll timeout. 0 ⇒ source default.
base_urlstringnoDefault https://meshwire.io.

network

Network sandbox enforced by the http.* Starlark built-ins.

network:
  allowed_domains:
    - api.github.com
    - "*.example.com"
FieldTypeDefaultNotes
allowed_domains[string]emptyWhen non-empty, switches to default-deny. Each entry matches the host and its sub-domains. The literal entry "*" disables host filtering while still rejecting non-http(s) schemes.

When network is omitted (or allowed_domains is empty), scripts may reach any host. This preserves backward compatibility with pre-5.5 configs. See the Network Sandboxing guide for full matching rules.


Defaults summary

The loader applies these defaults before validation:

FieldDefault
model.namegpt-4o
model.provideropenai
model.max_tokens4096
model.temperature0.7
model.api_key_envGITHUB_TOKEN
model.base_urlderived
context.max_history50
context.max_tokens128000
delegation.max_depth1
delegation.max_concurrent1

Validation

harness validate runs the same checks the runtime applies at boot:

  • model.name non-empty
  • model.temperature in [0, 2]
  • model.max_tokens > 0
  • tool.timeout_ms >= 0
  • No duplicate tool names
  • Every hook event is a recognized event
  • tools_policy.mode (if set) is allowlist or denylist
  • All tools_policy.allow / deny entries are non-empty strings
  • serve.sources non-empty when serve is present, with per-source required fields enforced
  • model.retry and per-models[i].retry field bounds (max_retries >= 0, backoffs >= 0, multiplier >= 0)

Validation errors are joined into one message: each individual issue is listed so a CI run shows everything wrong in one pass.

Worked example

The flagship governed-agent example ships a complete harness.md exercising every governance primitive. Use it as the copy-paste baseline:

  • Two models profiles (primary + cheap fallback)
  • tools_policy allowlist with explicit denies
  • delegation budget with per-depth iteration caps
  • meta enabled with caps
  • Companion artifacts under .harness/tools/ and .harness/hooks/

See also