Skip to content
Press / to search

Tutorial 3: Wire a typed Decision Catalog entry

Author a DecisionSpec with required_evidence so the Critic produces a typed, replayable DecisionRecord.

TutorialLast reviewed: Edit on GitHub
At a glance

In Tutorial 2 the runtime executed a destructive call. Now we close the audit substrate: every governed action emits a typed DecisionRecord validated against a DecisionSpec in the Decision Catalog. Without typed decisions, audit cannot replay and improvement cannot compound.

What we are building

consumed by emits persisted replays feeds DecisionSpecCriticDecisionRecordAudit StoreEvaluation EngineImprovement Loop
DecisionSpec → Critic → DecisionRecord → Audit Store → Evaluation Engine → Improvement Loop
1
Author the DecisionSpec

A spec declares the closed contract for one kind of decision.

{
  "decision_key": "support.refund.execute",
  "version": "1.0.0",
  "owner_role": "support_ops",
  "required_evidence": ["order_lookup", "policy.eval", "identity_verified"],
  "allowed_outcomes": ["approved", "denied", "escalated"],
  "approval_mode": "destructive",
  "decision_right": "execute",
  "challenge_required": false
}

Every field is enforced:

  • required_evidence — the Critic refuses to commit if any entry is unresolved.
  • allowed_outcomes — the runtime rejects an outcome outside the closed set.
  • approval_mode — must be ≥ the highest mode of any tool the decision may invoke.
  • decision_rightpropose / recommend / execute / escalate. The runtime refuses execute on a recommend-only spec.
2
Bind a policy rule to the spec

The decision_binding field on the rule (from Tutorial 2) is what ties the policy decision to the DecisionRecord.

{ "rule_id": "R_HIGH_VALUE_REQUIRES_APPROVAL", "decision_binding": "decision.support.refund.execute" }

The Critic uses this link to populate policy_decisions[] on the resulting record.

3
Inspect the emitted DecisionRecord

After the canonical loop completes, the Critic emits a record like this (truncated):

{
  "record_id": "dr_2026_05_04_a17",
  "decision_key": "support.refund.execute",
  "decision_version": "1.0.0",
  "status": "DECIDED",
  "actor": { "type": "AGENT", "id": "agt_support" },
  "outputs": { "refund_amount_inr": 4200, "currency": "INR", "transaction_id": "txn_q9..." },
  "evidence_refs": [
    "kg:order:ord_881#snapshot_kg_2026_05_03_T0930",
    "tool:adp_orders.lookup:tc_117",
    "tool:adp_policy.eval:tc_119",
    "tool:adp_payments.issue_refund:tc_121"
  ],
  "policy_decisions": [
    { "policy_decision_id": "pol_9900", "rule_ids": ["R_REFUND_REQUIRES_IDV"] },
    { "policy_decision_id": "pol_9901", "rule_ids": ["R_HIGH_VALUE_REQUIRES_APPROVAL"] }
  ],
  "approvals": [{ "gate_id": "GATE_FINANCE_APPROVAL", "approver": "user_finance_lead_77", "approval_mode_effective": "destructive", "evidence_snapshot_hash": "sha256:b2a1..." }],
  "controls_active": { "must_refuse": [], "must_escalate": [], "approval_gates_active": ["GATE_FINANCE_APPROVAL"], "redaction_rules_active": ["pan", "credit_card"] },
  "lineage": { "pack_version": "ctxpack.support@5.2.0", "snapshot_version": "kg_2026_05_03_T0930" },
  "trace_id": "4bf92f3577b34da6a3ce929d0e0e4736"
}

Notice three properties:

  • lineage pins both pack and snapshot — this is what makes replay deterministic.
  • evidence_refs covers all three entries from required_evidence in the spec.
  • policy_decisions[] lists every rule that fired, with policy_decision_id for cross-correlation.
4
Validate the record against the spec

The Decision Catalog component re-validates every record on emission. Failures are typed:

{ "error_code": "OUTCOME_OUT_OF_RANGE",   "detail": "Outcome 'partial_refund' is not in allowed_outcomes." }
{ "error_code": "EVIDENCE_MISSING",       "detail": "required_evidence 'identity_verified' did not appear in evidence_refs." }
{ "error_code": "APPROVAL_MODE_TOO_LOW",  "detail": "Spec requires destructive; tool emitted delegated." }

These are not exceptions — they are typed verdicts that the Critic surfaces back to the planner under the re-plan budget.

What’s next

Tutorial 4: Close the improvement loop — turn an operator correction into a release-gated StrategyRule.