A multiplayer, role-based coordination protocol for human-AI collaborative development.
Check out pvp.codes!
PVP is NOT a chatbot. It's a coordination layer where multiple humans and AI agents collaborate in real-time to:
- Shape prompts collaboratively
- Observe AI reasoning
- Gate actions before execution
- Record auditable streams of decisions
The code is a side effect; the real artifact is the recorded, auditable stream of human decisions mediated by AI execution.
- ✅ Real-time multiplayer collaboration
- ✅ Role-based access control (driver, navigator, adversary, observer, approver, admin)
- ✅ Approval gates for high-risk operations
- ✅ Live streaming of AI thinking and responses
- ✅ Context sharing and management
- ✅ Session forking and merging
- ✅ Interrupt mechanisms for human intervention
- ✅ WebSocket transport with reconnection
- ✅ Terminal UI (TUI) client
- ✅ Structured logging and monitoring
- ✅ Decision tracking - Git-based audit trail with automatic commit metadata
- ✅ Session tasks - Goal and task tracking with session persistence
- ✅ Strict mode - Validate agent actions against session tasks/goals
npm install github:agrathwohl/pvpInstall globally to use the CLI binaries from anywhere:
# From GitHub Package Registry
npm i -g @agrathwohl/pvp
# Verify installation
pvp --versionPrerequisites: The agent requires Bun runtime. Install it first:
curl -fsSL https://bun.sh/install | bashAfter global installation, three binaries are available:
| Binary | Runtime | Description |
|---|---|---|
pvp-server |
Node.js | WebSocket server for session coordination |
pvp-tui |
Node.js | Terminal UI client for human participants |
pvp-agent |
Bun | Claude AI agent with tool execution |
Start the PVP WebSocket server:
# Default (port 3000)
pvp-server
# Custom port and host
pvp-server --port 8080 --host 0.0.0.0Connect to a session with the Terminal UI:
# Create a new session
pvp-tui --url ws://localhost:3000 --name "Alice"
# Join existing session
pvp-tui --url ws://localhost:3000 --session <session-id> --name "Bob"
# Connect to remote server
pvp-tui --url wss://ws.pvp.codes --session <session-id> --name "Alice"Connect a Claude AI agent to a session:
# Basic usage (requires ANTHROPIC_API_KEY env var)
pvp-agent --url ws://localhost:3000 --session <session-id>
# With local working directory (agent executes commands in your local folder)
pvp-agent --url wss://ws.pvp.codes --session <session-id> --local
# Specify working directory path
pvp-agent --url wss://ws.pvp.codes --session <session-id> --local /path/to/project
# Full options
pvp-agent \
--url ws://localhost:3000 \
--session <session-id> \
--name "Claude Assistant" \
--model claude-sonnet-4-5-20250929 \
--api-key sk-ant-... \
--localpvp-agent options:
| Option | Description | Default |
|---|---|---|
-u, --url <url> |
WebSocket server URL | ws://localhost:3000 |
-s, --session <id> |
Session ID to join | (required) |
-n, --name <name> |
Agent display name | Claude Assistant |
-m, --model <model> |
Claude model | claude-sonnet-4-5-20250929 |
-k, --api-key <key> |
Anthropic API key | $ANTHROPIC_API_KEY |
-l, --local [path] |
Use local working directory | (disabled) |
--mcp-config <file> |
MCP servers config file | (none) |
--strict |
Enable strict mode (validate against tasks) | (disabled) |
Important: pvp-agent uses Bun runtime. If you see Cannot find package "bun", ensure Bun is installed and in your PATH.
# Clone and install dependencies
git clone https://github.com/your-username/pvp.git
cd pvp
npm install
# Build the project
npm run buildimport { startServer, startTUI } from "@agrathwohl/pvp";
// Start server
const server = await startServer({ port: 3000 });
// Start TUI client
await startTUI({
url: "ws://localhost:3000",
name: "Alice",
role: "driver",
});// Agent requires Bun runtime - import from submodule
import { startAgent } from "@agrathwohl/pvp/agent";
const agent = await startAgent({
url: "ws://localhost:3000",
session: "session-id",
apiKey: process.env.ANTHROPIC_API_KEY,
});
// With strict mode enabled
const strictAgent = await startAgent({
url: "ws://localhost:3000",
session: "session-id",
apiKey: process.env.ANTHROPIC_API_KEY,
strictMode: true, // Validate all actions against session tasks
});See API Documentation for complete reference.
This project uses different runtimes per component for optimal performance and security:
| Component | Runtime | Command | Notes |
|---|---|---|---|
| Server | Node.js | npm run server |
WebSocket server, session management |
| TUI | Node.js | npm run tui |
Terminal user interface client |
| Agent | Bun | npm run agent |
Claude AI agent with shell execution |
The agent component requires Bun runtime because its shell executor (src/agent/tools/shell-executor.ts) uses Bun.spawn for secure command execution:
- Security: Array-based arguments prevent shell injection attacks
- Performance: Native subprocess streaming without external dependencies
- Safety: Built-in timeout and buffer limits
Important: Do NOT run the agent with tsx or Node.js. It will fail with module not found errors. Always use npm run agent.
npm run server
# or
npm run server -- --port 3000 --host 0.0.0.0# Create a new session
npm run tui -- --server ws://localhost:3000 --name "Alice" --role driver
# Join an existing session
npm run tui -- --server ws://localhost:3000 --session <session-id> --name "Bob" --role navigator# Join an existing session with Claude AI agent
npm run agent -- --server ws://localhost:3000 --session <session-id>
# With custom settings
npm run agent -- \
--server ws://localhost:3000 \
--session <session-id> \
--name "Claude" \
--model claude-sonnet-4-5-20250929 \
--api-key sk-ant-...
# Note: Requires ANTHROPIC_API_KEY environment variable or --api-key flag
# Get your API key at: https://console.anthropic.com/Agent Capabilities:
- Native Anthropic tool use API for shell command execution
- Command safety categorization (safe/low/medium/high/critical)
- Approval gates for risky operations
- Real-time streaming output
- Multi-turn conversation with tool results
- Session-level task and goal tracking
- Strict mode for task-aligned execution
The agent includes a built-in tasks tool for managing session goals and task lists:
# Example: Agent can manage tasks during a session
# Tasks are persisted to the session and survive agent reconnections
# Operations available:
# - set_goal: Set the primary session objective
# - get_goal: Retrieve current goal
# - add: Create a new task
# - update: Modify task properties
# - complete: Mark task as done
# - remove: Delete a task
# - list: Show all tasks
# - clear: Remove all tasksTask Properties:
| Property | Type | Description |
|---|---|---|
id |
string | Unique task identifier |
title |
string | Task name |
description |
string | Optional details |
status |
pending | in_progress | completed |
Current state |
priority |
low | medium | high |
Task priority |
created_at |
ISO timestamp | Creation time |
updated_at |
ISO timestamp | Last modification |
completed_at |
ISO timestamp | Completion time |
Enable strict mode to ensure all agent actions align with session tasks:
pvp-agent --url ws://localhost:3000 --session <id> --strictWhen strict mode is enabled:
- Agent validates prompts against session tasks before responding
- Tool executions are checked for task alignment
- Agent receives task context at the start of each interaction
- Misaligned requests are flagged for review
Use Cases:
- Focused coding sessions with defined objectives
- Project management with explicit deliverables
- Supervised agent execution with guardrails
┌─────────────────┐ ┌─────────────────┐
│ TUI Client │─────▶│ PVP Server │
│ (Ink/React) │ │ (WebSocket) │
└─────────────────┘ └─────────────────┘
│
┌────────┴────────┐
│ Protocol │
│ - 40+ msgs │
│ - Gates │
│ - Context │
│ - Forks │
└─────────────────┘
PVP includes an integrated git decision tracking system that automatically captures session context and embeds it into git commits.
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
│ PVP Server │────▶│ Bridge Service │────▶│ Git Hooks │
│ │ │ (port 9847) │ │ │
│ • Messages │ │ • State mgmt │ │ • Trailers │
│ • Approvals │ │ • HTTP API │ │ • Git notes │
│ • Tool exec │ │ • Webhooks │ │ • Validation │
└─────────────────┘ └─────────────────┘ └─────────────────┘
-
Bridge Service - Local daemon that maintains session state
- Starts automatically with the PVP server
- HTTP API at
http://localhost:9847 - Unix socket at
/tmp/pvp-git-bridge.sock
-
TUI Integration - Real-time decision tracking display
- Shows messages, prompts, approvals since last commit
- Displays tool execution summary
- Polls bridge service every 5 seconds
-
Git Hooks - Automatic commit metadata injection
prepare-commit-msg- Injects PVP trailerspost-commit- Stores extended metadata in git-notespre-push- Validates PVP metadata coverage
When connected, the TUI shows a decision tracking panel:
┌─────────────────────────────────────────────────────────────────┐
│ 📊 Decision Tracking | Msgs: 15 | Prompts: 3 | Approvals: 2 │
│ Tools: shell_execute, file_write | Last: a1b2c3d │
└─────────────────────────────────────────────────────────────────┘
Commits include PVP trailers:
feat(auth): implement JWT validation [pvp:msg-01]
Added JWT token validation middleware.
PVP-Session: ses_01HX7K9P4QZCVD3N8MYW6R5T2B
PVP-Messages: msg-01,msg-03,msg-05
PVP-Confidence: 0.85
Decision-By: human:alice,ai:claude
| Endpoint | Method | Description |
|---|---|---|
/commit-context |
GET | Current session metrics |
/extended-metadata |
GET | Full session metadata |
/status |
GET | Bridge service status |
/health |
GET | Health check |
- Architecture Guide - Full system design
- Git Commit Protocol - Commit format specification
- Git Hooks README - Hook installation and usage
- Human: driver, navigator, adversary, observer, approver, admin
- Agent: AI assistants that execute actions
- Session: create, join, leave, end, config_update
- Participant: announce, role_change
- Heartbeat: ping, pong
- Presence: active, idle, away, disconnected
- Context: add, update, remove (files, references, structured data)
- Secrets: share, revoke (API keys, credentials)
- Prompts: draft, submit, amend
- Thinking: start, chunk, end (AI reasoning streams)
- Response: start, chunk, end (AI output streams)
- Tools: propose, approve, reject, execute, result
- Gates: request, approve, reject, timeout (approval workflows)
- Interrupts: raise, acknowledge (human intervention)
- Forks: create, switch (parallel exploration)
- Merge: propose, execute (combine forks)
Gates require human approval before executing high-risk operations:
// Configure which operations require approval
const config: SessionConfig = {
require_approval_for: ["file_write", "shell_execute", "deploy"],
default_gate_quorum: { type: "any", count: 2 }, // Require 2 approvals
// ...
};Quorum Rules:
any: N approvals from anyoneall: All approvers must approverole: N approvals from specific rolespecific: Specific participants must approvemajority: >50% of approvers
Share context between participants:
const contextMsg = createMessage("context.add", sessionId, participantId, {
key: "requirements",
content_type: "file",
content: fileContents,
visible_to: ["agent_01"], // Optional: restrict visibility
});Reserved Context Keys:
| Key | Content Type | Description |
|---|---|---|
session:tasks |
structured |
Tasks and goals state (managed by tasks tool) |
The session:tasks context is automatically managed by the agent's tasks tool. Frontend applications should subscribe to context.add messages with this key to display task state. See Tasks Frontend Integration for implementation details.
import { WebSocketClient } from "./src/transports/websocket.js";
import { createMessage } from "./src/protocol/messages.js";
import { ulid } from "./src/utils/ulid.js";
const client = new WebSocketClient("ws://localhost:3000", ulid());
client.on("connected", () => {
const createMsg = createMessage("session.create", ulid(), participantId, {
name: "My Session",
config: {
/* ... */
},
});
client.send(createMsg);
});
client.connect();See examples/ directory for complete examples:
basic-session.ts- Single participant sessionmulti-participant.ts- Multiple humans collaborating
| Key | Mode | Action |
|---|---|---|
p |
stream | Start composing prompt |
Ctrl+Enter |
compose | Submit prompt |
Esc |
compose | Cancel composition |
a |
gate | Approve gate |
r |
gate | Reject gate |
t |
stream | Toggle thinking panel |
Ctrl+C |
any | Exit |
Every message follows this structure:
{
v: 1, // Protocol version
id: string, // Message ID (ULID)
ts: string, // ISO timestamp
session: string, // Session ID
sender: string, // Participant ID
type: string, // Message type
ref?: string, // Reference to another message
seq?: number, // Sequence number (total ordering)
causal_refs?: string[], // Causal dependencies
fork?: string, // Fork ID
payload: object // Type-specific payload
}{
require_approval_for: ToolCategory[],
default_gate_quorum: QuorumRule,
allow_forks: boolean,
max_participants: number,
ordering_mode: "causal" | "total",
on_participant_timeout: "wait" | "skip" | "pause_session",
heartbeat_interval_seconds: number,
idle_timeout_seconds: number,
away_timeout_seconds: number
}Real-time bidirectional communication for TUI clients.
Server: src/transports/websocket.ts
Client: Automatic reconnection with exponential backoff
# Watch mode (server)
npm run dev
# Type checking
npm run build
# Run tests
npm testimport { createMessage } from "./src/protocol/messages.js";
const message = createMessage(
"prompt.submit", // type
sessionId, // session
participantId, // sender
{
// payload
content: "Build a login form",
target_agent: "claude_01",
contributors: [participantId],
context_keys: ["requirements"],
},
);import { ParticipantManager } from "./src/server/participant.js";
const pm = new ParticipantManager();
if (pm.canPrompt(participant)) {
// Submit prompt
}
if (pm.canApprove(participant)) {
// Approve gate
}import { GateManager } from "./src/server/gates.js";
const gm = new GateManager();
const gate = gm.createGate(gateRequest);
gm.addApproval(gate, approverId);
const { met, reason } = gm.evaluateQuorum(gate, participants);LOG_LEVEL=info # Logging level (debug, info, warn, error)
NODE_ENV=production # Environment# Build
npm run build
# Run server
NODE_ENV=production npm run server -- --port 3000
# Or with PM2
pm2 start dist/server/index.js --name pvp-server- Unit tests (protocol, session, gates, decision tracking)
- Integration tests (MCP server integration)
- Decision tracking system (git-based audit trail)
- Session tasks and goals with persistence
- Strict mode for task-aligned agent execution
- MCP transport support
- Agent adapters (Claude, OpenAI, etc.)
- Persistent session recovery
- Message replay functionality
- Web UI client
- Enhanced fork/merge workflows
- Decision tree visualization
This is an implementation of the PVP specification. Contributions should:
- Follow the protocol specification precisely
- Maintain type safety (TypeScript strict mode)
- Include tests for new features
- Use structured logging (pino)
- API Reference - Complete programmatic API documentation
- Changelog - Version history and release notes
- Decision Tracking Architecture - Full system design
- Git Commit Protocol - Commit format specification
- Git Hooks Guide - Hook installation and usage
- Testing Guide - Test patterns and coverage
- Tasks Frontend Integration - Frontend implementation guide for tasks/goals
MIT
Built following the Pair Vibecoding Protocol specification.