DocsNew Run

Policy Authoring Guide

Policies define the governance rules that control how specialist agent signals (evaluations and objections) are aggregated into final decisions during MACP coordination runs. This guide explains how the examples-service loads policies, how they flow to spawned agents, and how to author new ones for demo scenarios.

Canonical rule schema, voting algorithms, veto/confidence/ABSTAIN mechanics, and commitment-authority semantics live in the runtime docs — not here. See runtime/docs/policy.md for the full schema and behavioural reference. This guide covers only the examples-service-specific plumbing (loading, registration, hints mapping, scenario wiring).

Policy JSON Schema (at a glance)

Every policy under policies/*.json follows the runtime's PolicyDescriptor shape:

{
  "policy_id": "policy.<domain>.<variant>",
  "mode": "macp.mode.decision.v1",
  "schema_version": 1,
  "description": "Human-readable description of this policy",
  "rules": { "voting": { ... }, "objection_handling": { ... },
             "evaluation": { ... }, "commitment": { ... } }
}

For the full field reference (voting algorithms, thresholds, quorum shapes, objection handling, evaluation confidence, commitment authority, rule-level validation errors) see the canonical runtime doc linked above. The examples-service does not re-document or alter any of those semantics — it just registers whatever descriptors live on disk.

Included Policies

These are the policies shipped in policies/ for the demo scenarios:

Policy IDAlgorithmThresholdQuorumVetoMin Confidence
policy.defaultnone-0No0.0
policy.fraud.majority-vetomajority50%2 countYes (1)0.0
policy.fraud.supermajoritysupermajority67%2 countNo0.0
policy.fraud.unanimousunanimous-100%Yes (1)0.7
policy.lending.conservativesupermajority67%3 countYes (1)0.6
policy.claims.majoritymajority50%2 countNo0.0

Connecting Policies to Scenarios

Policies are referenced in scenario templates via the policyVersion field:

# In a scenario template (e.g., templates/unanimous.yaml)
spec:
  overrides:
    launch:
      policyVersion: policy.fraud.unanimous
      policyHints:
        type: unanimous
        threshold: 1.0
        vetoEnabled: true
        minimumConfidence: 0.7
        designatedRoles: []

The default template uses policy.default which requires no registration.

Policy Hints

policyHints are an examples-service-specific denormalized projection of the policy's rules that agents consume at bootstrap time. They are never sent to the runtime or the control-plane — only to the in-tree PolicyStrategy used by the Risk coordinator. The runtime evaluates governance against the registered policy_id directly.

Hint FieldMaps From (canonical)
typerules.voting.algorithm
thresholdrules.voting.threshold
vetoEnabledrules.objection_handling.critical_severity_vetoes
vetoThresholdrules.objection_handling.veto_threshold
minimumConfidencerules.evaluation.minimum_confidence
designatedRolesrules.commitment.designated_roles

How Policies Are Loaded

PolicyLoaderService reads all *.json files from the policies/ directory at startup:

  1. Parses each file and extracts policy_id
  2. Validates structure (non-blocking warnings)
  3. Caches policies in memory
  4. Excludes policy.default from the registrable set (auto-resolved by the runtime)

How Policies Are Registered

When REGISTER_POLICIES_ON_LAUNCH=true (the default) and MACP_RUNTIME_ADDRESS is set, PolicyRegistrarService.onApplicationBootstrap() registers every non-default policy with the runtime once per process start. Registration happens at service boot — not per-run — so that /examples/run requests never hit an UNKNOWN_POLICY_VERSION from the runtime.

Flow (src/policy/policy-registrar.service.ts):

  1. PolicyLoaderService.listRegistrablePolicies() returns every policy whose policy_id is not policy.default.
  2. AuthTokenMinterService.mintToken("examples-service", { can_manage_mode_registry: true, is_observer: false, allowed_modes: ["*"] }) mints an admin JWT from the auth-service.
  3. The registrar opens a short-lived gRPC channel to the runtime using that JWT and calls MacpClient.registerPolicy(descriptor) for each policy. (For the wire contract of RegisterPolicy, see runtime/docs/API.md.)
  4. Errors whose message contains "already" are treated as idempotent success (the runtime signals a duplicate).
  5. On completion, logs policy_registration_complete registered=<n> already=<n> failed=<n> total=<n>.

Registration is skipped (with a warning, not an error) when:

  • REGISTER_POLICIES_ON_LAUNCH=false — explicit opt-out.
  • MACP_RUNTIME_ADDRESS is unset — typically CI/test.

If the admin JWT mint fails (e.g. auth-service unreachable), the registrar aborts the entire registration pass and logs an ERROR. The service still starts, but downstream /examples/run requests will fail at the runtime with UNKNOWN_POLICY_VERSION. See "Troubleshooting" below.

Creating a Custom Policy

  1. Create the JSON file in policies/ — the shape is the runtime's PolicyDescriptor. Example:
{
  "policy_id": "policy.myteam.custom",
  "mode": "macp.mode.decision.v1",
  "schema_version": 1,
  "description": "Custom policy for my team's use case",
  "rules": {
    "voting": {
      "algorithm": "supermajority",
      "threshold": 0.75,
      "quorum": { "type": "count", "value": 3 }
    },
    "objection_handling": { "critical_severity_vetoes": true, "veto_threshold": 1 },
    "evaluation": { "minimum_confidence": 0.6, "required_before_voting": true },
    "commitment": {
      "authority": "designated_roles",
      "require_vote_quorum": true,
      "designated_roles": ["risk", "compliance"]
    }
  }
}

Refer to runtime/docs/policy.md for the legal values of each rule field.

  1. Reference it in a scenario template:
spec:
  overrides:
    launch:
      policyVersion: policy.myteam.custom
      policyHints:
        type: supermajority
        threshold: 0.75
        vetoEnabled: true
        vetoThreshold: 1
        minimumConfidence: 0.6
        designatedRoles: ["risk", "compliance"]
  1. Restart the servicePolicyLoaderService will discover the new file on next load, and PolicyRegistrarService will register it with the runtime during onApplicationBootstrap.

Troubleshooting

UNKNOWN_POLICY_VERSION at run time

Symptom: /examples/run compiles successfully but the runtime rejects the session with UNKNOWN_POLICY_VERSION.

Checklist:

  1. Startup logs. Look for policy_registration_complete on the most recent examples-service boot. If you see policy registration aborted: failed to mint admin JWT, fix the auth-service connection (MACP_AUTH_SERVICE_URL, network reachability, JWKS on the runtime).
  2. Scope. The admin mint uses can_manage_mode_registry. If the runtime's auth config does not accept this scope, registerPolicy returns an error and the policy stays unregistered.
  3. Runtime trust chain. The runtime must have MACP_AUTH_JWKS_URL=<auth-service>/.well-known/jwks.json and the matching MACP_AUTH_ISSUER / MACP_AUTH_AUDIENCE. A mismatch rejects the admin JWT at the runtime boundary, which logs as policy_register_exception. See runtime/docs/getting-started.md § Authentication for the full JWT setup.
  4. Manual re-register. Restart the examples-service once the auth-service is healthy — registration is idempotent, so any already-registered policies come back as already in the log summary.

AUTH_MINT_FAILED on /examples/run

The spawn-time JWT mint hit the auth-service and got a non-2xx response (or timed out). Check MACP_AUTH_SERVICE_URL, MACP_AUTH_SERVICE_TIMEOUT_MS, and the auth-service logs.

Local validation warnings

PolicyLoaderService runs a light structural check on load and warns (non-blocking) for missing policy_id, out-of-range values, or obviously invalid combinations. The authoritative schema validation happens at the runtime during RegisterPolicy — if a descriptor passes local load but fails at the runtime, the registrar logs policy_register_exception with the runtime's INVALID_POLICY_DEFINITION reason. See runtime/docs/policy.md § Registering a policy for the validation rules the runtime enforces.