parley-cli 2.2.0

dotnet tool install --global parley-cli --version 2.2.0
                    
This package contains a .NET tool you can call from the shell/command line.
dotnet new tool-manifest
                    
if you are setting up this repo
dotnet tool install --local parley-cli --version 2.2.0
                    
This package contains a .NET tool you can call from the shell/command line.
#tool dotnet:?package=parley-cli&version=2.2.0
                    
nuke :add-package parley-cli --version 2.2.0
                    

Parley

parley is a durable, role-addressed message channel for coordinating independent agent sessions through a shared JSONL transcript. It lets Claude Code, Codex, other agent harnesses, and humans communicate directly instead of relaying every message through a person.

Parley has no daemon of its own. Each command invocation is short-lived and the shared conversation is persisted under ~/.parley/channels/ (or PARLEY_HOME). An optional harness integration for Claude Code, Pi, and Codex can wake the exact session that currently owns a recipient role.

Parley is a free tool in the Zero to MVP toolkit. Explore the toolkit and subscribe for updates, or tell us what tool is missing.

Contents

Installation

Official packages

Channel Installation Runtime required
GitHub Releases Download the archive for your OS and architecture, then place parley (or parley.exe) on PATH No
NuGet dotnet tool install --global parley-cli .NET 10 runtime
Homebrew brew install zerotomvp/tap/parley No
Scoop scoop bucket add zerotomvp https://github.com/zerotomvp/scoop-bucket then scoop install parley No

Official releases are published through GitHub Releases, NuGet, Homebrew, and Scoop. Release archives include SHA-256 checksums and build-provenance attestations. Do not treat third-party packages with similar names as official Parley distributions. See CHANGELOG.md for release history.

Build from source

Use a source build when contributing or testing unreleased changes. Building requires the .NET 10 SDK:

git clone https://github.com/zerotomvp/parley-cli.git
cd parley-cli
./install.sh

The installer packs the current checkout and installs it as the global .NET tool parley-cli, pinning the exact version from the generated package so a dirty local prerelease cannot resolve to a stable package from another NuGet feed. It warns before installing a dirty worktree. To build without changing your global tools, run dotnet build -c Release instead. For an isolated install, use ./install.sh --tool-path /path/to/tools; --force permits a non-interactive dirty-checkout install.

Quick start

Choose a channel name with a random five-letter suffix, give every participant a distinct role, and have each participant join once:

parley join review-xyzab --as author
parley join review-xyzab --as reviewer

When the opener must not be sent until its recipient is actually present, wait on roster state instead of inferring a join from transcript silence:

parley members wait review-xyzab reviewer

Pass several roles as additional arguments, and optionally bound the wait with --timeout <seconds>. This command does not read messages or create a cursor.

The opener guards against accidentally reusing an existing channel with --expect-new:

parley send review-xyzab --as author --to reviewer --expect-new \
  -m 'Please review this change.'

Every receive declares a model-context checkpoint: the highest Parley sequence actually present in the caller's context. Use 0 before seeing any message:

parley recv review-xyzab --as reviewer --last-seen 0
parley recv review-xyzab --as reviewer --last-seen 12 --wait

This model-context checkpoint can replay messages when a previous CLI process emitted them but an agent harness failed to put that output into model context. A wake notice only reports that a sequence is pending: do not count the sequence printed in the notice as seen unless the message body itself is already in context. When uncertain, use the prior checkpoint or 0; replaying a message is safe. Parley reconciles the model checkpoint with its own delivery cursor and reads from the earlier one, favoring a harmless replay over silently skipping a message.

Reply to a role or broadcast to all participants:

parley send review-xyzab --as reviewer --to author -m 'The change looks good.'
parley send review-xyzab --as reviewer --broadcast -m 'Review complete.'

Every send requires exactly one delivery mode: --to, --broadcast, or --ack.

For two different agent sessions, run each role's commands from its own session. The example shows them together only to make the exchange easy to follow.

Bootstrap an agent session

Parley is self-guiding after an agent has joined, but it cannot discover the channel name or the role assigned out of band. Give a new participant this one bootstrap prompt:

Use Parley to communicate with the other session(s).
Channel: <CHANNEL-xxxxx>
Your role: <ROLE>
Other role(s): <OTHER-ROLE(S)>

Run `parley join <CHANNEL-xxxxx> --as <ROLE>` once, then follow its instructions.
Use the highest sequence actually present in your context as `--last-seen` (0 if
none). A wake notice is not the message itself. Do not add `--wait` or `&` to the
receive command in a wake notice, resend after a timeout, or reply to `[closed]`.

The prompt is deliberately explicit about facts the CLI cannot infer. From that point, join, wake notices, receive results, checkpoint footers, and command help provide the next action and recovery command.

Supported coding agents

Parley's durable filesystem protocol works with any agent that can invoke a CLI and share PARLEY_HOME. The integrations below add harness-specific identity or wake-up behavior; they do not change the transcript format.

Coding agent Tested version Support Wake value Session identity Identity in direct shell Wake integration
OpenAI Codex CLI 0.147.0 First-class codex CODEX_THREAD_ID Yes, including ! commands A persistent app-server starts or steers the exact loaded thread.
Anthropic Claude Code 2.1.226 First-class claude CLAUDE_CODE_SESSION_ID Yes, including ! commands A native channel injects the notice into the exact running session.
Pi 0.84.1 First-class pi PI_CODING_AGENT=true + PI_SESSION_ID No: !/!! omit PI_SESSION_ID; use the model's Bash tool The Parley extension starts or steers the exact running session.
All other coding agents Protocol-compatible never --sid, PARLEY_ID, or role fallback Harness-dependent Use a foreground recv --last-seen <seq> --wait.

The tested versions bound the observed environment behavior; later harness releases may change which variables they expose to model-run and directly invoked commands.

join defaults to --wake detect, resolving the active Codex, Claude, or Pi environment once and recording that concrete wake type with the role. Detection errors outside those harnesses; manual and other-agent sessions must explicitly use --wake never. If a harness marker is present without its required session ID, Parley identifies the partial harness and directs the user to rerun join through the model's shell tool.

Self-guiding, context-efficient output

After the bootstrap prompt, a coding agent should not need a person to keep translating Parley's protocol. Command-level --help, join instructions, wake notices, receive results, and checkpoint footers tell the model what it should do next, including the exact recovery command when delivery needs attention.

Those operational messages are intentionally compact. They preserve the role, channel, sequence checkpoint, and next action needed for durable recovery without spending model context on repeated explanation. Human-readable output remains terse for the same reason; use parley <command> --help when the model needs the complete command surface rather than carrying it in every prompt.

Roles and session identity

A role is the human-readable address claimed with --as, such as author or reviewer. A session ID is the ownership token behind that role. Parley detects the session ID from the supported-agent table in its listed order. --sid and PARLEY_ID provide higher-priority explicit overrides, and the role is used as the fallback.

Distinct sessions must use distinct roles. A role's wake type is immutable, including under --force; use another role name when changing harnesses. A claim held by another session is rejected. After a same-harness runtime restart, reclaim the role with join --force; a new session's receive position starts at the current end of the transcript so it resumes forward rather than draining the historical backlog.

Use parley members list <channel> to inspect one channel's current ownership. To discover every active channel and role held by the current session, run:

parley whoami

whoami uses the harness session ID, PARLEY_ID, or an explicit --sid. It does not accept --as, because discovering the roles is the purpose of the command. A manual session without an injected identity must pass --sid <id>. Departed, removed, and superseded claims are omitted; --json emits one JSON object per active membership.

Managing members

The first role to join a channel is its owner and is marked as such by members list. The owner role remains fixed for the lifetime of the channel, including if its session leaves and later rejoins after a restart.

A participant can vacate its own role when its work is finished:

parley leave review-xyzab --as reviewer

The channel owner can remove another active role:

parley members remove review-xyzab researcher --as author

Both operations append an audited roster event. They do not rewrite the transcript or delete cursor files. The departed SID immediately loses permission to send or receive under that role, and broadcasts stop waking it. The role may be joined again later, but its original wake type remains immutable. Send a final --close message before removal when the departing session should be notified.

Membership is a cooperative protocol guard, not an access-control boundary. Every process with filesystem access to PARLEY_HOME can read its plaintext files, and transcript inspection commands intentionally do not require active membership.

Scope and limitations

  • Parley coordinates processes that can access the same PARLEY_HOME. It is not a hosted relay or an internet transport.
  • Parley never sends conversations, message bodies, roles, session IDs, channel state, or diagnostics to a remote service. Its only default internet access is a cached request to GitHub for the latest public release, described under Update notices. Claude, Pi, and Codex wake integrations communicate only with their local harness processes.
  • Channel files are plaintext and have no built-in authentication, authorization, or encryption. Protect the storage directory with normal filesystem permissions and do not send secrets through an untrusted shared mount.
  • Delivery is durable, but automatic wake-up is best-effort. Receivers recover using their --last-seen model-context checkpoint.
  • Role claims, --expect-new, and message retraction use deliberately small best-effort concurrency windows. Parley is designed for coordinating agent turns, not as a transactional message broker.
  • Participants must agree on a channel and distinct roles out of band. Parley does not discover peers or mint globally unique channel names.

Acknowledging longer work

When substantive work will take time, acknowledge the exact request with a short, single-line status:

parley send review-xyzab --as reviewer --ack 12 \
  -m 'Running the integration tests.'

The sender of message #12 becomes the recipient. This writes an ordinary [ack #12] ... channel message; it is a convenience, not separate receipt state. Skip the acknowledgement when replying immediately, never acknowledge an acknowledgement, and always send the substantive result afterward.

Enable automatic wake-up

Automatic wake-up is an optional notification layer over the durable transcript. Install the integration for the harness you use; message delivery and checkpoint recovery continue to work if a wake attempt fails. Protocol details and invariants belong in SPEC.md.

Claude Code

Claude Code can load Parley as a one-way MCP channel. Add the server to a project .mcp.json (or the equivalent user-level MCP configuration):

{
  "mcpServers": {
    "parley": { "command": "parley", "args": ["integrations", "claude"] }
  }
}

Then start Claude Code with the development channel enabled:

claude --dangerously-load-development-channels server:parley

Channels are a Claude Code research-preview feature. The development flag displays a confirmation prompt because Parley is not on Anthropic's built-in channel allowlist. Several channels can coexist; pass their server names space-separated:

claude --dangerously-load-development-channels server:another-channel server:parley

The channel subprocess remains on the executable version with which Claude started. After upgrading Parley, restart Claude Code so the new channel process and its process-correlated endpoint registration are active; installing a new binary does not retrofit an already-running MCP subprocess.

/clear, /compact, and resume behavior is covered by the Claude lifecycle smoke test; the underlying session-rebinding protocol is documented in SPEC.md.

Pi

Install Parley's Pi package from the same release as the CLI, then restart Pi (or run /reload in an existing session):

pi install git:github.com/zerotomvp/parley-cli@v2.1.1

The package starts Parley's local wake helper with each Pi session. The wake notice does not advance a checkpoint; the model still runs the displayed nonblocking recv --last-seen command to consume the durable message.

Pi supplies PI_SESSION_ID to commands executed by the model's Bash tool, so the ordinary command is enough when the model runs it:

parley join review-xyzab --as reviewer

Pi's user-invoked ! and !! shell commands do not receive PI_SESSION_ID in the tested version. Ask the model to execute parley join through its Bash tool instead.

If the extension is unavailable or stale, join reports it and prints the foreground-listener fallback. See SPEC.md for the Pi bridge protocol.

Codex

Start one long-lived app-server and connect every interactive Codex TUI to its default Unix socket:

codex app-server --listen unix://
# In another terminal:
codex --remote unix://

For daily use, supervise the first command as a user service and reserve a short cx command for remote-backed interactive sessions. Keep the ordinary codex command unchanged because some subcommands do not accept --remote.

Add this function to ~/.bashrc:

cx() {
  codex --remote unix:// "$@"
}

Reload the shell with source ~/.bashrc, arrange for codex app-server --listen unix:// to run continuously under your platform's user service manager, and launch participating sessions with cx. Nix/Home Manager users can express the same setup as a user systemd service on Linux or launchd agent on macOS plus home.shellAliases.cx.

Native Windows can use Parley's filesystem delivery, but this automatic-wake design requires Codex's Unix-socket app-server transport. Run Codex and Parley together in WSL for the same persistent-service setup.

Wake and wait behavior

join defaults to --wake detect, which resolves the current harness using the supported-agent table and stores that concrete type with the role. It errors when no supported harness environment is present; pass join --wake never for filesystem-only participants. The supported-agent table lists the explicit wake values available when runtime detection is unavailable. A role's resolved wake type cannot change, even with --force.

Each send uses only the transport stored for the destination role. There is no send-side wake option, and wake failure never undelivers the transcript message. Retry and duplicate-notice behavior is specified in SPEC.md.

join reports the persisted wake type separately from current endpoint availability, then prints the appropriate automatic-wake or foreground-listener instructions:

  • With automatic Claude, Pi, or Codex wake available, do not maintain a blocking listener. An incoming send injects an event or starts/steers the loaded thread. Use a plain recv --last-seen <seq> for catch-up or recovery.
  • Without a live endpoint for the selected integration, use an unbounded foreground recv --last-seen <seq> --wait while expecting a reply. Do not background this fallback: its output must return to the model context that started it.

--wait is indefinite unless --timeout <seconds> is supplied. A Parley timeout returns exit code 2; the original message remains delivered, so continue receiving instead of sending it again. A harness command timeout is independent of Parley's timeout, which is why --last-seen remains authoritative.

Ending an exchange

When a final message needs no reply, mark it closed and do not wait:

parley send review-xyzab --as reviewer --to author --close \
  -m 'Approved, end of cycle — no reply needed.'

The recipient sees [closed]. Stop replying to that exchange, but remain reachable using the wake or listener mode reported by join if more work may arrive.

Commands

Command Purpose
parley join <channel> --as <role> [--force] Claim a role; force-reclaim it after a session restart.
parley whoami [--sid <id>] [--json] List every active channel and role held by this session.
parley leave <channel> --as <role> Vacate the current session's role.
parley send <channel> --as <role> (--to <roles> \| --broadcast) [options] Append an addressed message; accepts -m or stdin.
parley send <channel> --as <role> --ack <seq> -m <status> Send a short acknowledgement to the original sender.
parley recv <channel> --as <role> --last-seen <seq> [--wait] Read addressed peer messages after a model-context checkpoint.
parley drop <channel> --as <role> [--yes] Retract your last message and roll back affected cursors.
parley members list <channel> List active roles, ownership, and recent activity.
parley members wait <channel> <roles...> [--timeout N] Wait for current role owners without reading messages or advancing cursors.
parley members remove <channel> <role> --as <owner-role> Remove an active member as the channel owner.
parley messages log <channel> [--limit N] Preview recent transcript messages; --limit 0 shows all.
parley messages show <channel> <seq> Print one message in full.
parley integrations claude Run the one-way Claude Code MCP wake channel over stdio.
parley integrations pi Run the Pi extension wake bridge over JSONL stdio.
parley admin prune [--days N] [--dry-run] Remove channels idle longer than the retention threshold.

The message body comes from -m <text> or stdin, which is preferable for complete multi-line thoughts. Relevant command options include --sid <id>, --timeout <seconds>, --json, and --log-level. send prints its assigned sequence to stdout (a bare integer, or {"seq":N} with --json), while human status goes to stderr. With send --wait, the sequence is the first stdout line and the received reply follows.

Exit codes are 0 for success, 1 for an error, 2 for a bounded wait with no message, and 130 when interrupted. admin prune defaults to 30 idle days, previews its targets, and requires --yes when non-interactive; --dry-run never deletes. Run parley <command> --help for all options.

Storage and platform support

State defaults to ~/.parley/channels/. Set PARLEY_HOME to isolate a conversation store, test environment, or shared filesystem location:

export PARLEY_HOME=/path/to/parley-state

The CLI targets .NET 10 and is designed for Linux, macOS, and Windows. Release archives target linux-x64, linux-arm64, osx-x64, osx-arm64, win-x64, and win-arm64. Claude automatic wake requires its channel subprocess; Pi automatic wake requires the Parley extension; Codex automatic wake requires a running app-server and a loaded thread. All filesystem-only messaging works without these integrations.

Configuration and environment

Most users need no environment variables. These are the supported operator-facing overrides:

Variable Purpose
PARLEY_HOME Override the channel-state directory.
PARLEY_CONFIG Override the JSON config file without moving channel state or logs.
PARLEY_ID Supply an explicit session identity when a harness does not inject one.
PARLEY_TRACE Override trace logging: 1, true, yes, or on enables it; any other present value disables it.

Harness-provided variables such as CODEX_THREAD_ID, CLAUDE_CODE_SESSION_ID, PI_CODING_AGENT, and PI_SESSION_ID are detected automatically; users should not normally set them. Test-only variables are documented under Development.

Update notices

Update checks are enabled by default. On join, integrations claude, and integrations pi startup, Parley checks GitHub's public latest-release endpoint at most once every 24 hours. The check is failure-silent, has a two-second timeout, never runs on the latency-sensitive send or recv paths, and stores only release metadata in update-check.json beside the platform application-data config. No channel or conversation data is included in the request. Apart from standard HTTPS connection metadata, the request identifies only the public Parley version in its User-Agent.

When a newer stable version is found, Parley prints one notice for that version to stderr. It infers the installation method from the running executable path without executing a package manager and, when the match is unambiguous, prints one of:

brew upgrade parley
scoop update parley
dotnet tool update --global parley-cli
dotnet tool update --tool-path <directory> parley-cli

Manual or unrecognized installations receive the GitHub release URL instead. The inference is not persisted, so moving or reinstalling Parley changes the next suggestion naturally.

Disable update checks in the platform application-data parley-cli/config.json:

{
  "updates": {
    "check": false
  }
}

Set PARLEY_CONFIG=/path/to/config.json when a portable or isolated installation needs an explicit config location. This changes only the config file path; channel state continues to follow PARLEY_HOME, while logs and the update cache remain in platform application data.

Troubleshooting

A receive ran, but the model did not see its output

Run receive again with --last-seen set to the highest sequence actually visible in model context—not the CLI's cursor and not the sequence named by a synthetic wake notice:

parley recv <channel> --as <role> --last-seen <seq>

Parley will replay addressed messages after that checkpoint. This is the intended recovery path when an agent harness backgrounds a blocking command.

A role is already held

Confirm the participants with parley members list <channel>. If the prior session genuinely restarted and the old SID is gone, reclaim the role with:

parley join <channel> --as <role> --force

The current session's detected wake type must match the role's stored type. Do not force a role merely to bypass a collision: the previous owner immediately loses permission to send and receive as that role. Parley rejects an explicit wake value that conflicts with a clearly detected active harness, so copying the old role's --wake value cannot make a cross-harness takeover valid. Join under another role or use a new channel, then inform the other participants of the change.

A harness did not wake

The transcript message is still delivered. Receive it normally. Consult the supported-agent table and check that the listed wake integration is running. Confirm the roster SID shown by parley members list matches the session. join prints the currently detected wake mode; otherwise use recv ... --last-seen <seq> --wait as fallback.

To capture transport diagnostics without changing wake behavior, start the affected agent with tracing explicitly enabled:

PARLEY_TRACE=1 claude

Tracing can instead be enabled persistently in the platform application-data config file (~/.config/parley-cli/config.json on Linux):

{
  "trace": true,
  "updates": {
    "check": true
  }
}

For Claude Code, the same variable may be added to the Parley MCP server's env object. Restart Claude after changing MCP configuration. Tracing covers MCP initialization, named-pipe connections, probes, notifications, acknowledgements, timings, and exception details. It never records Parley message bodies or raw MCP frames. Trace events go to stderr and the rolling parley-cli-*.log files under the platform application-data directory (~/.config/parley-cli/logs on Linux). When PARLEY_TRACE is present it overrides the config file. Accepted opt-in values are 1, true, yes, and on (case-insensitive); any other present value disables tracing, so PARLEY_TRACE=0 is an explicit per-process override. If the variable is unset, the config value is used. A missing config file means tracing is off. An unreadable or malformed file also leaves tracing off and emits a warning.

parley is not found

For a .NET global-tool installation, ensure the .NET tools directory is on PATH ($HOME/.dotnet/tools on Linux and macOS, %USERPROFILE%\.dotnet\tools on Windows) and open a new shell after installation. For release archives, move the extracted executable into a directory already on PATH and, on Unix systems, ensure it is executable.

Waiting returned exit code 2

A finite Parley --timeout expired without a relevant message. This is not a delivery failure. Do not resend the original request; continue with recv --wait and the highest sequence actually seen.

Development

Clone the repository and build the Release configuration:

dotnet restore
dotnet build -c Release
dotnet run -- --help

PARLEY_HOME makes manual smoke tests safe to isolate from real conversations. The process-level integration suite launches the actual CLI with an isolated store:

dotnet test tests/ParleyCli.IntegrationTests/ParleyCli.IntegrationTests.csproj -c Release

Set PARLEY_TEST_EXECUTABLE=/absolute/path/to/parley to run the same suite against a published binary. Packaging smoke tests accept a release archive:

scripts/test-release.sh path/to/parley-linux-x64.tar.gz
./scripts/test-release.ps1 path/to/parley-win-x64.zip

The live Codex wake check is opt-in because it needs a running app-server and a loaded thread. Set PARLEY_LIVE_CODEX_SID and run scripts/test-codex-wake.sh. Claude's real /clear, /compact, and resume boundaries have a repeatable manual lifecycle smoke test; the process-level suite also simulates the confirmed in-process SID rotation.

Self-contained releases currently remain untrimmed. Strict full-trim analysis is clean for Parley's code but Serilog's runtime type loading and object destructuring produce IL2057/IL2072; suppressing those warnings would make the artifact less trustworthy. The untrimmed single-file build is validated by the complete integration suite instead.

Bug reports and feature requests belong in GitHub Issues. Contributions are welcome through pull requests; please describe the behavioral change and how it was verified. Parley is distributed under the GNU General Public License v3.0.

Product Compatible and additional computed target framework versions.
.NET net10.0 is compatible.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

This package has no dependencies.

Version Downloads Last Updated
2.2.0 98 8/25/2026
2.1.1 137 8/10/2026
2.1.0 99 8/9/2026
2.0.0 90 8/9/2026
1.3.2 99 8/8/2026
1.3.1 96 8/7/2026
1.3.0 89 8/7/2026
1.2.1 96 8/7/2026
1.2.0 106 8/6/2026
1.1.2 107 8/5/2026
1.1.1 111 8/5/2026
1.1.0 110 8/2/2026
1.0.0 110 7/31/2026