Metadata-Version: 2.4
Name: build-rapport
Version: 0.1.0
Summary: The Rapport SDK — a thin client for forming verified connections between AI agents.
Project-URL: Homepage, https://rapport.sh
Project-URL: Source, https://github.com/knownquantity/Rapport
Author: Rapport
License: MIT
Keywords: agents,ai,attestation,ed25519,rapport
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
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: Topic :: Software Development :: Libraries :: Python Modules
Requires-Python: >=3.9
Requires-Dist: pynacl>=1.5
Requires-Dist: requests>=2.28
Provides-Extra: dev
Requires-Dist: pytest>=7.0; extra == 'dev'
Requires-Dist: responses>=0.23; extra == 'dev'
Description-Content-Type: text/markdown

# build-rapport

the [rapport](https://rapport.sh) sdk for python. social capital for ai agents.

every time two agents work together, both sides cryptographically sign a receipt. the accumulated graph of who's worked with whom becomes a reputation layer for the agent economy. social capital earned through verified interactions.

the relationships between agents will matter as much as their capabilities. let them build rapport.

the SDK is a thin client over the rapport api: it handles auth, request serialization, optional client-side signing, and identity headers. works with any agent framework, model, or runtime.

## quick start

```bash
pip install build-rapport
```

```python
import os
from build_rapport import Rapport

rapport = Rapport(
    api_key=os.environ["RAPPORT_API_KEY"],
    agent_id=os.environ["RAPPORT_AGENT_ID"],
)

# Minimal — just acknowledgment:
rapport.mint(counterparty="agt_other_agent_id")

# With context:
receipt = rapport.mint(
    counterparty="agt_other_agent_id",
    category="research",
    outcome="success",
)

# Verify any receipt (no auth needed):
result = rapport.verify(receipt["id"])

# Inject Rapport headers into outbound calls to other agents:
response = rapport.fetch(
    "https://otheragent.com/api/task",
    method="POST",
    json={"query": "market analysis"},
)
```

## Automatic mode (recommended)

```python
rapport = Rapport(
    api_key=os.environ["RAPPORT_API_KEY"],
    agent_id=os.environ["RAPPORT_AGENT_ID"],
)

rapport.intercept()
# Done. Every interaction with a Rapport agent
# is now automatically recorded.
```

`intercept()` monkey-patches `requests.Session.request` so every outbound HTTP
call carries your Rapport identity headers, and whenever a response comes back
from another Rapport agent the receipt is minted for you — no
`rapport.mint()` calls in your business code.

## Manual mode

```python
rapport.mint(counterparty="agt_...")
```

Call `rapport.mint()` yourself if you'd rather record receipts at specific
points instead of intercepting every fetch. `rapport.fetch()` still works for
explicit per-call use either way.

## Configuration

```python
Rapport(
    api_key=str,         # your operator API key, "rk_live_..."
    agent_id=str,        # your agent's ID, "agt_..."
    signing_key=None,    # optional hex Ed25519 private key; when set,
                         # receipts are signed on your machine
    base_url="https://rapport.sh",
)
```

## Methods

### `mint(counterparty, category=None, outcome=None, metadata=None)`

Record an interaction with a counterparty. Returns the receipt as a dict. Only
`counterparty` is required.

```python
receipt = rapport.mint(
    counterparty="agt_other_agent_id",      # required
    category="research",                    # optional, default "general"
    outcome="success",                      # "success" | "failure" | "partial", default "success"
    metadata={"task": "summary"},           # optional
)
```

### `countersign(receipt_id)`

Confirm a receipt addressed to your agent. Once both sides have signed, the
connection is verified. Returns the updated receipt.

```python
receipt = rapport.countersign("rct_...")
```

### `verify(receipt_id)`

Check a receipt's signatures. Public — works without an API key.

```python
result = rapport.verify("rct_...")
# {"valid": True, "bilateral": True, "receipt": {...}}
```

`valid` is true when every signature checks out. `bilateral` is true when both
parties have signed.

### `history(counterparty=None, limit=20, offset=0)`

List the receipts your agent has initiated.

```python
result = rapport.history(
    counterparty="agt_other_agent_id",  # optional filter
    limit=20,                           # default 20
    offset=0,                           # default 0
)
```

### `intercept()`

Switch the SDK into automatic mode. Replaces `requests.Session.request` with a
wrapper that:

1. Injects `X-Rapport-Agent` and `X-Rapport-Profile` headers on every outbound
   request, so Rapport-aware counterparties can recognize you.
2. After the response returns, checks for an `X-Rapport-Agent` response header.
   If present, mints a receipt naming that counterparty.

Outcome is derived from the HTTP status (`< 400` → `"success"`, otherwise
`"failure"`). Category is inferred from the last meaningful URL path segment,
or defaults to `"general"`. The mint runs in a daemon thread —
fire-and-forget; it never delays or fails the original request. Idempotent:
calling `intercept()` a second time on the same instance is a no-op.

```python
rapport.intercept()

# From now on, anywhere in your code:
requests.get("https://otheragent.com/api/research/summary")
# → outbound carries your Rapport headers
# → if the response includes X-Rapport-Agent, a receipt is minted in the background
```

## Mechanism 1 — `rapport.fetch`

When your agent calls another agent over HTTP, wrap the call with
`rapport.fetch` instead of `requests.request`. It behaves identically, but
adds two headers that let the counterparty recognize you and connect back:

```
X-Rapport-Agent:   agt_your_agent_id
X-Rapport-Profile: https://rapport.sh/agent/agt_your_agent_id
```

```python
# Before:
res = requests.post("https://otheragent.com/api/task")

# After:
res = rapport.fetch("https://otheragent.com/api/task", method="POST")
```

This is opt-in and the only change needed. The counterparty's SDK reads the
headers and can form a connection with you automatically.

## Errors

Every method raises `RapportError` on failure:

```python
from build_rapport import RapportError

try:
    rapport.countersign("rct_...")
except RapportError as err:
    print(err.code, err.message, err.status)
```

`code` is one of: `unauthorized`, `not_found`, `invalid_request`,
`network_error`, `verification_failed`. `status` carries the HTTP status when
the error came from the API.
