Skip to content
Open
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 37 additions & 0 deletions code_puppy/cli_runner.py
Original file line number Diff line number Diff line change
Expand Up @@ -89,8 +89,40 @@ async def main():
parser.add_argument(
"command", nargs="*", help="Run a single command (deprecated, use -p instead)"
)
parser.add_argument(
"--acp",
action="store_true",
help="Start ACP Gateway (Agent Communication Protocol) server",
)
parser.add_argument(
"--acp-transport",
choices=["http", "stdio"],
default="http",
help="ACP transport: http (port 9001) or stdio (stdin/stdout). Default: http",
)
args = parser.parse_args()

# ACP stdio mode: early exit — skip banner, version check, renderers.
# stdio needs clean stdout (only JSON-RPC), all logs go to stderr.
if getattr(args, 'acp', False) and getattr(args, 'acp_transport', 'http') == 'stdio':
import logging
os.environ["ACP_ENABLED"] = "true"
os.environ["ACP_TRANSPORT"] = "stdio"
logging.basicConfig(
stream=sys.stderr,
level=logging.INFO,
format="%(asctime)s [%(name)s] %(levelname)s: %(message)s",
)
# Load plugins for agent discovery, but do NOT call callbacks.on_startup()
# because that would start a SECOND stdio server in a background thread.
plugins.load_plugin_callbacks()
try:
from code_puppy.plugins.acp_gateway.stdio_server import run_stdio_loop
await run_stdio_loop()
except KeyboardInterrupt:
pass
return

from code_puppy.messaging import (
RichConsoleRenderer,
SynchronousInteractiveRenderer,
Expand Down Expand Up @@ -320,6 +352,11 @@ def _uvx_protective_sigint_handler(_sig, _frame):
initial_command = None
prompt_only_mode = False

if getattr(args, 'acp', False):
os.environ["ACP_ENABLED"] = "true"
os.environ["ACP_TRANSPORT"] = getattr(args, 'acp_transport', 'http')
# http mode: falls through to interactive mode with ACP enabled in background

if args.prompt:
initial_command = args.prompt
prompt_only_mode = True
Expand Down
148 changes: 148 additions & 0 deletions code_puppy/plugins/acp_gateway/SETUP.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
# ACP Gateway Plugin — Setup Guide

## Quick Start

### 1. Install acp-sdk

```bash
uv pip install acp-sdk
```

### 2. Configure a Model Provider (Optional — for full agent execution)

The ACP Gateway works with any model Code Puppy supports. For testing with OpenRouter:

**a) Get an API key from [openrouter.ai](https://openrouter.ai)**

**b) Export it:**
```bash
export OPENROUTER_API_KEY="sk-or-v1-your-key-here"
```

**c) Copy the example models config:**
```bash
cp code_puppy/plugins/acp_gateway/extra_models.example.json ~/.code_puppy/extra_models.json
```

### 3. Environment Variables

| Variable | Default | Description |
|----------|---------|-------------|
| `ACP_ENABLED` | `true` | Enable/disable ACP Gateway |
| `ACP_HOST` | `0.0.0.0` | Bind host |
| `ACP_PORT` | `9001` | Bind port |
| `OPENROUTER_API_KEY` | — | OpenRouter API key (if using OpenRouter) |
Comment on lines +29 to +34
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

ACP_TRANSPORT is missing from the environment variables table.

The setup guide uses ACP_TRANSPORT in the launch examples (Lines 43, 57) and the architecture diagram (Line 146), but it is not documented in the "Environment Variables" table. Users will not know the valid values (http|stdio) or the default.

🔧 Suggested fix
 | `ACP_ENABLED` | `true` | Enable/disable ACP Gateway |
+| `ACP_TRANSPORT` | `http` | Transport to use: `http` or `stdio` |
 | `ACP_HOST` | `0.0.0.0` | Bind host |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@code_puppy/plugins/acp_gateway/SETUP.md` around lines 29 - 34, Add a row to
the Environment Variables table documenting ACP_TRANSPORT: include the variable
name `ACP_TRANSPORT`, the default value `http`, the allowed values `http |
stdio`, and a short description like "Transport mode for ACP Gateway (http or
stdio)"; update the same table where `ACP_ENABLED`, `ACP_HOST`, `ACP_PORT`, and
`OPENROUTER_API_KEY` are listed so readers know the valid values and default for
`ACP_TRANSPORT`.


### 4. Launch

```bash
export ACP_ENABLED=true
uv run code-puppy -i
```

ACP Gateway starts automatically on port 9001.

### 5. Verify

```bash
# Health check
curl http://127.0.0.1:9001/ping

# List agents
curl http://127.0.0.1:9001/agents

# Swagger docs
open http://127.0.0.1:9001/docs

# Send a prompt
curl -X POST http://127.0.0.1:9001/runs \
-H "Content-Type: application/json" \
-d '{
"agent_name": "code-puppy",
"input": [{"parts": [{"content": "Hello ACP!", "content_type": "text/plain"}]}]
}'
```

### 6. Standalone Test (no LLM needed)

```bash
python test_acp_local.py
```

## Architecture

```
Code Puppy CLI Process
└─ Startup callback
└─ Background thread (daemon)
└─ uvicorn serving ACP on :9001
└─ acp-sdk FastAPI app
└─ /agents, /runs, /ping, /docs
```

## Files

| File | Purpose |
|------|--------|
| `config.py` | ACPConfig from env vars |
| `agent_adapter.py` | Dynamic agent discovery |
| `acp_server.py` | ACP server with all agents |
| `run_engine.py` | Execution engine + RunRegistry |
| `session_store.py` | Multi-turn session management |
| `hitl_bridge.py` | Human-in-the-loop Await/Resume |
| `event_store.py` | Run progress event tracking |
| `register_callbacks.py` | Startup/shutdown hooks |

## stdio Transport

Subprocess-based communication via stdin/stdout. No port needed.

### Launch

```bash
python -m code_puppy.plugins.acp_gateway # direct
./run.sh stdio # via launcher
```

### Protocol

Newline-delimited JSON-RPC 2.0. Logs go to stderr.

```json
→ {"jsonrpc": "2.0", "method": "ping", "id": 1}
← {"jsonrpc": "2.0", "result": {"status": "ok", "transport": "stdio"}, "id": 1}
```

### Methods

| Method | Params | Description |
|--------|--------|-------------|
| `ping` | — | Health check |
| `agents/list` | — | List all agents |
| `agents/get` | `{name}` | Get agent metadata |
| `runs/create` | `{agent_name, input}` | Sync run |
| `runs/create_async` | `{agent_name, input}` | Async run |
| `runs/get` | `{run_id}` | Poll async run |
| `runs/cancel` | `{run_id}` | Cancel run |

### Orchestrator config

```json
{"type": "stdio", "command": "python", "args": ["-m", "code_puppy.plugins.acp_gateway"]}
```

### Test

```bash
echo '{"jsonrpc":"2.0","method":"ping","id":1}' | python -m code_puppy.plugins.acp_gateway
python test_acp_stdio.py
```

## Architecture (updated)

```
Code Puppy
└─ register_callbacks.py
├─ ACP_TRANSPORT=http → uvicorn :9001 (background thread)
└─ ACP_TRANSPORT=stdio → stdin/stdout JSON-RPC (background thread)
```
12 changes: 12 additions & 0 deletions code_puppy/plugins/acp_gateway/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
"""ACP Gateway Plugin.

Exposes Code Puppy as an ACP (Agent Communication Protocol) server,
allowing external agents and tools to communicate with Code Puppy
via the standardized ACP protocol.

The plugin gracefully degrades — if `acp-sdk` is not installed,
Code Puppy starts normally with a warning log.
"""

__version__ = "0.1.0"
__description__ = "ACP Gateway plugin for Code Puppy"
6 changes: 6 additions & 0 deletions code_puppy/plugins/acp_gateway/__main__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
"""Allow running ACP stdio server as: python -m code_puppy.plugins.acp_gateway"""

from code_puppy.plugins.acp_gateway.stdio_server import main

if __name__ == "__main__":
main()
Loading