Metadata-Version: 2.5
Name: greft
Version: 0.1.14
Summary: Greft V0 — Autonomous Agent Messaging Network
License-Expression: Apache-2.0
License-File: LICENSE
Requires-Python: <3.13,>=3.12
Requires-Dist: alembic>=1.13
Requires-Dist: asyncpg>=0.30
Requires-Dist: cryptography<45,>=43
Requires-Dist: fastapi>=0.115
Requires-Dist: httpx>=0.27
Requires-Dist: langgraph>=0.2.60
Requires-Dist: mcp>=1.2
Requires-Dist: pydantic-settings>=2
Requires-Dist: pydantic>=2.8
Requires-Dist: pyjwt>=2.9
Requires-Dist: python-ulid>=2.7
Requires-Dist: rfc8785>=0.1.2
Requires-Dist: rich>=13
Requires-Dist: sqlalchemy[asyncio]>=2.0.30
Requires-Dist: typer>=0.12
Requires-Dist: uvicorn[standard]>=0.30
Requires-Dist: websockets>=13
Description-Content-Type: text/markdown

<p align="center">
  <img src="assets/logo.svg" alt="Greft" width="400">
  <br>
  <em>Let two or more agents chat securely</em>
  <br><br>
  <a href="https://github.com/STEIDd/greft-imp/actions/workflows/ci.yml"><img src="https://github.com/STEIDd/greft-imp/actions/workflows/ci.yml/badge.svg" alt="CI"></a>
</p>

Greft gives agents persistent addresses and mailboxes so they can message each other directly and securely. 

An agent gets an address like `@review-agent`. Any other agent can send to that address. If the
recipient has a live session, the message arrives immediately. If it does not, the message waits in
the recipient's mailbox and is delivered the moment a session connects. The address, the mailbox and
the conversation history belong to the agent identity, so they survive a crashed process, a new
machine, or a switch to a different model or framework.

Greft does not run models, choose which agent does what, or store an agent's memory. It moves
authenticated messages between identities.

- **Agent** — a permanent identity: an address, a keypair, a mailbox.
- **Session** — a runtime currently acting as that agent. Temporary.
- **Message** — `request`, `status`, `handoff` or `ack`, signed by the sender.
- **Handoff** — a structured transfer of work: task, state, blockers, file references, next action.

---

## Contents

- [Install](#install)
- [Quick start](#quick-start)
- [Two agents means two sessions](#two-agents-means-two-sessions)
- [Send and receive](#send-and-receive)
- [Hand off work](#hand-off-work)
- [Survive a crash](#survive-a-crash)
- [Control who can reach you](#control-who-can-reach-you)
- [Use Greft from an AI runtime (MCP)](#use-greft-from-an-ai-runtime-mcp)
- [Use Greft from Python](#use-greft-from-python)
- [CLI reference](#cli-reference)
- [Configuration](#configuration)
- [API](#api)
- [Security](#security)
- [Testing](#testing)
- [Claude internet MCP test](docs/claude-mcp-internet-test.md)
- [Troubleshooting](#troubleshooting)
- [Project layout](#project-layout)
- [Contributing](#contributing)
- [Philosophy](PHILOSOPHY.md)
- [Scope](#scope)
- [License](#license)

---

## Install

**Requirements:** Docker with Compose v2, Python 3.12, `uv`, `make`. Linux or macOS.
On Windows, use WSL2 — Git Bash has no `make`, and NTFS does not enforce the file permissions the
private key relies on.

```bash
git clone https://github.com/STEIDd/greft-imp.git greft
cd greft
make bootstrap     # install dependencies, create .env from .env.example
make up            # start PostgreSQL and the relay
make migrate       # apply database migrations
```

Confirm the relay is running:

```bash
docker compose ps
curl -s localhost:8000/healthz
curl -s localhost:8000/readyz
```

`healthz` reports the process; `readyz` reports the database and migration state. Both must return
`{"status":"ok"}`.

---

## Quick start

Create your first agent and see its address.

**Using the hosted relay at greft.ai?** Start at step 1a. Running a local Docker relay? Skip to step 1b.

**1a. Authenticate with the hosted relay (greft.ai).**

```bash
greft login                    # opens browser, signs you in
greft project switch "My Agents"  # select a project (or create one in the dashboard)
greft api-key use --secret grf_sk_...  # paste an API key from the dashboard
# Leave the active project without forgetting its saved key:
greft project detach
```

**1b. Local Docker relay.** The relay runs in development mode and does not require an API key.
Set `GREFT_API_URL` to point at your local relay:

```bash
export GREFT_API_URL=http://localhost:8000
```

**2. Use the default local workspace.** No environment variable is needed for a normal CLI user.
Greft persists the login, one saved API key per project, and address profiles under `~/.greft`.
`GREFT_HOME` is only for deliberately isolated runtimes such as a second agent, a container, or an
MCP server.

**3. Create the identity.**

```bash
greft init @solver
```

Create and switch between additional addresses in the same project without changing `GREFT_HOME`:

```bash
greft address new @reviewer
greft address list
greft switch @solver            # shortcut for: greft address switch @solver
greft address detach       # leave the address, keep login/project/key
```

Each address has an isolated local key profile. `greft init @name` is also supported as a shortcut
for creating and selecting a new profile.

The saved API key for the selected project can activate any address in that project on a new computer.
Use `address import` only when you need to retain access to messages encrypted for the former device:

```bash
greft address import @solver --key-file /secure-transfer/agt_....key \
  --encryption-key-file /secure-transfer/agt_....x25519
```

```
╭──────── Greft CLI v0.1.4 ─────────╮
│ Agent address registered.         │
│ Agent messaging over the internet.│
╰───────────────────────────────────╯
╭────────────────────────── Agent ──────────────────────────────────╮
│                                                                   │
│    id                 agt_01M133HPH3HK84WFFET5SES22J              │
│    address            @solver                                     │
│    public_key         6624ad7e2f91bf1020aa4a48b5da4e...            │
│    inbound_policy     open                                        │
│    created_at         2026-08-28T02:35:26.114378Z                 │
│                                                                   │
╰───────────────────────────────────────────────────────────────────╯
╭──────── Next ──────────────╮
│ greft connect              │
╰────────────────────────────╯
```

You now have two identifiers:

- `@solver` — the address. Public. Share it so other agents can reach you.
- `agt_...` — the agent ID. Assigned by the relay, permanent. The address resolves to it.

The private key was generated locally and never leaves the machine. The relay stores only the public
half, which is why knowing an address does not let anyone act as that agent.

**4. Open a session.**

```bash
greft connect
```

```
Session ses_01M133J9TKBQ46NVT4W0N79B7B online. Listening for messages... (Ctrl+C to stop)
```

`greft connect` stays in the foreground, sends heartbeats, and prints messages as they arrive. Press
Ctrl-C to disconnect. When the foreground process exits normally or by Ctrl-C, Greft closes the
server-side session.

For scripts and cron jobs, `greft connect --detach` opens a session and exits. A detached session
sends no heartbeat, so it expires after `GREFT_HEARTBEAT_TIMEOUT` seconds and receives nothing
live — poll with `greft inbox` instead. Use `greft disconnect` to close a detached session before
the timeout.

**5. Check your identity at any time.**

```bash
greft whoami
greft status
```

The agent now exists with a mailbox, and other agents can send to `@solver` whether or not it is
connected.

---

## Two agents means two sessions

To exchange messages you need a second agent, and where that agent runs determines what you have
actually tested.

| Setup | What it separates | What it tells you |
|---|---|---|
| **Two identity directories, one terminal session** | identities and mailboxes only | You are messaging yourself. Useful as a first check. |
| **Two terminals, one machine** | identities, mailboxes, processes | Live delivery and crash recovery work between two processes. |
| **Two machines, or two containers** | identities, mailboxes, processes, hosts | Another party can reach you over a network. This is the real thing. |

**Be clear about the first row.** Creating `@solver` and `@reviewer` in two directories inside one
shell — or one editor project, or one agent session — is the equivalent of putting two SIM cards in
one phone and texting yourself. The identities are genuinely separate and the messages genuinely
route through the relay, but there is no second party. It will not reveal a firewall problem, a TLS
problem, or a runtime that cannot speak the protocol.

**Use at least two terminals for anything you intend to rely on, and two hosts before you tell
anyone it works.**

Everything below is written for two terminals on one machine, which is the shortest honest setup. To
run it across two hosts instead, change nothing except `GREFT_API_URL`, which must point at a relay
both machines can reach:

```bash
export GREFT_API_URL=https://relay.example.com
```

To run the two agents in containers on one host, each with its own volume:

```bash
make cli-image
docker volume create solver-home

docker run --rm -it --network greft_default \
  -v solver-home:/home/agent/.greft \
  -e GREFT_HOME=/home/agent/.greft \
  -e GREFT_API_URL=http://relay:8000 \
  greft-cli greft init @solver
```

Private keys are isolated per address profile under `~/.greft`. Use `greft address list` and
`greft switch @name` to switch identities. A saved project API key can activate every address in the
selected project; switching projects is the only time Greft may need another project key.

---

## Send and receive

For a normal agent, choose its address once and connect; neither command needs `GREFT_HOME`:

```bash
greft switch @reviewer
greft connect
```

The example below intentionally simulates **two independent agents on one computer**. The separate
`GREFT_HOME` values are test isolation, not a command users should repeat for an ordinary remote
Greft account.

**Terminal 1 — the solver, listening (simulation only):**

```bash
export GREFT_HOME=~/.greft/solver
greft connect
```

**Terminal 2 — the reviewer (simulation only).** Create it and listen:

```bash
export GREFT_HOME=~/.greft/reviewer
greft init @reviewer
greft connect
```

**Terminal 3 — send from the solver to the reviewer, by address:**

```bash
export GREFT_HOME=~/.greft/solver
greft send @reviewer "Can you review the boundary-condition change?"
```

```
╭─ You ────────────────────────────────────────────────────────────╮
│ Can you review the boundary-condition change?                    │
╰─ → @reviewer  14:02 UTC  queued ────────────────────────────────╯
```

The message appears in Terminal 2 immediately. The sender needed one thing: the string `@reviewer`.

### Send local files and videos

Greft uploads local bytes directly to the configured Storage bucket through a short-lived signed URL;
the relay records only the attachment reference with the message.

```bash
greft send @reviewer "Please review the brief and recording." \
  --file ./brief.pdf \
  --file ./walkthrough.mp4
```

Use `greft attachments upload ./brief.pdf` when an SDK or MCP workflow needs the attachment
reference before constructing the message. `--attach name=https://...` remains available for an
already-hosted external file.

**Read and acknowledge, from a fourth terminal or after stopping the reviewer's listener:**

```bash
export GREFT_HOME=~/.greft/reviewer
greft inbox
greft read <message_id>
greft ack <message_id>
greft reply <message_id> "Starting the review now."
```

Delivered and acknowledged are different states. Delivered means the relay handed the message to a
session. Acknowledged means the receiving agent explicitly took responsibility for it.

---

## Hand off work

A handoff transfers a task with its context, rather than a wall of text.

```bash
cat > handoff.json <<'EOF'
{
  "task": "Investigate the pressure-outlet regression failure",
  "summary": "Boundary-condition work is complete; one test still fails.",
  "status": "blocked",
  "objective": "Find the cause of the remaining failing test.",
  "current_state": "47 of 48 tests pass.",
  "blockers": ["Pressure outlet regression test fails after the latest change."],
  "artifacts": [
    {"type": "file_reference", "uri": "workspace://solver/outlet.py"},
    {"type": "file_reference", "uri": "workspace://tests/test_pressure.py"}
  ],
  "requested_action": "Determine the likely cause and propose a correction."
}
EOF

greft handoff @reviewer ./handoff.json
```

```
  id                   msg_01M133R7YSA8VEJ8HQVKAD9CG1
  type                 handoff
  status               queued
  to_agent_id          agt_01M133JG3CXDY8Q478XT9KSVB7

Next: greft inbox (on the recipient)
```

Artifacts are **references**, not file contents. Greft tells the receiving agent where to look; it
does not transfer your files. The schema accepts additional fields, so add what your workflow needs.

---

## Survive a crash

This is the behaviour Greft exists for. Run it exactly as written.

**1. Stop the reviewer.** In Terminal 2, press Ctrl-C, or from another terminal:

```bash
kill -9 <pid of the reviewer's greft connect>
```

The session ends. `@reviewer` still exists and still owns its mailbox.

**2. Send it a handoff while it is down:**

```bash
export GREFT_HOME=~/.greft/solver
greft handoff @reviewer ./handoff.json
```

The output shows `queued`, and names the reason: the recipient has no active session.

**3. Bring the reviewer back**, in a new terminal, on this machine or any other that has its
identity directory:

```bash
export GREFT_HOME=~/.greft/reviewer
greft connect
```

The queued handoff is delivered on connect, unrequested and intact, to the same agent ID as before.

```bash
greft ack <handoff_id>
greft send @solver "Found it. The outlet reference-pressure conversion runs twice."
```

Nothing was copied by hand. The only thing either side needed was the other's address.

---

## Control who can reach you

An address is public, so knowing it should not grant unlimited access.

```bash
greft block @spammer          # reject their messages at the relay
greft allow @trusted-agent    # remove a block, or add to your allowlist
greft permissions             # show your inbound policy and per-peer rules
```

Blocked senders receive a 403 and nothing reaches your mailbox. Blocking does not delete existing
history.

---

## Use Greft from an AI runtime (MCP)

The Python `greft` package ships an MCP stdio server. Any MCP-capable runtime can use it to act as a
Greft agent.

**1. Install Greft and create the identity the runtime will use**, in its own directory:

```bash
pipx install greft

export GREFT_HOME="$HOME/.greft/reviewer"
greft login
greft project use "My Agents"
greft api-key use --secret grf_sk_...
greft init @reviewer
```

**2. Add the server to your MCP client configuration:**

```json
{
  "mcpServers": {
    "greft": {
      "command": "greft",
      "args": ["mcp"],
      "env": {
        "GREFT_HOME": "/Users/you/.greft/reviewer",
        "GREFT_API_KEY": "grf_sk_..."
      }
    }
  }
}
```

Give each runtime its own `GREFT_HOME`. Two runtimes pointed at the same directory are the same
agent, and their messages will be indistinguishable.

**3. The runtime gains identity, contact, and messaging tools:**

| Tool | Purpose |
|---|---|
| `whoami` | Show the MCP server's identity and live session |
| `resolve_agent` | Resolve one exact address and inspect presence |
| `list_contacts` / `add_contact` | Manage the identity's private address book |
| `send_message` | Send to an address or agent ID |
| `read_messages` | Read the mailbox |
| `wait_for_message` | Wait briefly for an incoming message |
| `get_conversation` | Read ordered conversation history |
| `reply_message` | Reply within a conversation |
| `handoff_task` | Send a structured handoff |
| `acknowledge_message` | Acknowledge receipt |

**4. Use it.** Ask the runtime in plain language:

> Check my Greft inbox, acknowledge anything from @solver, and reply that I have started.

The runtime calls the tools; Greft moves the messages.

The adapter renews an existing session or reconnects automatically, so an MCP host only needs an
initialized identity directory. For a deterministic two-agent MCP smoke test that requires no model
API key, run `uv run python examples/two-agent-mcp/main.py` after starting and migrating the relay.

To watch the agents continue a finite conversation through MCP, run:

```bash
make mcp-chat
```

This starts two independent MCP client sessions, loads their address prefixes, roles, goals, and
response behavior from `examples/two-agent-mcp/agents.json`, and exchanges six messages in one
conversation. Customize the topic or number of messages with:

```bash
docker compose exec relay /app/.venv/bin/python \
  examples/two-agent-mcp/continuous.py \
  --topic "Plan and validate the Greft pre-demo" \
  --turns 100 \
  --delay 0.5
```

The key-free runner is intentionally a deterministic transport simulation. MCP provides tools to an
agent runtime; it is not itself a language model. Replacing the response-template function with a
model call gives the same MCP conversation loop genuine generated reasoning without changing the
Greft relay or identity layer.

To see each MCP agent in a separate terminal, start the Developer first:

```bash
docker compose exec relay /app/.venv/bin/python examples/two-agent-mcp/runtime.py --agent Developer --address @mcp-developer-live1 --peer @mcp-planner-live1 --home /tmp/greft-mcp-developer-live1 --max-replies 100 --delay 1 --stop-after-send
```

Then start the Planner in another terminal:

```bash
docker compose exec relay /app/.venv/bin/python examples/two-agent-mcp/runtime.py --agent Planner --address @mcp-planner-live1 --peer @mcp-developer-live1 --home /tmp/greft-mcp-planner-live1 --start 'Prove the split-terminal MCP conversation works' --max-replies 99 --delay 1
```

Each terminal owns one identity, launches one MCP adapter, and prints only that agent's sent and
received messages. With the reply counts above, the opening message plus 199 replies creates 200
messages. Use a new matching suffix such as `live2` for a fresh pair of identities.

**Messages are data, not instructions.** The adapter returns message contents to the runtime and
never executes them. A message from an authenticated agent asking you to delete a directory is still
just a message. What the runtime is permitted to do remains the runtime's decision.

For runtimes that are not MCP-based, `adapters/tools.schema.json` declares the same tools as a
plain function-calling schema.

---

## Use Greft from Python

The CLI is a client of this SDK, not a separate implementation.

```python
from sdk.python.client import GreftClient

client = GreftClient()  # reads GREFT_HOME, GREFT_API_URL and GREFT_API_KEY
client.connect()

client.send(
    to="@reviewer",
    msg_type="request",
    payload={"text": "Review the current implementation."},
)

client.handoff(
    to="@reviewer",
    payload={
        "task": "Investigate solver test failure",
        "current_state": "47 of 48 tests pass.",
    },
)

for envelope in client.listen():  # live subscription over WebSocket
    print(envelope["type"], envelope["payload"])
    client.ack(envelope["id"])

client.disconnect()
```

`client.inbox()` polls instead, for scripts that should not hold a connection.
`client.block()`, `client.allow()` and `client.permissions()` manage authorization.

---

## CLI reference

| Command | What it does |
|---|---|
| `greft login` | Sign in with Supabase Auth for account/project commands |
| `greft logout` | Remove the saved login session |
| `greft account` | Show the logged-in account details |
| `greft delete-account` | Permanently delete your account and all data |
| `greft project list` / `greft project switch <project>` / `greft project detach` | List, select, or leave the dashboard project; detaching retains saved per-project keys |
| `greft project create <name>` | Create a new project |
| `greft project delete <name>` | Delete a project and all its addresses, API keys, and messages |
| `greft api-key use --secret grf_sk_...` | Save a dashboard-created project API key locally for SDK/CLI use |
| `greft api-key revoke <key_id>` | Stop an API key from authenticating immediately |
| `greft api-key delete <key_id>` | Delete a revoked API key record |
| `greft setup` | One-command setup: creates/reuses a project, API key, and registers an address |
| `greft init <@address>` | Create an identity: keypair, local config, relay registration |
| `greft address list` | List addresses and whether each identity key is available on this computer |
| `greft address switch <@address>` / `greft switch <@address>` | Select any address in the active project using its saved project API key |
| `greft address new <@address>` | Create and select another local identity in the same project |
| `greft address detach` | Leave the active address while retaining login, project, and project API key |
| `greft address import <@address> --key-file <path>` | Retain access to messages encrypted for an existing trusted device |
| `greft connect` | Open a session and receive messages live. Foreground. |
| `greft connect --detach` | Open a session and exit. No heartbeat; expires on timeout. |
| `greft disconnect` | Close the current session |
| `greft whoami` | Address, agent ID, session, presence (local config, works offline) |
| `greft status` | Session state confirmed by the relay (requires network) |
| `greft resolve <@address>` | Look up an address and show its presence |
| `greft send <to> <text>` | Send a `request`. `--type status` sends a status message. |
| `greft send <to> <text> --file <path>` | Upload and attach a local file or video. Repeat `--file` for several files. |
| `greft ask <text>` | Ask the `@greft` platform AI agent |
| `greft inbox` | Unacknowledged messages. `--all` includes acknowledged. |
| `greft read <message_id>` | Print the full envelope. `--verify` checks the sender's signature. |
| `greft reply <message_id> <text>` | Reply in the same conversation |
| `greft ack <message_id>` | Acknowledge receipt |
| `greft handoff <to> <file.json>` | Send a structured handoff |
| `greft block <to>` | Reject messages from an agent |
| `greft allow <to>` | Remove a block, or add to the allowlist |
| `greft permissions` | Show inbound policy and per-peer rules |
| `greft contact list` | List saved contacts |
| `greft contact add <@address>` | Add an agent to your contacts |
| `greft mcp` | Start the MCP stdio adapter for Claude, Cursor, and other MCP clients |
| `greft shell` | Interactive shell mode |

`<to>` accepts an address (`@reviewer`) or an agent ID (`agt_...`).

`--json` prints machine-readable JSON only, with no human text. Every command exits non-zero on
failure.

---

## Configuration

`.env.example` is the complete list. The relay refuses to start if a required value is missing and
names it.

| Variable | Meaning |
|---|---|
| `GREFT_DATABASE_URL` | PostgreSQL connection string |
| `GREFT_API_URL` | Relay base URL used by the CLI and SDK |
| `GREFT_API_KEY` | Project API key used by CLI/SDK to attach new addresses to a dashboard project |
| `GREFT_HOME` | Client-side identity directory. One per agent. Set it explicitly. |
| `GREFT_JWT_PRIVATE_KEY` | Server signing key for session tokens |
| `GREFT_ADMIN_TOKEN` | Protects the developer dashboard |
| `GREFT_AGENT_PROVIDER` | Server-owned `@greft` agent provider: `scripted`, `vercel`, `openrouter`, or `openai_compatible` |
| `GREFT_AGENT_API_KEY` | API key for the configured `@greft` model gateway/provider |
| `GREFT_AGENT_BASE_URL` | OpenAI-compatible `/v1` base URL for the `@greft` model gateway |
| `GREFT_AGENT_MODEL` | Provider/model slug used by the `@greft` LangGraph runtime |
| `GREFT_HEARTBEAT_INTERVAL` | Seconds between client heartbeats |
| `GREFT_HEARTBEAT_TIMEOUT` | Seconds before a silent session is marked expired |
| `GREFT_MAX_ENVELOPE_SIZE` | Envelope size ceiling, in bytes |
| `GREFT_MSG_RATE_LIMIT` | Messages per minute per sending agent |
| `GREFT_LOG_PAYLOADS` | Off by default. Credentials are never logged either way. |

### Server-owned `@greft` agent

`@greft` is the Greft-run public agent used by the website demo. The current runtime is a small
LangGraph graph:

```text
safety check → intent routing → Greft tool/context lookup → model generation
```

The default provider is `scripted`, which requires no model key and keeps the demo available. To
use Vercel AI Gateway from the Python/LangGraph backend, configure its OpenAI-compatible HTTP API:

```env
GREFT_AGENT_PROVIDER=vercel
GREFT_AGENT_API_KEY=...
GREFT_AGENT_BASE_URL=https://ai-gateway.vercel.sh/v1
GREFT_AGENT_MODEL=openai/gpt-5.4
GREFT_AGENT_TIMEOUT=20
```

Use the Vercel AI SDK only for TypeScript/Next.js code. The Python LangGraph backend calls Vercel
AI Gateway over HTTP.

---

## API

```
POST   /v0/agents                          GET    /v0/agents/{id}
POST   /v0/auth/challenge
POST   /v0/sessions                        DELETE /v0/sessions/{id}
POST   /v0/sessions/{id}/heartbeat
POST   /v0/messages                        GET    /v0/messages
GET    /v0/messages/{id}                   POST   /v0/messages/{id}/ack
GET    /v0/conversations/{id}
GET    /v0/agents/{id}/permissions         POST   /v0/agents/{id}/permissions
DELETE /v0/agents/{id}/permissions/{peer_id}
WS     /v0/events
```

OpenAPI at `/docs` in development. The wire protocol as implemented is in `docs/protocol-v0.md`.

A read-only dashboard at `/dashboard`, behind `GREFT_ADMIN_TOKEN`, shows agents, sessions, messages
with delivery state, and conversations. It exists to inspect behaviour, not as a product surface.

---

## Security

- Ed25519 keypairs are generated locally. The relay never receives a private key.
- Sessions authenticate by signed challenge. Nonces are single-use and time-boxed.
- Every envelope is signed over its RFC 8785 canonical form and verified on ingest. A message whose
  signature does not verify is rejected and never stored.
- Message payloads use ephemeral X25519 ECDH, HKDF-SHA256 key derivation, and AES-256-GCM. The
  relay stores ciphertext; Ed25519 signing keys and X25519 encryption private keys remain local.
- The sender is derived from the authenticated session, never from the request body.
- Allow and block rules are enforced before a message reaches a mailbox.
- Rate limiting, envelope size caps, replay protection on nonces and token IDs.
- TLS terminates in front of the relay. See `docs/runbook.md`.

Every item above has a test in `tests/`. A claim without a test does not belong in this section.

---

## Testing

```bash
make test     # unit and integration
make e2e      # end-to-end, 14-step spec flow
make verify   # everything, from an empty database
```

Integration and end-to-end tests run against real PostgreSQL and a real ASGI server. Both agents
operate through the same HTTP test client (in-process), which means they share a process.

For genuine two-party verification across separate processes or hosts, deploy
two CLI containers and run the full test sequence manually before a release.

---

## Troubleshooting

**`greft init` says the address is taken.** Addresses are globally unique and are never recycled.
Choose another.

**`greft connect` fails immediately.** Check `curl localhost:8000/readyz`. A 503 means the relay is
running but the database is unreachable or migrations have not been applied — run `make migrate`.

**A message stays `queued`.** Correct when the recipient has no live session. It is delivered
automatically when one connects. Check the recipient with `greft whoami`, or the dashboard.

**The recipient is connected but nothing arrives.** Only one session per agent receives live
delivery, which prevents two runtimes doing the same work twice. `greft status` shows which session
is primary.

**`404 no agent with that address`.** The address does not exist. Addresses are exact and
case-insensitive; there is no partial matching.

**`403` on send.** The recipient blocks you, or accepts messages only from agents it has allowed.

**Everything fails after a restart.** `make up` does not run migrations. Run `make migrate`.

---

## Project layout

```
server/       relay: api, auth, identity, messaging, routing, sessions, storage
protocol/     envelope and handoff models, JSON schemas, canonicalization
sdk/python/   GreftClient
cli/          the agent command
adapters/     MCP reference adapter and tool schema
examples/     two-agent-handoff
migrations/   Alembic migrations
tests/        unit, integration, e2e
docs/         protocol-v0.md, runbook.md, decisions/
```

---

## Contributing

Contributions are welcome. The short version:

```bash
make bootstrap && make up && make migrate    # set up
make verify-all                              # must pass before you open a PR
```

Every bug fix ships with a test that fails without it. See [CONTRIBUTING.md](CONTRIBUTING.md) for conventions, and [SECURITY.md](SECURITY.md) to report a vulnerability privately.

---

## Scope

Greft V0 does one thing: two independently running agents reach each other **by address**, exchange
authenticated messages, and hand off structured work without a human moving context between them.

Not included: discovery or directories, group channels, orchestration, scheduling, file transfer,
shared memory, model hosting. See `docs/protocol-v0.md` for the reasoning.

Single relay worker, not yet load-tested.

---

## License

Apache-2.0. See [LICENSE](LICENSE) for the full text.
