Skip to main content
Every agent works out of a toolbox. Its environment supplies the core of it (a browser environment brings navigation, clicking, typing) and the agent layers its own built-in tools on top. Custom tools are the part you add: functions from your own code that complement that toolbox with anything it can’t reach on its own, like querying your database, calling an internal API, or looking up a customer record. Pass a function to the SDK and the agent uses it like any other tool: when it decides to call one mid-run, the SDK executes your function locally and the run continues with the result. The full loop is handled for you.

With the SDKs

Pass your functions via tools; the schema is derived from the signature and docstring in Python, or declared with tool() in TypeScript.
from hai_agents import Client

def get_order_status(order_id: str) -> str:
    """Look up the status of an order in our system."""
    return db.orders.get(order_id).status

client = Client()
result = client.run_session(
    agent="h/web-surfer-flash",
    messages="Check order 4242 and email the customer if it shipped.",
    tools=[get_order_status],
)
print(result.answer)
import { HaiAgentsClient, tool } from "hai-agents";

const getOrderStatus = tool({
  name: "get_order_status",
  description: "Look up the status of an order in our system.",
  inputSchema: {
    type: "object",
    properties: { order_id: { type: "string" } },
    required: ["order_id"],
  },
  fn: async ({ order_id }) => db.orders.get(order_id).status,
});

const client = new HaiAgentsClient();
const result = await client.runSession({
  agent: "h/web-surfer-flash",
  messages: "Check order 4242 and email the customer if it shipped.",
  tools: [getOrderStatus],
});
console.log(result.answer);
Functions may be sync or async, and exceptions are reported to the agent as tool errors instead of crashing the run. Tools execute in the process that polls the session, so they only run while your program is waiting on run_session / runSession (or a handle’s wait_for_completion / waitForCompletion with the same tools). Execution is at-least-once: if posting a result fails and the wait is retried, the tool may run again, so prefer idempotent tool functions for side-effecting operations.

Over the raw API

Without an SDK to run the loop, you declare the tools, watch for the agent to call one, and post the result yourself.
1

Declare the tools at session create

Declare the tools when creating the session, inline on the agent or via the agent.tools override for a registered agent:
Session create body
{
  "agent": {
    "name": "support-agent",
    "environments": [{ "kind": "web" }],
    "tools": [
      {
        "name": "get_order_status",
        "description": "Look up the status of an order in our system.",
        "input_schema": {
          "type": "object",
          "properties": { "order_id": { "type": "string" } },
          "required": ["order_id"]
        }
      }
    ]
  },
  "messages": "Check order 4242 and email the customer if it shipped."
}
2

Detect the pending call

Long-poll changes for an ActiveStateChangeEvent whose data.state is "awaiting_tool_results". Its data.pending_tool_calls lists each pending call as a { tool_name, args, id } object:
Pending tool call event
{
  "type": "ActiveStateChangeEvent",
  "data": {
    "state": "awaiting_tool_results",
    "pending_tool_calls": [
      { "tool_name": "get_order_status", "args": { "order_id": "4242" }, "id": "call_1" }
    ]
  },
  "timestamp": "2026-06-01T15:14:05Z"
}
3

Post the result

Execute the call and post the result, echoing the pending call back as tool_req:
curl -X POST "https://agp.eu.hcompany.ai/api/v2/sessions/$SESSION_ID/tool_results" \
  -H "Authorization: Bearer $HAI_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"kind": "tool_result", "tool_req": {"tool_name": "get_order_status", "args": {"order_id": "4242"}, "id": "call_1"}, "result": "shipped"}'
Send several at once with {"type": "batch", "results": [...]}. Report a failure as an error_event instead of a tool_result; it carries error, origin (both required), and the echoed tool_req:
Tool error report
{ "kind": "error_event", "error": "Order not found", "origin": "custom_tools", "tool_req": { "tool_name": "get_order_status", "args": { "order_id": "4242" }, "id": "call_1" } }
The agent resumes once every pending call has a result; calls still unresolved when the run ends (for example on max_time_s) fail with a model-visible error. Posting to a finished session returns 409.