This document specifies the complete harness.yaml format for Harness Protocol v1. Every field, constraint, and validation rule is defined here. The JSON Schema at https://harnessprotocol.io/schema/v1/harness.schema.json is the machine-readable complement to this document.
A harness.yaml file is a single YAML document. Top-level keys are defined in the sections below. Unknown top-level keys (other than those prefixed with x-) are a validation error.
$schema: https://harnessprotocol.io/schema/v1/harness.schema.json
version: "1"
kind: profile
metadata:
name: my-harness
description: "..."
plugins: [...]
skills: [...]
architectural-constraints: { ... }
mcp-servers: { ... }
env: [...]
instructions: { ... }
permissions: { ... }
policy: { ... }
extends: [...]| Field | Type | Required | Default |
|---|---|---|---|
$schema |
string (URI) | No | — |
A URI identifying the JSON Schema this document validates against. Including $schema enables schema-aware editors to provide validation and autocompletion.
Recommended value:
$schema: https://harnessprotocol.io/schema/v1/harness.schema.jsonThe presence or absence of $schema does not affect runtime validation. Implementations validate against the version-appropriate schema regardless.
| Field | Type | Required | Default |
|---|---|---|---|
version |
string | Yes | — |
The Harness Protocol version. For v1, this MUST be the string "1" — not the integer 1.
version: "1" # CORRECT: string
version: 1 # INCORRECT: integer — validation errorThe JSON Schema enforces "type": "string", so any conformant validator will reject the integer 1. Implementations SHOULD additionally provide a helpful error message suggesting version: "1" (string). Silently coercing the integer to a string is NOT permitted.
Non-normative: The legacy harness-kit format used version: 1 (integer) alongside marketplaces: and list-form plugins:. Implementations that need to support both formats SHOULD branch on typeof version: string signals Harness Protocol format; integer signals legacy format. Legacy format behavior is outside this specification.
Validation rule: If version is present and is not the string "1", validation fails with a clear error indicating the unsupported version.
| Field | Type | Required | Default |
|---|---|---|---|
kind |
enum | No | profile |
Accepted values: profile, fragment
Declares the document type:
profile— A complete harness document. All required fields are enforced. Suitable for direct application.fragment— A partial harness document. Required-field validation is relaxed. Used as a building block for composition via theextendsmechanism or the v2 Exchange layer.
Validation behavior:
| kind | Required fields enforced | Structural validation | Co-constraints enforced |
|---|---|---|---|
profile |
Yes | Yes | Yes |
fragment |
No | Yes | Yes |
A fragment that is referenced in extends MUST be structurally valid (correct types, valid enums, no forbidden field combinations) even though it may be incomplete.
metadata is required for kind: profile documents and optional for fragments.
| Field | Type | Required | Description |
|---|---|---|---|
name |
string | Yes (profile) | Identifier for this harness. Lowercase letters, digits, hyphens only. Max 64 characters. |
description |
string | No | Human-readable description of the harness's purpose. |
author |
object | No | Author information. |
author.name |
string | Yes (if author present) | Author's display name. |
author.url |
string (URI) | No | URL for the author (GitHub profile, personal site, etc.). |
version |
string | No | Semantic version of this harness profile (e.g., "1.0.0"). Not the same as the protocol version field. |
license |
string | No | SPDX license identifier (e.g., Apache-2.0, MIT). |
tags |
array of strings | No | Search tags for registry discoverability. Each tag: lowercase, max 32 characters. Max 10 tags. |
metadata.nameMUST match the pattern^[a-z0-9]([a-z0-9-]{0,62}[a-z0-9])?$. Must start and end with a lowercase letter or digit.metadata.version, if present, MUST be a valid semver string.metadata.license, if present, MUST be a valid SPDX expression.
metadata:
name: data-engineer
description: "Harness for data engineering workflows: SQL, lineage, dbt."
author:
name: alice
url: https://github.com/alice
version: "1.0.0"
license: Apache-2.0
tags: [data-engineering, sql, dbt]plugins is an array of plugin declarations. Each entry references a plugin by source and version.
| Field | Type | Required | Description |
|---|---|---|---|
name |
string | Yes | Local name for this plugin within the harness. Used for override targeting in team overlays. |
source |
string | Yes | Plugin source in owner/repo format (e.g., harnessprotocol/harness-kit). |
version |
string | No | Semver range specifying the required plugin version (e.g., ">=0.2.0", "^1.0.0"). If absent, the implementation selects the latest compatible version. |
description |
string | No | Human-readable note about why this plugin is included. Informational only. |
config |
object | No | Plugin-specific configuration. Schema is defined by the individual plugin's plugin.json. |
loading |
enum | No | When to load the plugin's tools and context into the agent's context window. Values: eager (default), deferred. See Loading Mode below. |
integrity |
object | No | Content verification. |
integrity.sha256 |
string | No | SHA-256 hash of the resolved plugin archive. Hex-encoded, lowercase. 64 characters. |
The loading field controls progressive skill disclosure — when a plugin's tools and context are injected into the agent's context window.
| Value | Behavior |
|---|---|
eager (default) |
All plugin tools and context are loaded at session start. This is the current behavior and the default when loading is absent. |
deferred |
Plugin tools are registered by name only at session start. Full tool schemas, context, and frontmatter are loaded the first time the agent invokes any tool from the plugin. Reduces initial context size. |
Design rationale: Research (Chroma, LangChain) shows that model performance degrades as context length increases — a phenomenon called context rot. Progressive disclosure is one of three core mitigations identified by LangChain: don't load all tool frontmatter at startup, load on demand. Claude Code already implements deferred tool loading internally; this field lets profiles declare the intent portably.
Precedence: If a plugin's plugin.json declares loading: deferred but the harness profile declares loading: eager (or omits the field, defaulting to eager), the harness profile wins. The plugin manifest expresses the author's recommendation; the harness author has final say.
Backward compatibility: This field is optional with a default of eager, preserving current behavior for all existing harness documents.
sourceMUST match the pattern^[a-zA-Z0-9_][a-zA-Z0-9_.-]*/[a-zA-Z0-9_][a-zA-Z0-9_.-]*$. See Source Resolution for howsourcefields are resolved.version, if present, MUST be a valid semver range expression.integrity.sha256, if present, MUST be a 64-character lowercase hex string.- Plugin
namevalues MUST be unique within thepluginsarray. - If
integrity.sha256is declared, implementations MUST verify the resolved plugin archive against this hash before loading. A mismatch is a fatal error.
When a child harness extends a parent, plugin lists are unioned. If both declare a plugin with the same name, the child's entire plugin object replaces the parent's. There is no field-level merge within a plugin entry — the child's version, config, integrity, and all other fields are used as-is.
plugins:
- name: data-lineage
source: harnessprotocol/harness-kit
version: ">=0.2.0"
description: "SQL lineage tracking and impact analysis"
config:
default-schema: public
integrity:
sha256: "a3f1e2b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f0a1b2c3d4e5f6a7b8c9d0e1f2"
- name: sql-assist
source: harnessprotocol/plugins
version: "^2.0.0"skills is an array of skill declarations. A skill is a portable, named capability — a directory containing a SKILL.md file and optional supporting resources — that an agent loads on demand. Skills may be bundled by a plugin or declared directly here; this section lets a harness declare a skill without authoring or depending on a full plugin. See HEP-4.
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
name |
string | Yes | — | Lowercase kebab-case identifier. Max 64 characters. Pattern ^[a-z0-9]([a-z0-9-]{0,62}[a-z0-9])?$. Should match the skill's SKILL.md frontmatter name. |
source |
string | Yes¹ | — | owner/repo, owner/repo/path/to/skill, or ./local/path. See Source Resolution. |
version |
string | No | latest | Semver range constraint for owner/repo sources. Ignored for local-path sources. |
description |
string | No | — | Display override for this skill in this profile's context. |
enabled |
boolean | No | true |
When false, the skill is declared but not activated — used to disable a skill inherited from a parent profile. |
loading |
enum | No | deferred |
When the skill's full content is loaded. See Loading Mode. |
integrity |
object | No | — | Content verification. |
integrity.sha256 |
string | No | — | SHA-256 hash of the skill archive. Hex-encoded, lowercase. 64 characters. |
¹ source is required for any skill that is being declared. It is optional when the entry only suppresses an inherited skill via enabled: false — in that case name alone identifies the skill to suppress. The JSON Schema enforces this conditionally.
A skill is progressive-disclosure by construction: only its name and description are needed at session start, with the body loaded on demand. The loading field controls this.
| Value | Behavior |
|---|---|
deferred (default) |
Only the skill's metadata (name, description) is loaded at session start; the body is loaded on first invocation. This is the default — it keeps initial context small. |
eager |
The full skill is loaded at session start. |
Note that this default is the inverse of plugins, which default to eager. Skills default to deferred because the format is designed for on-demand loading.
nameMUST match the kebab-case pattern and be unique within theskillsarray.sourceMUST resolve via the Source Resolution algorithm.integrity.sha256, if present, MUST be a 64-character lowercase hex string. If declared, implementations MUST verify the resolved skill archive against it before loading; a mismatch is a fatal error.- A skill is third-party content. Implementations MUST apply the installation-review requirements in Skill Behavioral Injection regardless of how the skill is declared.
A skill bundled by a plugin and a skill declared in skills are both registered for the session. If a directly-declared skill and a plugin-bundled skill share a name, the directly-declared skills entry wins — the harness author's explicit declaration is more specific than a transitive plugin payload. A directly-declared entry with enabled: false therefore suppresses both an inherited skills entry and a plugin-bundled skill of the same name.
The skills array is unioned by name, following the same rule as mcp-servers: the child's full entry replaces a parent's entry with the same name; new names from parent and child are both retained. A child sets enabled: false to suppress an inherited skill.
skills:
- name: pdf-forms
source: harnessprotocol/skills/pdf-forms
version: ">=1.0.0"
description: "Fill and extract PDF form fields."
integrity:
sha256: "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
- name: house-style
source: ./skills/house-style
loading: eagerarchitectural-constraints is an optional object that declares the structural rules, linters, and review policies enforced by this harness. It separates deterministic enforcement (linters, structural tests) from probabilistic review (LLM-based) — combining both creates defense-in-depth for AI-generated code.
The field is derived from Böckeler's analysis of production AI-maintained codebases, which identifies a three-component harness model: context engineering, architectural constraints, and garbage collection. This section encodes the second component as a first-class protocol field.
| Level | Mechanism | Behavior |
|---|---|---|
| Deterministic | Linters, structural tests | Cannot be overridden; violations block commits or merges |
| Review | LLM-based review agent | Probabilistic; can request exceptions or suggest alternatives |
| Advisory | Linters or tests with enforcement: warn |
Violations logged but do not block progress |
An array of deterministic enforcement rules. Each linter entry:
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
name |
string | Yes | — | Linter identifier. Must be unique within the linters array. |
description |
string | Yes | — | What architectural invariant this linter enforces. |
enforcement |
enum | No | block |
block = violations prevent merge. warn = violations logged but don't prevent merge. |
config |
object | No | — | Tool-specific linter configuration. Schema is tool-dependent. |
source |
string | No | — | custom (defined in this harness) or a GitHub path (e.g., owner/repo/path). |
An array of programmatic tests verifying architectural invariants (e.g., ArchUnit-style tests, layered architecture checks). Each entry:
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
name |
string | Yes | — | Test identifier. Must be unique within the structural-tests array. |
description |
string | Yes | — | What architectural invariant this test verifies. |
entrypoint |
string | No | — | Command to run the test (e.g., gradle architectureTest). |
enforcement |
enum | No | block |
block = failures prevent merge. warn = failures logged but don't prevent merge. |
source |
string | No | — | custom or a GitHub path. |
An object declaring LLM-based architectural review behavior:
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
enabled |
boolean | No | true |
Whether LLM review is active. |
model |
string | No | — | Model identifier for review (implementation-specific). |
patterns |
array | No | — | Architectural patterns to verify. Each has name, rule (prose), and severity (error, warning, info). |
guidance |
string | No | — | Prose document describing the harness's architectural philosophy. Agents use this to calibrate reviews. |
review-policy.patterns[].severity values:
error— blocks merge if reviewer detects a violationwarning(default) — flagged but merge allowedinfo— noted but non-blocking
- Linter
namevalues MUST be unique within thelintersarray. - Structural test
namevalues MUST be unique within thestructural-testsarray. - The
architectural-constraintsobject MUST contain at least one oflinters,structural-tests, orreview-policyif the field is present. source: customdeclarations should be accompanied by documentation or supporting files in the harness repository.
The deterministic and review layers serve different roles. Linters and structural tests provide guarantees that a review agent cannot: a deterministic boundary check will never be convinced to accept a module boundary violation. The LLM review layer catches semantic and design-level issues that are impractical to express as rules.
Together they form a defense-in-depth model: linters and structural tests catch mechanical violations, review agents catch architectural drift. This combination is well-evidenced in production AI-maintained codebases operating at scale.
Implementations MUST:
- Discover and report all
block-enforcement linters and structural tests declared in the harness - Honor
enforcement: warnby logging violations without blocking - Apply the
review-policyto configure LLM-based architectural review (if the implementation supports it)
A harness using only source: custom constraints with inline config is fully portable. Harnesses referencing external source GitHub paths depend on tool support for fetching those sources.
architectural-constraints:
linters:
- name: module-boundary-checker
description: Enforces that agents do not import across module boundaries
enforcement: block
config:
boundaries:
- module: /src/auth
may_import_from: [/src/common, /src/types]
- module: /src/database
may_import_from: [/src/common, /src/types]
source: custom
structural-tests:
- name: layered-architecture
description: Verifies that each layer only depends on layers below it
entrypoint: gradle architectureTest
enforcement: block
source: custom
review-policy:
enabled: true
patterns:
- name: module-cohesion
rule: Each module should have a single, clear responsibility.
severity: warning
- name: dependency-direction
rule: Dependencies flow strictly one direction. Circular dependencies are never acceptable.
severity: error
guidance: |
Architecture follows a layered model:
api/ → service/ → repository/ → types/ (shared by all).mcp-servers is a map (YAML object) from server name to server declaration. Keys are the server names as exposed to the agent at runtime.
| Field | Type | Required | Description |
|---|---|---|---|
transport |
string | Yes | Must be "stdio". |
command |
string | Yes | Executable to run (e.g., uvx, npx, node). |
args |
array of strings | No | Arguments to pass to the command. |
env |
object | No | Environment variable map passed to the process. Values may reference harness env declarations via ${VAR_NAME} syntax. |
source |
string | No | Provenance identifier: a registry identity in reverse-DNS form (e.g., io.github.owner/server) or owner/repo. Records where the server originates. Does not change how command is invoked. |
version |
string | No | Version or semver range for the server package, complementing any version pinned in args. |
integrity |
object | No | Content verification for the server package. |
integrity.sha256 |
string | No | SHA-256 of the server package archive. Hex-encoded, lowercase, 64 characters. |
| Field | Type | Required | Description |
|---|---|---|---|
transport |
string | Yes | "streamable-http" (canonical) or "http" (accepted alias — treated identically). |
url |
string (URI) | Yes | HTTP(S) URL of the MCP server endpoint. |
headers |
object | No | HTTP headers to include in requests. Values may reference harness env declarations via ${VAR_NAME} syntax. |
source |
string | No | Provenance identifier (reverse-DNS registry identity or owner/repo). |
version |
string | No | Version or semver range identifying the remote server build. |
streamable-http is the canonical remote transport and is RECOMMENDED for all remote servers. http is retained as an accepted alias and MUST be treated identically.
The sse (Server-Sent Events) and ws (WebSocket) transports share the same network field structure (transport, url, headers, plus optional source/version). sse is the legacy transport, deprecated for new servers and retained for compatibility; implementations MAY warn when it is declared. ws is non-standard and implementation-specific — not part of the recommended set, but retained for forward compatibility. New servers SHOULD use streamable-http. See MCP Declarations for full details, transport guidance, and security considerations for all network transports.
Values in env (for stdio) and headers (for http) may use ${VAR_NAME} syntax to reference variables declared in the harness env array.
Validation rule: Every ${VAR_NAME} reference in mcp-servers MUST correspond to a declared entry in the top-level env array. Referencing an undeclared variable name is a validation error. This rule ensures that all variable dependencies are visible in the harness document and that implementations can prompt for missing values before attempting to start servers.
When a child harness extends a parent, MCP server maps are unioned by server name. If both declare a server with the same name, the child's declaration takes precedence entirely (no field-level merge — the child's full server object wins).
mcp-servers:
postgres:
transport: stdio
command: uvx
args:
- mcp-server-postgres
- --connection-string
- "${DB_CONNECTION_STRING}"
env:
PGAPPNAME: my-harness
source: "io.github.example/postgres"
version: "1.4.2"
data-api:
transport: streamable-http
url: "https://api.example.com/mcp"
source: "io.github.example/data-api"
headers:
Authorization: "Bearer ${DATA_API_TOKEN}"
X-Client-Version: "1.0"env is an array of environment variable declarations. Each entry describes a variable that the harness requires or optionally uses at runtime.
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
name |
string | Yes | — | Variable name. Must match ^[A-Z_][A-Z0-9_]*$ — uppercase letters, digits, underscores; may start with an underscore. |
description |
string | Yes | — | Human-readable description of the variable's purpose and expected format. Shown to users when prompting for a value. |
required |
boolean | No | false |
If true, the implementation MUST verify the variable is set before applying the harness. Missing required variables are a fatal error. |
sensitive |
boolean | No | true |
If true, the variable contains secret data. See security constraints below. |
when |
string | No | — | Human-readable description of when this variable is needed (e.g., "When accessing private GitHub repositories"). Implementations MAY evaluate it as a condition expression but are NOT REQUIRED to do so; when not evaluated, it is displayed as informational text. |
default |
string | No | — | Default value used when the variable is not set in the environment. Forbidden when sensitive: true. |
sensitive: true+defaultis FORBIDDEN. A harness with bothsensitive: trueand adefaultvalue on the same env entry MUST fail validation. Providing a default for a sensitive variable defeats the purpose of keeping it out of the harness file.- Sensitive variable values MUST NOT appear in logs, error messages, or stored configuration.
- Implementations MUST treat env entries as
sensitive: trueby default. A variable is only non-sensitive when explicitly declaredsensitive: false.
Every variable referenced via ${VAR_NAME} in mcp-servers MUST have a corresponding entry in env. The reverse is not required — env may declare variables that are not used in mcp-servers (they may be used by plugins or instructions).
When a child harness extends a parent, env arrays are unioned by variable name. If both declare the same variable name, the child's declaration takes precedence.
env:
- name: DB_CONNECTION_STRING
description: "PostgreSQL connection string (e.g., postgresql://user:pass@host/db)"
required: true
sensitive: true
- name: DATA_API_TOKEN
description: "API token for the data service"
required: true
sensitive: true
- name: DEFAULT_SCHEMA
description: "Default schema to use for SQL operations"
required: false
sensitive: false
default: "public"
- name: ENABLE_LINEAGE
description: "Enable automatic lineage tracking (true/false)"
required: false
sensitive: false
default: "true"
when: "plugins contains 'data-lineage'"instructions maps three instruction scopes and controls how they combine with inherited instructions.
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
operational |
string or null | No | — | Instructions for how to work: build commands, architecture, gotchas. Maps to AGENTS.md — the cross-tool instruction file read natively by most implementations — and to CLAUDE.md (Claude Code) or the equivalent operational context file. Value is inline text, a file:// path, or an https:// URL. |
behavioral |
string or null | No | — | Instructions for how to behave: tone, autonomy level, workflow conventions. Maps to AGENT.md (Claude Code) or the equivalent behavioral instructions file. Same value formats. |
identity |
string or null | No | — | Identity context for the agent. Maps to SOUL.md (Claude Code) or the equivalent identity context file. Same value formats. Set to null to explicitly declare no identity instructions. |
import-mode |
enum | No | merge |
How child instructions combine with parent instructions. Values: merge, replace, skip. |
Each instruction field accepts three content formats:
| Format | Example | Description |
|---|---|---|
| Inline text | "Always use conventional commits." |
Literal string content included directly. |
| File reference | file://./instructions/operational.md |
Path relative to the harness file. Resolved at apply time. |
| URL reference | https://example.com/instructions.md |
Fetched at apply time. Must be HTTPS. |
merge (default): The child's instruction content is appended after the parent's instruction content for each field. Both sets of instructions are active. This is the safe default — it never silently discards parent instructions.
replace: The child's instruction content replaces the parent's entirely for fields the child declares. Fields the child does not declare pass through unchanged from the parent. Because replace discards parent instructions — which may include safety or policy constraints — conformant implementations MUST require explicit user confirmation before applying a profile that uses import-mode: replace.
skip: The child declares no instructions. The parent's instructions pass through to the session unchanged. Useful for fragments that are purely additive (plugins, MCP servers) and intentionally defer to whatever instructions the consuming profile uses.
import-mode governs how this harness's instructions relate to the instructions inherited from extends. It does not affect how a child of this harness inherits from this harness — that is governed by the child's own import-mode.
instructions:
operational: file://./instructions/operational.md
behavioral: |
Prioritize correctness over speed. Always explain SQL query plans
when writing new queries. Prefer CTEs over subqueries for readability.
identity: null
import-mode: mergepermissions defines the tool access, filesystem access, and network access boundaries for the agent session.
| Field | Type | Required | Description |
|---|---|---|---|
allow |
array of strings | No | Tool names or glob patterns the agent is permitted to use. |
deny |
array of strings | No | Tool names or glob patterns the agent is explicitly forbidden from using. Deny takes precedence over allow. |
ask |
array of strings | No | Tool names or glob patterns that require user confirmation before each use. |
Tool name patterns support * as a wildcard. Examples:
"Read"— the Read tool by exact name"mcp__*__delete_*"— any MCP tool whose name contains "delete""Bash"— the Bash tool
Inheritance rules for tools:
allow: Intersected across the inheritance chain. A tool is allowed only if every ancestor that defines apermissions.tools.allowlist includes it (or uses a matching pattern). Most restrictive wins.deny: Unioned across the inheritance chain. A tool is denied if any ancestor denies it. One ancestor's denial propagates to all children.ask: Unioned across the inheritance chain. If any ancestor requires confirmation for a tool, confirmation is required.
| Field | Type | Required | Description |
|---|---|---|---|
writable |
array of strings | No | Paths (relative to project root) the agent may write to. Glob patterns supported. |
readonly |
array of strings | No | Paths the agent may read but not write. |
Path entries are relative to the project root unless they begin with /. Both writable and readonly support glob patterns.
Inheritance rules for paths: Path lists are unioned across the inheritance chain. A child can add writable/readonly paths but cannot remove paths that a parent has restricted. To remove a path restriction, use the team overlay syntax (v2 Exchange layer).
| Field | Type | Required | Description |
|---|---|---|---|
allowed-hosts |
array of strings | No | Hostnames or hostname patterns (supporting * wildcard) that the agent may make network requests to. |
If allowed-hosts is not declared, network permission behavior is implementation-defined.
Inheritance rules for network: allowed-hosts lists are unioned across the inheritance chain.
permissions:
tools:
allow:
- Read
- Glob
- Grep
- Write
- Edit
- Bash
deny:
- "mcp__*__delete_*"
- "mcp__*__drop_*"
ask:
- Bash
- Write
paths:
writable:
- src/
- tests/
- migrations/
readonly:
- config/
- "*.lock"
network:
allowed-hosts:
- "*.github.com"
- "api.example.com"policy is an optional object expressing organization or team governance constraints. Unlike every other section, a policy is a ceiling: it constrains what extending or consuming profiles may grant, and downstream profiles may narrow it but never widen it. A document with no policy section imposes no managed constraints — exactly the behavior of v1 before this section existed. The protocol declares intent and specifies what implementations MUST reject at apply time; the runtime enforcement boundary remains the implementation's. See HEP-6 and Inheritance for precedence semantics.
| Field | Type | Description |
|---|---|---|
mcp-servers.allowed-sources |
array of strings | Allowlist of MCP server source/host patterns. A declared server matching none is rejected. |
mcp-servers.denied-sources |
array of strings | Denylist of MCP server source/host patterns. Deny overrides allow. |
plugins.allowed-sources |
array of strings | Allowlist of plugin owner/repo patterns. |
plugins.denied-sources |
array of strings | Denylist of plugin patterns. Deny overrides allow. |
plugins.allowed-marketplaces |
array of strings | Allowlist of marketplaces/registries plugins may be fetched from. |
skills.allowed-sources |
array of strings | Allowlist of skill source patterns. |
skills.denied-sources |
array of strings | Denylist of skill source patterns. Deny overrides allow. |
permissions.tools.allow |
array of strings | Ceiling: the maximum set of tools any profile may grant. A grant matching none of these is a violation. |
permissions.tools.deny |
array of strings | Tools always denied regardless of any grant. |
permissions.network.allowed-hosts |
array of strings | Ceiling: the maximum set of network hosts any profile may allow. |
require-integrity |
boolean (default false) |
When true, every plugin, skill, and MCP server package MUST carry a verifiable integrity hash; declarations without one are rejected. |
A policy is applied after extends resolution produces a candidate effective configuration (see Application). A violation — a server/plugin/skill source outside the allowlist or matched by the denylist, a permission grant exceeding a ceiling, or a missing integrity hash when require-integrity is true — is a validation error. The harness is not applied; there is no partial application and no silent stripping of violating entries.
Constraints within policy.plugins are conjunctive: when both allowed-sources and allowed-marketplaces are present, a plugin must satisfy both — its source must match an allowed source and it must be fetched from an allowed marketplace.
policy does not merge like other sections. The effective policy is the accumulation of all policy sections in the resolution set: allowlists intersect, denylists union, permission ceilings intersect, and require-integrity is true if any layer sets it. A child can only make a policy stricter, never laxer.
policy:
mcp-servers:
allowed-sources: ["io.github.acme/*", "io.modelcontextprotocol/*"]
denied-sources: ["*/experimental-*"]
plugins:
allowed-sources: ["acme/*"]
allowed-marketplaces: ["acme/internal-marketplace"]
skills:
allowed-sources: ["acme/*", "harnessprotocol/skills/*"]
permissions:
tools:
allow: [Read, Grep, Glob, Edit, Write, Bash, "mcp__*"]
deny: ["mcp__*__drop_*", "mcp__*__delete_*"]
network:
allowed-hosts: ["*.acme.internal", "api.anthropic.com"]
require-integrity: trueextends is an ordered array of parent harness references. The current document is the child. Parents are resolved and applied before the child's fields are merged on top. See Source Resolution for how source fields are resolved.
| Field | Type | Required | Description |
|---|---|---|---|
source |
string | Yes | Parent harness source. owner/repo format, owner/repo/path/to/file.yaml, or a local path (./, ../). |
version |
string | No | Semver range for the parent harness version. If absent, the implementation selects the latest compatible version. |
When multiple parents are listed, they are applied left to right. Later entries take precedence over earlier entries in the case of conflicts, before the child's own fields are applied. The child always takes final precedence.
For a harness with extends: [A, B]:
- A is resolved and applied.
- B is merged on top of A (B wins on conflicts).
- The child's own fields are merged on top (child wins on conflicts).
| Section | Merge behavior |
|---|---|
plugins |
Union by name. Child/later wins on conflict. |
skills |
Union by name. Child/later wins on conflict. enabled: false suppresses an inherited skill. |
mcp-servers |
Union by server name. Child/later wins on conflict (full object, not field-level). |
env |
Union by name. Child/later wins on conflict. |
instructions |
Governed by import-mode (child's setting). |
permissions.tools.allow |
Intersection (most restrictive). |
permissions.tools.deny |
Union (any ancestor's denial propagates). |
permissions.tools.ask |
Union (any ancestor's ask propagates). |
permissions.paths |
Union (additive only). |
permissions.network |
Union (additive only). |
policy |
Does not merge like other sections — it accumulates as a ceiling (allowlists intersect, denylists union, permission ceilings intersect, require-integrity monotonic). A child can only tighten it. See policy. |
metadata |
Child's metadata is used as-is; parent metadata is not merged. |
kind |
Child's kind is used. |
A harness MUST NOT extend itself directly or transitively. Implementations MUST detect circular extends chains and fail validation with a clear error.
extends:
- source: harnessprotocol/profiles/backend
version: ">=1.0.0"
- source: my-org/shared-harness
version: "^2.1.0"Any top-level key or nested key prefixed with x- is an implementation extension field. The core schema does not define their structure. Conformant implementations MUST:
- Not reject a harness document solely because it contains unrecognized
x-fields. - Ignore
x-fields they do not support. - Not allow
x-fields to shadow or override core schema fields.
Extension fields are not portable. Authors who use x- fields SHOULD document which implementation(s) support them.
# Example: Claude Code-specific model hint (ignored by other implementations)
x-claude-model: claude-opus-4
x-claude-thinking-budget: 10000$schema: https://harnessprotocol.io/schema/v1/harness.schema.json
version: "1"
kind: profile
metadata:
name: data-engineer
description: "Harness for data engineering: PostgreSQL, lineage, dbt."
author:
name: alice
url: https://github.com/alice
version: "1.2.0"
license: Apache-2.0
tags: [data-engineering, postgresql, sql, dbt]
plugins:
- name: data-lineage
source: harnessprotocol/harness-kit
version: ">=0.2.0"
config:
default-schema: public
integrity:
sha256: "a3f1e2b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f0a1b2c3d4e5f6a7b8c9d0e1f2"
mcp-servers:
postgres:
transport: stdio
command: uvx
args: [mcp-server-postgres, --connection-string, "${DB_CONNECTION_STRING}"]
env:
- name: DB_CONNECTION_STRING
description: "PostgreSQL connection string"
required: true
sensitive: true
instructions:
operational: file://./instructions/operational.md
behavioral: "Prioritize query correctness. Explain plans for new queries."
import-mode: merge
permissions:
tools:
allow: [Read, Glob, Grep, Write, Edit, Bash]
deny: ["mcp__postgres__drop_*"]
ask: [Bash]
paths:
writable: [sql/, migrations/, dbt/]
readonly: [config/]
network:
allowed-hosts: ["*.github.com"]
extends:
- source: harnessprotocol/profiles/backend
version: ">=1.0.0"