Skip to content
Patterns

Guardrails for agents

Put VAKYN between an agent and the APIs it calls. Before a call goes out, ask whether to allow it, hold it for a person or block it.

When to use it

  • An agent can take actions with real effects: refunds, emails to customers, changes to accounts or data.
  • Some of those actions are hard to undo, and you want a person to see them first.
  • You want a check on every call, not only on the ones you already suspect.

How it works

When the agent proposes a call, send the call, the agent's job and the context as the state. Ask a choice for the action (allow, hold for a person, block) and ask the facts that decide it as their own questions: can it be undone, how much damage could a mistake do, is it the agent's job. When every answer is sure, your code does what the action says: run the call or block it. Otherwise the call waits for a person.

  1. Agent

    Proposes a call

    • POST /payments/refunds
  2. Check

    State: call + job + context

    • actionchoice
    • irreversiblenoul
    • damagescore
    • within_jobnoul
  3. every answer sure

    Allow runs the call; block stops it.

    any answer unsure

    The call waits for a person.

The check runs before the tool call, outside the agent.

The example below runs this check on two EUR 900 refunds from a returns agent, with real answers. Step by step: How VAKYN works.

Questionsactionchoiceirreversiblenouldamagescorewithin_jobnoul
  • Clear case

    Acts automatically
    { "agent": { "name": "Returns assistant at Orla Cycles", "job": "Handles returns. May refund returned items to the original payment method, up to EUR 1,000 per order, once the warehouse has checked the return in." }, "call": { "api": "POST /payments/refunds", "body": { "order": "OC-58213", "amount": 900, "currency": "EUR", "to": "original payment method", "reason": "Returned, unused" } }, "context": { "order": "OC-58213, one e-bike battery, paid EUR 900.00 by card on 2026-09-28", "return": "Checked in by the warehouse on 2026-10-09: sealed, unused, serial number matches the order", "earlier_refunds_on_order": 0, "customer": "Customer since 2022, 11 orders, no earlier refunds, same card on file since 2022" }}
    QuestionAnswerConfidence
    actionallow1.00
    irreversibleyes 0.140.86
    damage0.21 of 30.79
    within_jobyes 0.990.99

    Every answer is at least 0.70 sure (lowest: damage 0.79), so your code acts on it.

  • Borderline case

    Goes to a person
    { "agent": { "name": "Returns assistant at Orla Cycles", "job": "Handles returns. May refund returned items to the original payment method, up to EUR 1,000 per order, once the warehouse has checked the return in." }, "call": { "api": "POST /payments/refunds", "body": { "order": "OC-60477", "amount": 900, "currency": "EUR", "to": "original payment method", "reason": "Parcel never arrived" } }, "context": { "order": "OC-60477, one e-bike battery, paid EUR 900.00 by card on 2026-10-02", "return": "None: the customer says the parcel never arrived", "carrier": "Tracking says delivered to a parcel locker on 2026-10-05", "earlier_refunds_on_order": 0, "customer": "First order, account opened 2026-10-01" }}
    QuestionAnswerConfidence
    actionblock0.93
    irreversibleyes 0.290.71
    damage0.77 of 30.23
    within_jobyes 0.420.58

    damage is only 0.23 sure, under 0.70, so a person decides.

Real answers from VAKYN-4B Q8_0. The rule: act when every answer is at least 0.70 sure; a noul's confidence is how far it leans either way.
curl -s "https://api.vakyn.com/v1/systemone" \  -H "Authorization: Bearer $TYPESAFE_API_KEY" \  -H "Content-Type: application/json" \  -o answer.json \  -d @- <<'JSON'{  "model": "jev-latest",  "state": {    "agent": {      "name": "Returns assistant at Orla Cycles",      "job": "Handles returns. May refund returned items to the original payment method, up to EUR 1,000 per order, once the warehouse has checked the return in."    },    "call": {      "api": "POST /payments/refunds",      "body": {        "order": "OC-58213",        "amount": 900,        "currency": "EUR",        "to": "original payment method",        "reason": "Returned, unused"      }    },    "context": {      "order": "OC-58213, one e-bike battery, paid EUR 900.00 by card on 2026-09-28",      "return": "Checked in by the warehouse on 2026-10-09: sealed, unused, serial number matches the order",      "earlier_refunds_on_order": 0,      "customer": "Customer since 2022, 11 orders, no earlier refunds, same card on file since 2022"    }  },  "questions": {    "action": {      "type": "choice",      "instructions": "What should the guardrail do with this API call?",      "criteria": {        "allow": "Let the agent make the call now",        "ask_human": "Hold the call until a person approves it",        "block": "Stop the call and tell the agent why"      }    },    "irreversible": {      "type": "noul",      "instructions": "Would this call be hard or impossible to undo once made?"    },    "damage": {      "type": "score",      "instructions": "If this call is a mistake, how hard is the damage to put right?",      "criteria": [        "Easy",        "Takes some work",        "Hard",        "Impossible"      ]    },    "within_job": {      "type": "noul",      "instructions": "Is this call part of the agent's job as described?"    }  }}JSON # Act when every answer is at least 0.70 sure; otherwise a person approves the call.jq -r '.answers as $a  | $a | map_values(if .type == "noul" then [.noul, 1 - .noul] | max else .confidence end)  | to_entries | min_by(.value) as $weakest  | if $weakest.value >= 0.70    then "act: \($a.action.choice) (damage \($a.damage.score) of 3)"    else "human: \($weakest.key) is only \($weakest.value * 100 | round / 100) sure" end' answer.json

The borderline case runs the same code with its own state. damage is only 0.23 sure, under 0.70, so it prints a human line and a person decides. Change the threshold to match what a wrong answer costs you.

Run it in the playground, with a threshold slider

Tips

  • Write the agent's job into the state in plain words: what it is for, what it may do and its limits. The question "is it the agent's job" is only as good as that description.
  • Ask the deciding facts as separate questions instead of one "is this safe?". When a call is held, the person sees why, and you can tune the rule per fact.
  • Log every check with its answers. It is a record of what the agent tried to do and why it was allowed.
  • The same rule as routing by confidence, applied to actions instead of messages.

Loading the docs…