> ## 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 Holo 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>;
};

Holo4 (`holo4-35b-a3b`, `holo4-27b`) and Holo3.1 (`holo3-1-35b-a3b`) use the standard OpenAI tool-calling API, so agent frameworks that speak it work as is. Read [Core concepts](/models-api/build-an-agent/core-concepts) first.

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

## Declare tools

Pass schemas in `tools` and set `tool_choice="required"` so the model acts every step. Do not put tool-format examples in the system prompt: the model renders calls in its native format from `tools`, and a conflicting example degrades quality.

<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

<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

Each call in `message.tool_calls` has a unique `id`, a `function.name`, and a JSON string in `function.arguments`. There is no `note` field: anything the model must carry across turns goes in the assistant `content`. Plug in your own `observation()` (the `<observation>` message from [Core concepts](/models-api/build-an-agent/core-concepts#observations)) and `execute(name, args)`. For a full runnable file, see the [browser agent](/models-api/cookbooks/browser-agent).

<CodeGroup>
  ```python Python theme={"system"}
  import json

  messages = [{"role": "system", "content": SYSTEM_PROMPT}, {"role": "user", "content": task}]

  for _ in range(MAX_STEPS):
      messages.append(observation())
      trim_to_last_n_images(messages, n=3)

      resp = client.chat.completions.create(
          model="holo4-35b-a3b",
          messages=messages,
          tools=tools,
          tool_choice="required",
          temperature=0.8,
          extra_body={"chat_template_kwargs": {"enable_thinking": True}},
      )
      msg = resp.choices[0].message
      messages.append({"role": "assistant", "content": msg.content, "tool_calls": msg.tool_calls})
      if not msg.tool_calls:
          continue

      call = msg.tool_calls[0]
      args = json.loads(call.function.arguments)
      if call.function.name == "answer":
          return args["content"]
      messages.append({"role": "tool", "tool_call_id": call.id, "content": str(execute(call.function.name, args))})
  ```

  ```typescript TypeScript theme={"system"}
  const messages: any[] = [{ role: "system", content: SYSTEM_PROMPT }, { role: "user", content: task }];

  for (let i = 0; i < MAX_STEPS; i++) {
    messages.push(await observation());
    trimToLastNImages(messages, 3);

    const resp = await client.chat.completions.create({
      model: "holo4-35b-a3b",
      messages,
      tools,
      tool_choice: "required",
      temperature: 0.8,
      ...({ chat_template_kwargs: { enable_thinking: true } } as any),
    });
    const msg = resp.choices[0].message;
    messages.push({ role: "assistant", content: msg.content, tool_calls: msg.tool_calls });
    if (!msg.tool_calls?.length) continue;

    const call = msg.tool_calls[0];
    const args = JSON.parse(call.function.arguments);
    if (call.function.name === "answer") return args.content;
    messages.push({ role: "tool", tool_call_id: call.id, content: String(await execute(call.function.name, args)) });
  }
  ```
</CodeGroup>

The loop rebuilds the assistant message from `content` and `tool_calls`, so it never replays the reasoning trace.

## Mix screen, code, and MCP tools

One toolbox can hold screen actions, a code runner, and an MCP server's tools. Holo4 picks one per step. The loop does not change: route each call to where it runs (screen driver, sandbox, or `session.call_tool`). Screenshots stay under the same 3-image budget whichever tool ran. Worked examples: [hybrid agent](/models-api/cookbooks/hybrid-agent) (browser plus code) and [MCP agent](/models-api/cookbooks/mcp-agent) (MCP schemas convert one to one).

## Format-specific pitfalls

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


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.