> For the complete documentation index, see [llms.txt](https://docs.meganova.ai/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.meganova.ai/libra-os/anthropic-sdk-quickstart.md).

# Anthropic SDK Quickstart

> **Note:** Point the official Anthropic Python or TypeScript SDK at your Libra OS gateway and have your existing code work unchanged. Three consumer patterns are supported: bare Messages API, the Claude Agent SDK, and the beta Managed Agents surface.

This is the partner integration path — for teams that already have an Anthropic SDK integration and want to evaluate Libra OS without rewriting it.

## Why route through Libra OS

| Need                       | What Libra OS gives you                                                                                                          |
| -------------------------- | -------------------------------------------------------------------------------------------------------------------------------- |
| **On-prem deployment**     | Single-binary install, no data leaves your network                                                                               |
| **Multi-provider routing** | The same call routes across Anthropic / OpenAI / Google / NebulaBlock based on your settings                                     |
| **Your own AI Firewall**   | Plug in pre-call validation hooks — you own the policy, not the LLM vendor                                                       |
| **Tenant isolation**       | Per-user filesystem, per-tenant agent overrides, JWT-scoped requests                                                             |
| **Cost control**           | Three-tier model split (cheap-fast for sections, premium for reader-facing answers) cuts report generation cost roughly six-fold |

## What you'll do

1. Get a Libra OS JWT.
2. Set two environment variables.
3. Run your existing Anthropic SDK code unchanged.

That's the entire migration. The "swap one line of `base_url`" pitch is real.

***

## Step 1 — Prerequisites

* A Libra OS gateway you can reach. For self-hosted instances this is typically `https://libra-os.your-company.example`. For the MegaNova-managed Libra OS service the URL is provisioned for you.
* A JWT bearer token. Get one by calling `POST /api/auth/login` with admin credentials, or use a long-lived service token your operator provisioned.
* One of the SDKs:
  * `pip install anthropic` (Messages API path)
  * `pip install claude-agent-sdk` (Agent SDK path)

***

## Step 2 — Configure environment variables

```bash
export ANTHROPIC_BASE_URL="https://libra-os.your-company.example"   # NO /v1 suffix
export ANTHROPIC_API_KEY="<your-libra-os-jwt>"
```

> **Important — no `/v1` suffix.** The Anthropic SDK appends `/v1/messages` itself. If you set `ANTHROPIC_BASE_URL=https://nova-os/v1`, requests hit `/v1/v1/messages` and fail with `405 Method Not Allowed`. Always use the host root.

Both auth header shapes are accepted:

| Env var                | Header sent                   | Use when                               |
| ---------------------- | ----------------------------- | -------------------------------------- |
| `ANTHROPIC_API_KEY`    | `x-api-key: <jwt>`            | Default for `Anthropic(api_key="...")` |
| `ANTHROPIC_AUTH_TOKEN` | `Authorization: Bearer <jwt>` | Required by `claude-agent-sdk`         |

If both are set, `Authorization: Bearer` wins.

***

## Step 3 — Pick your pattern

### Pattern A — Bare Messages API

```python
import os
from anthropic import Anthropic

client = Anthropic()
msg = client.messages.create(
    model="claude-opus-4-7",
    max_tokens=1024,
    messages=[{"role": "user", "content": "Summarize this filing in three sentences."}],
)
print(msg.content[0].text)
```

Streaming works the same as upstream Anthropic — Libra OS emits the canonical `message_start` → `content_block_delta*` → `message_stop` SSE event sequence, so the SDK's stream parser doesn't notice the difference.

### Pattern B — Claude Agent SDK

```python
import os
from claude_agent_sdk import query, ClaudeAgentOptions

# Agent SDK requires AUTH_TOKEN form (its bundled CLI binary uses it).
os.environ["ANTHROPIC_AUTH_TOKEN"] = os.environ.pop("ANTHROPIC_API_KEY", "")

async def run():
    async for msg in query(
        prompt="Draft a Bill 96 compliance review for Quebec.",
        options=ClaudeAgentOptions(),
    ):
        print(msg)
```

The bundled `claude` CLI binary inherits `os.environ` from the parent process, so setting these once at the top of your script reaches the binary unchanged.

### Pattern C — Managed Agents (beta)

```python
client = Anthropic(default_headers={"anthropic-beta": "managed-agents-2026-04-01"})

agent = client.beta.agents.create(
    name="intake-specialist",
    model="claude-opus-4-7",
    system="Convert client uploads into a structured BackgroundSummary.",
    tools=[],
)
session = client.beta.sessions.create(agent=agent.id, environment_id="env_stub")
client.beta.sessions.events.create(
    session_id=session.id,
    type="user.message",
    payload={"content": "Process the attached PDF."},
)
for evt in client.beta.sessions.stream(session_id=session.id):
    print(evt)
```

The Managed Agents endpoints require the `anthropic-beta: managed-agents-2026-04-01` header (Libra OS returns `426 Upgrade Required` without it; same posture as Anthropic upstream). Currently shipped as a POC — full container-runtime semantics (`Environment.config.runtime`) are intentionally out of scope.

### Pattern D — Libra OS native SDK

When the three Anthropic-compat patterns above can't express what you need — multi-model `model_config` cascade, structured-output `output_type` validation, custom-tool webhook callbacks, async jobs, idempotency keys, auto-paginated `list()` — reach for the Libra OS Python SDK directly:

```bash
pip install libraos-sdk
```

```python
from libraos import Client

async with Client(base_url="https://libra-os.your-company.example", api_key="msk_live_...") as c:
    await c.employees.create(id="my-employee", model_config={...})
    await c.agents.create(id="my-agent", type="skill", owner_employee="my-employee")
    resp = await c.messages.create(
        agent_id="my-agent",
        messages=[{"role": "user", "content": "..."}],
    )
```

Partner-facing examples for each pattern live in [`libraos/sdk/python/examples`](https://github.com/libraos/sdk/tree/main/python/examples):

| Use case                                                | Example                        |
| ------------------------------------------------------- | ------------------------------ |
| Fastest path from zero to a digital agent (3 SDK calls) | `00_quickstart.py`             |
| Token-on-the-wire streaming for chat UIs                | `09_streaming_messages.py`     |
| Mode A custom tools (SSE inline)                        | `04_custom_tool_inline.py`     |
| Mode B custom tools (webhook)                           | `05_custom_tool_webhook.py`    |
| Multi-model fallback cascade                            | `06_multi_model_fallback.py`   |
| Tenant onboarding via portable bundles                  | `07_bundle_export_import.py`   |
| Async jobs for long-running work                        | `08_async_job_long_running.py` |
| Idempotency keys for retry-safe `create()`              | `10_idempotency.py`            |
| Auto-pagination on `list()`                             | `11_pagination.py`             |

For full end-to-end vertical integrations — legaltech contract review, healthcare clinical-note triage, finance 10-K diff — see [`examples/`](https://github.com/libraos/sdk/tree/main/examples).

***

## Step 4 — Pick a specific agent

Libra OS deployments typically have multiple agents (`intake-specialist`, `legal-assistant`, `bi-financial-modeler`, etc.). The Anthropic SDK's `model=` parameter is **not** how you select among them.

Why: the SDK has one `model=` field. Libra OS has a three-tier internal model split (Brain / Skill / Answer) configured server-side. Pretending the SDK's single field maps onto our three tiers would leak confusing semantics — partners can't actually express "Opus for the answer tier, Flash for the skill tier" through the SDK anyway.

So: **agent selection comes from `metadata.agent_id`**, which the Anthropic SDK passes through to Libra OS unchanged:

```python
msg = client.messages.create(
    model="claude-opus-4-7",                        # cosmetic, echoed in response.model
    metadata={"agent_id": "intake-specialist"},     # actual routing knob
    max_tokens=1024,
    messages=[{"role": "user", "content": "..."}],
)
```

| Behaviour                                      | Effect                                                              |
| ---------------------------------------------- | ------------------------------------------------------------------- |
| `metadata.agent_id` matches a registered agent | Request runs against that agent                                     |
| `metadata.agent_id` is missing                 | Request runs against the `default` agent                            |
| `metadata.agent_id` matches no agent           | `HTTP 404 — agent "<name>" (from metadata.agent_id) not registered` |

The `model=` value is echoed back in `response.model` so the SDK's roundtrip works cleanly. It never picks the agent and never picks the LLM model — server-side configuration handles both.

***

## Step 5 — Configure model tiers (admin)

Libra OS picks the LLM through a **layered routing** stack. From most-specific to most-general:

```
1. Per-call override     — tool input args (e.g. summary_model on generate_report)
2. Per-agent frontmatter — model: field on the agent (set via the dashboard
                            Agent Configuration card or the Anthropic SDK
                            client.beta.agents.update(...))
3. Global tier default   — Settings UI: answer_model / skill_agent_model / ...
4. Provider fallback     — whatever the LLM provider's default is
```

This means **partners control which agent runs (via `metadata.agent_id`); the operator controls which LLM each agent uses** (via the dashboard or the SDK's agents.update). The two responsibilities are separate.

### Per-agent model — Agent Configuration card

For each agent in the dashboard (`/agents/<agent_id>`), the **Configuration** card lets the operator pin the model that agent uses:

* Drop down to a known model (Opus, Pro, Flash, etc.) or paste any string the LLM provider accepts
* Empty value = inherit the global Answer Model from Settings
* Save writes through to `data/agents/<agent_id>.md` and triggers registry reload — live, no restart needed
* Same edit also reachable via `client.beta.agents.update(agent_id, model="claude-opus-4-7")` from any Anthropic SDK

The recommended pattern for partners with a chat-heavy + reports workflow:

| Agent                                     | Frontmatter `model:`                       | Result                         |
| ----------------------------------------- | ------------------------------------------ | ------------------------------ |
| `front-desk` (chat)                       | `gemini/gemini-3-flash-preview` (or empty) | Fast cheap chat                |
| `report-drafter` (reports)                | `claude-opus-4-7`                          | Premium polish on reports      |
| `intake-specialist` (one-shot extraction) | (empty — inherits global)                  | Whatever the global default is |

Partner code stays bit-identical regardless of which model the operator chose:

```python
# Same call shape; the model behind it depends on which agent runs
client.messages.create(
    model="claude-opus-4-7",                      # cosmetic, echoed
    metadata={"agent_id": "report-drafter"},       # picks the agent
    messages=[...],
)
```

### Global tier defaults — Settings page

Used as the fallback when an agent has no per-agent model set. Editable at `/settings`:

| Tier                             | Setting                                                     | Used for                                                                            |
| -------------------------------- | ----------------------------------------------------------- | ----------------------------------------------------------------------------------- |
| **Answer Model**                 | `answer_model` (legacy aliases `llm_model`, `openai_model`) | Chat answer synthesis + report executive summary + report conclusion                |
| **Skill / Report Section Model** | `skill_agent_model`                                         | Skill agents + report outline + per-section + timeline + risk matrix                |
| **Brain / Planner Model**        | `brain_model`                                               | LibraBrain task planner when no local planner is wired                              |
| **Query Reformulator**           | `agent_inference_model`                                     | Local LLM that rewrites NL queries into keyword variants (+22-26pp search accuracy) |
| **Memory Worker**                | `memory_worker_model`                                       | Observer + Reflector for observational memory                                       |
| **Image Generation Model**       | `image_model`                                               | Image-generation skills                                                             |

The recommended split is a premium model on the Answer tier (Opus / GPT-4 / Pro tier — reader-facing polish) and a cheap-fast model on the Skill tier (Flash / Haiku — outline + sections + timeline). This cuts report generation cost roughly six-fold versus running everything on the premium tier, while keeping the reader-facing prose on the strong model.

Settings can also be set via the API:

```bash
curl -X PUT https://libra-os.your-company.example/api/config/settings/openai_model \
  -H "Authorization: Bearer $JWT" \
  -H "Content-Type: application/json" \
  -d '{"value": "gemini/gemini-3.1-pro-preview"}'
```

Changes take effect at the next agent invocation — no restart needed.

> **Per-instance, not per-agent.** Tier models are global to a Libra OS instance. To run different tier configurations for different tenants, deploy separate Libra OS instances. Per-agent tier overrides are a tracked enhancement.

***

## Worked example — three-line swap

This is the diff an existing Anthropic-SDK consumer makes to route through Libra OS:

```diff
  import os
  from anthropic import Anthropic
+ os.environ["ANTHROPIC_BASE_URL"] = "https://libra-os.your-company.example"
+ os.environ["ANTHROPIC_API_KEY"]  = "<libra-os-jwt>"
  client = Anthropic()
  msg = client.messages.create(
      model="claude-opus-4-7",
      max_tokens=1024,
      messages=[{"role": "user", "content": "Hello"}],
  )
  print(msg.content[0].text)
```

That's the entire migration. No SDK rewrite, no API rewiring — two environment variables.

***

## Troubleshooting

### `HTTP 401 — missing authorization header`

Either header is missing or empty. Set one of `ANTHROPIC_API_KEY` (sends `x-api-key`) or `ANTHROPIC_AUTH_TOKEN` (sends `Authorization: Bearer`).

### `HTTP 405 Method Not Allowed` on `/v1/messages`

You included `/v1` in `ANTHROPIC_BASE_URL`. The SDK appends it itself. Use the host root only.

### `HTTP 426 Upgrade Required` on `/v1/agents` or `/v1/sessions`

Managed Agents endpoints require the beta header. Set `default_headers={"anthropic-beta": "managed-agents-2026-04-01"}` on the `Anthropic` client constructor (or pass it per call). The Messages API does **not** require the beta header — it's GA.

### `HTTP 404 — agent "<name>" (from metadata.agent_id) not registered`

The `metadata.agent_id` you passed doesn't match any agent on the Libra OS instance. List the registered agents from your operator's admin dashboard, or omit `metadata.agent_id` to fall back to the `default` agent.

### Streamed responses look fine but `response.model` is wrong

That field is a cosmetic echo of what the consumer requested. The actual model that ran is determined by the agent's tier configuration, not by `req.Model`. This is by design — see "Pick a specific agent" above.

### `claude-agent-sdk` aborts with `"API Error: 405 Method Not Allowed"`

You're hitting an older version of Libra OS without the `/v1/messages/count_tokens` endpoint. Upgrade your Libra OS gateway. Recent versions implement the endpoint as a `char/4` token-count estimate, which is sufficient for the SDK's context-window pre-flight.

***

## Known limitations

* **Container runtime.** Libra OS doesn't run customer code in sandboxed containers. The Managed Agents `Environment.config.runtime: python|node|go` field is accepted but ignored. Tools that require code execution (`bash`, `str_replace_editor`, `write`, `read` over arbitrary paths) return `runtime_not_supported`. If you need code execution as part of the agent loop, use Anthropic Managed Agents proper or self-host with your own container plane.
* **Per-agent model tiers.** Tier configuration is global per Libra OS instance.
* **Native tool\_use round-trip.** `tools=[...]` and `tool_choice` are accepted but Libra OS doesn't currently emit Anthropic-shaped `tool_use` content blocks back through the wire. Agents bind tools via Libra OS's own registry; tool selection happens server-side.
* **Image content blocks in requests.** Text-only on `/v1/messages` for now. (The OpenAI-compat endpoint at `/v1/chat/completions` already decodes image blocks if you need image input today.)
* **Agent versioning.** Managed Agents `agent.version` is currently always returned as `1`. Sessions don't pin to a specific version yet.

These are intentional v0 scope cuts. None of them block the bare Messages API or Claude Agent SDK consumer-pattern roundtrip — both work end-to-end today.

***

## Next steps

* [**Documents & Knowledge**](/libra-os/documents-and-knowledge.md) — how to upload files into your Libra OS instance so agents can retrieve from them
* [**Admin Guide**](/libra-os/admin-guide.md) — agent-to-collection bindings, system status, troubleshooting
* [**Model Context Protocol (MCP)**](/integration-guides/mcp.md) — alternative integration path if your client speaks MCP instead of the Anthropic SDK
