libraos-sdk
Python reference SDK for LibraOS — the agentic operating system that lets you build vertical AI products on a multi-model, multi-tenant runtime.
Published to PyPI as libraos-sdk. Status: v1.0.3 — stable; the public API has been frozen since v1.0.0.
Install
pip install libraos-sdk
# Optional — for Anthropic SDK drop-in compatibility
pip install anthropic
Usage
from libraos import Client, AnthropicCompatClient, WebhookRouter
# LibraOS extended client (multi-model, employees, bundles, async jobs, ...)
async with Client(base_url="https://nova.partner.com", api_key="...") as c:
agents = [a async for a in c.agents.list()]
msg = await c.messages.create(
agent_id="...",
messages=[{"role": "user", "content": "hi"}],
)
# Drop-in Anthropic SDK compat — partners using the Anthropic SDK
# can switch base_url and ship without any other code changes.
client = AnthropicCompatClient(base_url="https://nova.partner.com", api_key="...")
msg = client.messages.create(
model="gemini/gemini-3.1-pro-preview",
messages=[{"role": "user", "content": "hello"}],
max_tokens=256,
)
# Mode B custom-tool webhook router (FastAPI mount shown; Flask/Lambda also supported)
router = WebhookRouter(secret="...")
@router.tool("fetch_invoice")
async def fetch_invoice(input, ctx): ...
app.include_router(router.fastapi_router(), prefix="/nova/cb")
See python/examples/ on GitHub for 20 worked examples covering every public surface, and
examples/simulator/ for end-to-end synthetic-customer evaluation runs. The examples are not
shipped inside the installed package — they are scripts to read and copy, not importable modules.
Synthetic-customer simulator
client.simulate() runs a synthetic customer against one of your agents and
tells you whether the agent got the job done. An archetype describes the
persona the simulator plays — including facts the customer will not volunteer
— so you can answer questions like "can this agent handle a customer who
withholds information?"
from libraos import Client
client = Client(base_url="https://nova-eval.partner.com", api_key="...")
result = client.simulate(
target_agent_id="intake-bot",
archetype={
"name": "cautious-applicant",
"description": "Applicant with a prior visa refusal they are reluctant to raise.",
"hidden_facts": [
"visitor visa refused in 2024 — only admits it when asked directly",
"partner holds a Brazilian passport — only mentions it when asked about family",
],
"disclosure_willingness": "cautious",
"success_signal": "lawyer matched for immigration with common-law representation",
"failure_signals": ["lawyer not matched after 10 turns"],
"termination_conditions": {"max_turns": 10},
},
)
print(result.outcome) # "success" | "failure" | "timeout" | "error"
print(result.outcome_reason) # e.g. "success_signal_matched", "max_turns_reached: 10"
for turn in result.transcript:
print(f"{turn.role}: {turn.content}")
archetype= accepts a plain dict (as above), an Archetype instance, or a path
to a YAML file — Archetype.from_dict(...) / Archetype.from_yaml_path(...).
All three run the full validation chain and raise ArchetypeValidationError
with a field path and a human-readable reason, before any call is made.
Archetype fields
| Field | Required | Meaning |
|---|---|---|
name |
yes | Lowercase kebab-case identifier. |
description |
yes | Who the customer is. |
hidden_facts |
yes | Facts the synthetic customer will not volunteer unless drawn out. |
disclosure_willingness |
yes | open / cautious / guarded — how readily hidden facts come out. |
success_signal |
yes | Defines what passing means. Prefix with re: for a regex. |
failure_signals |
no | Signals that end the run as a failure. |
termination_conditions |
no | max_turns (default 10), success_signal_in_target_response, failure_signal_match (any / all). |
language_register, demographic |
no | Flavour for the simulator persona. |
model_override |
no | Gateway-prefixed model for the simulator side (default anthropic/claude-haiku-4-5). |
What you get back
SimulationResult is a frozen dataclass: transcript (a list of Turn, each
with role — "simulator" or "target" — plus content, timestamp,
metadata), outcome, outcome_reason, evaluation_signals
(success_signal_match, failure_signal_matches, turn_count), duration_ms,
tokens_used, and error.
Streaming and async
Pass stream=True to watch a run live — an iterator of TurnEvent, one event
per turn plus a final outcome event. The outcome event always fires, even
on error, timeout, or cancellation; failures arrive as events rather than raised
exceptions:
from libraos.simulator import TurnEvent
for event in client.simulate("intake-bot", archetype, stream=True):
if event.kind in ("simulator_turn", "target_turn"):
print(f"{event.role}: {event.content}")
elif event.kind == "outcome":
print(event.outcome.outcome, event.outcome.outcome_reason)
client.async_simulate(...) is the async variant — await it for a
SimulationResult, or iterate it with async for when stream=True.
Run evaluations against a separate instance.
simulate()generates real traffic; pointing it at production accumulates eval rows in the productioncall_log. The recommended setup is a sibling LibraOS instance with its own database and empty knowledge collections.
A full CI-ready runner — loads every archetype in a directory, streams turns,
writes JSON transcripts, exits non-zero on error — is at
examples/simulator/run_eval.py.
Model names — vendor prefix required for the gateway
When LibraOS routes through the MegaNova gateway (the default for cloud + most self-hosted deployments), every model name MUST carry a <vendor>/ prefix:
| Right | Wrong (returns model_not_found) |
|---|---|
gemini/gemini-3.1-pro-preview |
gemini-3.1-pro-preview |
anthropic/claude-sonnet-4-6 |
claude-sonnet-4-6 |
anthropic/claude-haiku-4-5-20251001 |
claude-haiku-4-5-20251001 |
openai/gpt-5 |
gpt-5 |
This applies to:
- The
model=arg onc.messages.create(...)andc.jobs.create(...) model_config.{answer,planner,skill}.primaryin agent + employee YAML- The
model:field in agent markdown frontmatter
For partners using the Anthropic SDK directly: the SDK's natural default (claude-opus-4-7 without prefix) won't resolve through the gateway. Either pin to a gateway-safe prefixed model in your config (for example, ANTHROPIC_HIGH_MODEL=gemini/gemini-3.1-pro-preview) or add a translation layer that prefixes bare Anthropic model names with anthropic/ when routing through LibraOS.
The agent_inference_model and ollama_embed_model settings are exempt — they route to a local Ollama and use <tag>:<version> shape (e.g. gemma4:e4b).
To list all registered models (catalog discovery):
# Direct gateway query, requires a gateway-scoped key
import httpx
r = httpx.get("https://nova.partner.com/v1/models", headers={"Authorization": f"Bearer {api_key}"})
print([m["id"] for m in r.json()["data"]])
Server-side tool observability — known v1.0.0 limitation
Anthropic-provided server-side tools (web_search_20250305, code_execution_20250522, etc.) execute on Anthropic's infrastructure and DO NOT emit discrete content_block_start / content_block_stop events on the SSE stream. Audit hooks that fire on content_block_stop for tool_use blocks won't see these invocations.
Visible: the model's text response references the search; MessageResponse.content[] (non-streaming) contains server_tool_use blocks.
Not visible: discrete tool-invocation events on the streaming path. Affects observability hooks that watch the SSE stream for tool_use events.
This is a pre-existing constraint of the underlying Anthropic API; LibraOS forwards what it receives. Partner-defined custom tools (Mode B via WebhookRouter) emit custom_tool_use events normally.
Workaround for partners on v1.0.0: inspect MessageResponse.content after the stream completes for server_tool_use blocks; OR use the non-streaming messages.create path when discrete tool observability matters.
Tracking libraos/sdk#10 for v1.1 — adds gateway-side synthetic event emission so audit hooks Just Work for server-side tools.
Error handling
from libraos import (
NovaOSError,
NotFoundError,
RateLimitedError,
BillingError,
VertexSchemaError,
)
try:
agent = await c.agents.get("does-not-exist")
except NotFoundError:
print("agent not found")
except RateLimitedError as e:
print(f"rate limited — retry after {e.retry_after}s")
except BillingError as e:
print(f"billing issue: {e.code}")
except VertexSchemaError as e:
# Deterministic schema bug — do NOT retry, fix the tool schema
print(f"Vertex schema error on tool={e.tool_name} param={e.parameter_path}")
print(f"Hint: {e.fix_hint}")
Idempotency
Pass idempotency_key= to any create() call to safely retry on network failure:
agent = await c.agents.create(
id="marketing-assistant",
type="skill",
idempotency_key="create-marketing-agent-v1",
)
Resources
All twelve resources are bound on the client, and their methods are async
(c.messages.stream() returns an async context manager rather than a coroutine).
The sync mirror currently covers agents, employees, messages
and jobs only — reach the other eight through the async surface.
| Resource | Endpoints | What it's for |
|---|---|---|
c.agents |
create, get, update, delete, list |
Agent definitions — the things you send messages to. |
c.employees |
create, get, update, delete, list |
Model-routing owners; one employee can own many agents. |
c.messages |
create, stream |
Send a turn; stream returns an SSE context manager. |
c.jobs |
create, get, cancel, list |
Long-running async work. |
c.documents |
upload, list, delete |
Upload documents; auto-indexed on upload, then referenced by document_id. |
c.knowledge |
search, ingest, collections |
Hybrid search + ingest over knowledge collections. Collections are scoped by API-key auth — you cannot read another tenant's collection regardless of name; search defaults to the caller's own collection. |
c.hooks |
create, get, delete, list |
Register webhook subscriptions for platform events. |
c.filesystem |
list, read, write, delete |
Per-tenant/session agent workspace (tenant_id + session_id scoped). |
c.users |
create, get, delete, list |
Tenant user administration. |
c.settings |
all, get, put |
Read/write platform settings (heterogeneously typed values). |
c.sessions |
create, get |
Explicit sessions bound to an agent, with an optional session-default model. |
c.personas |
list, get |
Persona manifest discovery; list accepts if_none_match and returns None on a 304. |
Knowledge search, the most commonly looked-for surface:
collections = await c.knowledge.collections()
hits = await c.knowledge.search(query="refund policy", collection=collections[0])
Sync mirror
# For scripts and notebooks — not inside async handlers
sync_agents = c.sync.agents.list() # returns a plain list
agent = c.sync.agents.create(id="foo", type="skill")
Streaming
c.messages.stream() opens an SSE connection and returns an async context manager:
async with c.messages.stream(
agent_id="invoice-bot",
messages=[{"role": "user", "content": "Process invoice INV-9912"}],
) as stream:
async for event in stream:
if event["event"] == "text":
print(event["data"]["content"], end="", flush=True)
elif event["event"] == "done":
print() # newline at end
Mode A — custom-tool inline (intercept the LLM tool call, compute result, resume):
async with c.messages.stream(
agent_id="invoice-bot",
messages=[{"role": "user", "content": "Fetch invoice INV-9912"}],
message_id="my-request-id", # required for submit_tool_result before done
) as stream:
async for event in stream:
if event["event"] == "custom_tool_use":
result = await my_invoice_lookup(event["data"]["input"]["invoice_id"])
await stream.submit_tool_result(event["data"]["id"], result)
Webhook router (Mode B)
WebhookRouter receives LibraOS custom-tool dispatches on your HTTP endpoint, verifies the HMAC-SHA256 signature, dedupes by idempotency key, and dispatches to registered handlers:
from libraos import WebhookRouter
router = WebhookRouter(secret="your-webhook-secret")
@router.tool("fetch_invoice")
async def fetch_invoice(input: dict, ctx: dict) -> str:
invoice = await db.get_invoice(input["invoice_id"])
return f"Invoice {invoice.id}: ${invoice.amount}"
FastAPI mount:
from fastapi import FastAPI
app = FastAPI()
app.include_router(router.fastapi_router(), prefix="/nova/callbacks")
Flask mount:
from flask import Flask
app = Flask(__name__)
app.register_blueprint(router.flask_blueprint(), url_prefix="/nova/callbacks")
AWS Lambda mount:
handler = router.aws_lambda_handler() # pass to Lambda runtime
Changelog
See CHANGELOG.md.
Development
cd python
pip install -e ".[dev]"
pytest tests/ -v
License
MIT.
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file libraos_sdk-1.0.3.tar.gz.
File metadata
- Download URL: libraos_sdk-1.0.3.tar.gz
- Upload date:
- Size: 135.4 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
efa0e326d911c841bef1ea76c0995d7f8098aac23d780b79a47858c903371f35
|
|
| MD5 |
e30fc545f07d6f28475c58a470b91ff9
|
|
| BLAKE2b-256 |
0d47a2515501eeccdb5bf1baa4a1d12b57558d1ea0a32b45fe7c802fd2522fad
|
Provenance
The following attestation bundles were made for libraos_sdk-1.0.3.tar.gz:
Publisher:
release.yml on libraos/sdk
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
libraos_sdk-1.0.3.tar.gz -
Subject digest:
efa0e326d911c841bef1ea76c0995d7f8098aac23d780b79a47858c903371f35 - Sigstore transparency entry: 2350888962
- Sigstore integration time:
-
Permalink:
libraos/sdk@9bc8100bc567d1db5eecfb5aed7212ee74c6904e -
Branch / Tag:
refs/tags/v1.0.3 - Owner: https://github.com/libraos
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@9bc8100bc567d1db5eecfb5aed7212ee74c6904e -
Trigger Event:
push
-
Statement type:
File details
Details for the file libraos_sdk-1.0.3-py3-none-any.whl.
File metadata
- Download URL: libraos_sdk-1.0.3-py3-none-any.whl
- Upload date:
- Size: 232.3 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
457f22cd6bb797339c873109d838c22ad7e08a3eaa91fb5beac24cf87b5b9e6c
|
|
| MD5 |
f7a710b997f08ab904eb22c81b0bc10c
|
|
| BLAKE2b-256 |
00ac21b3b87a1fbc332cc1f4b8fcf82d023b86599ec6df9384ca39aef20a469e
|
Provenance
The following attestation bundles were made for libraos_sdk-1.0.3-py3-none-any.whl:
Publisher:
release.yml on libraos/sdk
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
libraos_sdk-1.0.3-py3-none-any.whl -
Subject digest:
457f22cd6bb797339c873109d838c22ad7e08a3eaa91fb5beac24cf87b5b9e6c - Sigstore transparency entry: 2350889610
- Sigstore integration time:
-
Permalink:
libraos/sdk@9bc8100bc567d1db5eecfb5aed7212ee74c6904e -
Branch / Tag:
refs/tags/v1.0.3 - Owner: https://github.com/libraos
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@9bc8100bc567d1db5eecfb5aed7212ee74c6904e -
Trigger Event:
push
-
Statement type: