Search

↑↓ to move↵ to openesc to closetop 6 documentation results

Book a demoTry TokenTriage

In development · Explore the product preview. Join the launch list or book a walkthrough.

Browse docs

Quickstart — arm one guard

Arm one PII guard in monitor mode end to end — the config, the boot, a request with an email in it, and the row that comes back.

TutorialLast updated

On this page

Ten minutes, one binding, no byte changed. At the end you will have a decision row carrying a guardrails block, and you will know what to read on it before you consider enforce.

In this tutorial we’ll arm the guard on a gateway you run yourself, in a configuration file. A managed gateway takes its bindings from the guardrail profile its applied release pins instead; which guards act on which request is the difference between the two.

1. Add a guardrails: block

Guardrails are off by default — an absent block is zero behavior change. This one arms two detectors on the input phase, in monitor mode.

# tokentriage.yaml
version: 1
server:
  listen: "127.0.0.1:8787"
  admin: "127.0.0.1:9090"
mode: ledger
upstreams:
  anthropic:
    base_url: https://api.anthropic.com
    dialect: anthropic
    api_key_env: ANTHROPIC_API_KEY
ledger:
  decision_log:
    path: ./decisions.jsonl

guardrails:
  enabled: true
  default_profile: standard    # applied to anonymous/keyless traffic; "" ⇒ off
  local_budget_ms: 50          # cumulative request budget for embedded detector work
  guards:
    - id: pii_in
      category: pii
      phases: [input]
      detectors: [pii.card, pii.email]
      mode: monitor            # enforce | monitor — `simulate` is not a mode
      action: redact           # one-rung sugar for `ladder: {low: redact}`
      rule_ref: POL-PII-1      # your auditable policy pointer
  profiles:
    standard: [pii_in]

2. Read the binding before you boot it

guardrails explain resolves one binding against the compiled engine — the action: sugar expanded, phases checked against each detector’s own declaration — so what it prints is what would actually run.

$ tokentriage guardrails explain --binding pii_in --config tokentriage.yaml
BINDING pii_in
  category:   pii
  phases:     input
  mode:       monitor   (a CONFIGURED mode; `simulate` is refused as one and is the simulate API's stamp)
  on_error:   open   (a detector that cannot run degrades to a disclosed suspension and traffic flows)
  rule_ref:   POL-PII-1

  GATES — what a finding must clear before the ladder is consulted
    threshold:  0
    bands:      none configured — every finding lands at severity `low`

  LADDER — the verb each severity resolves to (fall-down included)
    SEVERITY  ACTION  REVERSIBLE  THIS BUILD CAN DO IT
    low       redact  no          yes
    …

It goes on to print each armed detector’s covers and its NOT covered in full. Read the second one for anything you are about to arm — it is long on purpose.

3. Boot

$ tokentriage run -c tokentriage.yaml
{"level":"INFO","msg":"guardrails armed","bindings":1,"default_profile":"standard","detectors":2,"policy_sha":"c935358"}

4. Send a request with an email in it

Use obviously synthetic values. 4111 1111 1111 1111 is the standard test PAN, issued to nobody, and example.com is reserved for documentation.

$ curl -s http://127.0.0.1:8787/v1/messages \
    -H 'content-type: application/json' \
    -d '{"model":"claude-haiku-4-5","max_tokens":64,"messages":[
         {"role":"user","content":"Charge card 4111 1111 1111 1111 for [email protected]"}]}'

The response is unchanged — you are in monitor.

5. Read the row

After the request completes and its ledger append is visible, select the latest primary guardrail row. Primary rows have no kind; guardrail and other companion rows do. This single-request demo leaves those rows intact. For concurrent traffic, match the response’s x-tt-request-id instead of selecting the latest row; the Python/TypeScript recipe shows that lookup. A missing row means it is not available yet, not that inspection was clean.

$ jq -s 'map(select(.kind == null and .disclosures.guardrails != null)) | last | .disclosures.guardrails' decisions.jsonl
{
  "policy_ref": "standard@cfgc935358",
  "engaged": ["pii_in"],
  "fired": [
    {
      "binding": "pii_in",
      "category": "pii",
      "phase": "input",
      "detector": "pii.card",
      "detector_version": "pii.card-v1",
      "action": "redact",
      "effect": "annotated",
      "entities": [{ "type": "CREDIT_CARD", "count": 1 }],
      "reversible": false,
      "confidence": { "basis": "exact", "point": 1 },
      "rule_ref": "POL-PII-1"
    },
    {
      "binding": "pii_in",
      "category": "pii",
      "phase": "input",
      "detector": "pii.email",
      "detector_version": "pii.email-v1",
      "action": "redact",
      "effect": "annotated",
      "entities": [{ "type": "EMAIL", "count": 1 }],
      "reversible": false,
      "confidence": { "basis": "reported", "point": 0.85 },
      "rule_ref": "POL-PII-1"
    }
  ],
  "input_ms": 0,
  "output_ms": 0
}

Four things worth noticing, because each of them is load-bearing:

  • effect: annotated, not mutated. monitor mode changed nothing. The action still reads redact because that is the verb the ladder resolved to.
  • reversible: false on both. That bit is derived from the action table, never authored — redact is irreversible whatever the effect says.
  • Two different bases. pii.card says exact because a Luhn checksum and an issuer range passed. pii.email says reported — a raw, explicitly unlicensed score. Neither carries an interval, because no calibration artifact licenses one.
  • Findings omit matched text. Safe entity counts and execution metadata describe the guard. Separate content capture and other sinks retain their own access and retention policies.

6. Now measure, before you enforce

$ curl -s http://127.0.0.1:9090/tt/api/v1/guardrails/evidence | jq

Or open the guardrail panel in the dashboard’s Honesty center. Either way, the question you are answering is “what did this guard fire on that I did not want it to?” — Evidence & alerting is how.