Metadata-Version: 2.4
Name: stateset-nsr
Version: 0.9.1
Summary: Python SDK for the Stateset NSR AI platform — neuro-symbolic recursive reasoning
Project-URL: Homepage, https://nsr.stateset.app
Project-URL: Documentation, https://nsr.stateset.app/docs
Project-URL: Repository, https://github.com/stateset/stateset-nsr
Project-URL: Issues, https://github.com/stateset/stateset-nsr/issues
Project-URL: Changelog, https://github.com/stateset/stateset-nsr/releases
Author-email: StateSet <dom@stateset.com>
License-Expression: BUSL-1.1
License-File: LICENSE
Keywords: ai,knowledge-base,neuro-symbolic,nsr,reasoning,sdk,stateset,verified-decisions
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.8
Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Classifier: Typing :: Typed
Requires-Python: >=3.8
Requires-Dist: requests>=2.28
Provides-Extra: async
Requires-Dist: aiohttp>=3.9; extra == 'async'
Description-Content-Type: text/markdown

# Stateset NSR Python SDK

Python client for the [Stateset NSR AI platform](https://nsr.stateset.app) — neuro-symbolic recursive reasoning.

## Install

```bash
pip install stateset-nsr   # (publishing soon — not yet on PyPI; install from this repo for now)
pip install stateset-nsr[async]  # with the aiohttp-based async client
```

From this repo:

```bash
pip install ./sdks/python
```

## Quick Start

```python
from stateset_nsr import NSRClient

client = NSRClient(
    api_key="nsr_your_api_key",   # or set NSR_API_KEY
    org_id="org_your_org_id",     # or set NSR_ORG_ID
)
```

Credentials fall back to the `NSR_API_KEY` / `NSR_ORG_ID` environment
variables; explicit arguments always win.

```python

# Send a customer message
response = client.chat("I want to cancel my subscription")

print(response.reply)
# "We offer the option to pause your subscription..."

print(response.top_category)
# "subscription_management"

print(response.confidence)
# 0.61

# Execute ready tool calls
for tc in response.ready_tool_calls:
    print(f"{tc.function}() — rule: {tc.policy.rule_name}")
    # offer_pause() — rule: offer_pause_before_cancel
```

## Verified Decisions

```python
# One auditable decision: approved | denied | refused, with a cited proof chain.
decision = client.decide(
    "Can order A1 be refunded?",
    action="issue_refund",
)
print(decision["decision"], decision["proof"]["cited_rules"])

# Score a whole portfolio in one call — each item metered independently.
batch = client.decide_batch([
    {"query": "Can order A1 be refunded?", "action": "issue_refund"},
    {"query": "Can order A2 be returned?"},
])
print(batch["summary"])
# Items past the server's time budget return error code
# "batch_deadline_exceeded" — never evaluated, never billed. Retry those in
# a smaller batch.
```

See `examples/verified_decision.py` for a self-contained runnable version
with inline rules and facts.

## Knowledge Base

```python
# Add products
client.add_entity("Pro Plan", "product", {"price": "99.00"})

# Add business rules
client.add_rule(
    name="offer_pause_before_cancel",
    head_predicate="offer_pause",
    head_args=["?subscription"],
    body=[{"predicate": "cancel_request", "args": ["?subscription"]}],
    confidence=0.95,
)

# Check KB stats
print(client.kb_stats())
```

## NSR Machine

```python
# Provision
client.provision_machine(seed_from_gss=True)

# Train
client.train_machine([
    {"input": "cancel my plan", "expected_category": "subscription_management"},
    {"input": "where is my order", "expected_category": "order_management"},
])

# Check status
status = client.machine_status()
print(f"Vocabulary: {status.vocabulary_size}")
print(f"Programs: {status.programs_learned}")
```

## Advanced Reasoning

```python
response = client.chat("Is this safe during pregnancy?")

# Check if human review needed
if response.completion.needs_human_review:
    print("Route to human agent")

# Inspect the reasoning
ar = response.completion.grounding.advanced_reasoning
if ar:
    print(f"Strategy: {ar.recommended_strategy}")
    print(f"Confidence: {ar.machine_confidence}")
    print(f"Thoughts: {ar.thought_count}")
    print(f"Features: {ar.enabled_features}")
    if ar.uncertainty:
        print(f"Epistemic: {ar.uncertainty.epistemic}")
        print(f"Calibration: {ar.uncertainty.calibration_score}")
```

## Verifying webhooks

```python
from stateset_nsr import verify_webhook_signature

# In your handler: raw request body + the X-NSR-Signature header.
if not verify_webhook_signature(signing_secret, raw_body, signature_header):
    return abort(400)
```

Constant-time, case-insensitive hex comparison with a 5-minute replay window
by default (`tolerance_secs=None` disables the timestamp check). The HMAC is
computed over the raw `t=` token exactly as the server signed it; multiple
`v1=` signatures are accepted (any match passes — the key-rotation case) and
headers with a duplicated `t=` are rejected.

## Reliability

- Retries with exponential backoff + jitter on 5xx, 429, and connection
  errors (`max_retries`, default 3).
- Every mutating request (POST/PATCH/PUT/DELETE) automatically carries an
  `Idempotency-Key`, generated once per logical call and stable across its
  retries — a retried request can never re-execute or re-bill server-side.
  Pass your own key via `idempotency_key=` on `decide`/`decide_batch`/`reply`.
- `Retry-After` on 429 is honored in both its delta-seconds and HTTP-date
  forms (garbage falls back to backoff), capped at 60 seconds, and 429
  retries respect `max_retries`.

## Async client

`AsyncNSRClient` (extra: `pip install stateset-nsr[async]`) has full
method-for-method parity with `NSRClient` — enforced by an introspection test
in `tests/test_async_client.py`. Its exceptions subclass the sync ones, so
`except NSRError` catches failures from either client.

```python
from stateset_nsr import AsyncNSRClient

async with AsyncNSRClient() as client:   # env-var credentials
    decision = await client.decide("Can order A1 be refunded?")
```


## Audit & observability lookups

```python
client.get_decision("dec_abc123")   # resolve a decision_id from a response/webhook
client.gss_seed_info()              # {"grounded": true, "source": "embedded", ...}
```

## Brand machine onboarding

The full GSS lifecycle for your org — see
[BRAND_MACHINE_ONBOARDING.md](../../docs/BRAND_MACHINE_ONBOARDING.md):

```python
seed = client.seed_machine_pack({"catalog": [...], "policies": [...]})
spec = client.compile_machine_pack({"seed_id": seed["id"]})
report = client.evaluate_machine_seed({"compiled_id": spec["id"]})
client.activate_machine_pack({"compiled_id": spec["id"]})
```


## Webhooks & templates

```python
hook = client.create_webhook("https://yourapp.com/hook", ["outcome.produced"])
store(hook["signing_secret"])                  # shown ONLY at creation
client.webhook_deliveries(hook["id"])           # delivery log (status, attempts)
client.apply_template("ecommerce-returns")     # one-call KB seeding
```

## Coverage

Route groups covered by both clients (sync and async):

- Chat: `/api/v1/nsr/chat` (+SSE stream), OpenAI-compatible `/v1/chat/completions`, Anthropic-compatible `/v1/messages`
- Verified decisions: `/v1/decisions` (+batch, recent, stats, rule-stats, refusal-gaps, refusal-roadmap, calibration, export, `{id}`, outcome, outcome-by-ref), proof re-verification `/v1/proofs/verify`
- Replies & macros: `/api/v1/replies`, `/api/v1/macros` CRUD + batch + render
- Auth: `/api/v1/auth/signup|login|session`, API-key list/create/revoke
- Knowledge base: entities (CRUD, batch, search), rules (create/list), triples + triple evidence, `/api/v1/kb/stats|history`
- Reasoning: `/api/v1/reason`, `/api/v1/reason/induce-rules`, forward/backward chain, explain, RAG query
- Machines: provision/train/evaluate/infer/status/list + brand-machine pack lifecycle (seed/compile/evaluate-seed/activate)
- Webhooks (register/list/revoke/deliveries + signature verification), industry templates
- Sessions, conversations, flywheel (feedback/stats/train/demote-noisy), NSR-L, sandbox, agents (list/start/stop)
- Billing: usage/subscription/plans/credits/purchase/invoices
- Metering & marketplace: entitlement link/get, AWS + Azure marketplace registration
- Health: `/health`, `/ready`

Route groups NOT yet covered (call `client._request(method, path, ...)` as an
escape hatch if you need them today):

- `/api/v1/answer` + `/api/v1/answer/batch` (ticket answering surface)
- Rule mutation beyond create/list (get/patch/delete/lint/batch), entity patch/soft-delete/batch-delete, triple patch/delete
- `/api/v1/kb/graph`, `/api/v1/audit/deletions`, `/api/v1/reason/strategy-stats`, timestamped `GET /api/v1/reason`
- `/v1/messages/count_tokens`, low-level `/api/v1/nsr/infer` (+ensemble)
- Flywheel candidates/curve analytics, model checkpoint management, WorkOS/admin routes
- Beliefs (`/api/v1/beliefs/*`, 8 routes) and emulator (`/api/v1/emulator/*`, 8 routes)
- Types & predicates (`/api/v1/types`, `/api/v1/types/predicates[/{name}]`, `/api/v1/types/subtypes`, `/api/v1/types/validate`)
- Constraints (`/api/v1/constraints` CRUD + `/api/v1/constraints/validate`)
- Policy tooling (`/api/v1/policy/evaluate|infer|train|vocab/init`, `/api/v1/policy/sandbox/evaluate`)
- Jobs (`/api/v1/jobs`, `/api/v1/jobs/batch-infer`, `/api/v1/jobs/{id}[/cancel]`), standalone evidence (`/api/v1/evidence[/{id}[/chain]]`, `/api/v1/evidence/stats`), codegen (`/api/v1/codegen/generate|index|verify`), sources (`/api/v1/sources[/{id}]`)
- NSR internals: `/api/v1/nsr/got/*`, `/api/v1/nsr/vsa/*`, symbol/program mutation & evaluation (`/api/v1/nsr/symbols` batch, `/api/v1/nsr/programs[/evaluate|/{symbol_id}]`), `/api/v1/nsr/uncertainty`, `/api/v1/nsr/validate` (+batch, validation cases/runs), `/api/v1/nsr/recommend-strategy`
- NSR-L versioning (`/api/v1/nsrl/versions[/{id}[/rollback]]`)
- `POST /v1/metering/gcp/pubsub` (inbound Google push endpoint — not client-callable)
