BondFoundry

Materiality-Driven Access Control: Why Tier Routing Matters for Bond Desks

T0 to T3 tier routing for AI agents on a fixed-income desk — with a worked $5M corporate-bond example and the split-trade bypass nobody talks about.

· 10 min read · For buy-side Cornerstone

A buy-side desk that gives an AI agent a flat “needs human approval for everything” policy will produce two outcomes within a week: trader fatigue, and silent bypass.

A desk that gives the same agent a flat “approval at the agent’s discretion” policy will produce one outcome on day one: the compliance officer pulling the plug.

The thing both extremes miss is that “approval” is a finite resource the system should spend on actions that genuinely cannot be undone. That is what tier routing is.

The four tiers

BondFoundry’s policy gate routes every governed action by reversibility, not by category:

  • T0 — read-only. price_bond, calculate_risk, run_scenario, query_audit. The agent executes; the action is auditable, but the consequence is observation, not state change.
  • T1 — reversible writes. ingest_curve, generate_eod_report. The agent executes and writes to audit; rolling back is straightforward.
  • T2 — single-HITL above materiality. create_trade, upload_trades_csv. Human approval before commit.
  • T3 — dual-HITL irreversible. delete_instrument, purge_trades, update_portfolio_policy. Two distinct approvers, both distinct from the agent caller, with manager role on at least one.

Notice what tier routing is not: it is not “trades require approval, other things don’t.” create_trade is T1 below materiality and T2 above it. The same tool name routes to different tiers based on the action’s context.

A worked example: $5M corporate-bond rebalance

The PM tells the agent: “Rebalance the IG book to neutral duration.”

The agent loads positions (T0), runs a KRD shock (T0), and proposes selling $5M of an IG corporate bond.

The proposal hits the policy gate:

{
  "tool": "create_trade",
  "isin": "US...0X9",
  "notional": 5000000,
  "side": "sell",
  "portfolio": "IG-CORP-USD"
}

The gate fires. The portfolio’s materiality threshold is $1M. The notional exceeds it. The verdict:

{
  "verdict": "hitl_required",
  "rule_id": "BF-MAT-T2-001",
  "verbatim_text": "Trades exceeding $1M notional require human approval prior to FIX submission.",
  "tier": "T2",
  "framework_ref": ["AIR-OP-6", "AIR-OP-4"]
}

The agent now requests an approval envelope. The server generates it, HMAC-signs it with a key the agent never sees, and scopes it via scope_hash = sha256("create_trade" || "US...0X9" || 5000000 || "sell").

The trader receives a Slack message. They review the proposed action and approve. The envelope arrives at the FIX gateway. The gateway verifies:

  • HMAC signature with the server secret — valid
  • scope_hash matches the action being executed — valid
  • approver identity is distinct from the agent caller — valid
  • envelope has not expired — valid

The FIX 4.4 NewOrderSingle fires. The ExecutionReport comes back. Three audit rows are written, each carrying framework_ref pointing to the AIGF mitigations they satisfy, each in the sha256 chain.

The auditor pulls any of those three rows three months from now and can verify the entire decision chain from the row’s context payload.

The split-trade bypass — and why session-aggregate materiality matters

Now imagine the same agent, same portfolio, same $5M intent — but the agent splits the order into seven sub-materiality slices. Each slice is below $1M. Each evaluates to T1 (reversible write). No HITL fires. By the end of the session the position has moved $5M without the trader seeing an approval request.

This is not a hypothetical. We saw it during fuzz testing on the first cut of our gate. The fix is a session-aggregate materiality ledger: cumulative notional within a session triggers HITL even if no individual slice does.

def materiality_check(action, context, session_ledger):
    cumulative = session_ledger.notional_for(action.portfolio, action.isin)
    if action.notional > THRESHOLD:
        return "hitl_required", "BF-MAT-T2-001"
    if cumulative + action.notional > THRESHOLD:
        return "hitl_required", "BF-MAT-AGG-001"  # session-aggregate
    return "allow", "BF-T1-RV-001"

Why portfolio-level overrides matter

A single global materiality threshold is wrong for two reasons:

  1. Liquidity varies. $1M on a US Treasury is one thing. $1M on an illiquid EM corporate is something else. The same notional carries different consequence.
  2. Mandate varies. A core IG book has different rebalancing tolerances than a high-yield alpha sleeve.

BondFoundry’s policy editor lets you set per-portfolio thresholds, with overrides for specific instrument classes. The editor itself is T3 — modifying a policy bundle requires manager + second approver, both distinct from the policy author. The rules cannot be changed by the agent caller.

What this looks like in audit

For every T2 or T3 action, the audit row includes:

  • rule_id + verbatim rule text that caused the routing
  • envelope_id + HMAC for the approval
  • approver identity (or identities, for T3)
  • the scope_hash that bound the envelope to this action
  • the framework_refs (AIR-OP-6, AIR-OP-4, etc.)
  • session-ledger snapshot at the time of decision

The auditor’s first question — “why did the agent route this to HITL?” — is answered by the rule text in the row. The second — “why was this notional flagged?” — is answered by the ledger snapshot. The third — “who approved it?” — is answered by the envelope payload.

There is no “let me check the application logs.”

Where tier routing breaks (and how to spot it)

In every system we have seen that claimed tier routing, the failure mode was one of:

  1. Tier assignment was a static map. Every create_trade was T2 regardless of notional. Result: trader fatigue and “approve all” macros.
  2. Materiality was a single global number. No portfolio-level overrides. Result: same approval workflow for $50K on a Treasury as $50K on an illiquid EM bond.
  3. Aggregate check was absent. Split-order bypass possible. Result: silent breach during automated rebalancing.
  4. SoD enforced in the UI. API endpoint accepted self-approval directly. Result: a determined operator (or a compromised agent) could route around the workflow.
  5. Policy edits did not themselves require dual-HITL. Manager could change thresholds and re-run an action that previously blocked. Result: the audit chain shows the change, but the consequence is the same.

If you are evaluating a vendor, ask for a demo of all five failure modes. The right answer is “here is the test that fails when we try.”

Where to start

If you are adding tier routing to an existing AI agent:

  1. Inventory every tool call your agent can make. Tag each with its reversibility.
  2. Set portfolio-level materiality thresholds with your risk team. Default conservative.
  3. Implement the session-aggregate ledger before the single-notional check ships to production.
  4. Move SoD enforcement from the UI into the API. The UI is for UX; the API is the control.
  5. Make policy edits themselves T3. The most dangerous action is the one that changes the rules.

For the technical shape, see the policy gate deep-dive; for the HMAC envelope mechanics, see HITL envelopes; for how the four pillars combine, see The Four Pillars of Governed AI in Finance.

Approval is a resource. Spend it where reversibility ends.


BondFoundry’s tier routing is open-source and MIT-licensed. GitHub. Book a walkthrough of how it lands on a real desk.

See it on a real desk

A 20-minute walkthrough of the policy gate, HITL queue, and audit chain.