Metadata-Version: 2.4
Name: ace-sdk
Version: 1.0.1
Summary: ACE Protocol SDK: post-quantum agent identity, private packets and profiles
Author: ACE Protocol
License-Expression: Apache-2.0
Project-URL: Homepage, https://aceprotocol.org
Project-URL: Repository, https://github.com/aceprotocol/ace-sdk-py
Project-URL: Issues, https://github.com/aceprotocol/ace-sdk-py/issues
Keywords: ace,agents,post-quantum,ml-dsa,x-wing,encryption,identity
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Developers
Classifier: Operating System :: MacOS
Classifier: Operating System :: POSIX
Classifier: Programming Language :: Python :: 3
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: Programming Language :: Python :: 3.14
Classifier: Topic :: Security :: Cryptography
Requires-Python: >=3.10
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: cryptography>=48.0.1
Requires-Dist: typing-extensions>=4.1; python_version < "3.11"
Dynamic: license-file

# ace-sdk

Python SDK for the ACE protocol: post-quantum agent identity (ML-DSA key event logs),
private asynchronous packets (X-Wing prekeys + AES-256-GCM, ML-DSA signatures), signed receipts,
admission (permits, contact requests, blocks) and data-only profile manifests.
ACE lets agents express intent, see which interaction rules a peer supports and exchange
information safely. Who to work with, what to agree and whether to pay are application decisions.

Requires Python 3.10+ and `cryptography>=48` on a POSIX system (Linux, macOS; the file store uses
`fcntl` locks, so Windows is unsupported). Everything is synchronous. `ace/__init__.py` is the
only public module.

```bash
pip install ace-sdk
```

## Pieces

| Area | API |
|---|---|
| Identity | `SoftwareIdentity.generate/from_export/recover`, `.rotate()`, `.delegate()`, `verify_kel`, `extend_kel` |
| Prekeys | `PrekeyVault()` (memory) / `PrekeyVault(path)` (0600 file): `current()`, `rotate(now)`, `open(id)`, `prune(now)` |
| Packets | `encode_content`, `create_packet`, `decode_packet`, `verify_packet`, `decrypt_packet` (the content bytes), `decode_content` |
| Descriptors | `create_published_agent`, `verify_published_agent` -> `VerifiedAgent` |
| Card records | `create_card_record` (`pay`, `erc8004`), `verify_card_bundle` -> `VerifiedCard`, `is_caip10_account`, `validate_erc8004`, `verify_erc8004_registration(json, ace_id)`; `RelayClient.publish_card/card/handle`; `NetworkClient.lookup_card/lookup_with_card` (descriptor first: no live descriptor, no card) |
| Admission | `create_permit`, `verify_permit`, `PeerStore` (pins, `pending/admitted/blocked`, permits); `NetworkClient.create_invitation/import_invitation` |
| Delivery | `Outbox` (persist, re-encrypt per attempt, backoff 60 s→1 h, expiry after 7 days, `prune`), `Inbox.open` (receive pipeline, receipts, quarantine) |
| Profiles | `Profiles([TEXT, DELEGATION, COMMERCE, ProfileManifest(custom)])`; core (`ace:receipt:1`, `ace:contact:1`) is always installed |
| Rules | `ProfileRules` (`types`, `check(event, body)`, `commit(event, body)`; send-side `commit` on the `delivered` receipt, `thread_busy` while a governed message is in flight); optional `commerce_rules(store, ace_id)`, `delegation_rules(peers)` |
| Delegation | `ace:delegation:1`: `create_delegation_credential`, `validate_delegation_ext`, `verify_delegation_credential`, `NetworkClient.verify_delegation` (pins the issuer) |
| Network | `RelayClient` (SSRF-safe; `allow_loopback=True` only for local development), `NetworkClient.open`; optional wake hints `set_wake(gateway, token)` / `clear_wake()` (07 § Wake hints) |
| Relay auth | `create_auth_headers`, `create_registration_request` (relay-side verification lives in the relay) |

## Over a relay

```python
from ace import FileStore, NetworkClient, PrekeyVault, RelayClient, SoftwareIdentity, TEXT

me = SoftwareIdentity.generate()
net = NetworkClient.open(
    me,
    FileStore("state"),
    RelayClient("https://relay.aceprotocol.org"),
    PrekeyVault("state/prekeys.json"),
    handle="alice",  # card record (04 § Card record): set once at this home relay
    contact="open",  # strangers may send contact requests (default "closed")
    listed=True,  # appear in the relay directory (default False)
    on_message=print,
    on_receipt=lambda frm, body, entry: print(body),
)
net.publish()  # descriptor, then card record; registers on demand; prekey rotates every 24 h
net.lookup_card("@bob@relay.aceprotocol.org")  # verified card: handle, card, pay, contact
net.connect("@bob@relay.aceprotocol.org", profile=TEXT.id, note="hi")
net.pull()  # receive, send receipts, ack (or net.follow())
for request in net.requests():  # requests never admit anyone by themselves
    net.accept(request.from_id)
mid = net.send(peer_id, "ace:text:1", {"message": "hello"})
net.maintain()  # publish, push permits, sync blocks, retry, prune
```

The host calls `net.maintain()` once at start and then at least every 3600 seconds.

A relay's `{ok: true}` is not delivery: only a signed receipt settles an Outbox entry.
`examples/quickstart.py` is a tested in-memory walkthrough.

Only one process may hold the receive role of a store (`Inbox`, lock `receive`); others create
`NetworkClient.open(..., receive=False)` to publish and send. Pass `store=` to `RelayClient` so
registration timestamps stay strictly increasing across processes sharing an identity.

## Delegation

```python
from ace import DELEGATION, TEXT, NetworkClient, Profiles, create_delegation_credential

net = NetworkClient.open(..., profiles=Profiles([TEXT, DELEGATION]))  # runs delegation_rules itself
credential = create_delegation_credential(me, subject_id)  # 7 days at most; revoke by not renewing
net.send(subject_id, "ace:delegation:1", {"credential": credential})  # hand it to its subject
# the subject publishes it: ext={"ace:delegation:1": {"credentials": [credential]}}
net.verify_delegation(credential)  # anyone: looks the issuer up, pins it, checks the signature
```

A credential grants no execution, payment or communication right.

## Commerce

```python
from ace import COMMERCE, TEXT, Profiles, commerce_rules

profiles = Profiles([TEXT, COMMERCE])
rules = [commerce_rules(store, me.get_ace_id())]  # pass to NetworkClient / Inbox / Outbox
```

The state machine is `idle → requested → offered → accepted | rejected`, then
`invoiced → paid → delivered → confirmed`. Settlement is `stablecoin` (CAIP-19 asset) or
`stripe-link`. A received commerce message never authorizes a payment.

## Development

```bash
uv run pytest -q
uv run ruff check . && uv run ruff format --check .
```
