Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
4 changes: 2 additions & 2 deletions .github/ISSUE_TEMPLATE/bug_report.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ A short description of what went wrong.
- **opencode --version** (output of `opencode --version`):
- **Agent platform** (Hermes Agent, Clawdbot, custom):
- **OS** (Linux/macOS/Windows + version):
- **Python version** (only if relevant to `examples/acp_demo.py`):
- **Python version** (only if relevant to the bundled `scripts/run.py`):

## Reproduction

Expand All @@ -36,4 +36,4 @@ What actually happened — paste logs, error messages, or unexpected output.
## Notes

Anything else that might help (related issues, workarounds, agent-specific
quirks).
quirks).
8 changes: 4 additions & 4 deletions .github/ISSUE_TEMPLATE/feature_request.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,9 @@ What problem are you trying to solve? What works today and what is missing?

## Proposal

A clear description of the change. For `SKILL.md` rewrites, show the new
section structure (e.g. "Quick Reference → Protocol Rules → Workflow →
Failure Modes").
A clear description of the change. For
`skills/opencode-acp-control/SKILL.md` rewrites, show the new section structure
(e.g. "Quick Reference → Protocol Rules → Workflow → Failure Modes").

## Alternatives considered

Expand All @@ -24,4 +24,4 @@ What other approaches did you weigh and why is this one better?

- [ ] Changes only documentation (no agent behavior change)
- [ ] Changes behavior — list which agents are affected (Hermes, Clawdbot,
etc.) and what the migration path looks like
etc.) and what the migration path looks like
23 changes: 14 additions & 9 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,11 @@ jobs:
run: |
set -e
markdownlint --version
markdownlint SKILL.md README.md CHANGELOG.md CODE_OF_CONDUCT.md CONTRIBUTING.md .github/ISSUE_TEMPLATE/*.md
markdownlint README.md docs/*.md \
skills/opencode-acp-control/SKILL.md \
skills/opencode-acp-control/references/*.md \
skills/opencode-acp-control/assets/*.md \
.github/ISSUE_TEMPLATE/*.md

link-check:
name: link check
Expand All @@ -43,8 +47,8 @@ jobs:
run: |
set -euo pipefail
{
grep -hoE 'https?://[A-Za-z0-9._/?#&+=%-]+' \
SKILL.md README.md CHANGELOG.md CODE_OF_CONDUCT.md CONTRIBUTING.md \
grep -rhoE 'https?://[A-Za-z0-9._/?#&+=%-]+' \
README.md docs/ skills/opencode-acp-control/ \
| grep -vE '\.(png|jpg|jpeg|gif|svg|ico|pdf)$' \
| sort -u
} > urls.txt
Expand Down Expand Up @@ -75,18 +79,19 @@ jobs:
- name: Lint with Ruff
run: |
set -e
ruff check examples/acp_demo.py
ruff check skills/opencode-acp-control/scripts/run.py
ruff check tests/

- name: Byte-compile the demo script and tests
- name: Check shell and compile Python
run: |
set -e
python3 -m py_compile examples/acp_demo.py
python3 -m py_compile tests/test_acp_demo.py
echo "OK: Python sources compile"
bash -n skills/opencode-acp-control/scripts/helper.sh
python3 -m py_compile skills/opencode-acp-control/scripts/run.py
python3 -m py_compile tests/fake_opencode.py tests/test_transport.py
echo "OK: transport sources compile"

- name: Run unit tests
run: |
set -e
python3 -m pip install --quiet pytest
python3 -m pytest tests/ -v
python3 -m pytest tests/ -v
74 changes: 0 additions & 74 deletions CONTRIBUTING.md

This file was deleted.

195 changes: 93 additions & 102 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,136 +1,127 @@
# OpenCode ACP Control

> **A reusable AI agent skill that lets coding agents drive OpenCode CLI sessions over the Agent Client Protocol (ACP).**
A reusable Agent Skill for controlling `opencode acp` over newline-delimited
JSON-RPC without losing the stdio transport when a host runtime closes
background stdin.

[![ClawHub: opencode-acp-control-3](https://img.shields.io/badge/ClawHub-opencode--acp--control--3-FF6B35?style=flat-square&logo=data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAxMDAgMTAwIj48Y2lyY2xlIGN4PSI1MCIgY3k9IjUwIiByPSI0NSIgZmlsbD0iIzAwMCIvPjwvc3ZnPg==&logoColor=white)](https://clawhub.ai/berriosb/skills/opencode-acp-control-3)
[![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg?style=flat-square)](./LICENSE)
[![Protocol: ACP / JSON-RPC 2.0](https://img.shields.io/badge/Protocol-ACP%20%2F%20JSON--RPC%202.0-green?style=flat-square)](https://agentclientprotocol.com)
[![OpenCode ≥ v1.1.0](https://img.shields.io/badge/OpenCode-%E2%89%A5%20v1.1.0-black?style=flat-square)](https://opencode.ai)
[![Python 3.8+](https://img.shields.io/badge/Python-3.8%2B-3776AB?style=flat-square&logo=python&logoColor=white)](https://python.org)
[![CI: markdownlint + lychee + ruff + pytest](https://img.shields.io/badge/CI-markdownlint%20%2B%20lychee%20%2B%20ruff%20%2B%20pytest-success?style=flat-square)](./.github/workflows/ci.yml)
[![Version: 0.3.0](https://img.shields.io/badge/Version-0.3.0-orange?style=flat-square)](./CHANGELOG.md)
[![Release: v0.3.0](https://img.shields.io/badge/Release-v0.3.0-blue?style=flat-square)](https://github.com/berriosb/Opencode-Acp-Control/releases)
## What changed in 0.4.0

This repository contains a reusable **skill** (`.md`-based instruction set)
that enables AI coding agents to **start, control, and monitor OpenCode CLI
sessions** over the standardized [Agent Client Protocol (ACP)](https://agentclientprotocol.com),
which speaks **JSON-RPC 2.0** over stdio.
The old recipe launched `opencode acp` with a generic background tool and then
assumed `process.write()` could still reach its stdin. That is not portable.
OpenCode documents ACP as a stdin/stdout protocol, so closing stdin delivers EOF
and terminates the server.

In plain terms: an AI agent can spin up an OpenCode session, send it coding
tasks, stream responses back, resume old conversations by ID, and shut it down
— all through a documented JSON-RPC interface.
The skill now ships a FIFO controller with an explicit ownership model:

---
```text
short run.py send commands ──► stdin.fifo ──► OpenCode
▲
│ FD 3 stays open
controller shell

## What it does

- Start OpenCode in ACP background mode (`opencode acp`)
- Create, resume, and cancel sessions
- Send prompts and stream responses
- Resume past conversations from saved session IDs
- Handle server-to-client `requestPermission` requests for tool calls
- Detect and trigger OpenCode auto-updates

See [`SKILL.md`](./SKILL.md) for the full agent-side instructions.
OpenCode ──► stdout.fifo ──► controller FD 4 ──► frames.ndjson
```

---
The permanent FD prevents a one-shot writer from becoming the last writer and
accidentally ending the ACP session. The controller also owns the exact child
PID, drains stdout, and performs ordered cleanup.

## Repository layout

```text
Opencode-Acp-Control/
├── README.md
├── LICENSE
├── skills/
│ └── opencode-acp-control/
│ ├── SKILL.md
│ ├── scripts/
│ │ ├── run.py
│ │ └── helper.sh
│ ├── references/
│ │ ├── api.md
│ │ └── guidelines.md
│ └── assets/
│ ├── template.md
│ └── example.json
├── docs/
└── tests/
```

## Quick start
The installable skill is the complete
[`skills/opencode-acp-control`](skills/opencode-acp-control/) directory. Keep
the scripts, references, and assets beside `SKILL.md`.

### Install the skill into your agent
## Install

The skill is a single Markdown file. Pick whichever install path matches your
agent platform:
Clone the repository, then copy or link the skill directory into the skill root
used by your agent runtime:

```bash
# Clone the repo
git clone https://github.com/berriosb/Opencode-Acp-Control.git
cd Opencode-Acp-Control

# Hermes Agent — copy into the active profile's skills dir
cp SKILL.md ~/.hermes/profiles/<profile>/skills/opencode-acp-control.md

# Or load the whole directory
mkdir -p ~/.hermes/profiles/<profile>/skills/opencode-acp-control
cp SKILL.md ~/.hermes/profiles/<profile>/skills/opencode-acp-control/SKILL.md
cp -R Opencode-Acp-Control/skills/opencode-acp-control \
/path/to/your/agent/skills/
```

The agent will pick up the file on its next skills refresh.

### Try it locally with the demo script
Requirements:

`examples/acp_demo.py` spawns a real `opencode acp` subprocess and walks
through the full JSON-RPC handshake. Stdlib only — no third-party
dependencies.
- Unix-like system with Bash and `mkfifo`
- Python 3.9+
- OpenCode available as `opencode` on `PATH`

```bash
# Print the JSON-RPC frames the skill produces (no opencode needed)
python3 examples/acp_demo.py --dry-run
## Transport smoke test

# Spawn opencode acp, run initialize + session/new, and exit (no LLM call)
python3 examples/acp_demo.py --no-prompt
Start the controller against a project with the command in background mode:

# Full end-to-end run (requires a configured LLM provider)
python3 examples/acp_demo.py --cwd /path/to/project --prompt "list the files"
```bash
runtime_dir="/tmp/opencode-acp.example.$$"
python3 skills/opencode-acp-control/scripts/run.py start \
--foreground --cwd "$PWD" --runtime-dir "$runtime_dir" &
```
Comment on lines +77 to 80

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

Make the smoke test wait for controller readiness.

If a user runs this block without a manual pause, send can execute while the runtime is not yet ready. run.py rejects sends until the runtime reaches that state. Poll status or wait for the READY line in the shell snippet before invoking send.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@README.md` around lines 77 - 80, Update the README smoke-test shell snippet
around run.py start so it waits for controller readiness before invoking send.
Poll the runtime status or wait for the READY output, then preserve the existing
send flow once the runtime reports ready.


---
Wait for its `READY` line, then send the initialize frame in
[`assets/example.json`](skills/opencode-acp-control/assets/example.json):

## Requirements

- **OpenCode** (≥ v1.1.0) — installed and available on `$PATH`
- A terminal with background process support
- An ACP-compatible agent (Hermes, Clawdbot, custom, etc.)
- Python 3.8+ only if you want to run the demo script or the unit tests

---

## How it works

| Step | Action | Description |
|------|--------|-------------|
| 1 | `opencode acp` | Start OpenCode in ACP (background) mode |
| 2 | `initialize` | Initialize JSON-RPC 2.0 connection |
| 3 | `session/new` | Create a new coding session |
| 4 | `session/prompt` | Send prompts, stream responses |
| 5 | `session/cancel` | Cancel mid-response if needed |
| 6 | `session/load` | Resume a previous session by ID |
| 7 | `requestPermission` | Approve or deny tool-call requests |
```bash
python3 skills/opencode-acp-control/scripts/run.py send \
--runtime-dir "$runtime_dir" \
--file skills/opencode-acp-control/assets/example.json

The transport is **newline-delimited JSON-RPC 2.0** on stdio (one JSON
object per line, frames terminated by `\n`). OpenCode does **not** use the
LSP `Content-Length` framing.
python3 skills/opencode-acp-control/scripts/run.py read \
--runtime-dir "$runtime_dir" --from-line 0 --wait 2

---
python3 skills/opencode-acp-control/scripts/run.py stop \
--runtime-dir "$runtime_dir"
```

## Tool mapping for AI agents
## Controller commands

This skill uses generic tool names. Map them to your platform:
| Command | Purpose |
|---|---|
| `start --foreground --cwd DIR` | Run a controller under a background-process tool |
| `start --cwd DIR` | Detach only when the runtime preserves descendants |
| `send --runtime-dir DIR --frame JSON` | Write one validated JSON-RPC frame |
| `read --runtime-dir DIR --from-line N` | Poll complete frames using a cursor |
| `status --runtime-dir DIR` | Check controller and child liveness |
| `stop --runtime-dir DIR` | Close the permanent FD and clean the runtime |
| `clean --runtime-dir DIR` | Remove an already stopped retained runtime |

| Generic Name | Hermes Agent | Clawdbot |
|---|---|---|
| Run command (background) | `terminal()` | `bash()` |
| Write to process | `process.write()` | `process.write()` |
| Read process output | `process.poll()` | `process.poll()` |
| Kill process | `process.kill()` | `process.kill()` |
| Web fetch | `web_extract()` | `webfetch()` |
| User prompt | `clarify()` | `askUser()` |
See [`SKILL.md`](skills/opencode-acp-control/SKILL.md) for the agent workflow
and [`guidelines.md`](skills/opencode-acp-control/references/guidelines.md) for
FD ownership and recovery invariants.

---
## Development

## Files
```bash
bash -n skills/opencode-acp-control/scripts/helper.sh
python3 -m py_compile skills/opencode-acp-control/scripts/run.py
python3 -m pytest tests/ -v
```

- [`SKILL.md`](./SKILL.md) — The skill definition (load this into your agent)
- [`examples/acp_demo.py`](./examples/acp_demo.py) — Runnable Python script
that demonstrates the full ACP workflow against a live `opencode acp` process
- [`tests/`](./tests) — Pytest suite covering the JSON-RPC framing and the
demo CLI's `--dry-run` and `--no-prompt` paths
- [`.github/workflows/ci.yml`](./.github/workflows/ci.yml) — CI:
markdownlint + URL link check + ruff + pytest
- [`CHANGELOG.md`](./CHANGELOG.md) — Release notes (Keep a Changelog format)
- [`CODE_OF_CONDUCT.md`](./CODE_OF_CONDUCT.md) — Contributor Covenant v2.1
- [`CONTRIBUTING.md`](./CONTRIBUTING.md) — How to file issues and PRs
Project documents live under [`docs/`](docs/), including the
[`CHANGELOG`](docs/CHANGELOG.md) and
[`contribution guide`](docs/CONTRIBUTING.md).

## License

MIT — see [`LICENSE`](./LICENSE).
MIT — see [`LICENSE`](LICENSE).
Loading