@mager/acp
v0.2.0
Published
Agent Context Protocol — TUI-enabled SDK for agent teams
Maintainers
Readme
ACP — Agent Context Protocol
Build agent teams. Not chatbot pipelines.
@mager/acp is a lightweight SDK for wiring multiple AI agents together — each with a clear domain, passing full context between them. Now with a rich TUI for interactive agent chat.
What's New: TUI Mode 🖥️
The SDK now includes a full terminal UI built with Ink (React for terminals). Watch your agents think, hand off, and collaborate in real-time.
# Run the interactive TUI
ANTHROPIC_API_KEY=your_key npx acp
# Or with specific agents
ANTHROPIC_API_KEY=your_key npx acp --agents=magerbot,genny,franklin
TUI Features
- Live Agent Status — See which agent is thinking, idle, or handing off
- Handoff Visualization — Watch context pass between agents with visual indicators
- ACP Context Panel — Inspect the full context envelope at any handoff (press
H) - Message Navigation — Scroll through conversation history with arrow keys
- Session Management — Reset with
Ctrl+R, quit withEsc
The Idea
One agent can't do everything well. But three specialized agents that actually talk to each other can.
┌─────────┐ ACP Context Envelope ┌─────────┐
│ │ ─────────────────────────────→│ │
│magerbot │ identity, state, memory, │ genny │
│ ⚡ │ intent — nothing lost │ 🌿 │
│ │←──────────────────────────────│ │
└─────────┘ └─────────┘
↓ ↓
code/ops health/lifeZero re-briefing. Zero context loss. The second agent knows everything the first one did.
Quick Start
1. Install
npm install @mager/acp2. Run the TUI
export ANTHROPIC_API_KEY=your_key
npx acp3. Or use the SDK programmatically
import { ACPAgent, ACPMessage } from "@mager/acp";
import Anthropic from "@anthropic-ai/sdk";
const claude = new Anthropic();
class ResearchAgent extends ACPAgent {
constructor() {
super({
id: "researcher",
capabilities: ["search", "analyze", "summarize"],
systemPrompt: "You are a research agent. Be thorough.",
});
}
async handle(ctx: ACPMessage): Promise<string> {
const { intent, state, identity } = ctx;
// Full context available: who sent it, session state, memory
const response = await claude.messages.create({
model: "claude-sonnet-4-6",
max_tokens: 500,
system: this.systemPrompt,
messages: [{ role: "user", content: `Research: ${intent.target}` }],
});
return response.content[0].type === "text" ? response.content[0].text : "";
}
}Wire Two Agents Together
class WriterAgent extends ACPAgent {
constructor() {
super({ id: "writer", capabilities: ["write", "edit"] });
}
async handle(ctx: ACPMessage): Promise<string> {
const { intent, state, identity } = ctx;
// Writer gets FULL context from researcher
const response = await claude.messages.create({
model: "claude-sonnet-4-6",
max_tokens: 400,
messages: [{
role: "user",
content: `Write a summary about "${intent.target}".
Research: ${JSON.stringify(intent.payload)}
Confidence: ${state.metadata?.confidence}
Handed off by: ${identity.agent_id}`,
}],
});
return response.content[0].type === "text" ? response.content[0].text : "";
}
}
const writer = new WriterAgent();
const researcher = new ResearchAgent();
// Delegate with full context
const result = await researcher.delegate(writer, {
action: "write_summary",
target: "the Agent Context Protocol",
payload: { key_finding: "context dies at the boundary without ACP" },
}, {
current_task: "research_complete",
metadata: { confidence: 0.92, sources_checked: 8 },
});The ACP Envelope
Every handoff carries four things:
| Field | What it carries | |-------|----------------| | identity | Who sent this? What can they do? | | state | Session ID, turn count, current task, custom metadata | | memory | Long-term facts and preferences | | intent | Action, target, constraints, payload |
{
"acp_version": "0.2.0",
"identity": { "agent_id": "magerbot", "capabilities": ["code", "ops"] },
"state": {
"session_id": "sess_abc123",
"turn_count": 3,
"current_task": "research_complete",
"metadata": { "confidence": 0.92 }
},
"memory": { "retrieved": [{ "key": "user_timezone", "value": "America/Chicago" }] },
"intent": {
"action": "plan_trip_health_protocol",
"target": "japan_2026",
"payload": { "cities": ["Tokyo", "Kyoto"] }
}
}TUI Keyboard Shortcuts
| Key | Action |
|-----|--------|
| Enter | Send message |
| ↑/↓ | Navigate message history |
| H | Toggle ACP context panel |
| ? | Show/hide help |
| Ctrl+R | Reset session |
| Esc | Close panel / Quit |
Real-World Example: magerbot + genny
The reference implementation — two real agents that run my personal stack:
git clone https://github.com/mager/acp
cd acp && npm install
# Run the TUI with the real agent team
ANTHROPIC_API_KEY=your_key npx acp --agents=magerbot,genny
# Or run the programmatic example
ANTHROPIC_API_KEY=your_key npx ts-node examples/magerbot-genny/index.tsProgrammatic TUI
Build your own TUI by importing the components:
import React from "react";
import { render } from "ink";
import { App, ACPAgentRunner } from "@mager/acp";
const MyApp = () => {
const runner = new ACPAgentRunner(["researcher", "writer"]);
return <App agents={["researcher", "writer"]} />;
};
render(<MyApp />);API
new ACPAgent(options)
| Option | Type | Description |
|--------|------|-------------|
| id | string | Unique agent identifier |
| capabilities | string[] | What this agent can do |
| vendor | string? | Optional org/team name |
| systemPrompt | string? | System prompt for the agent |
agent.handle(ctx: ACPMessage): Promise<string>
Override this. Receives the full ACP context. Return a string.
agent.createContext(intent, state?, memory?)
Build an ACP message to send to another agent.
agent.delegate(target, intent, state?, memory?)
Pass an ACPAgent instance or handler function. Builds context, calls target.handle(ctx) automatically.
new ACPAgentRunner(agentIds: string[])
Create a runner that orchestrates multiple agents with real-time TUI updates.
ACPAgentRunner.process(input, callbacks)
Process user input through the agent team. Callbacks receive real-time updates:
onAgentStart(agentId)— Agent started thinkingonAgentHandoff(from, to, context)— Context passed between agentsonAgentComplete(agentId, response, acpContext)— Agent finished
Why Not MCP?
- MCP is for connecting agents to tools (databases, APIs, functions)
- ACP is for connecting agents to agents (full context, state, identity, intent)
- They're complementary — use both
Changelog
v0.2.0 — TUI Release
- ✨ Full terminal UI with Ink/React
- ✨ Real-time agent status visualization
- ✨ Interactive ACP context inspector
- ✨ Streaming Claude responses
- ✨ CLI entry point:
npx acp
v0.1.0 — Initial Release
- Core ACPAgent class
- Context envelope (identity, state, memory, intent)
- Basic delegation pattern
- magerbot + genny reference implementation
Roadmap
- [x] TypeScript SDK
- [x]
handle()+delegate()agent pattern - [x] Basic two-agent example
- [x] magerbot + genny reference implementation
- [x] TUI with Ink/React ← You are here
- [x] Streaming responses
- [x] CLI entry point
- [ ] HTTP transport helpers (call agents across services)
- [ ] Python port
- [ ] JSON Schema spec
- [ ] Agent registry (discover agents by capability)
License
MIT — mager
