Metadata-Version: 2.4
Name: shelfai-sdk
Version: 0.2.0
Summary: ShelfAI agent instrumentation SDK for Python.
Requires-Python: >=3.10
Description-Content-Type: text/markdown
Requires-Dist: httpx<1,>=0.25
Requires-Dist: pydantic<3,>=2
Provides-Extra: langchain
Requires-Dist: langchain<2,>=1; extra == "langchain"
Provides-Extra: langgraph
Requires-Dist: langchain<2,>=1; extra == "langgraph"
Requires-Dist: langgraph<2,>=1; extra == "langgraph"
Provides-Extra: crewai
Requires-Dist: crewai<2,>=1; extra == "crewai"
Provides-Extra: autogen
Requires-Dist: autogen-core<1,>=0.4; extra == "autogen"
Requires-Dist: autogen-agentchat<1,>=0.4; extra == "autogen"
Provides-Extra: autogen-legacy
Requires-Dist: pyautogen<0.4,>=0.3; extra == "autogen-legacy"
Provides-Extra: llamaindex
Requires-Dist: llama-index-core<1,>=0.10.20; extra == "llamaindex"
Provides-Extra: claude
Requires-Dist: claude-agent-sdk>=0.1.0; extra == "claude"
Requires-Dist: tiktoken>=0.7.0; extra == "claude"
Provides-Extra: all
Requires-Dist: langchain<2,>=1; extra == "all"
Requires-Dist: langgraph<2,>=1; extra == "all"
Requires-Dist: crewai<2,>=1; extra == "all"
Requires-Dist: autogen-core<1,>=0.4; extra == "all"
Requires-Dist: autogen-agentchat<1,>=0.4; extra == "all"
Requires-Dist: llama-index-core<1,>=0.10.20; extra == "all"
Requires-Dist: claude-agent-sdk>=0.1.0; extra == "all"

# ShelfAI Python SDK

The ShelfAI Python SDK instruments AI agents and forwards context telemetry to
ShelfAI without blocking agent execution.

## Install

```bash
pip install "shelfai-sdk==0.2.0"
```

For Claude Agent SDK integration:

```bash
pip install "shelfai-sdk[claude]==0.2.0"
```

## Pricing Snapshot

SDK wrappers use a standalone pricing snapshot in `shelfai_sdk/_pricing.py` so the
package can be imported without `shelfai_pro`.

When the canonical catalog changes:

1. Update `shelfai_pro/pricing/model_rates.py`.
2. Regenerate both SDK mirrors with `uv run python -m shelfai_pro.main billing pricing-sync`.
3. Verify there is no drift with `uv run python -m shelfai_pro.main billing pricing-sync --check`.
4. Run `uv run --with pytest pytest tests/test_model_rates_sanity.py tests/test_pricing_sync_cli.py shelfai-sdk/python/tests/test_sdk_standalone_imports.py`.

The server catalog remains canonical, historical `rate_snapshot` values still
win for past calls, and recent unknown models are surfaced through the billing
operator commands instead of silently drifting.

## Quick Start

Installing the package and setting environment variables does not automatically
intercept an existing provider client. The application must wrap its client and
use the returned object:

```python
from shelfai_sdk import ShelfAI
integration = ShelfAI.integrate(client=existing_model_client)
model_client = integration.client
```

Those are the only capture lines. If setup is unavailable,
`integration.client` is the exact original object and the app keeps working.
If the application continues using `existing_model_client` instead of
`integration.client`, ShelfAI captures nothing.

Every wrapped provider call also records privacy-safe runtime source evidence by
default: a repository-relative callsite and bounded customer-code stack. It
never records source text, local variables, arguments, environment values, or
absolute paths. It also works in packaged/container deployments that omit
`.git`; the revision is simply left unknown. Workbench uses that evidence to show likely upstream source
candidates without requiring handoff annotations. Set
`SHELFAI_RUNTIME_SOURCE_EVIDENCE=off` only when the workspace does not want
code-location metadata.

For one-click Workbench replay, pass the existing development workflow and
install the SDK endpoint once:

```python
integration = ShelfAI.integrate(client=existing_model_client, replay=run_workflow)
model_client = integration.client
integration.install(app)
```

The callback returns its normal result or `None`. ShelfAI owns the experiment
scope, run identity, flush, and trace response. Customer code does not manage
experiment environment variables, trace IDs, polling, or a custom replay server.
Register the workspace replay hook with `default_target_name` set to `default`;
that target is built into this standard callback integration.

After a provider call, `integration.last_trace_id` is the exact run to open or
pass to `shelfai verify-integration --trace-id ...`. Do not verify an unrelated
"latest" run in a concurrent application.

The wrapper is fail-open: queued telemetry cannot break the model call. The
replay handler is deliberately fail-closed: it rejects invalid signatures,
expired requests, another agent's experiment, duplicate deliveries, and
workflows that do not capture a provider event. The experiment is scoped to that one
execution context, so concurrent normal runs remain unchanged.

If the application cannot expose an HTTPS endpoint yet, omit `replay` and use
capture-only mode temporarily. Workbench labels that connection as limited.

Use the model client the application already has; ShelfAI does not select or
replace the provider. Current first-party wrappers support OpenAI Chat
Completions, OpenAI Responses, and Anthropic Messages
clients. They capture provider-neutral Context IR v1 items for instructions,
messages, tool definitions, tool calls, tool results, and produced output while
retaining the original structured provider payload. No swarm- or
domain-specific schema is required.

You can also instrument ordinary Python callables as framework-neutral tools:

```python
integration = ShelfAI.integrate(client=existing_model_client)
model_client = integration.client
customers = {"cus_123": {"tier": "enterprise"}}

@integration.tool(name="lookup_customer")
def lookup_customer(customer_id: str) -> dict:
    return customers[customer_id]

def draft_reply(customer):
    return model_client.responses.create(
        model="gpt-5",
        input=f"Draft a reply for {customer['tier']}",
    )

def workflow(customer_id: str):
    customer = lookup_customer(customer_id)
    return draft_reply(customer)

result = integration.run(workflow, "cus_123")
```

`integration.tool(...)` works as a decorator or direct wrapper, and
`integration.tools([...])` wraps a list, tuple, or registry of callables once.
When the integration is inactive, the original functions and containers are
returned unchanged. Use `integration.run(...)` or `await
integration.run_async(...)` around one workflow when you want generic tools and
wrapped provider calls to share a single trace.

For explicit configuration instead of environment variables:

```python
integration = ShelfAI.integrate(
    client=existing_model_client,
    api_key="sk-...",
    agent_id="content-writer",
    capture_mode="metadata_only",  # default
    replay=run_workflow,
    replay_signing_secret="a workspace-specific secret",
)
```

## Launch Capture Modes

ShelfAI launches with two live SDK capture modes:

- `metadata_only`
- `full`

`metadata_only` is the default and strips raw snapshot text before send while preserving structural telemetry.
`full` unlocks content-aware Workbench editing and should be enabled only with
customer approval; the SDK still scrubs obvious secrets before egress.

The observer SDK never sends captured context to an additional model provider.
It sends telemetry only to the configured ShelfAI endpoint. Shelfy's managed
suggestion model is a separate, explicit workspace setting; private deployments
can keep it disabled or use the BYOM route.

You can set this in code with `capture_mode=...` or through:

- `SHELFAI_CAPTURE_MODE`
- legacy compatibility alias: `SHELFAI_SENSITIVE_MODE`

The public trust boundary lives in `shelfai_sdk/capture_mode.py`.

## Context IR v1

Every wrapped call emits a versioned `context_ir` envelope in its context
snapshot and output event. Each item carries a stable ID, type, raw payload,
preview, causal parents, run/stage fields, source hash, timestamp, selection
state, provenance, permissions, side effects, and redaction metadata.

`metadata_only` keeps the typed graph but redacts text and raw content. `full`
keeps structured payloads after secret scrubbing. Pre-v1 snapshots continue to
work through the server's legacy adapter.

### Automatic source evidence, handoffs, and downstream experiments

The wrapped SDK records where each provider call entered customer code and
automatically connects a prior model output to a later wrapped
input when the substantial output text is reused. This lights the observed run
graph and gives ShelfAI repository-relative source candidates without handoff
annotations. Matching is conservative: short common values are ignored,
inference remains process-local, and an inferred candidate never unlocks a
production edit. Workbench labels source truth explicitly as observed, inferred,
or confirmed; only an owner-confirmed target can unlock **Fix at source**.

No additional code is required for runtime source evidence or experiments. The
optional annotation below is only for applications that want to add richer
causal information about a specific assembled handoff:

Add one optional annotation where an upstream agent's output is assembled into
another agent's context:

```python
brief = planner(...)
assignment = integration.handoff(
    build_assignment(brief),
    derived_from=brief,
    materialized_by=build_assignment,
    source_path="workflow/assignment.py",
)
writer(assignment)
```

The returned value is unchanged. Context IR records the explicit upstream
parents in addition to the automatic runtime evidence. The signed replay handler
selects the experiment automatically and applies it in memory only for that
execution.

## Claude Agent SDK

```python
import asyncio

from claude_agent_sdk import ClaudeAgentOptions, query
from shelfai_sdk.wrappers import connect_claude_agent

options = ClaudeAgentOptions(
    allowed_tools=[
        "Read",
        "Edit",
        "Write",
        "mcp__shelfai__recommend",
        "mcp__shelfai__health",
        "mcp__shelfai__ask",
    ],
)
integration, options = connect_claude_agent(options)


async def main():
    async for message in query(
        prompt="Fix the auth bug in src/auth.py",
        options=options,
    ):
        print(message)


asyncio.run(main())
```

## Framework Integrations

ShelfAI also ships framework-native adapters that map framework lifecycle hooks
onto the SDK's canonical event schema.

### LangChain

```bash
pip install "shelfai-sdk[langchain]"
```

```python
from shelfai_sdk import ShelfAI
from shelfai_sdk.wrappers.langchain import ShelfAICallbackHandler

integration = ShelfAI.connect()
handler = ShelfAICallbackHandler(integration.shelf) if integration.shelf else None
config = {"callbacks": [handler]} if handler else {}
result = chain.invoke({"input": "hello"}, config=config)
```

### LangGraph

```bash
pip install "shelfai-sdk[langgraph]"
```

```python
from shelfai_sdk import ShelfAI
from shelfai_sdk.wrappers.langchain import ShelfAICallbackHandler
from shelfai_sdk.wrappers.langgraph import wrap_graph

integration = ShelfAI.connect()
handler = ShelfAICallbackHandler(integration.shelf) if integration.shelf else None
wrapped_graph = wrap_graph(graph, handler) if handler else graph
result = wrapped_graph.invoke({"input": "hello"})
```

### CrewAI

```bash
pip install "shelfai-sdk[crewai]"
```

```python
from shelfai_sdk import ShelfAI
from shelfai_sdk.wrappers import register_crewai_listener

integration = ShelfAI.connect()
listener = register_crewai_listener(integration.shelf) if integration.shelf else None
result = crew.kickoff()                     # use the existing Crew unchanged
```

This uses CrewAI's public event bus and captures crews, flows, agents, tasks,
LLM calls, tools, and human feedback. `ShelfAICrew` remains available only for
pre-1.0 projects that cannot migrate yet.

### AutoGen

```bash
pip install "shelfai-sdk[autogen]"
```

```python
from shelfai_sdk import ShelfAI
from shelfai_sdk.wrappers import install_autogen_logging

shelf = ShelfAI(api_key="sk-...", agent_id="agent-1")
handler = install_autogen_logging(shelf)
```

The current adapter consumes AutoGen's structured event logger. The older
`register_shelfai_tracking(...)` reply hook remains under the
`autogen-legacy` extra.

### LlamaIndex

```bash
pip install "shelfai-sdk[llamaindex]"
```

```python
from shelfai_sdk import ShelfAI
from shelfai_sdk.wrappers import register_llamaindex_handler

shelf = ShelfAI(api_key="sk-...", agent_id="rag-agent")
handler = register_llamaindex_handler(shelf)
```

This attaches to LlamaIndex's root instrumentation dispatcher and captures LLM,
tool, retrieval, and workflow events. The legacy callback methods remain for
older applications.
