Skip to main content

Customizing execution

Shaping how a tool is called​

Tool execution options control how your agent calls a tool, without changing what the tool does. You can say where each argument must come from, have Parlant check a risky call before it runs, and choose the message the customer reads while the tool works. Use them when a tool needs exact input or a mistake would be costly.

What you'll learn
  • How parameter options shape where an argument comes from and how the agent asks
  • How to mark a consequential tool so Parlant checks the call first
  • How to tell the customer what is happening while a tool runs
  • How to keep a result out of the session and declare overlapping tools
Talk this page through with your coding agent

Once your tool functions exist, there's still the question of how your agent should call them. A tool may need an exact account number from the customer, a check before it changes an order, or a useful progress message while it runs. The settings below configure those parts of the interaction around your existing tool functions.

Parameter options​

The purpose of parameter options is to give the model more to go on when it fills in a tool's arguments.

By default, the model extracts each argument from the conversation, guided only by the parameter's name and type. Often that's enough. When it isn't, your coding agent annotates the parameter with p.ToolParameterOptions.

This annotation rides on Python's typing.Annotated, a standard mechanism that attaches extra information to a type hint without changing the type itself. The options shape both how your agent extracts the value and how it asks the customer for it:

from typing import Annotated

import parlant.sdk as p


@p.tool
async def transfer(
amount: int,
to_account: Annotated[str, p.ToolParameterOptions(
description="The recipient's account name",
source="customer",
)],
) -> p.ToolResult:
...
OptionEffect
descriptionHow to extract the argument. Shown to the model.
sourceWhere the value may come from: "customer" (must be stated by the customer, never guessed), "context" (taken from session data, never asked for), or "any".
significanceWhy the parameter is needed. Helps the model explain itself when asking for the value.
examplesExample values, folded into the description.
hiddenInternal-only. The model never reveals, mentions, or asks the customer about it.
display_nameThe customer-facing name. The model uses it, and never the internal name, whenever it talks to the customer about the parameter.

For example, in the tool above, to_account must come from the customer. So if they haven't stated it, your agent asks for it rather than guessing.

That's the general behavior whenever a required argument is missing: your agent asks for it conversationally. It uses the display names you set, respects each parameter's source, and never surfaces hidden parameters.

Check consequential actions before execution​

Now that your agent can gather the right arguments, the next question is whether it should run the call at all. Some tools are risky to get wrong: charging a payment, making a booking. For those, you'll want the proposed call checked before it executes, not after.

To get that, mark the tool consequential=True. Parlant then checks each proposed call against your agent's instructions before execution:

@p.tool(consequential=True)
async def book_flight(destination: str, date: str) -> p.ToolResult:
...

Customer confirmation is one requirement Parlant handles for you. It instructs your agent to seek confirmation for consequential actions. But eligibility, limits, and other business rules matter too, and those are yours to define. Write the requirements for your particular action in its policy.

Note that review is skipped at Effort.MIN (the lowest effort level).

Learn more about consequential tools

Tool reviews explains the triggers and effort levels, and Running consequential tools builds a worked example.

Telling the customer what's happening​

Once a call is approved and running, the customer is left waiting. The purpose of narration is to tell them what your agent is doing in the meantime, in your words, not a generic placeholder.

While a tool runs, the customer sees a status. By default it's a generic "Running tools". Give the tool a narration and Parlant emits your message as the tool starts, so that's what the customer reads instead:

@p.tool(narration="Looking up your order…")
async def check_order_status(order_id: str) -> p.ToolResult:
...

Pass several, and Parlant picks one at random per call. This way, a customer who waits through the same lookup twice doesn't read the same line twice:

@p.tool(narration=["Checking with the warehouse…", "Looking up your order…"])
async def check_order_status(order_id: str) -> p.ToolResult:
...

For a message that depends on who is asking, pass a function instead. Parlant resolves it on the server once per call and sends only the resulting string to the client. Declare context: p.ToolContext to receive the agent, session, and customer ids. The function may be sync or async:

async def narrate_lookup(context: p.ToolContext) -> str:
customer = await customers.find(context.customer_id)
return f"Looking up {customer.first_name}'s order…"


@p.tool(narration=narrate_lookup)
async def check_order_status(order_id: str) -> p.ToolResult:
...

Note that narration is best-effort. If resolving it fails, Parlant logs a warning, keeps the default status, and runs the tool anyway. A status message must never cost a customer their answer.

How narration reaches your client​

Parlant streams progress to your client as status events, which is how a chat UI shows what the agent is doing.

Your message arrives as a status event with status="processing", once per tool call, right after the generic one. So a client that shows the latest status shows yours.

A narration is written for the customer, so under the default production authorization policy no permission gates it: guests and customers receive it as it is. What that policy holds back from them is the model's own reasoning, not your narration; see What gets hidden.

Keeping a result out of the session​

Once a tool has run, its result has to live somewhere. By default, Parlant saves it to the session and replays it on every later turn.

Sometimes that's not what you want. Return control={"lifespan": "response"} and the result serves only the current reply. After that, it's gone.

Learn more about tool results

Context window control covers when to do that, the size limit on results, and the other things that add up in a long conversation.

Overlapping tools​

Finally, there's one more situation worth handling: two tools that cover similar ground, say, a specialized search next to a general one. If your agent calls both for the same item, it duplicates work, or worse, a side effect.

The purpose of the overlap option on @p.tool is to declare that relationship, so the model knows to pick one. There are three settings: p.ToolOverlap.ALWAYS treats the tool as an alternative to every other tool offered alongside it, NONE never does, and AUTO, the default, follows declared relationships.

When any of the tools offered on a turn overlap, Parlant adds a note to the prompt listing the pairs and telling the model to choose one tool per item.

Note that different items in one request may still use different tools. For example, "transfer 200 shekels to Alisse and $300 to Bob" uses transfer_shekels for one and transfer_money for the other. And with no overlap configured, nothing is added to the prompt at all.