Policy Engine
The Policy Engine evaluates rules at every decision point in the kernel — tool calls, task claims, and task completions. Its core invariant: a deny decision at any layer is final and cannot be reversed by any other layer.
The Four-Layer Stack
Section titled “The Four-Layer Stack”Policies are organized into four layers, evaluated in fixed order:
| Layer | Who sets it | Purpose |
|---|---|---|
| Workspace | Workspace administrator | Organization-wide rules (e.g., “no network access”) |
| Lane | Lane configuration | Domain-specific restrictions (e.g., “this lane is read-only”) |
| Pack | Pack manifest | Domain rules from the pack author (e.g., “gates must pass before completion”) |
| Task | Task specification | Per-task rules (e.g., “this task cannot modify src/auth/”) |
Each layer can contain a default decision and a list of rules.
Policy Rules
Section titled “Policy Rules”A rule has four parts:
| Field | Type | Description |
|---|---|---|
id | string | Unique identifier (e.g., software-delivery.gate.format) |
trigger | enum | When to evaluate: on_tool_request, on_claim, on_completion, on_evidence_added |
decision | enum | allow, deny, or approval_required |
reason | string? | Human-readable explanation |
Rules in pack manifests are static — they apply unconditionally when their trigger fires. Packs can also register runtime-authored rules through a manifest policy_factory, and those rules can include a when predicate for conditional evaluation (see Conditional Rules with when() below).
This split is useful when a pack needs runtime-aware governance. The agent-runtime pack, for
example, keeps a static baseline allow rule in the manifest, then uses policy_factory to add
intent-aware deny and approval_required rules after pack config has been resolved.
Deny-Wins Evaluation
Section titled “Deny-Wins Evaluation”The evaluation algorithm processes layers in order:
- Start with deny. The effective decision begins as
deny(fail-closed). - Apply defaults. The first layer evaluated (in order workspace, lane, pack, task) initialises the decision from its
default_decision, or leaves it at deny if it declares none. Later layers that declare a default can only tighten (deny), not loosen, unless explicitly grantedallow_loosening. The workspace layer is therefore the baseline. - Match rules. For each layer, find rules whose
triggermatches the current context. - Sticky deny. Once any rule emits
deny, no subsequentalloworapproval_requiredrule in any layer can reverse it.
approval_required sits between allow and deny: it blocks the attempted action, but it is still overridden by any explicit deny. Tool requests receive a structured pending request identifier. Claims and completions fail with a typed APPROVAL_REQUIRED error and append no protected lifecycle events.
Host policy layers cannot drop pack rules
Section titled “Host policy layers cannot drop pack rules”When a host passes policyLayers to initializeKernelRuntime, the kernel still builds the pack layer from the installed packs (manifest policies plus every policy_factory rule) and merges it in. This means a host that passes layers also runs every pack policy_factory at start.
- Workspace, lane and task layers you supply are used as given and can only tighten the result.
- The array you supply must include a workspace baseline: a
workspace-level layer that setsdefault_decision. The kernel inserts no default layer for a host that supplies layers (the mandatory pack layer declares no default and is evaluated before a task layer), so without a baseline every claim starts denied and a task-leveldefault_decision: 'allow'is rejected as loosening. Runtime start refuses this withPolicyLayerWorkspaceBaselineMissingError(POLICY_LAYER_WORKSPACE_BASELINE_MISSING), including for[]. The minimal shape is{ level: 'workspace', default_decision: 'allow', allow_loosening: true, rules: [] }; usedefault_decision: 'deny'for a deny-by-default host. OmitpolicyLayersentirely to keep the kernel default. Do not use a task-levelallow_looseninginstead. - A pack-level layer you supply contributes rules only. Setting
default_decisionorallow_looseningon it is refused at runtime start withPolicyLayerPackViolationError(POLICY_LAYER_PACK_VIOLATION). - At any level, a rule that reuses the id of a pack rule, or uses the reserved
kernel.id prefix, is refused. A layer level outsideworkspace,lane,packandtaskis refused. - Every
whenpredicate receives an isolated, frozen copy of the context built from the declared fields only, withtool_argumentssnapshotted through the canonical serializer (the same value domain as the input hash). Arguments outside that domain, such asNaN,Infinity,Map,Dateor atoJSONmethod, deny withkernel.policy.context-isolationinstead of being coerced. A predicate that tries to modify it, or that throws, produces aPOLICY_DENIEDfor its rule and nothing dispatches. Your own context and input are never frozen. The guarantee covers the context object only: predicates still run in-process with host authority. The cost is a JSON snapshot per evaluation (about 10 microseconds for a small argument object) and grows with argument size.
Fail-closed approval boundary
Section titled “Fail-closed approval boundary”The kernel applies approval_required consistently at every implemented trigger:
| Trigger | Fail-closed result |
|---|---|
on_tool_request | Handler is not called; the response includes a request ID and matching decisions. |
on_claim | No task_claimed or run_started event is appended. |
on_completion | No run_succeeded or task_completed event is appended. |
Rejecting a pending tool request never emits task_resumed, and resolving a request never dispatches the tool implicitly. Unknown and already-resolved identifiers fail deterministically.
Trusted provenance and pre-dispatch monitors
Section titled “Trusted provenance and pre-dispatch monitors”Trusted read adapters can attach a versioned provenance label to content returned from an external tool read, remote signal, knowledge retrieval, or authenticated channel ingress. A label identifies the source record; it is attribution data, not authority, and message text or caller-supplied metadata cannot create a trusted label.
Each active context is limited to 64 labels, and each nonempty source_id is at most 256
characters. Malformed or excessive provenance data fails closed; the kernel does not truncate it
and then treat the result as clean.
The agent runtime stores labels with tool-result messages in durable session/run state. Before a
consequential tool call, the kernel loads and validates labels from the active persisted context.
Trusted capability effect_classification identifies consequential calls; if absent, the kernel
derives consequence from permission. While labeled content remains in context, a consequential call
returns APPROVAL_REQUIRED with a typed provenance reason. Approval consumption checks the current
context again before dispatch. Missing or malformed context fails closed. Agent-runtime provider
computation is classified non_consequential for this effect check because it proposes steps by
returning model text. The provider call remains governed by authenticated caller, network, credential,
budget, purpose, and base policy controls; requested external effects continue through this gate.
Hosts that replace active context with a summary must use the governed workflow checkpoint boundary so source labels are inherited from the persisted messages and committed with the fenced compare-and-swap. The engine does not schedule compaction automatically.
A host may opt in to a pre-dispatch monitor. It receives the proposed tool step and active labels,
and may return unchanged, hold, or deny. Invalid or unavailable monitor results hold the call;
the monitor has no allow or grant result and cannot reverse a policy denial. Policy and monitor
denials record a coalesced provenance owner notice in the durable run aggregate.
The notice contains at most 64 sorted, deduplicated kind-prefixed source references, each at most 300 characters. Its key uses stable source references and decision classes rather than diagnostic text; invalid or excessive notice data holds the action.
The engine/reference proof uses a local initialized kernel and agent runtime. It covers same-batch external-read denial and persisted provenance across a killed process and resume; it does not prove hosted Cloud delivery, spend controls, or peer-consent behavior.
Triggers
Section titled “Triggers”Each trigger fires at a specific lifecycle moment:
| Trigger | When it fires | Example use |
|---|---|---|
on_tool_request | Every tool call, during authorization | Block specific tools, restrict write operations |
on_claim | When a task is claimed (ready → active) | Require approval before work starts |
on_completion | When a task completes (active → done) | Enforce gates (format, lint, test) |
on_evidence_added | When a new evidence record is written | Reserved for future use |
Gates as Policies
Section titled “Gates as Policies”In the Software Delivery Pack, quality gates (format, lint, typecheck, test) are implemented as pack-layer policies with trigger: on_completion. When you run pnpm wu:prep or pnpm gates, the system evaluates these policies:
| Gate | Policy ID | Trigger |
|---|---|---|
| Format check | software-delivery.gate.format | on_completion |
| Lint | software-delivery.gate.lint | on_completion |
| Type check | software-delivery.gate.typecheck | on_completion |
| Test | software-delivery.gate.test | on_completion |
This means gates are not just CLI commands — they are kernel-enforced policies. An agent cannot skip gates by calling completeTask directly; the policy engine will deny the completion.
Built-in Policy IDs
Section titled “Built-in Policy IDs”The kernel reserves several policy IDs for internal use:
| ID | Purpose |
|---|---|
kernel.policy.allow-all | Development-only allow-all hook |
kernel.scope.reserved-path | Blocks writes to kernel state |
kernel.scope.boundary | Denies when scope intersection is empty |
kernel.reconciliation | Marks crash-reconciled evidence entries |
Conditional Rules with when()
Section titled “Conditional Rules with when()”Runtime policy rules can include a when predicate — a function that receives the full PolicyEvaluationContext and returns true if the rule should apply. Rules without a when predicate match unconditionally whenever their trigger fires.
PolicyEvaluationContext
Section titled “PolicyEvaluationContext”The when predicate receives a PolicyEvaluationContext with these fields:
| Field | Type | Description |
|---|---|---|
trigger | string | The trigger type that fired this evaluation |
run_id | string | Current run identifier |
tool_name | string? | Name of the tool being called (for on_tool_request) |
task_id | string? | Active task identifier |
lane_id | string? | Lane the task belongs to |
pack_id | string? | Pack that provides the tool |
tool_arguments | Record<string, unknown>? | Parsed tool input arguments (see below) |
execution_policy_profile | string? | Persisted, kernel-derived selector for configured execution policy |
Argument-level policy evaluation
Section titled “Argument-level policy evaluation”The tool_arguments field provides the parsed input arguments for the tool being called. The kernel extracts this automatically from the raw tool input: if the input is a plain object, it is passed as tool_arguments; otherwise the field is undefined.
This enables argument-level policy decisions — rules that inspect what a tool is doing, not just which tool is being called:
Evaluation Result
Section titled “Evaluation Result”Every policy evaluation returns:
decision— the finalallow,deny, orapproval_requireddecisions[]— every rule that matched, with its individual decision and reasonwarnings[]— any loosening attempts that were rejected
All of this is recorded in the evidence store as part of the tool trace, providing a complete audit trail of why an action was allowed or denied.
Next Steps
Section titled “Next Steps”- Kernel Runtime — Where policy evaluation fits in the tool execution pipeline
- Scope Intersection — The permission model that runs alongside policies
- Evidence Store — How policy decisions are recorded
- Gates — How the Software Delivery Pack uses policies for quality checks