> ## Documentation Index
> Fetch the complete documentation index at: https://hub.hcompany.ai/llms.txt
> Use this file to discover all available pages before exploring further.

> ## Agent Instructions
> H Platform has four products: the Agents API (managed computer-use agents, base URL https://agp.eu.hcompany.ai/api/v2 or https://agp.hcompany.ai/api/v2 for the US), the Models API (OpenAI-compatible Holo vision-language models at https://api.hcompany.ai/v1), HoloDesktop CLI (Holo on the user's own desktop), and HoloTab (a free no-code Chrome extension that runs Holo in the user's browser, with recordable routines and schedules).
> Authenticate with a bearer API key from the HAI_API_KEY environment variable. SDKs: `pip install hai-agents` (Python, `from hai_agents import Client`) and `npm install hai-agents` (TypeScript, `import { HaiAgentsClient } from "hai-agents"`). CLI: `hai`.
> Agents do work in a browser or on a desktop; describe the task as an imperative instruction. To run a task quickly, prefer the pre-built agent `h/web-surfer-flash`. Read results from the session's `latest_answer` after it reaches a terminal status.
> Sessions are the unit of work; wait for a terminal status (completed, failed, timed_out, interrupted) before reading the answer. Use webhooks or the `changes` long-poll endpoint to follow progress.

# Function calling

> The Holo3.1 agent loop with OpenAI-style tools and tool_calls, and the mistakes specific to this format.

export const ChatStrip = ({rows, repeatFrom = 1}) => {
  const Body = ({text}) => text.split(/(`[^`]+`)/).map((part, i) => part.startsWith("`") ? <code key={i} className="rounded bg-zinc-100 px-1 py-0.5 font-mono text-[12px] text-zinc-800 dark:bg-zinc-800 dark:text-zinc-100">{part.slice(1, -1)}</code> : <span key={i}>{part}</span>);
  const Row = ({role, body}) => <div className="flex items-center gap-3">
      <span className={`${role === "system" ? "w-[84px] shrink-0 rounded-md px-2 py-0.5 text-center font-mono text-xs bg-zinc-900 text-white dark:bg-zinc-100 dark:text-zinc-900" : role === "user" ? "w-[84px] shrink-0 rounded-md px-2 py-0.5 text-center font-mono text-xs bg-zinc-100 text-zinc-700 dark:bg-zinc-800 dark:text-zinc-200" : role === "assistant" ? "w-[84px] shrink-0 rounded-md px-2 py-0.5 text-center font-mono text-xs border border-zinc-300 text-zinc-700 dark:border-zinc-600 dark:text-zinc-200" : "w-[84px] shrink-0 rounded-md px-2 py-0.5 text-center font-mono text-xs border border-dashed border-zinc-400 text-zinc-600 dark:border-zinc-500 dark:text-zinc-300"}`}>{role}</span>
      <span className="text-sm leading-5 text-zinc-700 dark:text-zinc-300"><Body text={body} /></span>
    </div>;
  return <div className="not-prose my-6 overflow-x-auto">
      <div className="flex min-w-[520px] flex-col gap-2 rounded-xl border border-zinc-200 bg-white p-4 dark:border-zinc-800 dark:bg-zinc-950">
        {rows.slice(0, repeatFrom).map((r, i) => <Row key={i} {...r} />)}
        <div className="my-1 flex items-center gap-3 text-xs text-zinc-500 dark:text-zinc-400">
          <span className="h-px flex-1 bg-zinc-200 dark:bg-zinc-800" />
          <span className="whitespace-nowrap">repeats every step</span>
          <span className="h-px flex-1 bg-zinc-200 dark:bg-zinc-800" />
        </div>
        {rows.slice(repeatFrom).map((r, i) => <Row key={i} {...r} />)}
      </div>
    </div>;
};

export const Notice = ({kind = "note", title, children}) => {
  const kinds = {
    warning: {
      label: "User notice",
      icon: <>
          <path d="m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3" />
          <path d="M12 9v4" />
          <path d="M12 17h.01" />
        </>
    },
    gotcha: {
      label: "Gotcha",
      icon: <>
          <circle cx="12" cy="12" r="10" />
          <path d="M12 16v-4" />
          <path d="M12 8h.01" />
        </>
    },
    note: {
      label: "Note",
      icon: <>
          <circle cx="12" cy="12" r="10" />
          <path d="M12 16v-4" />
          <path d="M12 8h.01" />
        </>
    }
  };
  const k = kinds[kind];
  return <div className="notice my-6 rounded-xl border border-zinc-200 bg-white p-5 dark:border-zinc-800 dark:bg-zinc-950">
      <div className={`${kind === "warning" ? "not-prose flex items-center gap-1.5 text-xs font-semibold uppercase tracking-wide text-red-400/80 dark:text-red-400/70" : kind === "gotcha" ? "not-prose flex items-center gap-1.5 text-xs font-semibold uppercase tracking-wide text-amber-500/80 dark:text-amber-400/70" : "not-prose flex items-center gap-1.5 text-xs font-semibold uppercase tracking-wide text-zinc-400 dark:text-zinc-500"}`}>
        <svg className="h-3.5 w-3.5" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
          {k.icon}
        </svg>
        {k.label}
      </div>
      {title && <div className="not-prose mt-2 text-base font-semibold text-zinc-900 dark:text-zinc-100">{title}</div>}
      <div className="notice-body mt-3 text-sm leading-6 text-zinc-700 dark:text-zinc-300">{children}</div>
    </div>;
};

Holo3.1 (`holo3-1-35b-a3b`) speaks the standard OpenAI tool-calling API, its most natural format, so it plugs into agent frameworks that already do.

<Notice kind="note" title="Holo3.1 only">
  Holo3 (`holo3-122b-a10b`) does not support `tools`. Use [structured outputs](/models-api/build-an-agent/structured-outputs) for it.
</Notice>

Read [Core concepts](/models-api/build-an-agent/core-concepts) first: reasoning, coordinates, observations, and the `trim_to_last_n_images` helper used below.

## Declare tools

Pass tool schemas via `tools`, and set `tool_choice="required"` so the model acts on every step. Do not put tool-format examples in the system prompt. The model renders the call in its own native format from `tools` alone, and a conflicting example degrades quality. The example ships two tools (click, answer) for illustration. Real agents register a wider toolbox following the same pattern.

<CodeGroup>
  ```python Python expandable theme={"system"}
  tools = [
      {
          "type": "function",
          "function": {
              "name": "click",
              "description": "Click at (x, y) coordinates",
              "parameters": {
                  "type": "object",
                  "properties": {
                      "element": {"type": "string", "description": "Detailed description of the target UI element"},
                      "x": {"type": "integer", "description": "X coordinate as integer in [0, 1000]"},
                      "y": {"type": "integer", "description": "Y coordinate as integer in [0, 1000]"},
                  },
                  "required": ["element", "x", "y"],
              },
          },
      },
      {
          "type": "function",
          "function": {
              "name": "answer",
              "description": "Provide a final answer",
              "parameters": {
                  "type": "object",
                  "properties": {"content": {"type": "string", "description": "The answer content"}},
                  "required": ["content"],
              },
          },
      },
  ]
  ```

  ```typescript TypeScript expandable theme={"system"}
  const tools = [
    {
      type: "function",
      function: {
        name: "click",
        description: "Click at (x, y) coordinates",
        parameters: {
          type: "object",
          properties: {
            element: { type: "string", description: "Detailed description of the target UI element" },
            x: { type: "integer", description: "X coordinate as integer in [0, 1000]" },
            y: { type: "integer", description: "Y coordinate as integer in [0, 1000]" },
          },
          required: ["element", "x", "y"],
        },
      },
    },
    {
      type: "function",
      function: {
        name: "answer",
        description: "Provide a final answer",
        parameters: {
          type: "object",
          properties: { content: { type: "string", description: "The answer content" } },
          required: ["content"],
        },
      },
    },
  ] as const;
  ```
</CodeGroup>

## Chat layout

Tool results go back as `tool`-role messages keyed by `tool_call_id`:

<ChatStrip
  rows={[
{ role: "system", body: "your prompt, without tool-format examples" },
{ role: "user", body: "`<observation>` screenshot and/or text `</observation>`" },
{ role: "assistant", body: "`tool_calls: [{ id, function: { name, arguments } }]`" },
{ role: "tool", body: "`tool_call_id` matching the call, plus the result" },
]}
/>

## A complete loop

The action comes back in `message.tool_calls`; each call carries a `function.name`, a JSON `function.arguments` string, and a unique `id`. There is no `note` field in this format: anything the model must carry across turns goes in the assistant `content`. Highlighted lines are the ones specific to function calling; everything else is the shared loop from [Core concepts](/models-api/build-an-agent/core-concepts). Plug in your own `screenshot()` (browser, OS, emulator) and `execute(name, args)` dispatcher.

<CodeGroup>
  ```python Python highlight={18-19,25-26,32-36} theme={"system"}
  import json, base64

  messages = [{"role": "system", "content": render_prompt()}]

  for _ in range(MAX_STEPS):
      image_bytes = screenshot()
      b64 = base64.b64encode(image_bytes).decode()
      messages.append({"role": "user", "content": [
          {"type": "text", "text": "<observation>\n"},
          {"type": "image_url", "image_url": {"url": f"data:image/png;base64,{b64}"}},
          {"type": "text", "text": "\n</observation>"},
      ]})
      trim_to_last_n_images(messages, n=3)

      resp = client.chat.completions.create(
          model=MODEL_NAME,
          messages=messages,
          tools=tools,
          tool_choice="required",
          temperature=0.8,
      )
      msg = resp.choices[0].message
      messages.append(msg)

      call = msg.tool_calls[0]
      args = json.loads(call.function.arguments)

      if call.function.name == "answer":
          return args["content"]

      result = execute(call.function.name, args)
      messages.append({
          "role": "tool",
          "tool_call_id": call.id,
          "content": str(result),
      })
  ```

  ```typescript TypeScript highlight={18-19,25-26,33-37} theme={"system"}
  const messages: any[] = [{ role: "system", content: renderPrompt() }];

  for (let i = 0; i < MAX_STEPS; i++) {
    const b64 = (await screenshot()).toString("base64");
    messages.push({
      role: "user",
      content: [
        { type: "text", text: "<observation>\n" },
        { type: "image_url", image_url: { url: `data:image/png;base64,${b64}` } },
        { type: "text", text: "\n</observation>" },
      ],
    });
    trimToLastNImages(messages, 3);

    const resp = await client.chat.completions.create({
      model: MODEL_NAME,
      messages,
      tools,
      tool_choice: "required",
      temperature: 0.8,
    });
    const msg = resp.choices[0].message;
    messages.push(msg);

    const call = msg.tool_calls![0];
    const args = JSON.parse(call.function.arguments);

    if (call.function.name === "answer") {
      return args.content;
    }

    const result = await execute(call.function.name, args);
    messages.push({
      role: "tool",
      tool_call_id: call.id,
      content: String(result),
    });
  }
  ```
</CodeGroup>

## Format-specific pitfalls

| Symptom                            | Likely cause                                                                                        |
| :--------------------------------- | :-------------------------------------------------------------------------------------------------- |
| Tool calls come back as plain text | `tool_choice` not set to `required`, or the system prompt contains conflicting tool-format examples |
| Tool result ignored                | Sent as a `user` message instead of a `tool`-role message with a matching `tool_call_id`            |
| Model forgets earlier facts        | Nothing written to the assistant `content`                                                          |

## Next steps

<CardGroup cols={2}>
  <Card title="Element localization" icon="crosshairs" href="/models-api/element-localization">
    Get click coordinates from a screenshot.
  </Card>

  <Card title="API reference" icon="code" href="/models-api/api-reference">
    Endpoint, models, parameters, and limits.
  </Card>
</CardGroup>
