Metadata-Version: 2.4
Name: centurian-sdk
Version: 0.2.0b1
Summary: Centurian Python SDK — thin MCP wrapper for AI agents. Auto-registers, instruments tool calls, attributes cost. Implements ADR-017 SDK perf primitives: local rule cache + per-action OPA decision cache + durable sqlite-backed event queue + async batched MCP calls. Owner: Connect agent (T12 W3-W5).
Author-email: Centurian <engineering@centurian.ai>
License: Proprietary
Project-URL: Homepage, https://centurian.ai
Project-URL: Documentation, https://centurian.ai/docs/quickstart
Project-URL: Repository, https://github.com/omniviewai/Centurian
Keywords: centurian,mcp,agent,observability,compliance
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Developers
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Topic :: Software Development :: Libraries
Requires-Python: >=3.10
Description-Content-Type: text/markdown
Provides-Extra: http
Requires-Dist: httpx>=0.27.0; extra == "http"

# centurian-sdk (Python)

Thin MCP wrapper for AI agents. See `docs/prd/v4.1/Centurian_Solo_Developer_Experience.md`.

## Status

Version `0.1.0b8` ships **real network calls** to the Centurian control plane. The previous `0.1.0b7` shipped a stub MCP transport (security audit C1) — anyone on `<= 0.1.0b7` should upgrade.

## Install

```bash
pip install centurian-sdk
# optional: enable httpx transport (otherwise stdlib urllib is used)
pip install 'centurian-sdk[http]'
```

For local-monorepo development:

```bash
pip install -e ./packages/sdk-python
```

## Quickstart

```python
from centurian import Agent

agent = Agent(
    name="my-agent",
    purpose="Answer questions about my docs",
    # token is the provisioning token from your /signup flow
    # (also auto-read from CENTURIAN_TOKEN env)
)

@agent.tool
def search_docs(query: str) -> str:
    return run_my_search(query)

agent.run()
```

`agent.run()` does this over the network:

1. POST `/api/mcp/tools/call` with `tools/call` JSON-RPC and tool `centurian.register_agent`.
2. GET `/api/mcp/resources/org_global_rules_manifest?org_id=...` to seed the local rule cache.
3. Start the durable sqlite-backed event queue. Tool invocations enqueue trajectory + tool-call events; the queue flushes via `centurian.report_*_bulk` JSON-RPC calls.

## Auth

| Call | Auth |
|------|------|
| `register_agent` | `Authorization: Bearer <provisioning_token>` (passed via `token` constructor arg or `CENTURIAN_TOKEN` env var) |
| All spine-ingest tools (`report_*`, `submit_*`) | `signing_credential` field inside the tool arguments — extracted from the agent identity returned by `register_agent` |

## Action evaluation

`agent.evaluate_action(verb, attributes)` consults the local rule cache and returns an `ActionDecision`:

| Effect | `decision.allowed` | When |
|--------|---------------------|------|
| `allow` | `True` | Rule explicitly allows or no rule matches and manifest is fresh |
| `deny` | `False` | Rule explicitly denies |
| `escalate` | `False` | Synchronous human-in-the-loop gate required |
| `miss` | `False` | Rule manifest stale or absent — treat as deny (Shadow #21 fix). Re-prime via `agent.run()` or `mcp.prime_rule_cache()`. |

Use `agent.assert_action_allowed(...)` to raise `ActionRefusedError` instead of branching.

## Configuration

Reads `CENTURIAN_TOKEN`, `CENTURIAN_MCP_URL`, `CENTURIAN_ORG_ID`, `CENTURIAN_OWNER_USER_ID`, `CENTURIAN_QUEUE_PATH` from env.

## Errors

- `McpRpcError(code, message, data)` — JSON-RPC error envelope from the server (4xx).
- `McpTransportError(message, status=None)` — network failure after retries exhausted (5xx, DNS, TLS).
- `ActionRefusedError(decision)` — local rule cache refused the action.

## Multi-rail payment support

A62 reserves rail-agnostic schema across the SDK surface. The exported
`CostSource` `Literal` accepts legacy uppercase values plus 13 A62-reserved
lowercase values (`model_provider_*`, `mcp_call`, `stablecoin_x402`, etc.);
P0 wires the first 4 (`model_provider_openai`, `model_provider_anthropic`,
`model_provider_bedrock`, `mcp_call`). The remainder, plus the new
`PaymentRail` / `PaymentProtocol` / `Facilitator` `Literal`s, the
`StablecoinAttributes` `TypedDict`, and `is_valid_wallet_identifier`,
are **schema reservation only** in `v0.1.0`.

Live x402 facilitator integration (Coinbase CDP, Cloudflare, Stripe)
ships in `v0.2.0+` per A63 / `task_plan_v4.1.2.md`. Until then, payloads
carrying `payment_rail` or stablecoin sub-attributes parse without error
but no consumer (cost router, attribute extraction, global rules) acts
on them.

## Known beta limitations (`v0.1.0b8`)

These are documented gaps tracked for the `0.1.0` GA release. Production usage on `--pre` should be aware:

### W3-C1c — `register_agent` provisioning-token binding is surface-only

The HTTP endpoint at `/api/mcp/tools/call` requires an `Authorization: Bearer <provisioning_token>` header for `register_agent`, but only validates that the token is ≥16 characters. The handler trusts the `org_id` and `owner_user_id` claims supplied in the call arguments; it does NOT verify that the bearer token is bound to those claims server-side. **Implication:** anyone with any 16+ char string can register an agent under any org/owner they claim. The registered agent's signing credential IS bound at the row level, so subsequent calls authenticate normally — but the registration itself is a trust gap.

**Mitigation in beta:** treat your provisioning tokens as confidential; only obtain them through the documented `/signup` flow; rotate via `centurian.rotate_credential` if compromised. **GA fix planned:** introspectable `provisioning_tokens` table that binds each token to a specific `(org_id, owner_user_id, expires_at)` and rejects mismatches.

### #13 — `validateSigningCredential` runs scrypt on every event ingest

Each `report_trajectory_step`, `report_tool_call`, etc. validates the `signing_credential` via scrypt (~30ms per call). High-throughput callers should batch via the bulk variants (`report_trajectory_steps_bulk`, `report_tool_calls_bulk`) which amortize the auth cost across up to 1,000 events per round trip. The SDK's `BatchClient` does this automatically; manual MCP callers should follow the pattern. **GA fix planned:** auth-cache layer keying validated credentials by `(prefix, secret_hash)` for 60s LRU.

### #17 — No nonce/JTI replay defense on signing credentials

Captured `cnt_live_*` credentials are valid for their full TTL (default 90 days) until rotated. There is no nonce/JTI/timestamp on signed requests. **Mitigation in beta:** rotate credentials proactively if you suspect interception; restrict your SDK runtime environment so credentials cannot be exfiltrated. **GA fix planned:** SDK signs requests with timestamp + nonce; server rejects nonces seen within rotation window or timestamps with >5min skew.

### A59 plain-English policy authoring — feature-flagged off

Customer-facing plain-English-to-Rego policy compilation is **disabled** in `v0.1.0b8` and ships in `v0.2.0` with the ADR-018 neuro-symbolic guardrail. The lower-level `compile_plain_english_to_rego` remains callable for internal use (e.g. audit-engine evidence predicates), but customer-facing authoring (`submit_for_review`, `approve_review`) is gated until the IR + symbolic verifier ship.

**v0.2.0 fix:** LLM emits an Intermediate Representation using only ontology primitives → deterministic compiler → symbolic verifier proves IR ↔ Rego logical equivalence → Dana reviews all three layers before applying. See `task_plan_v4.1.2.md` and `docs/prd/v4.1/Centurian_PRD_v4.1.2_Amendments.md` (ADR-018 §5.1).

### Multi-rail payment coverage — schema only

Schema reservation only in `v0.1.0`: `payment_rail` attribute and stablecoin sub-attributes (`payment_protocol`, `payment_amount_usdc`, `payment_token`, `payment_network`, `recipient_did`, `facilitator`, `transaction_hash`) are recognized in attribute extraction. Live x402 facilitator integration (Coinbase CDP, Cloudflare, Stripe) ships `v0.2.0` per A63. See `task_plan_v4.1.2.md`.

### Re-attestation cadence (GA #14)

Every agent is issued an `Attestation` row at `register_agent` time with a **30-day TTL**. The signing credential itself has a separate **90-day TTL**, so a credential can remain technically valid while the underlying attestation has lapsed. Per `docs/architecture/federated_model.md`, agents must re-attest every 30 days.

The Centurian backend now runs a **re-attestation worker** (`src/workers/reattestation.ts`) that emits an `agent.attestation.expired` spine event when an attestation's `expires_at` passes without a fresh attestation in place. The recommended cadence is **once per hour** (the worker is idempotent across runs and per-agent — multiple expired rows produce a single warning, and a fresh attestation heals the warning state).

**Caller responsibility:** observe `agent.attestation.expired` on your own activity feed and call `centurian.attest_owner` (which writes a fresh `Attestation` row, resetting the 30-day clock) before day 30. SDK auto-re-attestation on the next `agent.run()` is **deferred** to a future SDK release; for `v0.1.0b8` the re-attestation must be triggered explicitly by the operator or a wrapper script. Track the follow-up on the issue tracker.

### Other deferred items

- KMS-managed signing keys for tier-3 audit reports — not yet shipped; tier-3 audit emit throws in production until the host registers a real signer resolver.
- Per-org rate limiting at the MCP HTTP route — not yet implemented; free-tier $5/mo + 10K-step caps at the cost layer mitigate.

## License

Proprietary. © Centurian.
