Agents API

PyPI Python versions License: MIT

Python SDK for H Company's Agents API.

Documentation  ·  Get an API key  ·  PyPI  ·  TypeScript SDK  ·  H Company

## Installation ```bash pip install hai-agents ``` Add the optional command-line tools with the `cli` extra: ```bash pip install "hai-agents[cli]" ``` Python 3.10 or newer is required. Get an API key at [platform.hcompany.ai/settings/api-keys](https://platform.hcompany.ai/settings/api-keys) and export it: ```bash export HAI_API_KEY=hk-... ``` With the `cli` extra, `hai login` stores a key once instead, and `Client()` picks it up. ## Quickstart Launch the built-in `h/web-surfer-pro` agent, which ships with its own browser, and describe the task in plain language. `run_session` polls until the agent finishes and returns the final answer. ```python from hai_agents import Client client = Client() result = client.run_session( agent="h/web-surfer-pro", messages="What are the top 3 stories on Hacker News right now?", ) print(result.status) print(result.answer) ``` `Client()` reads `HAI_API_KEY` from the environment, else the key `hai login` stored in `~/.config/hai/.env`. `result` is a `SessionRunResult`: `id`, `status`, `answer`, the accumulated `events`, and `final_changes`. ## Local agents `Client.local()` and `await AsyncClient.local()` run the agent on this machine through a local agent runtime, started on demand or passed in with `runtime=...`. Each environment picks `host="user_device"` or `host="cloud"`. `Client()` keeps using the hosted Agents API. Closing the client stops a runtime it started, unless other clients still have active sessions there. ```python from hai_agents import Client with Client.local(local_options={"binary_path": "/path/to/hai-agent-runtime"}) as client: session = client.sessions.create_session( agent={ "name": "local-example", "description": "Local workstation example", "instructions": "Answer the user's task using the workstation tools.", "environments": [ {"id": "workstation", "kind": "workstation", "host": "user_device"} ], }, messages=[{"type": "user_message", "message": "Print hello using the shell."}], max_steps=8, max_time_s=120, ) # Poll or steer the session here, before leaving the client context. ``` Inference stays hosted (`HAI_API_KEY`) unless you pass `inference=Inference.self_hosted(url, model=...)` (`from hai_agents_local.runtime import Inference`). One agent drives this machine's desktop at a time: a second process asking for a `desktop` or `workstation` bridge fails fast instead of sharing the mouse. `hai local stop` stops every local turn on the machine; so does a double Esc once `hai_agents_local.killswitch.arm_esc_listener()` is armed (macOS, Windows, and X11 Linux). ## How a session works A session is one run of an agent against a task. It moves through a small set of states: `pending`, `running`, and then a settled state such as `completed`, `idle`, `failed`, `timed_out`, or `interrupted`. You drive a session two ways. `run_session` creates it and blocks until it settles, which suits one-shot tasks. `start_session` creates it and returns a handle right away, so you can read and steer the agent while it works. ```python session = client.start_session( agent="h/web-surfer-pro", messages="Find the top story on Hacker News", ) print(session.id) result = session.wait_for_completion() print(result.status, result.answer) ``` ## Watch and steer a running session A handle bound to the session `id` exposes the full lifecycle. Read the agent's progress at three levels of detail: ```python session.status() session.changes(from_index=0) session.get() ``` `status()` is a cheap snapshot with the state, step count, and token usage. `changes(from_index=0)` long-polls for new events and the final answer. `get()` returns the full Session resource. While the session is not in a terminal state, you can intervene: ```python session.send_message({"type": "user_message", "message": "Only consider the last 24 hours"}) session.pause() session.resume() session.force_answer() session.cancel() ``` `send_message` redirects the agent on its next step and wakes an `idle` session. `pause` halts with state preserved until `resume`. `force_answer` makes the agent stop exploring and answer from what it has. `cancel` ends the session as `interrupted`. ## Multi-turn sessions By default a session ends as soon as the agent answers. Set `idle_timeout_s` to keep it open: after each answer the session goes `idle` and waits that long for your next message, carrying its full context and browser state across turns. ```python session = client.start_session( agent="h/web-surfer-pro", idle_timeout_s=600, messages="Find the top story on Hacker News", ) first = session.wait_for_completion() session.send_message({"type": "user_message", "message": "Now summarize its comments"}) second = session.wait_for_completion() ``` ## Structured output Pass a pydantic model as `answer_schema` and the agent's final answer comes back as a validated instance. The model's JSON schema is sent as the agent's answer format; the raw wire value stays at `result.final_changes.answer`. ```python from pydantic import BaseModel from hai_agents import Client class Job(BaseModel): title: str company: str class Jobs(BaseModel): jobs: list[Job] client = Client() result = client.run_session( agent="h/web-surfer-pro", messages="Find 3 open ML engineering roles in Paris.", answer_schema=Jobs, ) for job in result.answer.jobs: print(job.title, "@", job.company) ``` A completed answer that does not match the schema raises `AnswerValidationError`, with the raw payload on `.raw`. Sessions that end without completing return their raw answer untouched. ## Custom tools Expose your own Python functions to the agent. Pass them to `run_session` and the polling loop runs each one when the agent calls it, then posts the result back so the session continues. Any function with typed parameters and a docstring works; the input schema is derived from the signature. ```python from hai_agents import Client def get_weather(city: str) -> str: """Get the current weather for a city.""" return f"Sunny in {city}" client = Client() result = client.run_session( agent="h/web-surfer-pro", messages="What should I wear in Paris today?", tools=[get_weather], ) ``` Use the `@tool` decorator to override the name or description: ```python from hai_agents import tool @tool(name="lookup_order", description="Look up an order by its id.") def lookup(order_id: str) -> dict: return {"id": order_id, "status": "shipped"} ``` A tool that raises is reported to the agent as a tool error rather than crashing the run. With `AsyncClient`, tools may be `async def`. ### Prebuilt: one-time passwords (2FA) `hai_agents_tools` ships ready-made tools. `otp_tool` lets the agent ask for a one-time password, verification code, or confirmation link when a login or signup step needs one. Without a handler it prompts on stdin; `imap_otp_handler` reads the code straight from a mailbox over IMAP (for Gmail, use an app password). ```python import os from hai_agents import Client from hai_agents_tools import imap_otp_handler, otp_tool handler = imap_otp_handler( host="imap.gmail.com", username="agent-inbox@gmail.com", password=os.environ["GMAIL_APP_PASSWORD"], ) client = Client() result = client.run_session( agent="h/web-surfer-pro", messages="Log in to example.com and check for new notifications", tools=[otp_tool(handler)], ) ``` Like every custom tool, the handler runs entirely in your process: the IMAP credentials never leave your machine, and the agent only receives the single extracted code or link -- never mailbox contents. ## Browser profiles and vaults Start a session on a browser that already knows the user. A [browser profile](https://hub.hcompany.ai/agents-api/browser/profiles) restores saved cookies and storage from an earlier session, and a [vault](https://hub.hcompany.ai/agents-api/vaults/overview) lets the agent sign in to sites with secrets that never enter its context. Bind both through per-run overrides: ```python result = client.run_session( agent="h/web-surfer-pro", messages="Open my dashboard and report any new alerts", overrides={ "agent.environments[kind=web].browser_profile_id": "", "agent.environments[kind=web].vault_id": "", }, ) ``` ## Async `AsyncClient` mirrors `Client` for asyncio. Every session method is a coroutine. ```python import asyncio from hai_agents import AsyncClient async def main(): client = AsyncClient() result = await client.run_session( agent="h/web-surfer-pro", messages="What are the top 3 stories on Hacker News right now?", ) print(result.answer) asyncio.run(main()) ``` ## Inspect and share sessions List past sessions and create a public replay link: ```python page = client.sessions.list_sessions(size=10) for summary in page.items: print(summary.id, summary.status) link = client.sessions.share_session("") print(link.share_url) ``` ## Regions and configuration The client targets the EU region by default; pass `environment` to use the US region instead: ```python from hai_agents import Client, HaiAgentsEnvironment client = Client(environment=HaiAgentsEnvironment.US) ``` `Client` also accepts a custom `base_url`, and an `api_key` when you do not want to use the environment variable: ```python client = Client(base_url="https://agp.hcompany.ai", api_key="hk-...") ``` ## Errors ```python from hai_agents import AnswerValidationError, UnprocessableEntityError from hai_agents.core import ApiError ``` `ApiError` is the base for HTTP failures and carries `.status_code` and `.body`. `UnprocessableEntityError` is the 422 raised when a request fails validation. `AnswerValidationError` is raised when a completed answer does not match `answer_schema`, with the unparsed value on `.raw`. ## Webhooks Verify the signature on an incoming webhook before trusting it: ```python from hai_agents import verify_webhook, WebhookVerificationError event = verify_webhook(request_body, signature, timestamp, secret) print(event.type, event.data) ``` ## Command line The `cli` extra installs the `hai` command for driving agents from your terminal: ```bash hai login hai run "What's the top story on Hacker News?" hai sessions list hai sessions watch hai mcp install ``` `hai login` signs in with Google in the browser, or takes a key you paste from [platform.hcompany.ai/settings/api-keys](https://platform.hcompany.ai/settings/api-keys), and stores it in `~/.config/hai/.env`. Any other command does the same on first use in a terminal, so `hai login` is optional. Scripts and `--json` runs never prompt: pipe a key into `hai login --key` or set `HAI_API_KEY`. `hai mcp install` adds the hai-agents MCP server to Cursor, VS Code, Claude Code, and other MCP clients. Credentials resolve from `--api-key`, then `HAI_API_KEY`, then `~/.config/hai/.env`. Run `hai --help` for the full command set. ## Documentation Guides, core concepts, and the full API reference live at **[hub.hcompany.ai/agents-api](https://hub.hcompany.ai/agents-api/introduction)**. ## License [MIT](LICENSE)