Skip to content

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.

Policies are organized into four layers, evaluated in fixed order:

workspace → lane → pack → task
LayerWho sets itPurpose
WorkspaceWorkspace administratorOrganization-wide rules (e.g., “no network access”)
LaneLane configurationDomain-specific restrictions (e.g., “this lane is read-only”)
PackPack manifestDomain rules from the pack author (e.g., “gates must pass before completion”)
TaskTask specificationPer-task rules (e.g., “this task cannot modify src/auth/”)

Each layer can contain a default decision and a list of rules.

A rule has four parts:

FieldTypeDescription
idstringUnique identifier (e.g., software-delivery.gate.format)
triggerenumWhen to evaluate: on_tool_request, on_claim, on_completion, on_evidence_added
decisionenumallow or deny
reasonstring?Human-readable explanation

Rules in pack manifests are static — they apply unconditionally when their trigger fires. Runtime rules (set programmatically) can include a when predicate for conditional evaluation.

The evaluation algorithm processes layers in order:

  1. Start with deny. The effective decision begins as deny (fail-closed).
  2. Apply defaults. The first layer to declare a default_decision sets the baseline. Subsequent layers can only tighten (deny), not loosen, unless explicitly granted allow_loosening.
  3. Match rules. For each layer, find rules whose trigger matches the current context.
  4. Sticky deny. Once any rule emits deny, no subsequent allow rule in any layer can reverse it.
Workspace: default=allow          → effective: allow
Lane:      (no default)           → effective: allow
Pack:      rule deny(on_completion) → effective: deny  ← sticky
Task:      rule allow(on_completion) → REJECTED (cannot loosen a hard deny)
                                    → final: deny

Each trigger fires at a specific lifecycle moment:

TriggerWhen it firesExample use
on_tool_requestEvery tool call, during authorizationBlock specific tools, restrict write operations
on_claimWhen a task is claimed (ready → active)Require approval before work starts
on_completionWhen a task completes (active → done)Enforce gates (format, lint, test)
on_evidence_addedWhen a new evidence record is writtenReserved for future use

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:

GatePolicy IDTrigger
Format checksoftware-delivery.gate.formaton_completion
Lintsoftware-delivery.gate.linton_completion
Type checksoftware-delivery.gate.typecheckon_completion
Testsoftware-delivery.gate.teston_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.

The kernel reserves several policy IDs for internal use:

IDPurpose
kernel.policy.allow-allDevelopment-only allow-all hook
kernel.scope.reserved-pathBlocks writes to .lumenflow/**
kernel.scope.boundaryDenies when scope intersection is empty
kernel.reconciliationMarks crash-reconciled evidence entries

Every policy evaluation returns:

  • decision — the final allow or deny
  • decisions[] — every rule that matched, with its individual decision and reason
  • warnings[] — 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.

  • 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