Tool calling

Tools are how agents reach outside the prompt — to query the knowledge graph, hydrate entities, propose actions, or delegate work. This page covers the mechanics: what a tool definition looks like, how multi-round calling works, and how errors flow back to the agent.

Tool definition shape

Every tool is defined with a JSON Schema parameter spec and a typed return:

{
  name: 'graph_semantic_search',
  description: 'Find entities by semantic + fuzzy match. Returns ranked nodes.',
  input_schema: {
    type: 'object',
    properties: {
      query: { type: 'string', description: 'Natural-language query.' },
      kinds: { type: 'array', items: { type: 'string' }, description: 'Filter by node kind.' },
      limit: { type: 'integer', default: 10, maximum: 50 },
    },
    required: ['query'],
  },
}

The description field is what the model reads to decide when to use the tool. Write it like a help line: short, action-oriented, explicit about what the tool returns.

Multi-round semantics

The orchestrator can call multiple tools in a single user turn, serialized as a tool-use loop:

user: "Show me my pipeline this week"
  └─ assistant turn 1
       └─ tool_use(graph_semantic_search, {query: "deals updated this week"})
            ← tool_result: [12 nodes]
       └─ tool_use(dashboard_query, {kind: "pipeline_by_stage"})
            ← tool_result: {by_stage: {...}}
       └─ tool_use(read_entity, {id: "deal_abc", kind: "deal"})
            ← tool_result: {...full deal payload}
  └─ assistant turn 1 (continued, with all 3 results in context)
       └─ final text + citations

Cap is 8 tool rounds per user turn by default. Hitting the cap forces a final response — the model can't infinitely chain.

Parallel calls

The model often emits multiple tool calls in one response block when they're independent. The platform runs them in parallel and returns one tool_result block containing all results, preserving order. From the agent's view this is "one turn" — important for keeping the context window small.

Example: three semantic searches across different node kinds run concurrently → one round-trip latency instead of three.

Available tools (core)

ToolPurposeWhen the orchestrator picks it
graph_semantic_searchFind entities by questionAlmost every run — the discovery primitive
read_entityFull payload by idAfter search narrows to specific entities to dig into
dashboard_queryTyped metric query"How many X" / "show me top Y" questions
propose_actionWrite a planned action to TASKSAnything that mutates real state
delegateSpawn a subagentSub-problem with a named workflow

Surface-specific tools (some examples):

ToolSurfacePurpose
write_card_draftTodayEmit a daily card payload
execute_planned_actionInboxRun a confirmed action
query_kpiAnalyticsRun a registered KPI query
compose_postContentDraft a social post

Error handling

Tool failures return as tool_result blocks with an error field and (when possible) a remediation hint:

{
  "type": "tool_result",
  "tool_use_id": "toolu_01...",
  "is_error": true,
  "content": "graph_semantic_search returned no matches. Try broader kinds: or a more general query."
}

The orchestrator can:

  • Adjust parameters and retry (most common).
  • Substitute a different tool.
  • Answer with partial data, flagging the gap.
  • Bail out to the user with "I tried X, here's what failed."

Transient errors (rate limits, 5xx) are handled by the runtime, not the model — the orchestrator sees a clean response or a final exhausted-retries error.

Idempotency

Tools that mutate state (propose_action, execute_planned_action, write tools added by integrations) are idempotent via a request_id that the platform generates per call. Retries with the same request_id are deduplicated server-side, so a flaky network or a model that re-emits the same tool call doesn't double-fire.

For external integration writes (sending an email, posting to a social network), idempotency is enforced via a per-tenant outbox table — the worker checks the outbox before executing, so even a worker restart mid-flight can't double-send.

Confirmation gate

propose_action is the platform's confirmation-gate hook. It NEVER executes the action; it only writes the planned action to TASKS. The actual execution path runs from the Inbox after user confirm. See Tasks.

This is intentional — there's no "auto-send" tool. If a tool exists that mutates external state, it's marked as such in the registry and can only be invoked from a confirmation-gated execution context.

MCP tools

MCP servers extend the available tool set per-tenant. When a tenant connects an MCP server, its tools become visible to the orchestrator under a namespaced prefix:

mcp_<server>__<tool>

The orchestrator treats them like any other tool — they show up in tool_use blocks and contribute to the same multi-round loop. The 8-round cap and idempotency contract apply equally.

Tool budget

The platform tracks tool call cost (LLM tokens spent reasoning about each result + the result size in the context). When the running budget approaches the per-turn cap, the runtime emits a synthetic tool_result to the model saying "budget low, finish with what you have" — the model wraps up rather than chaining further calls. Hard cuts are rare; this is the soft brake.

Telemetry

Every tool call is logged in the TurnRecorder trace with:

  • Tool name + input
  • Latency
  • Result size (bytes + truncated/full)
  • Success / error / retry count

Visible in Settings → Agent runs → <run> → Tool calls. Useful when an agent run "feels slow" — usually one tool call dominates and surfaces immediately.

Read next