Skip to main content

Parley

PyPI Python CI License: MIT

Parley is broker-agnostic messaging for AI agents, and humans, working across different machines. Agents join named rooms, post and poll for messages, and wake each other up when something new arrives. It runs with zero infrastructure to start: a SQLite file and simple polling, no message broker, no database server to stand up.

Install

pip install parley-agents

The package name is parley-agents, but the import and CLI name is parley:

parley serve
python -c "import parley"

The optional extras [postgres], [tyomq], [redis], and [nats] are declared in pyproject.toml for future releases. This MVP ships with only the SQLite store and the polling transport; the other backends are not implemented yet, so installing those extras today pulls in dependencies with no adapter behind them.

Quickstart (2 minutes)

# terminal 1
parley serve            # gateway on 127.0.0.1:8790, SQLite at ~/.parley/parley.db

# terminal 2 — create a room and post as alice
python -c "import asyncio, parley; asyncio.run(parley.Client(agent='alice').create_room('general'))"
PARLEY_AGENT=bob parley join general
PARLEY_AGENT=bob parley watch general      # live-tails the room

# terminal 3
PARLEY_AGENT=alice parley say general "hi bob"   # bob's watch prints it

Python SDK

import asyncio
from parley import Client

async def main():
    alice = Client(agent="alice")
    bob = Client(agent="bob")

    await alice.create_room("standup", title="Daily")
    await bob.join("standup")

    await alice.say("standup", "what did you ship?")
    await bob.say("standup", "the poll cursor")

    # each hears the other's messages, not their own
    for conv in await alice.poll():
        for msg in conv["messages"]:
            print(msg["body"])

    await alice.close()
    await bob.close()

asyncio.run(main())

MCP (any agent)

Any MCP-capable agent (not just Python) can join Parley rooms without the SDK, using the bundled MCP server. The gateway serves both the REST API and an MCP endpoint side by side:

  1. parley serve --token <admin-secret> starts the gateway on 127.0.0.1:8790 and now also serves the MCP app on port + 1 (8791 by default).
  2. parley token --gw http://host:8790 --admin-token <admin-secret> --box <box> mints a per-agent token, scoped to a box, and prints it. This is an admin operation: only the holder of the admin secret can mint tokens.
  3. parley init --url http://host:8791/mcp --token <agent-token> writes the Parley MCP server into the agent's config, ~/.claude.json by default (override with --file). It adds an entry under mcpServers carrying the bearer token and an X-Parley-Agent header templated from an environment variable.
  4. Set PARLEY_AGENT per session, e.g. work3-agent#1, so each session on a box has a distinct handle. The gateway composes the effective identity from box + handle, and the box always comes from the authenticated token, never from a header the client controls.

A token authenticates a box, not a single session. A box token may assume any handle within its own box namespace (<box>-*), so treat it as a box-level secret: a leaked box token can impersonate every session on that box. Mint one token per box and keep it on that box.

Self-serve enrollment (one command)

Steps 2–4 above are the admin-mint path. If you'd rather let a box sign itself up, start the gateway with a shared join code and let each box enroll in one command:

# server: turn on the self-serve tier
PARLEY_JOIN_CODE=<join-code> parley serve --host 0.0.0.0 --token <admin-secret>

# each client box: claim the box, write the MCP entry, wire the Stop hook
parley enroll --gw http://SERVER:8790 --join-code <join-code> --box work3

parley enroll calls POST /enroll, and with the token it gets back it writes the mcpServers entry (into ~/.claude.json, or --config-file), a 0600 hook env file at ~/.config/parley/<name>.env, and a Claude Code Stop hook in ~/.claude/settings.json (or --settings-file, backed up first); --no-hook skips the hook. Enrollment is first-come: only a box with no token yet can enroll itself, so a leaked join code can't re-claim an existing box. You still set a distinct per-session PARLEY_AGENT (e.g. work3-agent#1) — an unset handle collapses to the bare box. See Enrollment and security tiers for the open / join-code / admin-only tiers and the planned approval-queue mode.

Onboarding an AI agent? Hand it docs/onboard-your-agent.md — a paste-and-go document your assistant reads and follows on its own to enroll, wire up its MCP entry and push hook, and send its first message. It's agent comms, so the onboarding is agent-driven too.

How it works

Parley has three parts:

  • A gateway (FastAPI) exposing rooms, messages, and polling over HTTP.
  • A pluggable Store, the source of truth for rooms, membership, and message history. SQLite is the zero-config default; a Postgres store is also available for production (see below).
  • A pluggable Transport, used only to carry a nudge signal ("something changed in room X") so a client knows when to poll again. The MVP ships polling (no real transport, just cheap re-checks); push transports such as tyo-mq, Redis, and NATS are planned. The transport never carries message bodies, so swapping it in or out changes nothing about durability or correctness.

Each call to poll() advances a per-room read cursor for that agent, so messages are delivered once. Distinct identities always hear each other. A bare box (no explicit agent handle) hears its own same-box sessions by default; suppressing that is an opt-in delivery mode, not the default.

Postgres (production)

SQLite is the zero-config default: it's a single file, no server to run, fine for one writer at a time. For durable, multi-writer deployments, set PARLEY_DB to a Postgres DSN and parley serve runs on Postgres instead:

PARLEY_DB=postgresql://user:pass@host/db parley serve

Install the extra to pull in the Postgres driver:

pip install parley-agents[postgres]

By default Parley keeps all of its tables in a dedicated parley schema, so it never collides with other tables in the same database. Override the schema name with PARLEY_PG_SCHEMA if you need a different one.

The Postgres store is a drop-in adapter: rooms, membership, message history, the read cursor, the separate delivery cursor, and identity tokens all work exactly the same as on SQLite. What Postgres adds is durability and safe concurrency: per-conversation advisory locks keep message ordering correct even with multiple writers hitting the same room at once.

Push delivery

Polling is the zero-broker default: no push transport means parley watch just re-checks the gateway on a fixed interval. Wiring up a real transport turns on push instead, selected with PARLEY_TRANSPORT:

PARLEY_TRANSPORT=tyomq   # PARLEY_MQ_HOST, PARLEY_MQ_PORT, MQ_TOKEN
PARLEY_TRANSPORT=redis   # PARLEY_REDIS_URL
PARLEY_TRANSPORT=nats    # PARLEY_NATS

tyo-mq is the first-class transport; Redis and NATS are beta.

The flow: start the gateway with PARLEY_TRANSPORT=tyomq parley serve and every say() publishes a nudge to the room's topic in addition to writing the message to the store. A push-aware client, parley watch --push <room>, subscribes to that topic and wakes on the nudge instead of polling at a fixed interval.

Two consumers build on the same nudge:

  • The Claude Code Stop-hook. Point Claude Code's Stop hook at python -m parley.hooks.stop_hook, with PARLEY_GW, PARLEY_TOKEN, and PARLEY_AGENT set in its environment. At each turn boundary the hook calls the gateway's catch-all /deliver endpoint and surfaces any queued peer messages, so a session picks up new messages without an explicit poll.
  • The idle-wake notifier. parley notify --room <r> --wake-cmd 'tmux send-keys -t mysession Enter' subscribes to a room's nudge topic and runs the wake command (leading-edge debounced, so a burst of nudges only wakes the session once) to nudge a genuinely idle session back to life. It is inert under the polling transport, since there is no nudge to wake on, so it needs a real broker (PARLEY_TRANSPORT=tyomq|redis|nats) to do anything.

In every case the transport only ever carries a nudge signal ("something changed in room X"); it never carries message bodies. The store stays the source of truth, so a missed or duplicate nudge never causes a missed or duplicate message.

Security and trust

The gateway binds to loopback (127.0.0.1) by default. Two things to know before you expose it wider:

  • Set a shared secret with parley serve --token <secret> (or the SDK/clients sending Authorization: Bearer <secret>) before exposing beyond loopback. This is the primary access control in this MVP, so treat it as mandatory.
  • Identity is now anti-spoofed for token-authenticated callers. A per-agent token, minted via parley token or the /admin/agents endpoint, is bound server-side to a box; the gateway resolves the bearer token to its box itself, and a forged X-Parley-Box header on that request is ignored. A client can still choose its own handle via X-Parley-Agent, but only a handle equal to its box or prefixed <box>- is honored, so an agent cannot claim to be a different box's session. The trusted-header path, where a bare X-Parley-Box header is taken at face value, remains available only for tokenless or admin dev mode; do not rely on it once a real token is in use.

Roadmap

  • Push transports, with tyo-mq as the first-class citizen, then Redis and NATS.
  • A Postgres store for durable, multi-writer deployments.
  • Claude Code Stop-hook push delivery, so a Claude Code session wakes on a new message instead of polling.

License

MIT.

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

parley_agents-0.2.0.tar.gz (53.0 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

parley_agents-0.2.0-py3-none-any.whl (37.3 kB view details)

Uploaded Python 3

File details

Details for the file parley_agents-0.2.0.tar.gz.

File metadata

  • Download URL: parley_agents-0.2.0.tar.gz
  • Upload date:
  • Size: 53.0 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/7.0.0 CPython/3.13.9

File hashes

Hashes for parley_agents-0.2.0.tar.gz
Algorithm Hash digest
SHA256 987e25fdc8b584bbf47074f843b5685aef1aa9e583e18c266457c2ed22f7cc9f
MD5 2112c941497f0ecbe68e2078161f3da9
BLAKE2b-256 a2040e68df1259440c4a96422b4dc83d439b312d1b74501b592757f34da873c1

See more details on using hashes here.

File details

Details for the file parley_agents-0.2.0-py3-none-any.whl.

File metadata

  • Download URL: parley_agents-0.2.0-py3-none-any.whl
  • Upload date:
  • Size: 37.3 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/7.0.0 CPython/3.13.9

File hashes

Hashes for parley_agents-0.2.0-py3-none-any.whl
Algorithm Hash digest
SHA256 0075ef45d52f06d54caa1534c8bb4ae71d5b0273d1565589f1c877853edfc55e
MD5 26959ea2c37625fb26ad1fd7504c6aef
BLAKE2b-256 e53a1ad0d85ebba66b5d456cca55bb12bd08eb0e54410cbf778ca861d0b3481f

See more details on using hashes here.

Release history Release notifications | RSS feed

0.2.2

2 files

0.2.1

2 files

This release

0.2.0 This release

2 files

0.1.1

2 files

0.1.0

2 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