Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Chapter 38 — The Policy Agent

Resolution proposed an exception path for O-1001: stalled shipment, draft apology with reship-or-refund options, order total above the auto-refund teaching cap. The draft includes store identity and order number. SMS send window does not apply — this is email.

Policy decides whether that proposal is allowed. It runs the deterministic shop_v1 pack first: opt-out, send window (08:00–21:00 local for SMS/push), contact frequency, required message fields (store identity, order number, purpose), return window, refund/reship thresholds. Only if those checks pass may an optional model flag genuinely ambiguous wording for human review.

ResolutionProposal + draft body → deterministic shop_v1 → PASS | FAIL | NEEDS_HUMAN | GRAY → Outreach may draft; send waits on PASS + approval

A prompt that says “follow policy” is not enforcement. The same stalled-shipment evidence and refund amount must produce the same verdict in a test, in Maya’s review UI, and at send time. Without code-first Policy, send-window rules and required-field checks drift with model mood — and a model cannot turn a deterministic FAIL into PASS.

This chapter adds Policy: repeatable permit/deny with inspectable rule IDs, plus an optional gray classifier that may only tighten, never widen.

First principles

  1. Evaluate deterministic rules first. The fixture suite must be green before model assistance is enabled.
  2. Let the model narrow, never widen. It may add a GRAY review path; it cannot permit a denied action.
  3. Judge the action, not the prose alone. A draft, refund, reship, and customer contact can require different checks.
  4. Encode send window and required fields in code. SMS/push outside 08:00–21:00 local fails; email drafts can still be written. Missing order-number / store-identity tokens fail.
  5. Make the verdict inspectable. Store status, rule IDs, reasons, and policy version as data.
  6. Route uncertainty to a person. NEEDS_HUMAN and GRAY stop automatic execution.
  7. Reuse the same pack at enforcement. Separate rule text for review and execution will drift.

Concrete example

Resolution proposes an exception path for O-1001: the shipment is stalled, the order evidence is complete, the customer has not opted out, and there have been no recent contacts.

PolicyReview
  status: PASS
  policy_version: shop_v1.0.0
  rule_ids: [order_state_ok, contact_ok]
  reasons: [evidence complete, remedy within threshold]

The same proposal with an unsupported refund amount → NEEDS_HUMAN. An optional gray classifier that flags a confusing or overly assertive draft produces GRAY, even when the deterministic pack passes.

Diagram

flowchart TD
  Prop[ResolutionProposal + body] --> D[Deterministic pack]
  D -->|FAIL / NEEDS_HUMAN| Out[PolicyReview]
  D -->|PASS| G{gray classifier enabled?}
  G -->|no| Pass[PASS]
  G -->|yes| C{model ok?}
  C -->|yes| Pass
  C -->|no| Gray[GRAY → HITL]
 100% deterministic suite ──► green required
         │
         ▼
 optional gray-text model ──► can only tighten, never loosen

Caption: Notice the model has no edge that turns FAIL into PASS.

Implementation

Module: shopops/agents/policy_agent.py + pack policy/packs/shop_v1.py.

from shopops.agents.policy_agent import PolicyAgent, PolicyReviewStatus
from shopops.agents.resolution import ResolutionAgent

proposal = ResolutionAgent().propose(profile)  # from Ch 36–37
body = (
    "Your shipment has stalled. We can review a reship or refund for order O-1001."
)
comp = PolicyAgent()
result = comp.review(
    proposal,
    body=body,
    contacts_last_24h=0,
    contacts_last_7d=1,
    order_evidence_complete=True,
    send=False,
)
assert result.status == PolicyReviewStatus.PASS

# Deterministic suite before enabling model assist
fixtures = [
    {
        "name": "refund_requires_review",
        "expect_allow": False,
        "case_id": "O-1001",
        "channel": "email",
        "body": body,
        "contacts_last_24h": 0,
        "contacts_last_7d": 0,
        "order_evidence_complete": True,
        "refund_cents": 12_900,
        "is_draft_only": False,
        "customer_opted_out": False,
    },
]
assert all(ok for _, ok, _ in comp.deterministic_suite(fixtures))

Failure modes

FailureRiskFix
Prompt-only policyInjection / driftPack engine
Model overrides denyIllegal sendCode structure above
Divergent rules in prompt vs packFalse greenSingle pack source
Order-detail substring gamesWeak evidence checkStructured evidence references
Skipping suite in CIRegressionsGate deploys on suite

Production considerations

  • Jurisdiction packs selected by tenant (Ch 33).
  • Policy agent identity has policy:judge only — no send (Ch 27).
  • Record every verdict on the ledger; CONFLICT if Resolution disputes (Ch 17).
  • Gray-text model: version, eval rubrics, never in the hot path until suite green.

Chapter summary

  • Deterministic checks first; model only for gray text.
  • FAIL cannot be loosened by the model.
  • Draft and send differ under the pack.
  • Verdicts carry versions and rule ids.
  • Suite green is a release gate.
  • HITL for NEEDS_HUMAN and GRAY.
  • Same pack as executor enforcement.
  • Policy is SoD, not a personality prompt.

Exercises

  1. Mechanical. Run deterministic_suite with 10 fixtures covering shipment state, refund threshold, frequency, and opt-out; all green.
  2. Subordinate model. Inject a classifier that always returns ok=True on a denied refund; assert the verdict remains FAIL.
  3. Design. Define what “gray text” means for your channels; list labels humans must see.

References

  • Chapters 28, 17, 27, 29
  • Illustrative store return/refund and contact rules — not legal advice. [VERIFY SOURCE]