Governance Model
KCP Harness enforces a deterministic, fail-closed governance model. This page explains the principles and mechanics.
Fail-Closed
The default posture is deny. If any of the following are true, knowledge access is blocked:
- The manifest can't be loaded
- The plan rejects all units
- A unit fails any of the 14 gates
- The budget ceiling is exceeded
- The temporal validity check fails
There is no "best-effort" mode. Either the request is explicitly approved through the gate cascade, or it's blocked.
The 14-Gate Cascade
Every knowledge unit is evaluated through 14 deterministic gates, in order (kcp-agent v0.16.0, skill_eligibility added #100/#101):
| # | Gate | What it checks |
|---|---|---|
| 1 | audience | Is the requester in the target audience? |
| 2 | not_for | Is the requester explicitly excluded? |
| 3 | temporal | Is the unit valid at the current time? |
| 4 | deprecated | Has the unit been deprecated? |
| 5 | supersession | Has a newer unit superseded this one? |
| 6 | relevance | Is the unit relevant to the task? |
| 7 | skill_eligibility | For a kind: skill unit: does it carry an explicit load_eligible: true grant? |
| 8 | attestation | Does the unit have required attestations? |
| 9 | payment | Does access require payment? |
| 10 | access | Does the requester have access rights? |
| 11 | strict | In strict mode, is relevance high enough? |
| 12 | max_units | Would this exceed the unit count limit? |
| 13 | money_budget | Would this exceed the monetary budget? |
| 14 | context_budget | Would this exceed the token budget? |
A unit must pass all gates to be included in the plan. The gate that blocks it is recorded in the decision trace. See Governed Skills below for what happens once a kind: skill unit clears gate 7 and is actually invoked.
Decision Traces
Every plan produces a decision trace — a structured record of which gates each unit passed or failed. Traces are:
- Deterministic — same inputs produce identical traces
- Complete — every unit in the manifest is evaluated
- Timestamped — temporal gates are evaluated against a pinned time
- Replayable — traces can be re-evaluated against different parameters
Budget Enforcement
The harness tracks spend via an append-only ledger:
- Each
kcp_loadrecords the cost of loaded units - Running totals are maintained per currency
- If a load would exceed the budget ceiling, the entire load is rejected (no partial loads)
- The ledger can be queried via
harness_budget
Temporal Governance
Knowledge units can have temporal constraints (valid-from, valid-until, embargo dates). The harness:
- Pins the evaluation time when a plan is created
- Registers the plan with the temporal watcher
- On subsequent calls, re-evaluates plans against current time
- If units have drifted (expired, newly valid), emits a
temporal_driftevent - The agent can check drift via
harness_temporal_check
Human-Approval Gates
Some governed actions must not be decided by the automated cascade alone: org policy demands a named human sign off, and that can take minutes or days. Calls matching a governance.approvals rule enter a durable ticket state machine:
pending_review ──▶ approved (terminal, named reviewer)
│─────────▶ dismissed (terminal, named reviewer)
└─────────▶ expired (terminal, TTL — fail-closed)Three invariants:
- Approval rules outrank every automated path. An approved plan cannot bypass a human gate — the rule check runs first.
- Resolutions are never anonymous. A resolution requires a named reviewer and a policy citation (
policyRef).approved: truealone is rejected as evidence. The evidence is generated at approval time, never reconstructed from logs. - Tickets survive restarts. Sessions are ephemeral; human review is not. The default file provider persists every ticket, and a CLI in another process resolves it.
MCP has no async answer, so a pending call is denied with a structured reason carrying the ticket id and required role. The agent re-tries after approval (or checks harness_approvals). On retry the governor honors the resolution: approved → allowed with the resolution attached; dismissed → terminal block.
The provider interface (submit / check / resolve / list) is channel-agnostic — Slack, email, or ticketing integrations are org-side implementations of the same surface the built-in kcp-harness approvals CLI uses.
Post-Synthesis Confidence Gate
The 14 gates all evaluate declared unit properties before anything is generated. Confidence is a property of the model's output — so it is a separate, later stage, downstream of synthesis:
The planner decides what may be loaded; grounding decides what may be asserted;
harness_assessdecides what may be acted on.
The harness calls kcp-agent's assess(): confidence is a proposal (the answer's self-report, or an injected evaluator); the gate adjudicates deterministically against the configured threshold. The verdict is binary with a written, specific reason — the same contract as the 13 pre-selection gates.
- Strictest threshold wins — a caller may tighten org policy, never loosen it
- Fail-closed — no obtainable confidence signal fails the gate with a specific reason
- Route-to-human — a failed verdict on a
route_to_roleconfig opens an approval ticket with the full verdict embedded as evidence ("below threshold on critical → route to a human" is a pending approval) - Every adjudication is a
confidence_verdictaudit event — score, threshold, reasoning; never the answer text
Governed Skills — the harness enforces skill_eligibility
A kind: skill unit (spec §4.3a) is a procedure, not a document — something an agent could do. When a governed tool call is classified as a skill invocation, the harness runs kcp-agent's skill_eligibility gate itself, before the skill's tool call is ever forwarded downstream:
- Ineligible → refused, fail-closed. No
load_eligible: truegrant means the call never reaches the downstream tool. Askill_loadedaudit event witheligible: falserecords the gate's exact written reason. - Eligible → loaded, and its
action_scopebecomes binding. The skill's declaredaction_scope(tools/paths/capabilities) is attached to the session as the active skill — every subsequent governed call in that session is now checked against it (see Procedural Conformance, next). A skill with no declared scope binds an empty one — fail-closed, not permissive. - Skill invocations skip the generic plan governor entirely — a skill id is not a file path to plan against, so
skill_eligibilityis the whole story for whether it runs.
Every verdict is a skill_loaded (eligible: true or false) audit event, carrying the skill's id, the deciding gate, its written reason, and its action_scope.
Authoring skill units: the conventions for what a good
kind: skill+action_scopelooks like, the SK001–SK008 linter, the conformance vectors (canonical fixtures for testing any producer or consumer of skill units — including this harness's gate), and a curated library of governed playbooks live in Cantara/kcp-skill. Lint a manifest's skill units withnpx kcp-skill-lint knowledge.yaml.
Procedural Conformance — grounding for actions
Loading a skill is not a blank check. Once one is active, every subsequent governed tool call in that session is adjudicated against that skill's declared action_scope before the generic governor runs — the same "cite it or it doesn't count" discipline kcp-agent's answer-grounding applies to claims, applied to actions:
- A call that stays within the active skill's
tools/paths/capabilitiesproceeds. - A call that strays outside it is held fail-closed — surfaced as a gap, routed to a human, never silently narrowed or silently allowed. The reviewer role and policy citation come from
governance.conformance— falling back togovernance.confidence's routing if that block is absent, then to a hardcoded default role (governance-reviewer) with no policy citation (#43). - This check runs before plan governance: a scope violation is decided by the loaded skill alone, independent of whether a plan would otherwise have approved the call.
Every adjudication is a conformance_verdict audit event, naming the active skill, the tool invoked, the deciding target (the violating one, on a hold), and — on a hold — a ticket id if the violation was routed for review.
Decision-Record Correlation
Every tool call the harness intercepts produces a chain of verdicts as it moves through classification, governance, skill-gating, and confidence adjudication. A single correlation id ties that whole chain together in the audit log, so a reviewer (or an export) can reconstruct exactly which verdicts belong to which action instead of correlating timestamps by hand.
Per the KCP spec (§3.2 propagation / §17 observability), the harness reuses an incoming W3C traceparent when the caller supplies one — its trace-id becomes the correlation id, its span-id becomes the parent — so harness records stitch directly into the caller's own distributed trace. Absent a valid traceparent, the harness mints a fresh id. Every audit event in a chain carries correlationId (and parentId, when derived from an incoming trace).
Session Dedup
The harness tracks which units have been loaded in the current session. If an agent requests a unit that's already loaded (same SHA-256 hash), the harness returns an "unchanged" stub instead of re-loading the content. This prevents:
- Redundant knowledge loading
- Double-counting in the budget ledger
- Context window waste