# parley.
### Several owners, private limits, one decision that crosses nobody's red line
**Give each party's agent a private red line. Parley picks the option that crosses nobody's line and lifts
the least-happy party, and every owner can check on their own machine that their limit held.** Nobody
shows their sheet, not even to the coordinator.
Red lines are code, not prompts · multilateral (N>2) · non-crypto · self-hosted · zero-dependency Python · Apache-2.0.
[](https://github.com/5uper0/parley/actions/workflows/ci.yml)
[](LICENSE)
[](pyproject.toml)

[](CONTRIBUTING.md)
**[Try it live](https://parleyprotocol.com/demo/)** · **[See it in 30 seconds](#see-it-in-30-seconds)** · [Roadmap](docs/ROADMAP.md) · [Changelog](CHANGELOG.md) · [Security](SECURITY.md) · [Contributing](CONTRIBUTING.md) · [Español: herencia entre hermanos](https://parleyprotocol.com/es/reparto-de-herencia-entre-hermanos/) · [Español: ¿unanimidad para vender?](https://parleyprotocol.com/es/unanimidad-vender-piso-heredado/) · [English: do all heirs have to agree to sell a flat in Spain?](https://parleyprotocol.com/spain-inherited-flat-do-all-heirs-have-to-agree/) · [Ukrainian](https://parleyprotocol.com/uk/zghoda-spadkoyemtsiv-prodazh-kvartyry/)
Rival parties reach a max-min decision · a shortcut that would cheat someone is **BLOCKED** by a red line · every party verifies the tamper-evident receipt.
Works from an MCP host (Claude, Cursor) and from [OpenAI Agents SDK, smolagents and LangGraph](examples/frameworks/): the three recipes return the same receipt hash for the same decision.
---
## Install
```bash
pip install parley-consensus # zero-dependency core
pip install "parley-consensus[crypto]" # + Ed25519-signed verdicts
```
The distribution is `parley-consensus` (PyPI's `parley` is an unrelated project); the import name is `parley`.
### Install as a Claude Code plugin
```
/plugin marketplace add 5uper0/parley
/plugin install parley@parley
```
This registers the `parley-mcp` stdio server, launched with `uvx --from parley-consensus parley-mcp`, so [uv](https://docs.astral.sh/uv/) must be installed.
### Quickstart
```python
from parley.agent import Agent
from parley.consensus import run_consensus
from parley.preferences import HardConstraint, PreferenceSheet
options = [
{"venue": "rooftop", "cost": 900},
{"venue": "garden", "cost": 600},
{"venue": "diner", "cost": 300},
]
ana = PreferenceSheet(
"Ana", utility=lambda o: 1.0 if o["venue"] == "rooftop" else 0.4)
# a red line: code, not a preference
bob = PreferenceSheet(
"Bob",
hard=[HardConstraint("budget", lambda o: o["cost"] <= 700)],
utility=lambda o: 1 - o["cost"] / 1000)
cara = PreferenceSheet(
"Cara", utility=lambda o: 0.9 if o["venue"] == "garden" else 0.5)
agents = [Agent(s.owner, s) for s in (ana, bob, cara)]
result = run_consensus(agents, options)
print(result.status, result.decision)
print("receipt sha256:", result.transcript.hash())
# each owner replays their own private sheet, locally
for sheet in (ana, bob, cara):
ok = result.transcript.verify_non_betrayal(sheet, result.decision)
print(sheet.owner, "not betrayed:", ok)
```
```
agreed {'venue': 'garden', 'cost': 600}
receipt sha256: 30483303e4d94e22833161d9bb8c292c0effff88c3303d7e195e57d49c88f516
Ana not betrayed: True
Bob not betrayed: True
Cara not betrayed: True
```
The hash above is what version 0.2.1 and later print: a rejected option's score is reported as 0. Version 0.2.0 prints `a9cb75c0...` for the same snippet; the decision and the verdicts are identical.
The rooftop is Ana's favourite and loses anyway: it crosses Bob's budget red line, so it is rejected
before any score is weighed. The coordinator only ever saw `red-line`, never the budget or the sheet.
Contributors install from a clone instead: `pip install -e ".[dev]"` (see [Status](#status-v0-working-core)).
### Use it from an MCP host (Claude, Cursor)
The base install ships a stdlib-only MCP server over stdio. Point your host at it:
```json
{"mcpServers": {"parley": {"command": "parley-mcp"}}}
```
If the console script is not on the host's `PATH`, launch it through the interpreter that has
the package: `{"command": "python", "args": ["-m", "parley.mcp"]}`.
The server speaks both MCP eras: hosts on the 2026-07-28 revision (no `initialize`, version and
capabilities in each request's `_meta`, `server/discover` for discovery) are served statelessly,
and hosts on 2025-11-25 or earlier get the usual `initialize` handshake. Same tools, same receipt
hash on both.
Three tools appear: `parley_decide` (options + each party's red lines and preferences in,
`status`, `decision`, `transcript` and `transcript_sha256` out), `parley_verify_receipt`
(recompute the hash over a transcript, and recompute the max-min decision from its recorded
verdicts) and `parley_check_party` (replay one party's red lines against a decision). To check what `parley_decide` returned,
pass its `transcript` and `transcript_sha256` to `parley_verify_receipt`: `scripts/verify-receipt.py`
reads the receipt file `examples/real_decision.py` writes, not the `parley_decide` result, and rejects it.
The input
is the same JSON as the recipes in [`examples/demo/`](examples/demo/), passed under one `spec` key. If you
call the tools yourself instead of through a host, the three calls look like this (`arguments` of `tools/call`):
```json
{"name": "parley_decide",
"arguments": {"spec": {"title": "...", "options": [...],
"parties": [...]}}}
{"name": "parley_verify_receipt",
"arguments": {"transcript": {...},
"sha256": "