Thanks to visit codestin.com
Credit goes to github.com

Skip to content

feat(acp): support ACP service for OpenCodeReview - #1218

Draft
chaojixinren wants to merge 14 commits into
alibaba:mainfrom
chaojixinren:feat/acp-server
Draft

feat(acp): support ACP service for OpenCodeReview#1218
chaojixinren wants to merge 14 commits into
alibaba:mainfrom
chaojixinren:feat/acp-server

Conversation

@chaojixinren

@chaojixinren chaojixinren commented Sep 11, 2026

Copy link
Copy Markdown
Contributor

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 ocr binary 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 --> T
Loading

The client sends prompts and commands through the transport. ocr-acp parses 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:

  • CLI commands: ocr review, ocr scan, ocr session, and ocr delegate;
  • a VS Code extension;
  • CI/CD integrations such as GitHub Actions and GitLab CI;
  • integrations with multiple LLM providers; and
  • host-specific plugins or skills for Claude Code, Codex, Cursor, and OpenCode.

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 ocr binary instead of embedding protocol code into OCR's internal review engine.

  • The adapter will provide a standalone ACP Server executable and communicate with clients using JSON-RPC 2.0 over stdio and Streamable HTTP in the later protocol phase.
  • The adapter invokes ocr review, ocr scan, and ocr session as child processes and consumes their structured JSON output.
  • The adapter owns ACP sessions, prompt parsing, clarification, progress/display updates, cancellation, timeout handling, and protocol-channel hygiene.
  • OCR continues to own review execution and the review LLM; the adapter does not import OCR internal packages or duplicate OCR's provider system.
  • available_commands_update will 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:

  • Phase 3 - CLI contract and mock OCR: review/scan intent types, OCR result decoding, safe flag validation, and deterministic mock OCR fixtures.
  • Phase 4 - Prompt parsing and clarification: deterministic slash-command grammar, one submit_intent tool call for natural-language prompts, independent parser-LLM configuration, strict validation, and single-pending clarification state.
  • Phase 5 - OCR process orchestration: binary resolution and version probing, isolated stdout/stderr streams, bounded output and waits, deterministic cancellation/timeout outcomes, single-result decoding, and managed process cleanup.

Staged Plan

Phase Scope Status
1 CLI investigation Complete
2 Architecture and ownership boundaries Complete
3 CLI contract and mock OCR Implemented in this PR
4 Prompt parsing and clarification Implemented in this PR
5 OCR process orchestration Implemented in this PR
6 ACP server, session lifecycle, and real-client smoke test Planned next
7 End-to-end, race, coverage, static, vulnerability, and platform regression Planned
8 Real OCR client integration, packaging, and release preparation Planned
9 Delivery review and release handoff Planned

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-review repository and close issue #674.

Type of Change

  • New feature (non-breaking change that adds functionality)

How Has This Been Tested?

  • make -C acp test passes locally (-race, no network, no API key; parser tests use deterministic fakes)
  • make -C acp check passes (license, formatting, english-check, go vet, and staticcheck)
  • make -C acp coverage passes with total coverage of approximately 90.1% (minimum 90%)
  • Windows cross-compilation test passes for the orchestration package
  • git diff --check passes

Known 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

  • My code follows the project's coding style
  • I have performed a self-review of my code
  • I have added tests that prove my feature works
  • New and existing unit tests pass locally with my changes
  • I have updated the documentation accordingly
  • I have signed the CLA

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.

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%.
@github-actions

github-actions Bot commented Sep 11, 2026

Copy link
Copy Markdown
Contributor

🔍 OpenCodeReview found 10 issue(s) in this PR.

  • ✅ Successfully posted inline: 10 comment(s)

Comment thread acp/internal/intent/natural.go
Comment thread acp/internal/intent/parser.go
Comment thread acp/internal/intent/parser.go
Comment thread acp/internal/intent/slash.go
Comment thread acp/internal/llmresolve/client.go
Comment thread acp/internal/llmresolve/client.go
Comment thread acp/internal/llmresolve/client.go
Comment thread acp/internal/llmresolve/client.go
Comment thread acp/internal/llmresolve/client.go
Comment thread acp/Makefile
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
@chaojixinren chaojixinren changed the title feat(acp): parse prompts into review/scan intents with clarification feat(acp): add CLI contract, intent parsing, and OCR orchestration Sep 11, 2026
@chaojixinren chaojixinren changed the title feat(acp): add CLI contract, intent parsing, and OCR orchestration feat(acp): support ACP service for OpenCodeReview Sep 11, 2026
@chaojixinren
chaojixinren marked this pull request as draft September 11, 2026 17:19
@chaojixinren

Copy link
Copy Markdown
Contributor Author

@Qiyuanqiii Thanks for the detailed review. I addressed the two clarification-state issues you identified.

  1. Commit reviews now preserve reviewCommit when --commit is missing. The parser emits a commit-specific clarification question and keeps the pending state typed as a commit review. Regression coverage now verifies both the pending state and the question text.

  2. Natural-language follow-ups now merge the new tool-call fields with the adapter-owned compatible pending slots. A follow-up response may provide only the newly supplied field without discarding values collected in the previous turn. A two-turn regression test covers the from=main followed by to=feature case.

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 39cd254 and c094012. Verification passes with:

  • make -C acp test
  • make -C acp check
  • make -C acp coverage (approximately 90.1% total coverage)

I agree that protecting structural ExtraBody fields and the managed Anthropic headers is worthwhile hardening. I kept those items separate from the clarification-state fix so the current change remains focused. The lower-severity URL, error-snippet, and Makefile suggestions are also reasonable follow-up improvements.

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 PrayWithYou left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

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:

  1. actionClarify still stores slotsFromRaw(raw) directly. A partial clarify -> clarify follow-up can therefore replace, rather than merge with, compatible adapter-owned pending state. I think this should use the same deterministic merge principle as the actionReview path.

  2. 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 in await() is related to the same lifecycle contract.

  3. 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.

@PrayWithYou

Copy link
Copy Markdown

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
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants