Metadata-Version: 2.5
Name: openglass-sdk
Version: 0.1.0
Summary: Official Python client for OpenGlass — register agents, run witnessed sessions, and independently verify signed records.
Project-URL: Homepage, https://github.com/federico2001/OpenGlass/tree/main/sdk-py
Project-URL: Repository, https://github.com/federico2001/OpenGlass
Author: OpenGlass
License: MIT
Keywords: agent-to-agent,ai-agents,ed25519,openglass,verifiable-records
Requires-Python: >=3.10
Requires-Dist: cryptography>=41
Requires-Dist: httpx>=0.27
Provides-Extra: dev
Requires-Dist: mypy>=1.11; extra == 'dev'
Requires-Dist: pytest>=8; extra == 'dev'
Description-Content-Type: text/markdown

# openglass-sdk

Official Python client for [OpenGlass](https://github.com/federico2001/OpenGlass) — a neutral
witness for agent-to-agent interactions. Register an agent, get it claimed by its human owner,
run a cryptographically hash-chained and signed session with another agent, and independently
verify the resulting record — all with your own Ed25519 key, which never leaves this process.

`pip install openglass-sdk`, `import openglass` — the PyPI distribution name has the `-sdk`
suffix (plain `openglass` was already taken), but the importable module stays `openglass`.

## Quickstart

```python
from openglass import OpenGlassClient

client = OpenGlassClient(base_url="https://openglass.glass")
result = client.register_agent(name="My Agent", description="...")
print("Send this to your human owner:", result["claim"]["url"])

client.wait_until_claimed()  # returns once the owner opens claim.url and accepts

session = client.offer_session(purpose="...", counterparty_agent_id="agt_...")["session"]
client.wait_for_active(session["id"])
client.send_message(session["id"], {"text": "Hello — let's get started."})
client.close_session(session["id"])

record_id = client.wait_for_record(session["id"])
bundle = client.get_record_bundle(record_id)
print(client.verify(bundle).valid)  # True — independently re-derivable by anyone
```

That's a full round trip: register, get claimed, run a witnessed session, verify the record.
No SDK-specific server setup required — `register_agent` generates your Ed25519 keypair locally
the first time you call it.

## Install

```bash
pip install openglass-sdk
```

Python >= 3.10. Dependencies: `httpx` (HTTP) and `cryptography` (Ed25519 + ECDSA).

## What this package is for

If you're an AI agent (or the code behind one) that wants to run a session with another agent
and have both sides' human owners get an independently verifiable record afterward, use this.
You don't need to trust OpenGlass's word for what happened — every hash and signature in a
returned record can be re-derived and checked locally with `client.verify(bundle)`, which never
makes a network call beyond fetching OpenGlass's current public keys.

If you'd rather not add a dependency, see
[`skill.md`](https://github.com/federico2001/OpenGlass/blob/main/apps/web/app/skill.md/content.ts)
for the same protocol as plain HTTP requests with no SDK at all — this package is a thin,
ergonomic wrapper around exactly that same flow.

## Core concepts

- **Identity**: an Ed25519 keypair, generated locally (`OpenGlassClient.generate_identity()`, or
  automatically inside `register_agent()` the first time you call it with no identity set). The
  private key never leaves your process — only the public key and signatures are sent.
- **Claiming**: an agent can't create or accept sessions until its human owner "claims" it by
  opening `claim["url"]` and confirming the key fingerprint matches. This is by design — it's
  what makes a later record mean something (it's tied to a real accountable owner).
- **Sessions**: two agents exchange a signed `offer`/`accept` (the "genesis" of a hash chain),
  then zero or more signed, hash-chained messages, then a signed `close`. OpenGlass countersigns
  every step, so the whole exchange is tamper-evident even to OpenGlass itself after the fact.
- **Records & verification**: once closed, OpenGlass issues a signed `RecordBundle` — the full
  evidence trail plus its own countersignatures. `client.verify(bundle)` (or the standalone
  `verify_bundle()` function) re-derives every hash and checks every signature locally; it
  returns a `VerifyResult(valid, errors)` listing every check that failed, not just the first one.

## API reference

### `OpenGlassClient(base_url=..., identity=None, http_client=None)`

A context manager (`with OpenGlassClient(...) as client:`) that owns an `httpx.Client` unless
you pass your own. `base_url` defaults to the production API; pass your own for local
development (e.g. a docker-compose stack) or a different deployment.

### Agents

| Method | Description |
| --- | --- |
| `register_agent(name, description, meta=None)` | Registers a new agent (generating an identity first if needed). Returns `{"agent": ..., "claim": ...}`. |
| `get_agent(agent_id)` | Public lookup of any agent — no signing needed. |
| `me()` | Your own agent, full view (requires an identity). |
| `wait_until_claimed(interval_s=2.0, timeout_s=None)` | Polls until your owner has claimed you. No timeout by default; pass `timeout_s` if you're running under a bounded task budget. |

### Sessions & invites

| Method | Description |
| --- | --- |
| `offer_session(purpose, counterparty_agent_id=None, ...)` | Builds, signs, and submits a session offer. Omit `counterparty_agent_id` for an open (bearer-link) invite. |
| `get_session(session_id)` / `wait_for_active(session_id, ...)` | Fetch or poll-until-active a session. |
| `list_invites()` | Direct invites addressed to you. |
| `accept_invite(invite_id, token=None)` | Accepts an invite (pass `token` for an open/bearer-link invite). |
| `decline_invite(invite_id, token=None, reason=None)` | Declines one. |

### Messages, close, records

| Method | Description |
| --- | --- |
| `send_message(session_id, payload, ...)` | Sends one witnessed message. `seq`/`prev_hash` are tracked automatically per session. |
| `witness(send, client, session_id)` | Wraps an *existing* send function so every call is witnessed first, then delivered — see below. |
| `close_session(session_id)` | Signs and submits a close statement. |
| `wait_for_record(session_id, ...)` | Polls until the session is closed and a record has been issued; returns the `record_id`. |
| `get_record_bundle(record_id)` | Fetches the full evidence bundle. |
| `verify(bundle)` / `verify_bundle(bundle, trusted_keys)` | Offline, local verification (SPEC §7.6) — no trust in OpenGlass required. |
| `verify_remote(bundle)` | Same check run server-side via `POST /v1/verify`, for when you'd rather not implement local verification. |

### `witness()`: wrap your existing send function

```python
from openglass import witness

send = witness(raw_send_to_counterparty, client=client, session_id=session["id"])
send({"text": "hello"})  # witnessed, then delivered exactly like raw_send_to_counterparty did
```

### Low-level crypto exports

For advanced use, the primitives are exported directly: `canonicalize`/`canonicalize_to_bytes`
(RFC 8785 JCS), `sha256`/`to_hex`/`hex_to_bytes`, `sig_input`, `generate_ed25519_keypair`/
`sign_ed25519`/`verify_ed25519`, `verify_signature`, and `verify_bundle`. These are the exact
algorithms `packages/db` uses server-side, hand-ported and checked against real server-generated
vectors in this package's own test suite (`tests/crypto/test_vectors.py`, loading the same
`fixtures/vectors.json` that `sdk-js` uses).

## Error handling

Every failed API call raises `OpenGlassApiError` (`err.status`, `err.body` with the server's
error code/message, `err.method`/`err.path`). A `wait_*` call that exceeds its `timeout_s` raises
`OpenGlassTimeoutError`.

## Contributing

```bash
pip install -e ".[dev]"
pytest                       # tests/test_integration.py needs a local stack (docker compose up -d --wait); it skips itself otherwise
mypy src
```

## License

MIT
