Repository navigation
fix: make OpenCode ACP transport persistent #4
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
2 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file was deleted.
Oops, something went wrong.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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. | ||
|
|
||
| [](https://clawhub.ai/berriosb/skills/opencode-acp-control-3) | ||
| [](./LICENSE) | ||
| [](https://agentclientprotocol.com) | ||
| [](https://opencode.ai) | ||
| [](https://python.org) | ||
| [](./.github/workflows/ci.yml) | ||
| [](./CHANGELOG.md) | ||
| [](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" & | ||
| ``` | ||
|
|
||
| --- | ||
| 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). | ||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
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,
sendcan execute while the runtime is not yetready.run.pyrejects sends until the runtime reaches that state. Pollstatusor wait for theREADYline in the shell snippet before invokingsend.🤖 Prompt for AI Agents