# hollow-sdk (Python)

Python SDK for [Hollow](https://github.com/Badgerion/hollow) — serverless web perception for AI agents.

Gives any Python application a browser that runs without a machine: no Chromium, no BaaS, no GPU.

## Install

```bash
pip install hollow-sdk
```

## Usage

### Single page

```python
from hollow import HollowClient

client = HollowClient()
page = client.perceive("https://news.ycombinator.com")

print(page.gdg_map)
# → [TEXT: news.ycombinator.com]
#   [Stories:]
#     [1] "Ask HN: Who is hiring? (March 2026)"  comments:834
#     ...

print(page.tier)        # 'text'
print(page.confidence)  # 0.95
```

### Agent task

```python
from hollow import HollowClient, run_agent

client = HollowClient()

# Default — Anthropic (reads ANTHROPIC_API_KEY)
result = run_agent(
    client,
    task="What are the top 3 stories on Hacker News right now?",
    on_step=lambda s: print(f"Step {s.step}: {s.tier} ({s.confidence:.2f})"),
)

print(result)
```

## Works with any model

The agent loop is model-agnostic. Pass any adapter — or a plain callable.

### Anthropic (default)

```python
from hollow import AnthropicAdapter, run_agent

result = run_agent(client, task="...",
    model=AnthropicAdapter(model="claude-opus-4-6"))
```

### OpenAI

```bash
pip install 'hollow-sdk[openai]'
```

```python
from hollow import OpenAIAdapter, run_agent

result = run_agent(client, task="...",
    model=OpenAIAdapter(model="gpt-4o"))
```

### Any OpenAI-compatible API (Groq, Together, Ollama…)

```python
from hollow import OpenAIAdapter, run_agent
import os

# Groq
result = run_agent(client, task="...",
    model=OpenAIAdapter(
        api_key=os.environ["GROQ_API_KEY"],
        base_url="https://api.groq.com/openai/v1",
        model="llama-3.3-70b-versatile",
    ))

# Local Ollama
result = run_agent(client, task="...",
    model=OpenAIAdapter(
        api_key="ollama",
        base_url="http://localhost:11434/v1",
        model="llama3.2",
    ))
```

### Bring your own model

Implement `complete(messages) -> str` — one method:

```python
class MyAdapter:
    def complete(self, messages: list[dict]) -> str:
        # messages: [{"role": "system"|"user"|"assistant", "content": str}, ...]
        return my_model.chat(messages)

result = run_agent(client, task="...", model=MyAdapter())
```

Or use a plain callable:

```python
result = run_agent(client, task="...",
    model=lambda msgs: my_model.chat(msgs))
```

### Custom endpoint

```python
client = HollowClient(endpoint="https://my-hollow-instance.vercel.app")
# or: export HOLLOW_ENDPOINT=https://my-hollow-instance.vercel.app
```

### Context manager

```python
with HollowClient() as client:
    page = client.perceive("https://example.com")
    print(page.gdg_map)
# httpx client is closed automatically
```

## API

### `HollowClient(endpoint?)`

| Param | Default |
|-------|---------|
| `endpoint` | `https://hollow-tan-omega.vercel.app` |

Also reads `HOLLOW_ENDPOINT` from the environment.

#### `client.perceive(url, session_id?, state_id?) → PerceiveResult`

Load a URL. Returns a `PerceiveResult`.

#### `client.act(session_id, action, intervention?) → PerceiveResult`

Interact with the current page.

```python
from hollow.types import Action

client.act(page.session_id, Action(type="click", element_id=3))
client.act(page.session_id, Action(type="fill", element_id=4, value="query"))
client.act(page.session_id, Action(type="scroll", direction="down"))
```

#### `client.get_session(session_id) → dict`

Get current session state without acting.

#### `client.close_session(session_id) → None`

Free a session's Redis state. Idempotent.

### `run_agent(client, task, *, model?, max_steps?, start_url?, on_step?) → str`

| Param | Default |
|-------|---------|
| `model` | `AnthropicAdapter()` |
| `max_steps` | `15` |
| `start_url` | `https://www.startpage.com` |
| `on_step` | `None` |

### `AnthropicAdapter(api_key?, model?)`

| Param | Default |
|-------|---------|
| `api_key` | `ANTHROPIC_API_KEY` env var |
| `model` | `claude-opus-4-6` |

### `OpenAIAdapter(api_key?, model?, base_url?)`

Requires `pip install 'hollow-sdk[openai]'`.

| Param | Default |
|-------|---------|
| `api_key` | `OPENAI_API_KEY` env var |
| `model` | `gpt-4o` |
| `base_url` | OpenAI default |

## Types

```python
from hollow.types import Action, PerceiveResult, AgentStep

# PerceiveResult
page.session_id   # str
page.gdg_map      # str — the spatial map
page.confidence   # float 0.0–1.0
page.tier         # 'hollow'|'vdom'|'text'|'cache'|'mobile'|'partial'
page.js_errors    # list[str]

# AgentStep (from on_step callback)
step.step         # int
step.gdg_map      # str
step.action       # Action | None
step.confidence   # float
step.tier         # Tier
```

## Deploy your own Hollow

This SDK points at the public demo by default (10 req/min rate limit per IP).
For production use, [deploy your own instance](https://github.com/Badgerion/hollow):

```bash
git clone https://github.com/Badgerion/hollow
cd hollow && npm install && vercel deploy
```

## License

Apache 2.0
