Building Your AgentTools

Creating Tools

Create and manage tools for your agents

Tools extend what your agent can do during a session. You define what each tool does, what parameters it accepts, and whether the tool call is delivered to your client (implicit) or to your server (explicit).

Tools are reusable. Create a tool once, then attach it to as many sessions as you need.

Creating a custom tool

Create a tool via POST /public/functions:

curl -X POST https://companion-api.napster.com/public/functions \
  -H "X-Api-Key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "data": {
      "name": "get_order_status",
      "description": "Look up the current status of a customer order by order ID",
      "parameters": {
        "type": "object",
        "properties": {
          "order_id": {
            "type": "string",
            "description": "The unique order identifier"
          }
        },
        "required": ["order_id"]
      }
    },
    "flow": "explicit",
    "url": "https://your-api.example.com/tools/get-order-status",
    "headers": {
      "x-api-key": "your-internal-api-key"
    },
    "receiveMessages": false,
    "prompt": "Use this tool when the user asks about the status of their order."
  }'

The response returns the created tool with its ID, which you use to attach it to sessions. For the full request and response schema, see the API reference:

Execution flows

The execution flow determines where the tool call is delivered when the agent invokes it. You set the flow per tool when you create it.

You can mix implicit and explicit tools in the same session. Use implicit for tool calls handled by your client, and explicit for tool calls handled by your server.

Implicit

When flow is set to "implicit", the tool call is delivered to the connected client. For WebRTC sessions, this means the Web SDK running in the user's browser; for WebSocket sessions, your WebSocket client. Your client receives a function_implicitly_called event, executes the logic, and sends the result back. See Responding to tool calls for the event format.

{
  "flow": "implicit"
}

Explicit

When flow is set to "explicit", the tool call is forwarded to a URL you specify — either an HTTP endpoint or a WebSocket endpoint on your server. You can also pass custom headers for authentication or routing.

The flow is the same regardless of protocol:

  1. The LLM decides to call the tool based on the conversation and populates the parameters.
  2. The Companion API delivers the tool call to the url you configured, including the tool name and populated arguments.
  3. Your server executes the logic — for example, querying a database, calling an internal API, or triggering an action in a third-party system — and returns the result.
  4. The Companion API passes the result back to the LLM, which uses it to continue the conversation with the user.

Explicit tools require url and optionally headers in addition to the common fields.

Using an HTTP endpoint:

{
  "flow": "explicit",
  "url": "https://your-api.example.com/tools/get-order-status",
  "headers": {
    "x-api-key": "your-internal-api-key"
  }
}

Using a WebSocket endpoint:

{
  "flow": "explicit",
  "url": "wss://your-api.example.com/tools/get-order-status",
  "headers": {
    "x-api-key": "your-internal-api-key"
  }
}

When you use a WebSocket URL, the connection is established at the start of the session — not when the tool is invoked. This means the WebSocket is open for the entire duration of the session, making tool calls faster since there's no connection overhead when the agent invokes the tool.

receiveMessages

The receiveMessages field is only relevant for WebSocket-based explicit tools. When set to true, the conversation between the user and the agent is streamed to your WebSocket endpoint in real time as it happens — not just when the tool is called.

Because the WebSocket connection is open from the start of the session, enabling receiveMessages gives your server a live feed of the full exchange. By the time the agent invokes your tool, your server already has the complete conversation context and can provide a more informed response.

This is particularly useful for tools that need to understand the broader conversation — not just the specific parameters the agent populated for the tool call.

Writing effective tool prompts

The prompt field on a tool definition is the most important lever you have for controlling when and how the agent calls a tool. At runtime, the contents of prompt are injected into the system instruction alongside the tool's function declaration:

## tool_name(param1, param2)
[contents of the prompt field]

The tool name and parameters are generated automatically from your tool definition. The prompt field is your space to control everything else — when the tool fires, when it doesn't, and what the agent says (or doesn't say) to the user.

Structure of a good prompt

Every prompt should cover three things:

  1. When to use the tool — what user intent or conversation state should trigger it.
  2. When NOT to use the tool — what situations look similar but should not trigger it.
  3. How to behave when calling it — should the agent announce it, ask for confirmation, or call it silently?

A well-written prompt:

Use when the user asks to book, reserve, or schedule a table.
Do NOT use when the user is asking about menu items, hours, or general restaurant info.
Before calling, confirm the reservation details (name, date, time, party size) with the user and wait for explicit confirmation.

A poorly-written prompt:

Books a restaurant reservation.

The second version gives the agent no guidance on invocation timing, edge cases, or user interaction. It will misfire.

Invocation conditions

Write explicit positive and negative conditions using "Use when" and "Do NOT use when" to control when the agent reaches for a tool.

Positive conditions describe the user intent that should trigger the tool. Be specific — use concrete language the user might say:

  • Good: Use when the user asks to book, reserve, or schedule a table.
  • Bad: Use when relevant.

Negative conditions describe situations that look similar but should not trigger the tool. These matter more than most developers expect — models often call tools when the intent is close but not quite right:

  • Good: Do NOT use when the user is asking about menu items, restaurant hours, or general info.
  • Bad: (omitted entirely)

Sequencing conditions control the order in which tools fire. Use "only after" to require a prerequisite and "only if" to add conditional logic:

  • Only call after the user has provided their name, date, and time.
  • Only call if check_availability was called first and returned available slots.
  • After calling this tool, you can call confirm_booking or cancel_booking.

This prevents the agent from calling tools out of order or with missing information. If a tool requires parameters the user hasn't provided yet, instruct the agent to ask for them first.

"prompt": "Use when the user wants to book an appointment. Before calling, make sure the user has provided a preferred date, time, and service type — ask for any missing details. Only call after all three are confirmed. Do NOT use for cancellations."

Behavior patterns

You control what the agent says to the user around a tool call through the prompt field. There are three patterns, and you can mix them across tools in the same session.

Proactive (silent)

The agent calls the tool without announcing it or asking permission.

Best for read-only or low-stakes operations: looking up context, logging events, enriching data, tagging conversations. As a rule of thumb, if the tool only reads data and has no side effects, proactive is a safe default.

{
  "data": {
    "name": "log_mood",
    "description": "Logs the user's current mood for session analytics"
  },
  "prompt": "Call automatically whenever the user expresses a clear emotion (happy, frustrated, anxious, etc.). Do not announce it to the user. Do NOT call when the user is asking a factual question without emotional content.",
  "flow": "explicit",
  "url": "https://your-api.example.com/tools/log-mood"
}

Preamble (brief acknowledgment)

The agent says a short bridging phrase before or during the tool call so the user doesn't hear silence. This is the recommended default for most tools.

{
  "data": {
    "name": "check_order_status",
    "description": "Looks up the status of a customer order by order ID"
  },
  "prompt": "Use when the user asks about their order, delivery, or shipment status. Before calling, briefly let the user know you're looking it up (e.g., 'Let me check that for you'). Do NOT use when the user is asking about products, pricing, or returns.",
  "flow": "explicit",
  "url": "https://your-api.example.com/tools/order-status"
}

If you want a specific phrase instead of letting the agent generate one:

"prompt": "Use when the user asks about their order status. Before calling, say: 'One moment while I pull up your order.' Do NOT use for returns or cancellations."

Keep bridging phrases neutral — they should not imply success or failure before the tool has returned. Good examples: "Let me check that," "One moment," "Let me look into that." Bad examples: "Great news, let me confirm that," "I found your order, one second."

You can also guide what the agent says after the tool returns. This helps the agent transition naturally back into the conversation:

"prompt": "Use when the user asks about their order. Before calling, say 'Let me check on that.' After receiving the result, start with a brief opener like 'Here's what I found' and then share the details."

Confirmation (ask first)

The agent describes what it's about to do and waits for the user to say yes before calling the tool.

Best for write operations and high-stakes actions: anything that costs money, sends a message, makes a booking, changes account settings, or can't be easily undone. As a rule of thumb, if the tool changes state or has side effects, require confirmation.

{
  "data": {
    "name": "book_appointment",
    "description": "Books an appointment for the user"
  },
  "prompt": "Use when the user wants to schedule an appointment. Before calling, describe the appointment details (date, time) to the user and wait for explicit confirmation before proceeding. Do NOT call without confirmation. Do NOT use for cancellations or rescheduling.",
  "flow": "explicit",
  "url": "https://your-api.example.com/tools/book-appointment"
}

Mixing patterns across tools

A single session can include tools with different behavior patterns. Control the behavior per tool through each tool's prompt field:

get_weather (proactive)
→ "Call automatically when the user mentions weather, outdoor plans, or travel. Do not announce it."

search_products (preamble)
→ "Use when the user asks about products or wants recommendations. Before calling, say something like 'Let me find that for you.' Do NOT use for order status or returns."

process_payment (confirmation)
→ "Use when the user is ready to pay. Before calling, confirm the total amount and payment method with the user. Do NOT call without explicit confirmation. Do NOT use for refunds."

Voice-specific considerations

Tool calls in voice conversations behave differently than in text chat. Keep these in mind when writing prompts:

  • Silence is bad. In text, the user sees a loading indicator. In voice, they hear nothing — and assume the agent froze. If a tool takes more than a moment, use the preamble pattern so the user knows something is happening.
  • Fewer tools is better. Each tool adds tokens to the system instruction. More tools also increases the chance the agent calls the wrong one. Keep the set focused.
  • Keep prompts short. Every token in prompt counts against the instruction budget. Use short, direct sentences. Avoid paragraphs.
  • Don't duplicate the description. The description field already tells the agent what the tool does. Use prompt for when, when not, and how — not to repeat the description.

If prompt is left empty, the agent relies entirely on the tool's description to decide when to call it. This works for simple tools but gives you no control over invocation timing, negative conditions, or call behavior.

Managing custom tools

You can list, update, and delete your custom tools through the API:

On this page