feat(acp): support ACP service for OpenCodeReview - #1218
Conversation
Add acp/, a nested Go module for an ACP adapter over the ocr CLI, and its first layer: the CLI contract. internal/contract turns a ReviewIntent or ScanIntent into an argv slice and models the JSON the CLI writes back. Both builders append "--format json --audience human --color never" last, so the integration contract holds whatever a caller passes in Extra: flag parsing is last-wins, so even a flag that slipped past validation could not override them. Extra is not a free-form passthrough. Each command validates it against its own whitelist, derived from registerReviewFlags and registerScanFlags in cmd/opencodereview/shared_flags.go. The lists differ -- review has --effort, scan has --batch -- so a flag valid on one command is rejected on the other. Values outside a flag's enum are rejected too, because the CLI silently falls back to a default for an unknown value on some flags (--batch) rather than reporting it. A separated value that itself looks like a flag is rejected instead of consumed, so ["--effort", "--no-filter"] reports a missing value rather than reading the next flag as the effort. testdata/mock-ocr is a test double with nine scenarios: normal review and scan, partial results, no findings, stderr pollution, malformed JSON, a non-zero exit that still reports through the document, a blocking run that emits a partial result on SIGINT, and a spawn-child placeholder. Its output is fixed, so nothing here depends on an LLM, an API key or the network. mock_contract_test.go builds the double and decodes its real stdout into the contract types. Without it, a struct tag typo or a renamed JSON field on either side would surface as an empty result at runtime instead of a failure here. The mock's -scenario default is pinned by a test, since running the binary bare is how a developer first tries it. Coverage is 96.8%, enforced at 90% by make coverage.
acp/ is a nested Go module. The root ci.yml resolves its package list with `go list ./...`, which does not descend into a nested module, so nothing in acp/ was built or tested by any workflow. pages/ has the same problem, and the repository already solved it there with pages-ci.yml; this follows that precedent. The pull_request trigger deliberately has no branches filter. Development lands on an integration branch first and only reaches main at the end, so filtering to main would mean the integration pull requests run no CI at all. make check reports a missing staticcheck and carries on, so the job asserts `staticcheck -version` succeeds before calling it. Without that step the workflow could go green having never run the analyser. staticcheck is pinned to v0.8.1, whose go.mod requires Go 1.26.0; the container ships 1.26.6, so no extra toolchain is downloaded. The go.mod tidy check reads `git status --porcelain` rather than `git diff`, because a newly created go.sum is untracked and git diff would not report it.
The result DTO did not match the JSON the OCR CLI actually writes, so decoding a real `ocr review --format json` result failed: summary is an object, findings use content/start_line/end_line, and the review manifest carries the terminal state and the cancellation classification. Model the real shape and leave per-comment thinking out so it cannot leak. BuildScanArgs appended one --path per path, but --path is a single comma-separated scalar and pflag keeps only the last occurrence, so every path but the last was silently dropped. Emit one comma-joined --path and reject paths that contain a comma. Rewrite the mock to emit the real envelope, assert decoded field values in the contract tests, and add real-shape fixtures plus an opt-in OCR_BINARY integration test that pins the --path rule end to end. Harden `make check`: staticcheck reporting "matched no packages" is now a failure rather than a vacuous pass, and inline boolean values are validated with strconv.ParseBool to match pflag.
Implement phase 4 of the ACP adapter. acp/internal/intent turns one session/prompt into exactly one of a runnable ReviewIntent/ScanIntent, a clarification, or a rejection. A leading "/" selects the deterministic slash grammar; everything else is extracted by a single submit_intent tool call, with strict decoding and deterministic ref/path/extra validation through the phase 3 contract builders. The parsing LLM is configured independently from the review LLM (--parser-provider/model/base-url and OCR_ACP_PARSER_*), so the adapter does not copy or drift with OCR's provider system. make -C acp check passes; make -C acp coverage total 93.9%.
|
🔍 OpenCodeReview found 10 issue(s) in this PR.
|
Only resume a pending range clarification when the next slash command supplies exactly the missing ref. New commands now start from clean slots, and conflicting review selectors are rejected explicitly. Constraint: Preserve direct slash-based completion of pending range clarifications. Rejected: Disable all slash state restoration | would break the existing clarification workflow. Confidence: high Scope-risk: narrow Tested: make -C acp check; make -C acp test; make build; ocr review --audience agent Not-tested: Real OCR and real parsing LLM opt-in integrations.
Implement phase five execution of validated OCR requests with binary discovery, bounded stdout and stderr handling, structured result decoding, cancellation and timeout precedence, and Unix process-group cleanup. Extend the mock OCR binary with a real child-process scenario so lifecycle behavior is testable without network or real LLM calls. Constraint: OCR remains an external executable and the orchestrator must not depend on ACP SDK types Constraint: stdout and stderr must remain isolated with bounded retention Rejected: Windows Job Object support in this change | platform implementation and runtime evidence are still pending Confidence: high Scope-risk: moderate Reversibility: clean Directive: Do not mark Windows process-tree cleanup as formally supported until Job Object runtime evidence exists Tested: make -C acp test; make -C acp check; make -C acp coverage (90.5%); macOS race tests; Windows cross-compilation Not-tested: Linux runtime matrix and Windows process-tree behavior
Clarify runner channel semantics, preserve critical stream warnings under backpressure, and retain the first observed local termination cause while reporting cleanup failures. Align version probing and result decoding with the documented CLI compatibility contract, and refuse unsupported Windows process-tree execution. Constraint: Windows execution requires verified Job Object process-tree control. Rejected: Silently degrade to main-process-only cleanup | it cannot prove descendant reclamation. Confidence: high Scope-risk: moderate Directive: Keep terminal outcomes independently buffered and preserve local cancellation identity when adding cleanup diagnostics. Tested: make -C acp test; make -C acp coverage (90.1%); make -C acp check; Windows orchestrator cross-compile; git diff --check Not-tested: Linux runtime process-group behavior and Windows Job Object runtime behavior.
Keep commit clarifications typed as commit reviews and merge compatible natural-language follow-ups with the adapter-owned pending slots. Constraint: Clarification state must remain deterministic and scoped to the current session. Rejected: Require the model to repeat all known fields | model output is not authoritative state. Confidence: high Scope-risk: narrow Tested: make -C acp check; make -C acp test; make -C acp coverage Not-tested: Real LLM endpoint behavior.
The process runner now owns explicit stdout and stderr pipes and closes their write ends only after cmd.Wait returns. This keeps stream readers from reporting file-closed errors before the command's real non-zero exit status is classified.\n\nConstraint: Preserve separate bounded stdout and stderr handling.\nRejected: Keep Cmd.StdoutPipe and Cmd.StderrPipe | cmd.Wait may close them before readers finish.\nConfidence: high\nScope-risk: narrow\nReversibility: clean\nDirective: Keep pipe ownership and cmd.Wait ordering synchronized when changing process lifecycle code.\nTested: make -C acp check; make -C acp coverage; git diff --check\nNot-tested: GitHub Actions rerun after push
|
@Qiyuanqiii Thanks for the detailed review. I addressed the two clarification-state issues you identified.
I also added coverage for clearing clarification state when the model returns no partial intent, and for preventing stale slash-command selectors from leaking across review types. The fixes are included in commits
I agree that protecting structural I have also updated the AI/LLM disclosure to explicitly identify the tools and models used during development. |
Bound stream reader shutdown and process-group cleanup so ACP runs cannot close event channels while producers remain active or leave descendants behind. Replace unbounded stderr line buffering with bounded fragments while preserving diagnostics and tail capture. Constraint: ACP protocol events must remain deliverable while process trees and pipes are reclaimed Rejected: Close event channels immediately after the leader exits | descendants may still emit and cause send-on-closed-channel races Confidence: high Scope-risk: moderate Directive: Keep process-tree cleanup and stream-reader completion coupled before channel closure Tested: make -C acp check; make -C acp test Not-tested: Windows runtime Job Object behavior and version-probe descendant pipe retention
Keep event channel ownership valid for both pre-start failures and normal process runs. A deferred once-only close handles validation and startup exits immediately, while the stream-drain defer preserves the existing guarantee that normal events are delivered before closure. Add a regression test for cancellation of blocked pipe readers so the new stream lifecycle remains covered.\n\nConstraint: ACP callers wait for both the terminal outcome and event channel closure.\nRejected: Restore an unconditional early close alongside the stream close | normal runs could close events before readers finish.\nConfidence: high\nScope-risk: narrow\nReversibility: clean\nDirective: Keep event channel closure once-only when adding additional process or stream exit paths.\nTested: make -C acp check; make -C acp test; make -C acp coverage (90.3% total).\nNot-tested: GitHub-hosted CI rerun.
PrayWithYou
left a comment
There was a problem hiding this comment.
Thanks for the follow-up. I rechecked the latest changes, and there has been good progress here.
The two clarification-state issues from my original review are addressed:
- commit clarification now preserves the commit review type and asks for
--commit; - natural-language review follow-ups now merge compatible pending slots instead of losing previously known values.
The recent process-lifecycle fixes also look useful. In particular, the stream/process cleanup changes and the once-only event-channel closure address the lifecycle regressions introduced while Phase 5 was being added. ACP CI is green again as well.
I still see a few items before I can approve:
-
actionClarifystill storesslotsFromRaw(raw)directly. A partialclarify -> clarifyfollow-up can therefore replace, rather than merge with, compatible adapter-owned pending state. I think this should use the same deterministic merge principle as theactionReviewpath. -
A context that is already cancelled, or a request deadline that has already expired, still reaches
cmd.Start()before cancellation/deadline handling begins. I would prefer these to terminate before spawning OCR. The completion-vs-cancellation race inawait()is related to the same lifecycle contract. -
The PR disclosure currently names OpenAI Codex, but the repository policy asks for the specific model as well, so that still needs to be added.
I would keep the ExtraBody/header/URL/UTF-8 hardening items non-blocking as before.
Overall, this is much closer now. Once the remaining clarification-state and pre-start cancellation semantics are tightened up, I think the implementation will be in a much better position for approval.
|
My main GitHub account was suspended, so I’m replying using this one. |
Finalize ACP v1 session handling, command discovery, safe ResourceLink validation, result locations, lifecycle cleanup, and client interoperability coverage. Tested: make -C acp check; make -C acp test; make -C acp coverage; official Python ACP SDK smoke test Not-tested: Linux runtime evidence, Windows Job Objects, graphical ACP client UI, real LLM quality
The phase six adapter imports the ACP SDK from production code, so Go classifies it as a direct dependency. Record that classification to keep the committed module file identical to go mod tidy output in CI.\n\nConstraint: ACP CI rejects any go.mod or go.sum change produced by Go 1.26.6 tidy.\nConfidence: high\nScope-risk: narrow\nReversibility: clean\nTested: go mod tidy; make -C acp check; make -C acp coverage; make -C acp build.\nNot-tested: govulncheck under Go 1.26.6 because the local Docker daemon is unavailable.
…ellation Merge compatible pending intent fields across clarification turns while preserving explicit list replacement. Check termination before process start and at completion decisions without relying on watcher scheduling, retaining bounded cleanup and consumed wait results. Constraint: Keep the fixes local to clarification and process lifecycle semantics Tested: make -C acp check; make -C acp test (race); make -C acp coverage (90.4%); git diff --check Tested: User reported OCR review found no issues Not-tested: Linux and Windows runtime validation
Current Stage
This PR is currently at phase 5 of a nine-phase plan to add ACP service support to OpenCodeReview (OCR). Phases 3-5 are implemented here: the CLI contract, prompt intent parsing and clarification, and OCR process orchestration. The ACP transport/server layer itself is phase 6 and is intentionally kept as the next integration step.
ACP Module Architecture
The planned module keeps the ACP protocol boundary independent from OCR's core review engine. The adapter owns client-facing sessions and orchestration, while the existing
ocrbinary remains responsible for review execution and its review LLM.flowchart LR C["ACP clients<br/>Paseo / Zed / JetBrains"] T["JSON-RPC 2.0 transport<br/>stdio / Streamable HTTP"] S["ocr-acp<br/>ACP Server"] SE["Session and command layer<br/>available_commands_update<br/>session/prompt / session/cancel"] IP["Intent parser<br/>slash commands / natural language<br/>clarification state"] O["OCR process orchestrator<br/>timeouts / cancellation / cleanup<br/>stdout-stderr isolation"] CLI["Existing ocr CLI<br/>review / scan / session"] R["Structured OCR result<br/>progress / diagnostics / findings"] C <--> T T <--> S S --> SE SE --> IP SE --> O O --> CLI CLI --> R R --> O O --> SE SE --> S S --> TThe client sends prompts and commands through the transport.
ocr-acpparses them into a review or scan intent, asks for clarification when required, launches the existing OCR CLI as a child process, and streams progress and structured results back through the same ACP session. Cancellation and timeout signals flow into the process orchestrator, which is responsible for terminating and cleaning up the managed process tree before delivering the final outcome.Why ACP Support
OpenCodeReview (OCR) is Alibaba's open-source AI code review CLI, implemented in Go under the Apache-2.0 license. It has been used by tens of thousands of developers inside Alibaba and has found millions of code defects. OCR uses a hybrid architecture of deterministic engineering and agents: file selection, file-level context isolation, rule matching, comment locations, and reflection are protected by deterministic engineering logic, while agents handle dynamic decisions and context retrieval. This improves accuracy and reduces token consumption under the same model.
Today, OCR maintains a separate plugin or skill for each host, such as Claude Code, Codex, Cursor, and OpenCode. This makes integration work grow with every new host and makes behavior and documentation harder to keep consistent. OCR also cannot currently be discovered or consumed by ACP clients such as Paseo, Zed, and JetBrains ACP Agent Registry, so it has no standard entry point for multi-agent workflows where a general coding agent writes code and OCR acts as a dedicated review agent.
Agent Client Protocol (ACP) is an open protocol for connecting editors/clients with AI agents, with a role similar to LSP. A client implements ACP once and can then use any compatible agent. The client communicates with the agent through a JSON-RPC 2.0 session, sends prompts and commands, receives streaming progress and tool-call updates, gets structured results with file/line locations, and can cancel an in-flight session. The agent only needs to provide an ACP server. This lets OCR replace “one host, one integration” with “one server, every compatible client” and enables standard multi-agent collaboration.
Existing Work and Motivation
OCR already provides:
ocr review,ocr scan,ocr session, andocr delegate;Issue #674 requested registering OCR as an independent provider through ACP for tools such as Paseo. PR #679 added an “ACP server adapter” to the H2 2026 roadmap, with the explicit direction to expose OCR as a dedicated review agent while keeping it decoupled from the core review engine. That roadmap PR is a planning placeholder and contains no implementation.
Design and Scope of This PR
This work follows the roadmap boundary: an independent ACP adapter process wraps the existing
ocrbinary instead of embedding protocol code into OCR's internal review engine.ocr review,ocr scan, andocr sessionas child processes and consumes their structured JSON output.available_commands_updatewill expose/review,/scan, and parameter hints so clients can discover the supported entry points.The implementation already delivered in phases 3-5 provides the foundation for that server boundary:
submit_intenttool call for natural-language prompts, independent parser-LLM configuration, strict validation, and single-pending clarification state.Staged Plan
Final Project Goal
Deliver a usable, tested, and documented standalone ACP Server for OCR. A user should be able to configure one command in Paseo or another compatible ACP client, use natural language or slash commands to start a review or scan, see streaming progress and structured line-level findings in the client UI, and cancel the operation at any time. The completed work will land the ACP roadmap item in the upstream
alibaba/open-code-reviewrepository and close issue #674.Type of Change
How Has This Been Tested?
make -C acp testpasses locally (-race, no network, no API key; parser tests use deterministic fakes)make -C acp checkpasses (license, formatting, english-check, go vet, and staticcheck)make -C acp coveragepasses with total coverage of approximately 90.1% (minimum 90%)git diff --checkpassesKnown verification limits: Linux runtime process-group cleanup evidence and Windows Job Object runtime evidence are not yet complete; cross-compilation alone does not establish Windows runtime support. Tests do not depend on a real OCR binary, real LLM endpoint, or network access. An opt-in real parser endpoint check remains available via
OCR_ACP_REAL_LLM=1.This module is a nested Go module, so the gates are
make -C acp ...; its CI is.github/workflows/acp-ci.yml.Checklist
Related Issues
AI / LLM Disclosure
This PR was developed with assistance from OpenAI Codex gpt-6-astra. The implementation and tests were reviewed locally and with the repository's OpenCodeReview automation. No external LLM API or API key is required for the test suite; parser LLM integration is covered by deterministic fakes, with real-endpoint coverage remaining opt-in.