Skip to main content

Tool reviews

Checking a tool call before it runs​

A tool review is a second check on an action before your agent takes it. When the agent wants to call a tool, such as cancelling an order, Parlant first compares that call with your instructions. If the call would break a rule, it never runs, and the agent tries again with a correction. You turn reviews on only where a mistake would be costly, so you don't pay for them everywhere.

What you'll learn
  • What a tool review checks, and why it runs as a separate pass
  • Three ways to turn reviews on: for the whole agent, per rule, or per tool
  • What happens on a clean verdict and on a breach, and how many restarts a turn gets
  • How to pick the review model and watch reviews with a hook
Talk this page through with your coding agent

Here is a small example. A customer asks your agent to cancel an order that has already shipped. The agent understands the request. But if your instructions say that only pending orders can be cancelled, calling the cancellation tool would break that policy. A tool review catches this: Parlant checks the proposed action against your instructions before it runs.

If the proposed calls would break your agent's instructions, Parlant drops them and restarts the step with corrected reasoning. A rejected call never runs.

To see where review fits, remember that a turn can take several steps. In each step, the model decides what to do next and may propose tool calls. The last step drafts the reply. Tool review sits between proposing a call and running it.

Why a separate pass?​

The model that proposes a call does not check itself, because writing a response and judging it are different jobs, and they pull in different directions. While trying to be helpful, the model can call a tool the situation doesn't justify, or skip a confirmation that a policy requires.

Asking the same pass to catch its own mistake isn't reliable, because the reasoning that made the mistake is the same reasoning asked to find it. So Parlant gives the check its own model pass and its own prompt, with no attachment to the draft.

Turning it on​

You can ask for tool review in three ways, and it runs if any of them applies. Nothing can cancel a review that something else requested. Your coding agent adds these settings; here is what each one does.

Agent-wide. Parlant reviews every proposed call set (i.e., every batch of tool calls a step proposes):

import parlant.sdk as p

agent = await server.create_agent(
id="banking",
name="Banking Agent",
instructions="Help customers manage their accounts.",
tool_review=True,
)

This is the simplest way to try the feature, but it adds delay and cost to every turn that calls a tool. In practice, the more targeted triggers below usually serve you better.

Per rule. A rule is either a policy or an observation that Parlant checks against the conversation. A policy is a rule that says what to do; an observation only names a situation. Either can request review, but only on the turns where it matched:

await agent.create_policy(
title="Wire transfers",
content="Execute wire transfers only after verbal confirmation of the amount and recipient.",
tools=[execute_wire_transfer], # the tools this policy may call
trigger_tool_review=True, # review only on turns where this policy matched
)

Per tool. A consequential tool is one whose effect is hard to undo once it runs, such as cancelling an order or moving money. When you mark a tool consequential, Parlant reviews every call set that includes it, whatever your agent or rule flags say:

@p.tool(consequential=True)
async def cancel_order(order_id: str) -> p.ToolResult:
...
Worked example

Running consequential tools shows the three triggers on one worked example.

Setting trigger_tool_review on a rule also makes Parlant match that rule more carefully. Review should fire only on turns where the rule really applies, so Parlant gives it its own "does this apply right now?" check each turn, instead of relying on similarity search alone.

What happens on a verdict​

Parlant hands the reviewer the step's reasoning and the proposed calls, together with the turn's instructions. The reviewer then answers one question: do these calls break or diverge from those instructions?

A clean verdict​

On a clean verdict, Parlant commits the calls and executes them in order.

Even then, the reviewer may return a todo: a short summary of what your agent must still do before responding. Parlant stores it in the turn state and renders it into the next step's instructions. This keeps a multi-step task on track without rejecting anything.

A breach​

On a breach, the reviewer returns adjusted_reasoning: a complete replacement, written as if your agent had reasoned correctly from the start.

Parlant discards the proposed calls without running them, replaces the faulty reasoning, and restarts the step. The next attempt never sees the rejected calls, only the corrected reasoning.

How many restarts a turn gets​

Each rejection restarts the step, but not forever. How many restarts a turn allows depends on its effort: the per-turn setting that decides how much model work a turn gets, from MIN to MAX. Your agent sets a baseline, and a rule can raise it.

The budget is two restarts at LOW, three at MEDIUM, five at HIGH, and ten at MAX. At MIN, no reviews run at all, whatever was requested.

info

Adaptive effort covers the effort levels in detail, and Message reviews shows how to change the restart budget.

Choosing the review model​

A model route is a named kind of work in the models mapping you pass to the server, so that each kind of work can run on its own model. The reviewer requests its own route, review.tools. This lets you run the check on a cheaper or stricter model than generation uses, and even vary it by effort:

async with p.Server(
models={
"*": "openai",
"review.*": p.ModelConfig("anthropic:claude-haiku-5-5"),
"review.tools[effort>=high]": p.ModelConfig("openai:gpt-6-luna", reasoning="medium"),
},
) as server:
...

When your route doesn't set reasoning, the reviewer picks its own depth. It looks at the turn's effort and whether any high-weight rules matched. So a high-stakes turn gets a more thorough review on its own, without you having to ask.

Watching reviews happen​

You'll often want to see reviews as they happen: for an audit trail, for metrics, or to work out why a step restarted. Parlant supports this with engine hooks, which your coding agent can add.

An engine hook is a function you register once at startup, and Parlant calls it at a named point in every turn. Two hooks bracket the review path: on_reviewing_tool_calls fires before the verdict, with the proposed calls, and on_tool_calls_reviewed fires after it.

You register hooks through initialize_container, a callback the server runs at startup. It receives the container (the object that holds the server's components), and p.CompassHooks is the component that keeps the hook lists:

async def initialize_container(container: p.Container) -> None:
hooks = container[p.CompassHooks] # the component that holds every hook list

async def on_tool_calls_reviewed(
context: p.EngineContext,
payload: p.CompassHookPayload.ToolCallsReviewed,
exc: Exception | None,
) -> p.CompassHookResult:
if not payload.passed:
context.logger.warning(f"Tool calls rejected: {payload.adjusted_reasoning}")
return p.CompassHookResult.CALL_NEXT # let any later hook on this point run too

hooks.on_tool_calls_reviewed.append(on_tool_calls_reviewed)


async with p.Server(initialize_container=initialize_container) as server:
...

The payload carries tool_calls, passed, todo, and adjusted_reasoning. Every engine hook receives (context, payload, exc) and returns a p.CompassHookResult.

Learn more about hooks

Hooking into the engine lists the other hook points.