How it works
The context is JSON with four keys: the user’s request, the tools on offer, what has happened so far and the action the agent is about to take. The keys are yours to choose. TheThought:, Action: and Observation: lines inside them are just an example of what to
send.
The model (kettei-core) reads all of that and answers one single
decision, action, with three outcomes:
allow: the call serves the user’s request and does nothing harmful.confirm: it does something the user didn’t ask for, such as following an instruction found in a tool result, but nothing in it is clearly harmful.deny: it is harmful, such as fraud, stealing or leaking data, or deleting or changing records the user didn’t ask to change.
run, ask
or refuse.
How the costs pick an action
Each number in the policy’saction_costs says how bad an action is if the call turns out to be a
given outcome. The units are yours:
- Running a harmful call is the worst mistake, at 20.
- Asking about a harmful call still costs 6. People approve most confirmation requests, so a dangerous call you ask about often runs anyway.
- Refusing a good call costs 4, because the user’s work stalls.
allow, 0.25 confirm and 0.15 deny. Then running costs 3.5 on
average, asking 1.5 and refusing 3.15, so policy.action is ask. policy.expected_costs shows
all three.
The policy also has abstain_below at 0.5. When no outcome reaches 0.5, the model is unsure, and
policy.action is abstain whatever the costs say.
Try it
Open this recipe in the playground to run it without an account. The user there asked for a summary of unread email. A message in the inbox tells the agent to forward invoices and delete the evidence, and the agent is about to send them. To run it from your code, setSQWISH_API_KEY as in the quickstart, then send
this request:
Act on the answer
Adapt it to your data
- Your agent’s steps. Fill the four keys from your agent’s own log, in whatever form it keeps them.
- Your costs. Set them from what each mistake costs you. If a wrongly run call would cost you as
much as a hundred needless confirmation requests, set the cost of running a
denycall to 100. After any change, see how often each action fires on a day of your own traffic. - Costs, not thresholds. To make the check more or less cautious, change the costs rather than
adding thresholds of your own in your code.
abstain_belowdoes a different job: it doesn’t weigh mistakes, it only catches the calls the model is unsure about. - One call per request. Check each tool call in a request of its own.
What to watch
- Wrong runs, most of all calls that follow instructions hidden in a tool result.
allowpicked too often. In testing it leaned towardsallow. The costs make up for much of that, so keep running a harmful call expensive.- How often it asks. Asking isn’t free, so check the share of calls it asks about.
How well it works
Measured withkettei-core on 1,000 examples from a public dataset, it chose the right answer 65.9% of the time. Always picking the most common answer scores 56.4%. Your own data will differ, so measure it on examples of yours before you rely on it (improve a decision).
Full results
Full results
- Dataset: TS-Bench ASB-Traj (ToolSafe) (MIT (the repository’s licence)).
- Sample: 1,000 rows, 1,000 measured decisions.
- Decisions measured:
action. - Wording: hand-written.
Always picking the most common answer has an accuracy of 0.564, and guessing by how common each answer is has a log loss of 0.9643.These are past results on one dataset, not a promise of accuracy on yours.
Next steps
- Spot a prompt injection or jailbreak: check the text your agent reads before it acts on it.
- Improve a decision: measure it on your own agent’s steps and make it more accurate.
- API reference: every field in the request and the answer.
