Persistent process server for AI coding agents. Like tmux, but purpose-built for long-running LLM processes — with native terminal scrolling, copy/paste, and no weird key chords.
Codewire runs as a background node inside your development environment (e.g., a Coder workspace). You launch AI agent sessions with a prompt, and they keep running even if you disconnect. Reconnect anytime to pick up where you left off.
Works with any CLI-based AI agent: Claude Code, Aider, Goose, Codex, or anything else.
brew install codewiresh/codewire/codewirecurl -fsSL https://raw.githubusercontent.com/codewiresh/codewire/main/install.sh | bashThis downloads the latest binary, verifies its SHA256 checksum (and GPG signature if gpg is installed), and installs cw to /usr/local/bin.
Options:
# Install a specific version
curl -fsSL .../install.sh | bash -s -- --version v0.1.0
# Install to a custom prefix
curl -fsSL .../install.sh | bash -s -- --prefix ~/.localgo build -o cw ./cmd/cw
sudo mv cw /usr/local/bin/cwOr use make:
make install# Launch a session (node auto-starts)
cw launch -- claude -p "fix the auth bug in login.ts"
# Use a different AI agent
cw launch -- aider --message "fix the auth bug"
cw launch -- goose run
# Specify a working directory
cw launch --dir /home/coder/project -- claude -p "add tests"
# List running sessions
cw list
# Attach to a session
cw attach 1
# Detach: Ctrl+B then d
# View what the agent did while you were away
cw logs 1codewire.yaml is the shared preset file for both managed environments and local
containers.
# Write a preset for this repo
cw preset init --image full --install "pnpm install" --startup "pnpm dev"
# Launch a cloud environment from the preset
cw env create --file codewire.yaml
# Or create a local container from the same preset
cw local create --backend dockerLocal runtimes become normal cw targets:
cw local info my-app
cw exec --on my-app -- pwd
cw use my-app
cw exec -- pwd
cw use localNotes:
cw execworks across the current target, including remote envs and local Docker/Incus runtimes.cw sshremains for remote environments; local runtimes use backend-native exec.- Incus with OCI registry images like
docker.io/...andghcr.io/...requiresskopeoon the host.
cw preset init # Write codewire.yaml
cw preset list # List reusable server presets
cw preset create <slug> --image full ... # Save a reusable server preset
cw env create --file codewire.yaml # Create a cloud environment from a preset
cw env create --preset full # Create from a saved server preset
cw local create --backend docker # Create a local Docker runtime
cw local create --backend incus # Create a local Incus runtime
cw local list # List local runtimes
cw local info <name> # Show local runtime details
cw local start <name> # Start a stopped local runtime
cw local stop <name> # Stop a running local runtime
cw local rm <name> # Delete a local runtime
cw use <target> # Set current target
cw current -v # Show current target details
cw exec -- <command> # Run on the current target
cw exec --on <target> -- <command> # Run on a specific targetcw env create --preset python # From a preset
cw env create --image openclaw --ttl 1h # From a custom image
cw env create --preset node https://github.com/org/repo # Clone a repo
cw env list # List environments
cw env info <name-or-id> # Show details
cw env logs <name-or-id> # Startup/provisioning logs
cw env exec <name-or-id> -- npm test # Run a command
cw env cp local.txt <id>:/workspace/local.txt # Upload a file
cw env cp <id>:/workspace/out.txt ./out.txt # Download a file
cw env stop <name-or-id> # Stop
cw env start <name-or-id> # Restart
cw env rm <name-or-id> # Delete
cw env prune # Clean up stale envsStart a new session running the given command in a persistent PTY. Everything after -- is the command and its arguments. An optional positional name before -- gives the session a stable identifier for messaging. Tags enable filtering and coordination.
cw launch -- claude -p "refactor the database layer"
cw launch planner -- claude -p "plan the refactor"
cw launch --dir /home/coder/project -- claude -p "add unit tests for auth"
cw launch --tag worker --tag build -- claude -p "fix tests"
cw launch -- bash -c "npm test && npm run lint"Options:
- Positional name (before
--) — Unique name for the session (alphanumeric + hyphens, 1-32 chars). Used for addressing in messaging. Equivalent to--name. --name— Alternative to positional name (useful for programmatic/MCP use)--dir,-d— Working directory (defaults to current dir)--tag,-t— Tag the session (repeatable)
Show all sessions with their name, status, age, and command.
cw list
# ID NAME COMMAND STATUS AGE
# 1 planner claude -p "plan the refactor" running 2m ago
# 2 coder claude -p "implement changes" running 45s ago
cw list --json # machine-readable outputTake over your terminal and connect to a running session. You get full terminal I/O — native scrolling, native copy/paste, everything your terminal emulator supports.
Detach with Ctrl+B d (press Ctrl+B, release, then press d). The session keeps running.
View captured output from a session without attaching.
cw logs 1 # full output
cw logs 1 --follow # tail -f style, streams new output
cw logs 1 --tail 100 # last 100 linesWorks on completed sessions too — review what the agent did after it finished.
Terminate a session. Supports tag-based filtering.
cw kill 3
cw kill --all
cw kill --tag worker # Kill all sessions tagged "worker"Send input to a session without attaching. Useful for multi-agent coordination.
cw send 1 "Status update?" # Send text with newline
cw send 1 "test" --no-newline # No newline
echo "command" | cw send 1 --stdin # From stdin
cw send 1 --file commands.txt # From fileMonitor a session in real-time without attaching. Perfect for observing another agent's progress.
cw watch 1 # Watch with recent history
cw watch 1 --tail 50 # Start from last 50 lines
cw watch 1 --no-history # Only new output
cw watch 1 --timeout 60 # Auto-exit after 60 secondsSend a direct message to a session. Target can be a session ID or name.
cw msg coder "start with the auth module" # by name
cw msg 2 "start with the auth module" # by ID
cw msg -f planner coder "start with the auth module" # with sender
cw msg --delivery pty coder "check this out" # PTY injection only
cw msg --delivery both coder "review please" # inbox + PTY injectionDelivery modes:
inbox— write to inbox/message log only (safe default for shell sessions)pty— inject a formatted prompt into the recipient's PTY onlyboth— inbox logging + PTY injectionauto(default) — usesbothwhen called from inside another cw session (--fromorCW_SESSION_IDset), otherwiseinbox
Read messages from a session's inbox. Shows direct messages and pending requests.
cw inbox coder # latest 50 messages
cw inbox planner -t 10 # last 10 messagesSend a request to a session and block until a reply arrives. Like msg but synchronous — the caller waits for a response.
cw request -f planner coder "ready for review?"
# [reply from coder] yes, PR #42 is up
cw request coder "status?" --timeout 30 # 30s timeout (default: 60s)
cw request --delivery both coder "need approval" # inbox + PTY injectionUses the same delivery modes as cw msg. When --delivery pty or both is used, the recipient sees a formatted prompt in their terminal with a reply hint.
Reply to a pending request. The request ID comes from cw inbox or from a message.request event.
cw reply req_x7y8z9 "yes, PR #42 is up" -f coderStream all message traffic on the node in real-time. Shows direct messages, requests, and replies as they happen.
cw listen # all message traffic
cw listen --session planner # only messages involving plannerOutput format:
[planner → coder] start with the auth module
[coder → planner] REQUEST (req_x7y8): need the DB schema?
[planner] REPLY (req_x7y8): use the existing users table
Get detailed session status including PID, output size, and recent output.
cw status 1 # Human-readable format
cw status 1 --json # JSON outputSubscribe to real-time session events. Events stream until you disconnect.
cw subscribe --tag worker # Events from sessions tagged "worker"
cw subscribe --event session.status # Only status change events
cw subscribe dev-1 --tag build # Events from remote nodeBlock until sessions complete.
cw wait 3 # Wait for session 3 to complete
cw wait --tag worker --condition all # Wait for ALL workers to complete
cw wait --tag worker --condition any --timeout 60 # Wait for ANY worker, 60s timeoutList nodes. By default this uses the current network. Use --network to scope explicitly or --all to widen across networks.
cw node list
cw node list --network project-alpha
cw node list --allSelect the current network. New environments join this network by default unless you pass --network or --no-network.
cw network use project-alphaShow a QR code for SSH access to this node, routed through the current network when one is selected.
cw node qrJoin a network from an invite token and enroll this machine for relay access.
cw network join --relay-url https://relay.codewire.sh CW-INV-...Run a relay server. The relay provides SSH gateway access, node discovery, and shared KV storage.
cw relay serve --base-url https://relay.example.com --data-dir /data/relayStart an MCP (Model Context Protocol) server for programmatic access.
cw mcp-serverSee MCP Integration section below for details.
Start the node manually. Usually you don't need this — the node auto-starts on first CLI invocation.
cw startStop the running node gracefully.
cw stopManage saved remote server connections.
cw server add my-gpu ws://gpu-host:9100 --token <token> # Save a server
cw server remove my-gpu # Remove it
cw server list # List saved serversSaved servers can be referenced by name with --server:
cw --server my-gpu list
cw --server my-gpu attach 1Codewire is a single Go binary (cw) that acts as both node and CLI client.
Node (cw node): Listens on a Unix socket at ~/.codewire/codewire.sock. Manages PTY sessions — each AI agent runs in its own pseudoterminal. The node owns the master side of each PTY and keeps processes alive regardless of client connections.
Client (cw launch, attach, etc.): Connects to the node's Unix socket. When you attach, the client puts your terminal in raw mode and bridges your stdin/stdout directly to the PTY. Your terminal emulator handles all rendering — that's why scrolling and copy/paste work natively.
Logs: All PTY output is teed to ~/.codewire/sessions/<id>/output.log so you can review what happened while disconnected.
Pass the exact command you want to run after --. No magic — what you type is what runs:
cw launch -- claude -p "fix the bug" # Claude Code
cw launch -- aider --message "fix the bug" # Aider
cw launch -- goose run # Goose
cw launch -- codex "refactor auth" # CodexCommunication between client and node uses a frame-based binary protocol over the Unix socket:
- Frame format:
[type: u8][length: u32 BE][payload] - Type
0x00: Control messages (JSON) — launch, list, attach, detach, kill, resize - Type
0x01: Data messages (raw bytes) — PTY I/O
~/.codewire/
├── codewire.sock # Unix domain socket
├── codewire.pid # Node PID file
├── token # Auth token (for direct WS fallback)
├── config.toml # Configuration (optional)
├── servers.toml # Saved remote servers (optional)
├── sessions.json # Session metadata
└── sessions/
├── 1/
│ ├── output.log # Captured PTY output
│ └── events.jsonl # Metadata event log
└── 2/
├── output.log
└── events.jsonl
All settings via ~/.codewire/config.toml or environment variables:
[node]
name = "my-node" # CODEWIRE_NODE_NAME
listen = "0.0.0.0:9100" # CODEWIRE_LISTEN — direct WebSocket (optional)
external_url = "wss://host/ws" # CODEWIRE_EXTERNAL_URL
relay_url = "https://relay.codewire.sh" # CODEWIRE_RELAY_URL — opt-in remote accessWhen no config file exists, codewire runs in standalone mode (Unix socket only, no relay-backed networking).
Codewire uses an SSH gateway for remote access. Nodes establish persistent WebSocket connections to a relay server — no root required, works behind NAT.
# Log in once
cw login
# Pick or create the current network
cw network create project-alpha
# or
cw network use project-alpha
# Start the local node; it auto-enrolls in the selected network
cw nodeFor invite-based onboarding on another machine:
cw login
cw network join --relay-url https://relay.codewire.sh CW-INV-...
cw nodecw login provides user auth for relay-backed network commands. cw node auto-enrolls the machine into the selected network and maintains the persistent relay connection.
Relay mode now has three distinct scopes:
network: membership and node discoverygroup: which named sessions may message each other inside a networkaccess: temporary observer grants for remoteinbox/listen
Use a dedicated network when you want a hard outer boundary, then use groups for smaller private meshes inside that network.
# Create or select a private network
cw network create private-agents --use
# Create a group for one isolated agent mesh
cw group create mesh
# Launch named sessions directly into that group
cw run --name agent-1 --group mesh -- claude
cw run --name agent-2 --group mesh -- claude
cw run --name agent-3 --group mesh -- claude
# Inspect and manage membership
cw group members mesh
cw group policy mesh
cw group delete meshDefault group policy is:
messages=internal-onlydebug=observe-only
That means grouped sessions can msg and request each other, but sessions
outside the group cannot deliver messages into them.
Remote reads are separate. cw inbox <node>:<session> and cw listen --session <node>:<session>
require an explicit observer grant:
# Owner/admin issues a short-lived observer grant
cw access grant dev-1:agent-2 --to alice --for 10m
# Recipient can use it directly
cw inbox dev-1:agent-2 --grant <token>
cw listen --session dev-1:agent-2 --grant <token>
# Or accept it once and use normal commands
cw access accept <token>
cw access list --accepted
cw access list --accepted --node dev-1 --session agent-2
cw access inspect <grant-id>
cw access drop <grant-id>
cw access prune
cw access watch
cw inbox dev-1:agent-2
cw listen --session dev-1:agent-2When relay auth is configured, cw access list --accepted, cw access inspect,
and cw access prune reconcile the local accepted-grant cache against the relay
and automatically drop revoked or missing grants.
For immediate invalidation across terminals or machines, run:
cw access watchThat opens a relay-backed SSE stream for the current network and removes accepted grants from the local cache as soon as the relay sees them revoked.
All commands accept an optional node prefix for remote access:
# Current network
cw node list # Nodes in the current network
cw node list --all # Nodes across all networks
# Current environment target
cw list --runs # Envs and runs in the current org
cw attach 3 # Attach to a local/current-target session
# Remote (node prefix)
cw list dev-1 # Sessions on dev-1
cw attach dev-1:3 # Session 3 on dev-1
cw launch dev-1 -- claude -p "fix bug" # Launch on dev-1
cw kill dev-1:3 # Kill on dev-1You can also connect directly via WebSocket without a relay:
cw server add my-server wss://remote-host:9100 --token <token>
cw --server my-server list
cw --server my-server attach 1 INTERNET
|
+--------+--------+
| cw relay |
| HTTPS :443 | <- Clients connect here
| SSH :2222 | <- SSH into any node
| /node/connect | <- Nodes connect here (WS)
+--------+--------+
| |
WS agent | | WS agent
(outbound) | | (outbound)
| |
+-------+--+ +---+-------+
| cw node | | cw node |
| "dev-1" | | "gpu-box" |
+----------+ +-----------+
- Network = user-facing group/scope for related envs and nodes
- Group = smaller communication boundary for named sessions inside a network
- Access grant = short-lived observer capability for remote
inbox/listen - Relay = SSH gateway + HTTP API backing those networks (node discovery, shared KV, device auth)
- Nodes = connect outward via persistent WebSocket agents (no inbound ports needed)
- Clients = SSH into
<node>@relay:2222; same PTY experience
The repo includes a Docker Compose stack:
- Relay — SSH gateway + HTTP API on
localhost:8080 - Caddy — TLS reverse proxy on
localhost:9443with wildcard subdomain support - Codewire — containerized node (
docker-test) onlocalhost:9100
# Start the stack
docker compose up -d --build
# List nodes
cw nodes
# Tear down
docker compose downhelm install my-relay oci://ghcr.io/codewiresh/charts/codewire-relay \
--set relay.baseURL=https://relay.example.comSee charts/codewire-relay/values.yaml for full configuration. Verify with helm test my-relay.
# Install the binary
curl -fsSL https://codewire.dev/install.sh | sh
# Configure
sudo cp deploy/systemd/codewire-relay.service /etc/systemd/system/
sudo cp deploy/systemd/codewire-relay.env /etc/codewire-relay/env
sudo editor /etc/codewire-relay/env # set CW_BASE_URL, CW_AUTH_TOKEN
# Start
sudo systemctl enable --now codewire-relaySee Local Development (Docker Compose) above for a ready-to-use docker-compose.yml.
This repository includes a real end-to-end tailnet relay/network messaging test that stands up a relay in kind and exercises three sessions across two nodes:
- delegated remote
msg - targeted remote
listen - remote
request - reply ownership enforcement between two sessions on the destination node
The message RPC itself runs over tailnet/WireGuard. The relay provides network auth, peer coordination, and DERP assistance for the test.
Run it locally with:
./scripts/run-relay-kind-e2e.shEnvironment overrides:
CLUSTER_NAMEto reuse an existing kind clusterRELAY_PORTto change the local port-forward portRELAY_TOKENto change the relay auth token
CodeWire is designed for LLM-driven multi-agent workflows. Tags, subscriptions, and wait provide structured coordination primitives.
Label sessions at launch for filtering and coordination:
cw launch --tag worker --tag build -- claude -p "fix tests"
cw launch --tag worker --tag lint -- claude -p "fix lint issues"
# List sessions by tag (via MCP or API)
# Kill all workers
cw kill --tag workerStream structured events from sessions:
# Watch all status changes for "worker" sessions
cw subscribe --tag worker --event session.status
# Subscribe to all events from session 3
cw subscribe --session 3Event types: session.created, session.status, session.output_summary, session.input, session.attached, session.detached, direct.message, message.request, message.reply
Block until sessions finish — replaces polling:
# Wait for session 3 to complete
cw wait 3
# Wait for ALL workers to complete
cw wait --tag worker --condition all
# Wait for ANY worker to complete, 60s timeout
cw wait --tag worker --condition any --timeout 60Supervisor pattern — one orchestrator coordinates workers:
# Launch tagged workers
cw launch --tag worker -- claude -p "implement feature X"
cw launch --tag worker -- claude -p "write tests for X"
# Wait for all workers to finish
cw wait --tag worker --condition all --timeout 300
# Check results
cw listAgent messaging — named agents exchanging structured messages:
# Launch named agents
cw launch planner -- claude -p "plan the refactor"
cw launch coder -- claude -p "implement changes"
# Send a direct message
cw msg -f planner coder "start with the auth module"
# Request/reply — synchronous coordination
cw request -f planner coder "ready for review?"
# [reply from coder] yes, PR #42 is up
# Monitor all message traffic
cw listenAgent swarms — parallel agents with cross-session communication:
cw launch --tag backend -- claude -p "optimize queries"
cw launch --tag frontend -- claude -p "optimize bundle"
# Send coordination message
cw send 1 "Backend ready for integration"
# Monitor in real-time
cw watch 2 --tail 100Multiple attachments — multiple clients can attach to the same session:
# Terminal 1
cw attach 1
# Terminal 2 (both see output)
cw attach 1Install the codewire skill so Claude Code knows how to use cw for session management:
curl -fsSL https://raw.githubusercontent.com/codewiresh/codewire/main/.claude/skills/install.sh | bashThis installs two skills to ~/.claude/skills/:
- codewire — teaches Claude Code to launch, monitor, and coordinate sessions
- codewire-dev — development workflow for contributing to the codewire codebase
For programmatic tool access, add CodeWire as an MCP server:
# User-level (available in all projects)
claude mcp add --scope user codewire -- cw mcp-server
# Or project-level
claude mcp add codewire -- cw mcp-serverThis exposes 26 tools across sessions, environments, messaging, and shared state:
Sessions
| Tool | Description |
|---|---|
codewire_launch_session |
Launch new session (with name and tags) |
codewire_list_sessions |
List sessions with enriched metadata |
codewire_read_session_output |
Read output snapshot |
codewire_send_input |
Send input to a session |
codewire_watch_session |
Monitor session (time-bounded) |
codewire_get_session_status |
Get detailed status (exit code, duration, etc.) |
codewire_kill_session |
Terminate session (by ID or tags) |
codewire_subscribe |
Subscribe to session events |
codewire_wait_for |
Block until sessions complete |
Messaging
| Tool | Description |
|---|---|
codewire_msg |
Send a direct message to a session |
codewire_read_messages |
Read messages from a session's inbox |
codewire_request |
Send a request and block for reply |
codewire_reply |
Reply to a pending request |
Environments (requires cw login)
| Tool | Description |
|---|---|
codewire_create_environment |
Create environment from preset or image |
codewire_list_environments |
List environments with filters |
codewire_get_environment |
Get environment details |
codewire_start_environment |
Start a stopped environment |
codewire_stop_environment |
Stop a running environment |
codewire_delete_environment |
Delete an environment |
codewire_exec_in_environment |
Execute command in sandbox |
codewire_list_files |
List files in sandbox |
codewire_upload_file |
Upload file to sandbox |
codewire_download_file |
Download file from sandbox |
codewire_get_environment_logs |
Get startup/provisioning logs |
codewire_list_presets |
List available presets |
Network
| Tool | Description |
|---|---|
codewire_list_nodes |
List nodes from relay |
codewire_kv_set |
Set key-value (shared KV store) |
codewire_kv_get |
Get value by key |
codewire_kv_list |
List keys by prefix |
codewire_kv_delete |
Delete key |
# Build
make build
# Run unit tests
make test
# Run all tests (unit + integration)
go test ./internal/... ./tests/... -timeout 120s
# Lint
make lint
# Manual CLI test
make test-manual
# Run with debug logging
cw node # slog outputs to stderr by defaultRelease binaries are signed with GPG. The public key is committed to this repository at GPG_PUBLIC_KEY.asc.
To verify a release:
# Import the public key
curl -fsSL https://raw.githubusercontent.com/codewiresh/codewire/main/GPG_PUBLIC_KEY.asc | gpg --import
# Verify checksums signature
gpg --verify SHA256SUMS.asc SHA256SUMS
# Verify binary checksum
sha256sum --check --ignore-missing SHA256SUMSMIT
Latest release: v0.2.82 — 2026-03-26