Skip to content

OpenClaw: share one opencode serve via a small ACP-to-HTTP adapter #1243

Closed

Description

@dezren39

Problem

OpenClaw's only coding-harness protocol is ACP, and opencode acp starts a private OpenCode server per process by design (OpenCode v2 docs: "starts a private OpenCode server for its own use. It doesn't connect to the shared background service"). Meanwhile every other OpenCode client (TUI opencode attach, opencode run --attach, JS SDK createOpencodeClient({ baseUrl }), desktop, web, OpenChamber via OPENCODE_HOST + OPENCODE_SKIP_START=true) can drive one shared opencode serve. Result on the laptop today: OpenChamber's managed serve + a background serve --service + per-session ACP processes, each spawning its own llama.cpp embedding child — duplicate processes, split session state, and per-turn cold boots against the shared SQLite DB (a third-party integrator measured 0/24 success under 8-concurrent load cold-booting vs 6/6 through one shared server).

Research (2026-10-26): ~/workspace/research_notes/openclaw-opencode-single-server-research-20261027-0359/report.md — no fork of OpenClaw or OpenCode is needed; no existing ClawHub plugin bridges ACP to the serve HTTP API.

Design: a small ACP↔HTTP adapter

A single-purpose shim executable that speaks ACP (JSON-RPC over stdio) on one side and the OpenCode serve HTTP API on the other, registered in OpenClaw exactly where opencode acp sits today:

plugins.entries.acpx.config.agents.opencode = {
  command = "<adapter>";
  args = [ "--server" "http://127.0.0.1:4096" ];
};

Mapping:

  • ACP session/new → POST /session (cwd → directory scoping)
  • ACP session/prompt → POST /session/:id/message (or prompt_async + GET /event SSE for streaming updates back as ACP session/update notifications)
  • serve permission events → ACP session/request_permission → reply via POST /session/:id/permissions/:permissionID
  • ACP session/cancel → POST /session/:id/abort
  • Auth: OPENCODE_SERVER_PASSWORD basic auth, sourced from sops like the other OpenClaw secrets.

Companion changes:

  • Run ONE opencode serve as a user service on a fixed port (4096, loopback; password via sops) started before front-ends.
  • OpenChamber pointed at it (OPENCODE_HOST, OPENCODE_SKIP_START=true) so it stops spawning its own managed server.
  • Retire the stale background serve --service instance once the shared server is canonical.

Acceptance

  • /acp spawn opencode in OpenClaw creates a session on the shared server (visible via GET /session and in OpenChamber/TUI attached to the same server).
  • A prompt round-trip through OpenClaw streams output and completes; permission requests surface in OpenClaw and resolve via the permissions endpoint.
  • Only one opencode serve process (and one embedding child) on the laptop during an OpenClaw coding session.
  • Adapter lives in this repo, is covered by the OpenClaw Nix config, and the interim opencode acp override remains a one-line fallback.

Notes

  • Interim state (already live): ACP per-session processes; keep sessions non-persistent by default.
  • Related adoptables from the same research pass, tracked separately: memory-lancedb in plugins.slots.memory (verify the official npm package name first — two candidates claim it), OmOC-style persona/tool-restriction shapes remapped to fleet quota pools (do NOT adopt its flagship-model category defaults).

Acceptance criteria

  • The adapter executable speaks ACP (JSON-RPC over stdio) on one side and the OpenCode serve HTTP API on the other, and registers in OpenClaw exactly where opencode acp sits today (agents.opencode command/args).
  • Session mapping works end-to-end against one shared opencode serve: session/new → POST /session (cwd → directory), session/prompt → message POST with streaming updates returned as ACP session/update notifications, and serve permission events round-trip through ACP session/request_permission.
  • An OpenClaw coding session run through the adapter appears in the shared server's session list (visible to OpenChamber/TUI attach) — no private per-process server is spawned.
  • Duplicate llama.cpp embedding children from per-session ACP processes are eliminated for adapter-routed sessions (process count check before/after).

Activity

  1. developing-today commented on Oct 27, 2026

    @developing-today
    Contributor
  2. added
    effort/mMedium: expected under 1 day of work
    meta/enrichedIntake review done: acceptance criteria, links, and normalized labels added
    priority/normalOrdinary priority (the default)
    on Oct 27, 2026
  3. dezren39 commented on Oct 27, 2026

    @dezren39
    MemberAuthor

    Intake review

    Related: Research: ~/workspace/research_notes/openclaw-opencode-single-server-research-20261027-0359/report.md (no fork of either project needed). Sibling pilot world: #1190 (OpenClaw comment loop), #1185 (babysitter v1).

    Notes: Well-evidenced problem: every other OpenCode client can share one opencode serve; OpenClaw's ACP path cannot, and the cost is measured (duplicate embedding children, split session state, 0/24 cold-boot concurrency vs 6/6 shared). The body's mapping table is most of a spec.

    Risks: Streaming semantics are the hard part — prompt_async + SSE event translation must not drop or reorder session/update notifications, and permission replies are on the critical path (a hung permission event hangs the session). Version drift between the ACP dialect OpenClaw expects and the adapter's implementation will be a maintenance tail.

    Suggested approach: Spike the three mappings (new/prompt/permission) against a local serve first with a scripted ACP client, then wire OpenClaw to it on the laptop for one real session before calling it done.

  4. dezren39 commented on Oct 27, 2026

    @dezren39
    MemberAuthor

    T3 Code recon (2026-10-27): attach is built-in config, no code changes

    T3 Code 0.1.2 is already installed on amd (Nix-packaged from this repo, running as the t3code user service, fleet-integrated 2026-10-26 — see doc/2026-10-26T21-18-21Z_feature_t3code-v2-integration/main.md). Today it spawns its OWN opencode serve on an ephemeral port (37249 at recon time), separate from OpenChamber's managed serve (37621) — the side-by-side duplication to eliminate.

    Attach mechanism exists in 0.1.2: the OpenCode provider instance config schema is binaryPath, serverUrl, serverPassword (+ model). The settings UI says of serverUrl: "Leave blank to let T3 Code spawn the server when needed." Setting it switches T3 to the external server (corroborated by upstream pingdotgg/t3code#15155). Version requirement (OpenCode >= 2.0.18) is satisfied by the fleet fork 2.0.30+56590c5.

    Implementation path (no T3 fork):

    1. Stand up the canonical shared opencode serve on a FIXED loopback port with a password (this issue's main task).
    2. Extend pkgs/t3code-seed-providers/seed.sh's fleet-owned refresh for the opencode-v2 instance to set/refresh config.serverUrl (+ config.serverPassword) — same pattern as its existing binaryPath refresh. settings.json stays writable (T3 writes it at runtime).
    3. Resolve how serverPassword is stored: T3 has a userdata/secrets/ store — check for a reference mechanism before putting any credential in plain settings.json.

    Coordination note: another workstream was mid-nixos-rebuild switch from ~/code-t3wt during recon, and the T3 feature doc records a previous same-day flip-flop from concurrent activations. Sequence any T3-affecting activation accordingly.

  5. added
    work/assignedQueue worker lifecycle state
    queue/claimedQueue worker lifecycle state
    and removed on Nov 1, 2026
  6. dezren39 commented on Nov 1, 2026

    @dezren39
    MemberAuthor

    🤖 Agent note · picked up

  7. 10 remaining items

  8. added
    queue/queuedHas a queued task in the Supabase agent queue
    work/assignedQueue worker lifecycle state
    queue/claimedQueue worker lifecycle state
    and removed
    queue/claimedQueue worker lifecycle state
    work/assignedQueue worker lifecycle state
    queue/queuedHas a queued task in the Supabase agent queue
    on Nov 1, 2026
  9. dezren39 commented on Nov 1, 2026

    @dezren39
    MemberAuthor

    🤖 Agent note · picked up

  10. dezren39 commented on Nov 1, 2026

    @dezren39
    MemberAuthor

    🤖 Agent note · done

  11. added a commit that references this issue on Nov 2, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

effort/mMedium: expected under 1 day of workkind/enhancementmeta/enrichedIntake review done: acceptance criteria, links, and normalized labels addedpriority/normalOrdinary priority (the default)

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions