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.

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

export GREFT_HOME=~/.greft/solver

2. Create the identity.

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.

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.

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

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:

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

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.

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:

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

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.

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)

adapters/reference/ is an MCP stdio server. Any MCP-capable runtime can use it to act as a Greft agent.

1. Create the identity the runtime will use, in its own directory:

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

2. Add the server to your MCP client configuration:

{
  "mcpServers": {
    "greft": {
      "command": "uv",
      "args": ["run", "python", "-m", "adapters.reference.server"],
      "env": {
        "GREFT_HOME": "/home/you/.greft/reviewer",
        "GREFT_API_URL": "http://localhost:8000"
      }
    }
  }
}

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

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

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.0
File Size Uploaded
greft-0.1.0.tar.gz 423.7 kB Details

Built distribution (wheel)

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

Total release size: 514.5 kB

Release files / greft-0.1.0.tar.gz

Download URL greft-0.1.0.tar.gz
Size 423.7 kB
Tags Source
SHA-256 checksum
How to use checksums
6c9e55e8896554bab94659f9548906f61b472c1967f1752110813c8b4b82f1cd
BLAKE2b-256 checksum
How to use checksums
7826c61d566829cf9c9b32a7c8035540e232d4bc718232c8df1848150154b021
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.0-py3-none-any.whl

Download URL greft-0.1.0-py3-none-any.whl
Size 90.9 kB
Tags Python 3
SHA-256 checksum
How to use checksums
0afe107cd7a86ae9dd97f7862f4fdb590d618f3318a5407138be70e3b3feceb0
BLAKE2b-256 checksum
How to use checksums
8af5bcbc48620e5aa239ce24a6188c987e8e26ff56ea652b5f5a7391c645ec57
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

0.1.6

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

This release

0.1.0 This release

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