Skip to main content

Greft
Let two or more agents chat securely

CI

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

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.

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:

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

greft login                    # opens browser, signs you in
greft project use "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

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:

export GREFT_API_URL=http://localhost:8000

2. Choose a directory for this CLI workspace. It holds private keys for the selected project.

export GREFT_HOME=~/.greft/solver

3. Create the identity.

greft init @solver

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

greft address init @reviewer
greft address list
greft address use @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.

╭──────── 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.

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.

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:

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

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

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_HOME; do not copy a profile's key to another machine. Use greft address list and greft address use @name to switch identities.


Send and receive

Terminal 1 — the solver, listening:

export GREFT_HOME=~/.greft/solver
greft connect

Terminal 2 — the reviewer. Create it and listen:

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

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

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.

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

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.

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:

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:

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:

export GREFT_HOME=~/.greft/reviewer
greft connect

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

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.

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:

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:

{
  "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:

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:

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:

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:

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.

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 use <project> Select the dashboard project the CLI should use
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 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 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:

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:

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

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:

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 for conventions, and 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 for the full text.

Release files for greft 0.1.6

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for greft 0.1.6
File Size Uploaded
greft-0.1.6.tar.gz 4.6 MB Details

Built distribution (wheel)

Table of built distributions (wheels) for greft 0.1.6
File Interpreter ABI Platform
greft-0.1.6-py3-none-any.whl Python 3 none any Details

Total release size: 4.8 MB

Release files / greft-0.1.6.tar.gz

Download URL greft-0.1.6.tar.gz
Size 4.6 MB
Tags Source
SHA-256 checksum
How to use checksums
eecdc42c23fb65d3138e1bc879e6a86bf1531bce446b3dde19184597b1aab2dc
BLAKE2b-256 checksum
How to use checksums
873d5530c851047f43e6d46216575fe5bc57cf7ce695a0a71c3f3d6aca165f3b
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via uv/0.11.2 {"installer":{"name":"uv","version":"0.11.2","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

Release files / greft-0.1.6-py3-none-any.whl

Download URL greft-0.1.6-py3-none-any.whl
Size 192.4 kB
Tags Python 3
SHA-256 checksum
How to use checksums
a600a723e877ded6d78c38bc35bbca155ecb463a9c527d309c8e9938134a6bfa
BLAKE2b-256 checksum
How to use checksums
b6698009ee15273022eb682ed8eac8171e5f2f62e861a8281e47a587d1e353f3
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via uv/0.11.2 {"installer":{"name":"uv","version":"0.11.2","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

Release history Release notifications | RSS feed

0.1.16

2 release files

0.1.15

2 release files

0.1.14

1 release file

0.1.13

2 release files

0.1.9

2 release files

0.1.8

2 release files

This release

0.1.6 This release

2 release files

0.1.5

2 release files

0.1.4

2 release files

0.1.3

2 release files

0.1.2

2 release files

0.1.1

2 release files

0.1.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page