OpenHands / OpenHands/software-agent-sdk

[Epic] Built-in ACP harness parity: Kimi, OpenCode, Pi, Hermes, Antigravity

Open
#4,820 2 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

acp enhancement tracker
Dominant language
Python
Stars
1.1k
Forks
539
Avg merge
1d 19h
Merged PRs (30d)
137

Description

Goal

Ship Kimi, OpenCode, Pi, Hermes and Antigravity as built-in ACP providers with the same features, capabilities and implementation patterns as claude-code, codex and gemini-cli — not as one-off partial additions.

Status (2026-09-04): 2 of 4 landed. Kimi (#4714) and Pi (#4419) merged 2026-09-03; OpenCode is open as #4827; Antigravity is deferred as a watch item (#4624), and #4823 is closed with it. Each was originally scoped independently, so "supported" meant something different for each — this issue defines a single bar and tracks all four against it.

Harnesses

Harness Issue PR in flight Distribution Blocked by
Kimi #4819 #4714 (@ysntony) merged @moonshot-ai/kimi-code (npm) done
Pi #4635 #4419 (@Deep070203) merged pi-acp + @earendil-works/pi-coding-agent (npm) SDK done; eval wiring open
OpenCode #4639 #4827 open opencode-ai (npm; per-platform compiled binaries, ~185 MB linux) in review
Hermes #4634 git+NousResearch/hermes-agent@<tag> (Python, via uvx) needs a catalog entry for the non-npm shape
Antigravity #4624 binary — 650.4 MiB from dl.google.com ⏸️ watch item, unscheduled

Distribution decides how much of this applies

#4805 changed the economics for npm providers: a provider absent from the image is no longer broken, because resolve_acp_command() leaves the version-pinned npx -y <pkg>@<ver> invocation in place, and it installs on first use and caches under ~/.openhands. So an npm provider costs nothing in image size unless deliberately added to the preinstall list.

[!IMPORTANT]
Registry membership is not image membership (constraint carried from #4643). Registering a provider makes it selectable; it must not put it in the image. Joining the default INSTALL_ACP_PROVIDERS=claude-code,codex,gemini-cli grows every published image for every user and is a separate, deliberate decision per provider — the default for a new npm harness is registry-only.

That escape hatch is npm-only, and Antigravity is not npm. It is first-party Google, listed in the canonical ACP registry ("authors": ["Google LLC"]), but its distribution block contains only binary: five platform archives from dl.google.com, cmd: ./agy_acp_server.par. There is no download-on-demand path for that shape — _prefer_pinned_binary() returns the command unchanged and the launch simply fails if the binary is absent. So Antigravity is preinstalled or unavailable, and preinstalling means ~518 MiB on every image, against the effort in #4643 that just removed 324 MB.

Antigravity is now a watch item, not scheduled (#4624, notice added 2026-09-04). The artifact grew to 650.4 MiB (registry v1.1.1, unpacking to ~1.9 GiB), so it would need both a non-npm install mechanism and a dedicated image variant — "neither is justified without demand." #4823, which specced that mechanism, is closed as a watch item for the same reason: with Antigravity deferred and Hermes needing only a small piece of it — a registry entry without an npm catalog entry, now a P0 on #4634 — the full runtime-fetch scope had no consumer. Its reduced design is recorded there — a preinstall-only recipe needing zero SDK runtime changes, roughly a week — so nothing is re-derived if a gate trips.

So this epic is three harnesses in practice: Kimi and Pi merged, OpenCode in review. Antigravity is tracked, not queued.

[!NOTE]
Beware a name collision on this one: npm antigravity-acp is a 184 KB third-party wrapper published by an individual, not Google's server. Verify by the dl.google.com URL, never by package name.

The parity contract

A harness is done when it does everything the three reference providers do. Every item is a real touchpoint in the current code.

1. Registry recordopenhands-sdk/openhands/sdk/settings/acp_providers.py, the single source of truth:

Field What it must specify
key / display_name settings discriminator + UI label
default_command npx -y --prefer-offline <pkg>@<VERSION>; may carry trailing args
binary_name wrapper binary _prefer_pinned_binary() looks for; without it the provider always uses npx
api_key_env_var / base_url_env_var API key env var; proxy/base-URL routing var, or None
data_dir_env_var env var pointed at a per-conversation dir so credentials/sessions/caches don't leak between runs
default_session_mode the mode ID that disables permission prompts
agent_name_patterns lowercase substrings for runtime auto-detection
supports_set_session_model whether the initial model is applied via a protocol call
supports_runtime_model_switch whether mid-conversation model switching works
session_meta_key top-level _meta key for model selection, or None
available_models / default_model curated model-picker list and preselection
file_secrets ACPFileSecretSpec if the CLI authenticates from a file on disk rather than an env var

The two supports_* flags and file_secrets are where partial additions usually stop. A provider that registers but reports supports_runtime_model_switch=False when the server actually supports it silently loses a user-visible feature — determine each flag from the server's real behaviour, don't copy a neighbour's value.

2. Version constant<PROVIDER>_VERSION, alongside CLAUDE_AGENT_ACP_VERSION / CODEX_ACP_VERSION / GEMINI_CLI_VERSION.

3. Pins — nothing to add per provider any more.Resolved by #4832. ACP_INSTALL_CATALOG (acp_install_catalog.py) is now the single source of truth for every pinned npm package; the registry derives default_command/binary_name from it, and the Dockerfile renders its install plan from it rather than carrying hardcoded case arms. _REGISTRY_ACP_PACKAGES and the fail-open Dockerfile-regex test were deleted, not hardened — the drift they failed to catch is no longer representable. --prefer-offline is centralised there too, so no provider can omit it.

Adding an npm provider is now a catalog edit. Two further gates work unchanged: tests/sdk/settings/test_acp_providers.py asserts the registry key set exactly, so a registry addition cannot be silent; and check-acp-drift.py compares the registry to the TS mirror field-for-field.

4. TypeScript mirrorclients/typescript/src/models/acp-providers.json is hand-maintained and enforced by clients/typescript/scripts/check-acp-drift.py. CI fails if you skip it.

5. Dockerfile — only if preinstalling. A case branch (package@version + wrapper bin) plus the expected one of: error text, in openhands-agent-server/openhands/agent_server/docker/Dockerfile. Note the default list is also hardcoded in .github/workflows/server.yml and asserted verbatim by a test.

6. Tests — the paths that already cover the three reference providers: tests/sdk/agent/test_acp_agent.py, tests/agent_server/test_credential_binding.py, test_settings_router.py, test_conversation_info_model.py. Also ACPServerKind in openhands-sdk/openhands/sdk/settings/model.py.

7. Live verification — a real ACP turn against the actual server: session created, a tool call executed, a reply returned. The registry record is a claim about a server's behaviour, and only a live run substantiates it.

What the existing providers actually set

The contract above is abstract; this is the concrete shape, read from the registry on main.

Field claude-code codex gemini-cli
display_name Claude Code Codex Gemini CLI
default_command npx -y --prefer-offline @agentclientprotocol/claude-agent-acp@0.63.0 npx -y --prefer-offline @agentclientprotocol/codex-acp@1.1.7 npx -y --prefer-offline @google/gemini-cli@0.46.0 --acp
binary_name claude-agent-acp codex-acp gemini
api_key_env_var ANTHROPIC_API_KEY OPENAI_API_KEY GEMINI_API_KEY
base_url_env_var ANTHROPIC_BASE_URL OPENAI_BASE_URL GEMINI_BASE_URL
data_dir_env_var CLAUDE_CONFIG_DIR CODEX_HOME HOME
default_session_mode bypassPermissions agent-full-access default
agent_name_patterns ["claude-agent"] ["codex-acp"] ["gemini-cli"]
supports_set_session_model True True True
supports_runtime_model_switch True True True
session_meta_key "claudeCode" None None
default_model opus[1m] gpt-5.5 auto
available_models 5 7 7
file_secrets none CODEX_AUTH_JSON GOOGLE_APPLICATION_CREDENTIALS_JSON
What to take from it
  • All three set both supports_* flags to True. That makes False the value that needs justifying: it should mean this server genuinely cannot do it, verified against the running server, not not implemented yet.
  • session_meta_key is the exception, not the rule — only claude-code sets one, because claude-agent-acp ignores session-_meta model selection and needs the model pushed via a protocol call. Default to None unless the server actually reads a _meta key.
  • data_dir_env_var is not always a dedicated variable. gemini-cli uses plain HOME, because the CLI has no config-dir override — so isolation for it means relocating HOME. If a harness only respects HOME, say so explicitly on its issue.
  • default_command may carry arguments, not just a package: gemini-cli appends --acp.
  • binary_name is easy to omit and quietly costly — without it the provider always pays the npx path even when the pinned binary is installed.
file_secrets, if the CLI authenticates from a file
// codex — env var points at the DIRECTORY containing auth.json
{ "secret_name": "CODEX_AUTH_JSON", "filename": "auth.json",
  "env_var": "CODEX_HOME", "subdir": "codex",
  "env_points_to": "dir", "warn_if_unset": [] }

// gemini-cli — env var points at the FILE itself
{ "secret_name": "GOOGLE_APPLICATION_CREDENTIALS_JSON", "filename": "gcloud-credentials.json",
  "env_var": "GOOGLE_APPLICATION_CREDENTIALS", "subdir": "gemini-cli",
  "env_points_to": "file", "warn_if_unset": ["GOOGLE_CLOUD_PROJECT", "GOOGLE_CLOUD_LOCATION"] }

env_points_to distinguishes the two shapes and is the field most likely to be set wrong — Codex wants a directory, Gemini wants the file. warn_if_unset names companion variables that are useless alone. The SDK owns the mechanism — materialising the file under the conversation's durable root, seed-if-absent, setting the env var; the spec is only policy.

Relationship to other tracked work

  • #4823install_method for the provider registry. Closed as a watch item; reopen if #4624's gate 1 or 3 trips (gate 2 would obviate it).
  • #4830 — ACP CI hardening backlog (intra-SDK): conformance, model-acceptance and pin-installability probes. #4832 and #4834 landed its first block. Its measured baseline also found live drift in the existing records — _CODEX_MODELS lists a gpt-5.6 the server rejects, _GEMINI_MODELS lists an absent gemini-2.5-flash and omits a live gemini-3.5-flash.
  • #4833 — what each consuming repo (benchmarks, evaluation, canvas, enterprise) must assert against the registry.
  • OpenHands/automation#414 — weekly automation to detect harness drift. Adding four providers roughly doubles a maintenance surface nothing currently watches: all three existing pins are 12–14 stable releases behind (measured 2026-09-02), against ~78 upstream releases per 90 days. No dependency tooling can see these pins — they are Python constants and Dockerfile shell strings — and drift is behavioural as well as numeric (gemini-cli ≥0.43 broke the set_session_mode default we had recorded). Worth having before the count grows.
  • #4627[Harness Watch] Epic: automated paired ACP harness comparison. Overlapping but distinct: that epic needs OpenCode/Pi/Hermes working well enough to measure, this one needs providers shipped as product. #4635 and #4639 are shared children carrying both sets of criteria.
  • #4643 — image reorganisation. Source of the registry-vs-image constraint above, and of the #4805 fallback this depends on.
  • OpenHands/enterprise#235 — the user-facing half of Antigravity (frontend secrets, branding, analytics).

[!NOTE]
Hermes (#4634) belongs here too. It is a built-in provider like the others — usable by OSS users in Canvas, and kept out of the default INSTALL_ACP_PROVIDERS so it adds no image weight. The difference is only the install mechanism: it is a Python project installed from a pinned git tag, so uvx (already in the image) stands in for npx. That needs a small catalog change, tracked on #4634.

Definition of done

All four harnesses satisfy every item in the parity contract, each backed by a live ACP turn rather than a registry entry alone; no harness is in the default preinstall list without an explicit, recorded decision to put it there.

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start with openhands-sdk/openhands/sdk/settings/acp_providers.py and acp_install_catalog.py, then compare the TypeScript mirror at clients/typescript/src/models/acp-providers.json and run the ACP settings tests. Check the referenced provider issues and live ACP servers before choosing a remaining harness; completion requires registry parity, synchronized metadata, passing tests, and a verified ACP turn.

Written by the indexing model from the issue text.

Assessment

Tech stack
docker, python, typescript
Domain
backend-api-design, build-system, devtools
Issue type
Feature
Difficulty
5/5
Estimated time
Over a week
Activity status
Stale
Clarity
Mostly clear
Newbie friendliness
25/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.