Guide
Business rules
Turn a rule like "credits under $50 go through, up to $500 need a check, above that a finance lead approves" into a threshold ladder. Each value gets exactly one outcome, and you can test it before it runs.
When to use this
Use a ladder when the decision depends on where a number falls: a credit or discount amount, an export's row count, a number of days. You could write it as three ordinary policy rules, but that fails in two quiet ways. lt 50 and gt 50 leave exactly 50 uncovered. And because policy rules all fire and the strongest wins, a higher band can never be more lenient than a lower one. A ladder has bands that are half-open by construction, so it has no gaps or overlaps. It selects one outcome per value, and it adds verify: run a check, then decide.
| You want to | Run |
|---|---|
| Author or update a ladder from YAML | agentfox policy rules apply |
| See the resolved bands | agentfox policy rules show |
| Try values without running anything | agentfox policy rules test |
| Find where two teams' ladders disagree | agentfox policy rules check |
| See every kind of guardrail that exists | agentfox policy catalogue |
| Map a sentence to a guardrail kind | agentfox policy rules suggest |
| Compile a written policy | agentfox policy compile |
| See the decision path stage by stage | agentfox policy rules graph |
A credit approval ladder, end to end
The agent is billing-ops. It credits customer accounts through a tool called billing.credit, which takes an amount in dollars. Everything below ran in a fresh project after agentfox init.
Write the ladder
credit-ladder.yaml key: billing-credit-ladder name: Account credit approval description: How much credit billing-ops may grant without a person. tool: billing.credit field: arguments.amount unit: USD owner: finance mode: observe bands: - upto: 50 outcome: allow reason: small goodwill credit - upto: 500 outcome: verify verify: check: billing.credit_history expect: {credits_last_90d: {op: lt, value: 3}} on_fail: escalate - upto: 5000 outcome: escalate approver_role: finance-lead - outcome: block reason: credits above $5,000 go through the finance system, not an agentfieldis a dotted path into the request. For a tool call, the arguments are underarguments.unitis one ofUSD,EUR,GBP,JPY,USD_CENTS,EUR_CENTS,GBP_CENTS,countordays. Usecountfor row counts.- Each band covers everything above the previous band's
upto, up to and including its own. Bands must ascend, and the last band must have noupto, so every value lands somewhere. outcomeisallow,verify,escalate,blockorredact. Averifyband must say what to check.on_pass(defaultallow),on_failandon_error(both defaultescalate) say what follows.toollimits the ladder to one tool. Leave it out and the ladder applies to every tool call that carries the field.
Apply it, and read back what you wrote
bash agentfox policy rules apply credit-ladder.yamlOutput ✓ billing-credit-ladder — 4 bands on arguments.amount (USD) (−∞, 50] → allow (50, 500] → verify via billing.credit_history, on fail → escalate (500, 5000] → escalate approver: finance-lead (5000, ∞] → block observe mode — the outcome is recorded, not applied.bash agentfox policy rules show billing-credit-ladderOutput billing-credit-ladder finance · observe How much credit billing-ops may grant without a person. (−∞, 50] → allow (50, 500] → verify via billing.credit_history, on fail → escalate (500, 5000] → escalate approver: finance-lead (5000, ∞] → blockApplying the same key again replaces the ladder and bumps its version.
--modeoverrides the file'smode.--agentscopes the ladder to one registered agent.showwith no key lists every ladder.Test values on both sides of every boundary
bash agentfox policy rules test billing-credit-ladder "50,50.01,500,4999,5000.01"Output value outcome why 50 allow small goodwill credit 50.01 verify arguments.amount = 50.01 USD, between 50 (exclusive) and 500 → v 500 verify arguments.amount = 500 USD, between 50 (exclusive) and 500 → ver 4999 escalate arguments.amount = 4999 USD, between 500 (exclusive) and 5000 → 5000.01 block credits above $5,000 go through the finance system, not an agentagentfox test ruleis the same command. Values that the ladder cannot read escalate rather than pass:bash agentfox test rule billing-credit-ladder '$120,120 EUR,-5,abc'Output value outcome why $120 verify arguments.amount = 120 USD, between 50 (exclusive) and 500 → ver 120 EUR escalate value is in EUR but the ladder is in USD; refusing to convert — -5 escalate negative value -5 — the bands were written for positives abc escalate 'abc' is not a numberA request that does not carry the field at all also escalates. A missing field is never a way past the ladder.
Watch it decide real tool calls
Ladders run on the tool-argument surface. The agent needs a grant for the tool first; without one, every call is refused by default-deny before the ladder matters. See Contain tool calls.
bash agentfox declare tool billing.credit --impact write --description "Credit a customer account" agentfox permit grant billing-ops billing.credit --yes agentfox policy rules apply credit-ladder.yaml --mode enforcecredit_agent.py from agentfox import AgentFox, ApprovalRequired, PolicyViolation nom = AgentFox(agent="billing-ops") for amount in (25, 120, 900, 8000): with nom.session(intent="resolve a billing complaint") as s: try: r = s.guard_tool("billing.credit", {"account": "acct_42", "amount": amount}) print(f"{amount:>5} {r.verdict:<9} {r.reason}") except ApprovalRequired as e: print(f"{amount:>5} approval {e.result.reason} (approval {e.result.approval_id})") except PolicyViolation as e: print(f"{amount:>5} blocked {e.result.reason}")Output 25 allow no policy rule matched 120 verify arguments.amount = 120 USD, between 50 (exclusive) and 500 → verify 900 approval arguments.amount = 900 USD, between 500 (exclusive) and 5000 → escalate (approval apr_01m46a0bpx5gr3knnz) 8000 blocked credits above $5,000 go through the finance system, not an agentescalatecreates an approval in the queue (see Approvals). The fired rule's id isbusiness.<key>, and itsevidenceis the full ladder decision.Run the check for a verify band
AgentFox does not own your tools, so it does not call the check itself. A
verifyresult hands you the check to run; you supply a runner, andrun_verificationappliesexpectand theon_pass/on_fail/on_erroroutcomes. A runner that raises, or no runner at all, giveson_error. A check that could not run has not passed.verify_band.py from agentfox import AgentFox from agentfox.capabilities.business import VerifySpec, run_verification nom = AgentFox(agent="billing-ops") def run_check(check: str, arguments: dict) -> dict: # Your code: look up the account's credit history. return {"credits_last_90d": 4} with nom.session(intent="resolve a billing complaint") as s: r = s.guard_tool("billing.credit", {"account": "acct_42", "amount": 120}) if r.verdict == "verify": rule = next(f for f in r.rules_fired if f["rule_id"].startswith("business.")) outcome = run_verification(VerifySpec(**rule["evidence"]["verify"]), run_check) print(outcome.outcome, "-", outcome.detail)Output escalate - billing.credit_history: credits_last_90d=4 fails lt 3
When two teams disagree
Support wrote its own limit on the same field: allow up to $100, then escalate to a support lead.
key: support-credit-limit
tool: billing.credit
field: arguments.amount
unit: USD
owner: support
bands:
- upto: 100
outcome: allow
- outcome: escalate
approver_role: support-leadagentfox policy rules apply support-credit.yaml
agentfox policy rules check1 conflict(s) across 2 rule(s)
high contradiction
at arguments.amount = 50.01 (USD), 'billing-credit-ladder' says verify and
'support-credit-limit' says allow. The stricter wins, but one of the two authors believes something
that is not happening
between billing-credit-ladder and support-credit-limitAt run time, when several ladders match, the strictest outcome wins. check exists because that hides a disagreement between two people. It tests every boundary of every ladder on the same tool and field, and reports one contradiction per pair. Ladders on the same field in different units are reported as critical instead:
critical unit-mismatch
ladders on 'arguments.amount' declare different units ['USD', 'USD_CENTS'] — one team's 100 is
another's 1.00, and no precedence rule makes that safeagentfox policy rules check exits 1 when it finds a conflict, and 0 with ✓ 1 rule(s), no conflicts otherwise, so it works as a CI step. --json prints the conflicts as a list with code, severity, detail, between, field and at_value.
agentfox policy lint is a different check. It lints the policy hierarchy (the security and containment packs, and their org, team, agent and environment layers) and does not look at ladders. With the two conflicting ladders above it printed no policy issues. See the policy language reference.
What can be expressed: the catalogue
Before you write YAML, check that the rule you have in mind is a kind AgentFox can enforce. There are 22 kinds. Ladders are one; others include approval requirements, entitlement filters, knowledge boundaries and PII detection.
agentfox policy catalogue --intent require_human kind intent stage decides
threshold_ladder require human tool_args Which of several outcomes applies, based on where a number falls.
approval_requirement require human tool_args Whether a named role must approve before the action proceeds.
escalation_policy require human conversation When a conversation must be handed to a person.
3 kind(s). `agentfox policy rules explain <kind>` for parameters and an example.Leave out --intent for all of them, or add --json. agentfox policy rules catalogue is the same command. agentfox policy rules explain gives one kind's parameters, what the request must carry for it to work, and an example:
agentfox policy rules explain threshold_ladder╭─ threshold_ladder ───────────────────────────────────────────────────────────╮
│ Threshold ladder │
│ Which of several outcomes applies, based on where a number falls. │
│ │
│ intent require_human │
│ nature deterministic │
│ stage tool_args │
│ yields allow, verify, escalate, block, redact │
╰──────────────────────────────────────────────────────────────────────────────╯
Needs the request to carry:
· a numeric field on the request
· an explicit unit or currency
Without these it is configured but inert.
Parameters:
field dotted path, e.g. arguments.amount
unit USD | EUR | GBP | JPY | *_CENTS | count | days
bands ordered; each has `upto` (inclusive) and an outcome; the last omits
`upto`
Example:
…agentfox policy rules suggest maps one sentence to likely kinds. It matches signals in the wording deterministically; it is not a model. Treat it as a starting point:
agentfox policy rules suggest "Credits over $500 need sign-off from a finance lead."
agentfox policy rules suggest "Do not answer questions about legal matters."
agentfox policy rules suggest "Be courteous to customers." approval_requirement 0.17 · Whether a named role must approve before the action proceeds.
threshold_ladder 0.08 · Which of several outcomes applies, based on where a number falls.
…
knowledge_boundary 0.50 · Whether the question is answerable from what this agent can reach.
…
No guardrail kind matched that wording.
Browse them with `agentfox policy catalogue`. A policy we cannot express is worth knowing about early.agentfox policy rules graph lists every guardrail in the order it runs, by stage (input, retrieval, tool_args, output, conversation, offline). Your ladders appear under tool_args:
tool_args
action_analysis What a generated SQL, shell or HTTP artefact would actua
approval_requirement Whether a named role must approve before the action proc
billing-credit-ladder arguments.amount in USD, 4 bands finance
capability_scope Whether this identity may call this tool with these argu
…
support-credit-limit arguments.amount in USD, 2 bands support
…
24 guardrail(s) across 6 stages.Compile a written policy
agentfox policy compile reads a .txt or .md policy and writes the rules it can. It lists every assumption it made, the questions it needs answered, and the sentences it cannot express.
# Billing assistant policy
Credits under $50 are auto-approved. Credits between $50 and $500 must run a fraud check before proceeding, and credits over $500 require approval from the finance team.
Never include an email address or phone number in a reply.
Do not answer questions about legal matters.
Agents must always be courteous to customers.agentfox policy compile billing-policy.md╭─────────────────────────────────── Compiled billing-policy.md ───────────────────────────────────╮
│ 60% of the governance in this document compiled without a question. │
│ 2 rule(s) ready · 1 to answer · 1 not expressible │
╰──────────────────────────────────────────────────────────────────────────────────────────────────╯
billing-policy-pii-detection confidence 0.85
kind pii_detection
entities ['EMAIL_ADDRESS', 'PHONE_NUMBER']
redaction mask
billing-policy-billing-credit confidence 0.70
kind threshold_ladder
tool billing.credit
field arguments.amount
unit USD
mode observe
≤ 50 allow
≤ 500 verify via risk.check
above escalate → finance
assumed governs the tool 'billing.credit'
inferred from the wording; no tool was named explicitly
assumed verification calls 'risk.check'
the policy says to validate but does not name the check
assumed each threshold is inclusive — 'under $10' and 'from $10' both put 10 in the lower band
the wording leaves the endpoint open and only one reading leaves no gap
Needs a decision
blocking This reads like knowledge boundary. What should systems_of_record, coverage_months,
answerable_types be?
the guardrail is clear from the wording but its settings are not stated, and a rule with empty
settings enforces nothing
from: Do not answer questions about legal matters.
not expressible as a guardrail: Agents must always be courteous to customers.Read the assumptions before you accept anything. Here the tool name was inferred and the check is a placeholder called risk.check. "Under $50" was read as including 50.
--json prints the same result as data: rules, review, unmappable, ignored, auto_rate and governance_sentences. For this document, auto_rate was 0.6 over 5 governance sentences. The heading was ignored, and the courtesy sentence was unmappable.
--apply saves the threshold ladders, in observe mode, and nothing else:
agentfox policy compile billing-policy.md --apply…
Saved 1 ladder(s) in observe mode. Run agentfox policy rules check, then promote with agentfox
policy rules apply <ladder.yaml> --mode enforce.
1 other rule(s) are not threshold ladders and were not saved — author them with their own
commands.The PII rule and the knowledge boundary have to be authored with their own commands (for the boundary, agentfox declare boundary; see Retrieval and answers). Without --apply, the compiler only reads the document; it writes nothing. To get suggestions for a single sentence, use agentfox policy rules suggest.
In the web app Policies → Rules
What can go wrong
band 1 ends at 50.0, at or below the previous band's 100.0 — bands must ascend. Order the bands byupto, smallest first.the final band must omit 'upto'. The last band is open-ended so every value is covered. Make itblockorescalateif large values should not pass.unit must be one of ('USD', …), got 'dollars'anda 'verify' band must declare what to check. The file is validated before anything is saved.- Every call is blocked, including small ones. The reason starts
no resolved identity for the caller, so it holds no grants (default deny). Register the agent (run it once), declare the tool and grant it. - Bands are reported but nothing happens. No enforcing policy governs the call; see the warning in the walkthrough.
unknown agent 'research-bot'fromrules apply --agent: nothing is saved, and the message lists the agents that exist. Register a new one withagentfox agents register research-bot.unknown rule '…'fromtestandunknown kind '…'fromexplain. The second one lists the valid kinds.- A ladder carries a band you did not mean. Count thresholds ("over 10,000 rows") become their own
countladder, separate from money ones, but the compiler still guesses the tool and the field. Check every band'sreasonin--jsonoutput before--apply, and prefer writing the YAML yourself for anything that moves money.
Limits
- A ladder bands one numeric field, on tool arguments only. It cannot read inputs or outputs.
- AgentFox does not run
verifychecks; your code runs them. compile --applysaves threshold ladders only. Compiled rules of other kinds are shown, not saved.- The compiler is deterministic and covers structured prose. It does not use a model.
- Business rules are partial overall; see Limits.