Skip to main content

Let a customer navigate your app through chat

Section: DOC-MA-delegation-approvals-build-agent-with-tools#build-an-agent-that-uses-your-tools.

Let a user ask the assistant to open their profile in your app. Define a navigation tool, receive the requested action, check and execute it in your app, then return the actual result so the assistant can finish its reply. The optional connected-app step extends the workflow to a supported third-party account such as Google Calendar.

Client-side tools do not go through the approval queue. Approval is a separate mechanism for tools the platform runs but a human must authorise — see approval patterns. Your application must check the user's permissions, validate the arguments and obtain any required confirmation before running its own tool; the run waits for your result.

Start with one allowed route and one tool. The completed recipe opens the customer's profile, returns the actual navigation result and displays the assistant's reply for that run. Connected SaaS tools are a separate extension after the client-tool loop works.

Prerequisites​

Section: DOC-MA-delegation-approvals-build-agent-with-tools#prerequisites.

  • A secret key (sk_…) for your tenant, issued through your enabled management surface or account administrator. Backend calls use the secret key plus X-On-Behalf-Of to act on behalf of a specific user. See Authentication & API Keys.
  • A conversation thread. Create one with create-thread if you do not have one. See Build an AI chat assistant for the full setup.

Step 1: Describe the navigation your app can perform​

Section: DOC-MA-delegation-approvals-build-agent-with-tools#step-1-define-a-client-side-tool.

Client-side tools are actions that run in your app or backend, not on the platform. Declare them in clientTools when creating the thread — or per turn via overrideGenerationConfig:

curl -X POST https://api.travila.ai/api/v1/llm/create-thread \
-H "X-API-Key: sk_your_key_here" \
-H "X-On-Behalf-Of: user_123" \
-H "Content-Type: application/json" \
-d '{
"defaultGenerationConfig": {
"clientTools": [
{
"name": "navigate_to",
"serverId": "client",
"description": "Navigate the app to a given screen",
"parametersJsonSchema": {
"type": "object",
"properties": {
"screen": {
"type": "string",
"description": "Screen identifier, e.g. \"/settings\""
}
},
"required": [
"screen"
]
}
}
]
}
}'

Reference: Create a new conversation thread · Request fields.

Response:

{
"thread": {
"threadId": "b81d5345-c1f9-4fb9-b558-a6327c75b842"
}
}

Reference: Create a new conversation thread · Response fields.

Save the threadId — you'll pass it as conversationKey in every subsequent request.

Implement the corresponding action in your application before sending the test question. For this recipe, /profile must be an allowed route for the signed-in user. Declaring the tool schema does not implement navigation or validate arguments.

Step 2: Ask to open the profile and retain the pending call​

Section: DOC-MA-delegation-approvals-build-agent-with-tools#step-2-send-a-message-and-receive-the-call.

Send a message that makes the model want to navigate. The model reads your tool description and decides whether to call it.

Use send-message-sync for a bounded wait that can return the pending call directly. If the wait ends first or the connection drops, use the recovery flow below; do not resend the message.

curl -X POST https://api.travila.ai/api/v1/llm/send-message-sync \
-H "X-API-Key: sk_your_key_here" \
-H "X-On-Behalf-Of: user_123" \
-H "Content-Type: application/json" \
-d '{
"conversationKey": "b81d5345-c1f9-4fb9-b558-a6327c75b842",
"userMessage": {
"role": "ROLE_USER",
"content": [
{
"type": "CONTENT_PART_TYPE_TEXT",
"content": "Navigate to my profile."
}
]
}
}'

Reference: Send a message and wait for the result · Request fields.

Response:

{
"runId": "64403669-5989-4ec3-ad9c-d84223f9679f",
"status": "AGENT_STATUS_AWAITING_CLIENT_TOOLS",
"clientToolCursor": 1,
"pendingClientTools": [
{
"id": "call_abc123",
"name": "navigate_to",
"argumentsJson": {
"screen": "/profile"
},
"isClientTool": true,
"clientToolDeadlineAt": "2026-08-10T10:04:11Z"
}
]
}

Reference: Send a message and wait for the result · Response fields.

AGENT_STATUS_AWAITING_CLIENT_TOOLS means the run is paused on you. Take the id and name off the pending call, and keep clientToolCursor — you need all three in the next step.

Step 3: Navigate once and report what happened​

Section: DOC-MA-delegation-approvals-build-agent-with-tools#step-3-return-the-result-and-get-the-reply.

For navigate_to, check that screen names an allowed route and that the current user may open it. Run the action, then post its actual result with submit-client-tool-results-sync. The response may contain the assistant reply, another tool batch or a still-active outcome; inspect status before treating the turn as finished.

curl -X POST https://api.travila.ai/api/v1/llm/submit-client-tool-results-sync \
-H "X-API-Key: sk_your_key_here" \
-H "X-On-Behalf-Of: user_123" \
-H "Content-Type: application/json" \
-d '{
"conversationKey": "b81d5345-c1f9-4fb9-b558-a6327c75b842",
"clientToolCursor": 1,
"results": [
{
"toolCallId": "call_abc123",
"toolName": "navigate_to",
"resultJson": {
"navigated_to": "/profile"
}
}
]
}'

Reference: Submit client-side tool results and wait for the next segment · Request fields.

Response:

{
"runId": "64403669-5989-4ec3-ad9c-d84223f9679f",
"status": "AGENT_STATUS_COMPLETED",
"messages": [
{
"role": "ROLE_ASSISTANT",
"content": [
{
"type": "CONTENT_PART_TYPE_TEXT",
"content": "Taking you to your profile."
}
],
"finishReason": "stop"
}
]
}

Reference: Submit client-side tool results and wait for the next segment · Response fields.

After submitting the real result, handle another pending batch if one is returned; otherwise inspect the correlated run outcome. Follow the result contract before declaring the customer’s navigation complete.

Check the customer’s completed navigation​

Section: DOC-MA-delegation-approvals-build-agent-with-tools#run-it.

After the request above, verify the whole customer journey:

  1. The app received the complete navigate_to call, ID and current clientToolCursor.
  2. It validated /profile and the user's access before executing the action.
  3. The profile screen actually opened, and the submitted result describes that outcome.
  4. The same run reached a recognized terminal outcome, or another tool batch was handled with its new cursor.

Finished result: the screen is open and the assistant's available reply is tied to the completed action. If navigation failed, submit a truthful failure outcome rather than claiming it opened. The default five-minute answer window ends the wait on expiry; it does not establish whether an external action happened. Recover the original call before repeating an uncertain action.

Recover the call when your connection cannot stay open​

Section: DOC-MA-delegation-approvals-build-agent-with-tools#if-you-cant-hold-a-connection.

A backend worker or queue consumer may not want to park an HTTP request. The same tools work fire-and-forget: send with send-message, learn about the call from the llm.tool_call_started event or by polling list-pending-client-tools, and answer with submit-client-tool-results. Both styles are written out side by side in Use and approve tools.

Use the same route to recover after a dropped connection. Do not resend the original message — that can create duplicate work or apply the configured cancel, queue, reject or ignore policy.

Separate recipe: let the customer connect a calendar​

Section: DOC-MA-delegation-approvals-build-agent-with-tools#step-4-connect-a-third-party-app-optional.

Use a connected calendar when the assistant needs a supported external account. The hosted Pipedream integration must be enabled for your deployment; it exposes connected accounts through pipedream:{appSlug} MCP servers without your application storing their credentials.

  1. Follow the connected-account recipe to issue a single-use, expiring Connect link when the user chooses to connect. Redirect them through the authorized flow.
  2. After the return, list accounts to confirm the intended calendar exists; a redirect alone is not the result.
  3. Select pipedream:google_calendar for the conversation and inspect its discovered tools. Discovery occurs per turn, so a later connection is picked up on the next message.
  4. Ask for work supported by those tools, apply the required platform approval policy, and follow the run to its actual calendar outcome.

Finished result: the chosen calendar is connected and the requested calendar work has a known outcome. A connection or tool list alone does not establish that the work finished. Use connected-app recovery for an unhealthy account and tool recovery for a lost call response.

Diagnose a platform MCP tool independently​

Section: DOC-MA-delegation-approvals-build-agent-with-tools#try-it-out.

If the connected-app variant fails before a useful tool result, test an authorized platform tool directly using the exact discovered server, name and argument schema. That diagnostic bypasses the conversation; it cannot test the navigate_to action implemented in your app.

Keep direct memory-tool experiments to isolated or operator-qualified data. A caller-supplied memory user ID needs trusted-backend authorization; the authentication header does not automatically rewrite that argument. See memory scope before using real records.

Next steps​

Section: DOC-MA-delegation-approvals-build-agent-with-tools#next-steps.

I want to...Go to
Understand all tool sources and how they interactAgent tools
Connect users' third-party SaaS accountsConnected apps
Discover what MCP servers and tools are availableList available servers
Configure approval policies for platform toolsAgent tools — approval flow
Browse all LLM API endpointsLLM API Reference

Document ID: DOC-MA-delegation-approvals-build-agent-with-tools. Section identities and revisions.