Metadata-Version: 2.5
Name: blankstate-sdk
Version: 0.5.0
Summary: Python client for the Blankstate API (sense, protocols, programmatic dive, stage authoring)
Project-URL: Homepage, https://blankstate.ai
Project-URL: Documentation, https://api.blankstate.ai/docs
Project-URL: Repository, https://github.com/blankstate-ai/blankstate-py
Project-URL: Issues, https://github.com/blankstate-ai/blankstate-py/issues
Author-email: Blankstate <hello@blankstate.ai>
License-Expression: MIT
Keywords: blankstate,ibf,protocol,sdk,sgm
Requires-Python: >=3.10
Requires-Dist: httpx<1.0.0,>=0.27.0
Requires-Dist: pydantic<3.0.0,>=2.0.0
Provides-Extra: dev
Requires-Dist: build>=1.0; extra == 'dev'
Requires-Dist: pytest-asyncio>=0.23; extra == 'dev'
Requires-Dist: pytest>=8.0; extra == 'dev'
Requires-Dist: respx>=0.21; extra == 'dev'
Description-Content-Type: text/markdown

# blankstate-sdk

Python client for the Blankstate API (import package **`blankstate_sdk`**; PyPI distribution **`blankstate-sdk`**).

Measure interactions against behavioral protocols — get a score, per-metamarker resonance, signal analysis, and fidelity assessment. The engine runs server-side; this package handles auth, request formatting, and typed responses.

## Install

```bash
pip install blankstate-sdk
```

Requires Python >= 3.10. Uses `httpx` for async HTTP and `pydantic` for typed responses.

---

## Quick Start

```python
import asyncio
from blankstate_sdk import Blankstate

bks = Blankstate(token="your_api_token")

async def main():
    result = await bks.sense("proto-xxx:1.0", "The agent resolved the issue promptly.")

    print(result.score)             # 0.82
    print(result.resonance)         # {"Solution-Oriented": 0.96, "Empathy": 0.41}
    print(result.fidelity)          # index=0.74 sufficient=True
    print(result.ics.consumed)      # 2

asyncio.run(main())
```

Or set your token via environment variable:

**macOS / Linux:**

```bash
export BLANKSTATE_API_TOKEN=bks_your_token
```

**Windows:**

```powershell
$env:BLANKSTATE_API_TOKEN = "bks_your_token"
```

```python
import os
from blankstate_sdk import Blankstate

bks = Blankstate(token=os.environ["BLANKSTATE_API_TOKEN"])
```

---

## API

### `Blankstate(token, *, api_url=..., user_agent=...)`

| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| `token` | `str` \| `BlankstateOptions` | — | API token (required), or pass `BlankstateOptions` as the only positional argument |
| `api_url` | `str` | `https://api.blankstate.ai` | API base URL (`BLANKSTATE_API_URL` if omitted) |
| `user_agent` | `str` | `blankstate-sdk` | User-Agent header sent with every request (including `healthy()`) |

Equivalent to the JS options object:

```python
from blankstate_sdk import Blankstate, BlankstateOptions

bks = Blankstate(BlankstateOptions(token="...", api_url="https://api.blankstate.ai"))
```

Response models live in `blankstate_sdk.models` (import types from there for annotations).

---

### Sense

Measure content against a protocol. The measurement engine version is set by the protocol definition — not by the caller.

```python
result = await bks.sense(
    "proto-xxx:1.0",
    content,
    language="en",          # default: "en"
    profile="detailed",     # "raw" | "detailed" | "discovery"
    depth="full",           # "measure" | "full"
)

result.score          # overall alignment (0.0–1.0)
result.resonance      # {"Empathy": 0.91, "Solution-Oriented": 0.76}
result.signal         # spread, coherence, dominant_mode (v1.5 protocols)
result.actant_flow    # entity influence direction (v1.5 protocols)
result.temporal       # temporal trend analysis (v1.5 protocols)
result.fidelity       # index, sufficient
result.ics            # consumed, cost_usd, sgm_version, pool
```

Measure against multiple protocols in parallel:

```python
results = await bks.sense_multiple(
    ["proto-aaa:1.0", "proto-bbb:2.0"],
    content,
)
# returns dict[protocol_id, SenseResponse]
# failed protocols are silently dropped
```

---

### Fidelity Check — 0 ICS

Pre-check whether content has enough signal for measurement before spending ICS:

```python
check = await bks.fidelity_check("proto-xxx:1.0", content)

check.fidelity.sufficient     # True | False
check.fidelity.index          # 0.74
check.estimated_ics           # ICSRange(min=1, max=3, typical=2)
```

---

### Status and Usage

```python
status = await bks.status()
# status.authenticated
# status.sgm_versions       — {"1.0": "stable", "1.5": "extended"}
# status.ics.remaining
# status.ics.member         — member-level cap if set

usage = await bks.usage()
# usage.ics_used, usage.ics_remaining, usage.ics_total

estimate = await bks.usage_estimate(content_length=500, modality="text")
# estimate.estimated_ics, estimate.will_exceed_quota
```

---

### Protocols

```python
# List protocols in your account
protocols = await bks.protocols()
# [ProtocolSummary(id=..., name=..., versions=[...], sgm_version=...)]

# Get a protocol's full definition
definition = await bks.protocol("proto-xxx:1.0")
# definition.id, definition.name, definition.version, definition.sgm_version
# definition.metamarkers  — [ProtocolMetamarker(label=..., description=...)]
# definition.taxonomy, definition.supported_modalities
```

---

### Programmatic Dive

```python
opened = await bks.dive.open({"stage_id": "stg_…", "agent": {"id": "my-agent", "version": "1"}})
await bks.dive.act(opened.dive_id, {"action": "Inspect the records", "turn": opened.observation.turn or 0})
await bks.dive.status(opened.dive_id)
await bks.dive.close(opened.dive_id, {"summary": "done"})
```

### Stages

A Stage is the situation you author and version; a World is one run of it. Writes are applied whole or
refused whole: a `BlankstateAPIError` carries `issues` (422, 423) or the current `rev` (409).

```python
made = await bks.stages.create({
    "world": "proxy",
    "premise": "Claims appeals in the UK: members contest decisions, assessors review.",
    "ops": {"actants": [{"label": "Member", "kind": "group"}, {"label": "Assessor", "kind": "person"}]},
})
rev = (await bks.stages.get(made.id)).rev
await bks.stages.edit(made.id, {
    "if_rev": rev,
    "ops": {"links": [{"from": "Member", "to": "Assessor", "surface": "chat", "relation": "files the appeal"}]},
})
checked = await bks.stages.check(made.id)
stages = await bks.stages.list()
estimated = await bks.stages.estimate(made.id, {"span": 20})
started = await bks.stages.start_world(made.id)
await bks.stages.set_world(made.id, started.world_id, "paused")
world = await bks.stages.world(made.id, started.world_id, {"trajectories": True})
```

| Method | Arguments | Does |
|--------|-----------|------|
| `stages.list()` | — | Your Stages: id, title, `world`, rev, Worlds run |
| `stages.create(params)` | `world` (`"proxy"`), `premise`, `scale?`, `ops?` | A Stage from a premise, with typed operations. Applied whole or refused whole |
| `stages.get(stage_id)` | | The outline and `rev` |
| `stages.edit(stage_id, params)` | `if_rev`, `ops` | Typed operations against the rev you read. A stale rev is a `BlankstateAPIError` carrying the current `rev` (409) |
| `stages.check(stage_id)` | | What is thin, each with a question, and the gaps |
| `stages.estimate(stage_id, params)` | `span`, `seed?` | What `span` days (or turns) ask on a seed: interactions, agents, `minutes` / `minutes_max`, ICS range, and what each objective read on events would count (`expect`). Spends nothing |
| `stages.start_world(stage_id, run=None)` | `budget_ics?`, `minutes?`, `until_day?`, `pauses?`, `target?`, `seed?` | Start a World. Answers at once; follow it with `world` |
| `stages.set_world(stage_id, world_id, state, run=None)` | `state`: `"running"` \| `"paused"` \| `"stopped"` | Pause, stop, or carry on. Run options go only with `"running"` |
| `stages.worlds(stage_id)` | | The Stage's Worlds, newest first |
| `stages.world(stage_id, world_id, opts=None)` | `trajectories?` | Status and outcome: day, objectives, `spread`, `levers`, proposals, ICS, `next_steps` |
| `stages.review(stage_id, world_id, params)` | `keep?`, `discard?` | Keep or discard proposals by id |
| `stages.rename_world(stage_id, world_id, name)` | | Rename a World |
| `stages.delete_world(stage_id, world_id)` | | Delete a World. One still running stops first |
| `stages.versions(stage_id, opts=None)` | `from?`, `to?` | Kept versions, and what changed between two |
| `stages.restore(stage_id, params)` | `if_rev`, `version` | Make a kept version the Stage again |
| `stages.create_variant(params)` | `stage`, `world`, `label`, `varies`, `question?` | A Variant on the version that World ran on. `varies` is one or more of `system`, `population`, `environment`, `clock` |

The token needs `stages:read` and `stages:write` to create or edit, and `runs:dry` to estimate, start, pause, and carry on Worlds.

---

### Health

```python
ok = await bks.healthy()  # True | False — 0 ICS, no auth required
```

---

## Error Handling

```python
from blankstate_sdk import BlankstateAPIError

try:
    result = await bks.sense("proto-xxx:1.0", content)
except BlankstateAPIError as err:
    print(err.status_code)      # HTTP status code
    print(err.error_message)    # human-readable message
    print(err.code)             # machine-readable error code

    if err.is_auth_error():     # 401/403 — check your token
        ...
    if err.is_quota_error():    # 429 — ICS quota exceeded
        print(err.ics_info)     # {"quota": 50, "used": 50, "remaining": 0}
    if err.is_server_error():   # 5xx — retryable
        ...
```

---

## Development

This package has its own [`pyproject.toml`](pyproject.toml). From the repo root:

```bash
git clone https://github.com/blankstate-ai/blankstate-py.git
cd blankstate-py

python3 -m pip install -e "./packages/sdk[dev]"
pytest packages/sdk/tests
```

Or from `packages/sdk/`:

```bash
cd packages/sdk
pip install -e ".[dev]"
pytest
```

---

## Concepts

| Term | Description |
|------|-------------|
| **Protocol** | A behavioral sensor — defines metamarkers that characterize an interaction pattern |
| **Metamarker** | A named behavioral signal (e.g., "Empathy", "Escalation Risk") |
| **Resonance** | Activation strength of each metamarker (0.0–1.0) |
| **Score** | Aggregate alignment between content and the protocol |
| **Fidelity** | Signal quality — whether content has enough substance for reliable measurement |
| **ICS** | Interaction Computed Signal — billing unit per measurement |
| **SGM** | Blankstate measurement engine — v1.0 (stable), v1.5 (extended signal analysis) |

---

## Requirements

- Python >= 3.10
- API token from [atlas.blankstate.ai](https://atlas.blankstate.ai)

## Links

- [Atlas](https://atlas.blankstate.ai) — Dashboard, API tokens, protocol management
- [API Reference](https://api.blankstate.ai/docs) — Full REST API documentation
- [Issues](https://github.com/blankstate-ai/blankstate-py/issues) — Bug reports and feature requests

## License

MIT — [blankstate.ai](https://blankstate.ai)
