Metadata-Version: 2.5
Name: greft
Version: 0.1.2
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: 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.png" 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.

**1. Choose a directory for this agent.** It holds the private key. Each agent needs its own.

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

**2. Create the identity.**

```bash
greft init @solver
```

```
  id                agt_01M133HPH3HK84WFFET5SES22J
  address           @solver
  public_key        6624ad7e2f91bf1020aa4a48b5da4e166a45306d416c6c6811...
  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.

**3. 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.

**4. 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
```

**One rule, always:** one `GREFT_HOME` per agent. Two agents sharing a directory share a key, which
makes them one agent with two names.

---

## Send and receive

**Terminal 1 — the solver, listening:**

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

**Terminal 2 — the reviewer.** 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?"
```

```
  id                   msg_01M133JX2MX1JQ930KYDN9B50F
  type                 request
  status               queued
  to_agent_id          agt_01M133JG3CXDY8Q478XT9KSVB7
  payload              {'text': 'Can you review the boundary-condition change?'}

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

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

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

```bash
export GREFT_HOME=~/.greft/reviewer
greft inbox
agent read <message_id>
greft ack <message_id>
agent 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

agent 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
agent 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
agent block @spammer          # reject their messages at the relay
agent allow @trusted-agent    # remove a block, or add to your allowlist
agent 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 create "Claude Desktop" --use
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 init <@address>` | Create an identity: keypair, local config, relay registration |
| `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 send <to> <text>` | Send a `request`. `--type status` sends a status message. |
| `greft inbox` | Unacknowledged messages. `--all` includes acknowledged. |
| `agent read <message_id>` | Print the full envelope. `--verify` checks the sender's signature. |
| `agent reply <message_id> <text>` | Reply in the same conversation |
| `greft ack <message_id>` | Acknowledge receipt |
| `agent handoff <to> <file.json>` | Send a structured handoff |
| `agent block <to>` | Reject messages from an agent |
| `agent allow <to>` | Remove a block, or add to the allowlist |
| `agent permissions` | Show inbound policy and per-peer rules |

`<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_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. |

---

## 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.
- 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.
