#understanding ACP agents

1 messages · Page 1 of 1 (latest)

radiant oriole
#

I am trying to find some best practices for using ACP agents.

  1. when I ask my agent to start an acp thread, how does my agent control the claude code harness?
  2. how do I sent permissions? how do I start it in plan mode? can my agent change it to build mode after the plan is approved?
  3. how do I get claude code to use a specific plugin, like the ralph loop?

my use case is to have my main agent create a plan, then pass it to my coding agent which uses ACP to spawn a claude code thread which uses the ralph loop to implement the plan.

restive flumeBOT
#

To help others find answers, you can mark your question as solved via Right click solution message -> Apps -> âś… Mark Solution

exotic iglooBOT
#

Here’s the mental model that makes ACP “click” in OpenClaw:

  • OpenClaw doesn’t “remote control” Claude Code directly. It starts an ACP session via an ACP backend plugin (usually acpx), then routes messages to that session (optionally bound to a Discord thread).
  • Once the thread is bound, the thread is the control surface: anything you (or your agent) say in that thread is forwarded to the harness session until you /acp close it (or it expires/unbinds).

Docs: https://docs.openclaw.ai/tools/acp-agents


1) “How does my agent control the Claude Code harness?”

Two ways:

A) Thread-bound routing (most common)

If you spawn with thread binding, OpenClaw stores a binding “this Discord thread → that ACP session”, and then:

  • new messages in the thread get routed to the ACP session
  • outputs come back into the same thread
#

To make this work on Discord you need the thread binding flags enabled, including:

  • channels.discord.threadBindings.spawnAcpSessions=true

Docs: https://docs.openclaw.ai/channels/discord (thread bindings section)

B) Explicit ACP controls

You can always control the session out-of-band with /acp …:

  • /acp status
  • /acp steer …
  • /acp set-mode plan|build
  • /acp close

Docs: https://docs.openclaw.ai/tools/acp-agents#acp-controls


2) Permissions + plan mode + switching to build mode

#

There are two different “permission-ish” layers people conflate:

(a) acpx non-interactive permissions (this is the big one)

ACP sessions are non-interactive (no TTY), so if the harness tries to pop an “Approve this write/exec?” prompt, it can hard-fail.

The acpx backend exposes:

  • plugins.entries.acpx.config.permissionMode: approve-all | approve-reads | deny-all
  • plugins.entries.acpx.config.nonInteractivePermissions: fail | deny

If you want Claude Code to actually edit files + run commands, the practical setting is usually:

  • permissionMode=approve-all
    (and then pick whether you’d rather hard-fail or silently-deny when something still prompts)

Docs: https://docs.openclaw.ai/tools/acp-agents#permission-configuration

(b) /acp permissions <profile> (runtime option)

/acp permissions strict etc maps to an ACP runtime option key called approval_policy.

#

Important caveat: it only helps if the backend/harness actually supports that profile, and it doesn’t magically solve non-interactive prompts unless the backend uses it to avoid prompting.

Docs: https://docs.openclaw.ai/tools/acp-agents#runtime-options-mapping

Plan mode / Build mode

You can start in plan mode and later switch:

  • /acp set-mode plan
  • /acp set-mode build

Yes: switching after approval is exactly what /acp set-mode is for (assuming the harness honors those modes).

Docs: https://docs.openclaw.ai/tools/acp-agents#acp-command-cookbook


3) “How do I force Claude Code to use a specific plugin (ralph loop)?”

OpenClaw’s ACP layer mainly controls:

#
  • which harness alias (agentId: "claude")
  • thread binding
  • cwd / timeout / model overrides
  • runtime mode / approval policy

It does not currently have a first-class “Claude Code plugin selection” knob.

So the reliable pattern is:

  1. Make Claude Code use ralph loop when run normally (outside OpenClaw) so you know the harness supports it.
  2. Then make sure acpx launches Claude Code with that configuration.

Practically, that usually means one of these:

  • Configure Claude Code so the plugin/loop is enabled by default in its own config, or
  • Create a custom acpx agent alias (e.g. claude-ralph) that runs the Claude Code ACP adapter with the right flags/env, then add that alias to acp.allowedAgents, and spawn using agentId: "claude-ralph".

OpenClaw docs note that acpx can use built-in aliases and that ~/.acpx/config.json agent overrides replace defaults, so that’s the “hook point” (the exact shape depends on acpx’s config format/version).

#

Your proposed workflow (good) — small tweak

main agent creates a plan → pass to coding agent → coding agent spawns Claude Code ACP thread → implement

That’s solid. The tweak: since ACP rejects inline file attachments, pass the plan either:

  • directly in the initial task: when you spawn the ACP session, or
  • write it into the repo as PLAN.md and tell Claude Code to follow that file.

If you paste (redacted) snippets of:

  • your acp { ... } config block
  • your plugins.entries.acpx { ... } block
  • and how you currently run “ralph loop” with Claude Code locally
    …I can tell you the cleanest way to wire it so agentId: "claude" reliably picks it up.
radiant oriole
#

acp config:
"acp": {
"enabled": true,
"dispatch": {
"enabled": true
},
"backend": "acpx",
"defaultAgent": "claude",
"allowedAgents": ["claude", "codex", "gemini", "opencode"],
"maxConcurrentSessions": 8,
"stream": {
"coalesceIdleMs": 300,
"maxChunkChars": 1200
},
"runtime": {
"ttlMinutes": 120
}
} }

acpx config:
"acpx": {
"enabled": true,
"permissionMode": "approve-all",
"nonInteractivePermissions": "fail"
}

exotic iglooBOT
#

Your ACP config looks basically right, and your acpx permission settings are the “works for coding” combo. A couple of practical best-practices + one likely config gotcha:


Sanity check: what you pasted vs what OpenClaw expects

1) Make sure the acpx config is under plugins.entries

In OpenClaw, the acpx backend is a plugin. So the config usually lives at:

plugins: {
  entries: {
    acpx: {
      enabled: true,
      config: {
        permissionMode: "approve-all",
#
        nonInteractivePermissions: "fail",
      }
    }
  }
}

If you literally have a top-level "acpx": {...} block, OpenClaw won’t read it. (Easy to miss.)

Ref: https://docs.openclaw.ai/tools/acp-agents#plugin-setup-for-acpx-backend

2) Thread spawning also needs Discord thread binding flags

To get “spawn ACP session into a thread” behavior, make sure you have:

  • session.threadBindings.enabled=true
  • channels.discord.threadBindings.enabled=true
#
  • channels.discord.threadBindings.spawnAcpSessions=true

Ref: https://docs.openclaw.ai/channels/discord (thread bindings section)


1) How your agent “controls” Claude Code in an ACP thread

Mechanically it’s simple:

  • sessions_spawn(runtime:"acp", agentId:"claude", thread:true, mode:"session") creates the ACP session and binds it to the Discord thread.
  • After that, messages in the thread are the control channel. OpenClaw routes them to the ACP session until you close/unbind it.

If you need explicit steering/state changes, use /acp status, /acp steer …, /acp set-mode … in that same thread.

Ref: https://docs.openclaw.ai/tools/acp-agents


#

2) Permissions + plan mode + switching to build mode

Permissions (the important part)

Your settings:

  • permissionMode: "approve-all" âś… (lets the harness write files + run commands without prompting)
  • nonInteractivePermissions: "fail" âś… for debugging / correctness

Best practice: keep fail while you’re setting things up. If you later prefer “don’t crash the whole run if something prompts”, switch to:

  • nonInteractivePermissions: "deny"

That makes it degrade gracefully instead of hard-failing.

Ref: https://docs.openclaw.ai/tools/acp-agents#permission-configuration

Plan → Build mode

#

You can do:

  • /acp set-mode plan
  • (review/approve)
  • /acp set-mode build

Yes, flipping modes mid-session is exactly the intent. Whether it changes behavior depends on the harness honoring that mode, so I still recommend you also enforce it in the prompt:

  • In plan mode: “Do not edit files or run commands; output only a plan; stop and wait.”
  • In build mode: “Now implement exactly the approved plan.”

3) Claude Code plugin selection (ralph loop)

OpenClaw’s ACP layer doesn’t currently have a first-class “enable Claude Code plugin X” switch. The plugin/loop has to be enabled at the Claude Code / adapter launch level.

So the best-practice wiring is:

#
  1. Verify you can run Claude Code locally with ralph loop enabled (whatever the canonical way is).
  2. Make acpx launch the claude adapter with that same setup:
    • either by configuring Claude Code so ralph is the default
    • or by defining a custom acpx agent alias (e.g. claude-ralph) that runs the adapter with the needed flags/env, then add that alias to acp.allowedAgents, and spawn with agentId: "claude-ralph".

If you tell me how you normally enable “ralph loop” (command line flags? env var? config file?), I can suggest the cleanest acpx-side place to bake it in.


Recommended workflow for your use case

If you want “main agent plans, coding agent builds” with clean guardrails:

  1. Main agent (non-ACP) produces a plan + acceptance checklist.
  2. Spawn Claude Code ACP session already constrained: start in plan mode, ask it to restate plan and risks, then wait.
  3. After approval: switch to build mode and implement; require it to summarize diffs + how to test.
  4. /acp close when done to avoid stale context.
#

If you paste your threadBindings config and confirm where your acpx plugin config lives (plugins.entries vs top-level), I can point out anything else that’ll bite you.

radiant oriole
#

both threadBindings and acpx plugins configs are clean, everything works fine with spawing ACP agents, but I am trying to figure out how to have my agent use acp to spawn claude code in ralph mode. this is the missing piece.

exotic iglooBOT
#

Yep — that “missing piece” is that Ralph isn’t an ACP/OpenClaw mode. It’s a Claude Code plugin, so you enable/use it inside the Claude Code session after you spawn agentId: "claude".

So the pattern is:

  1. Spawn Claude Code via ACP (what you already have working)
  2. In that ACP thread/session, send Claude Code’s plugin commands:
    • install the plugin (once)
    • run the ralph loop command

Official Claude Code docs for marketplace install syntax:
https://code.claude.com/docs/en/discover-plugins


The practical way to do “spawn Claude Code in Ralph mode”

Option A (recommended): pre-install once, then just run the loop

#

In any Claude Code session (manual or ACP), run:

  • /plugin install ralph-wiggum@claude-plugins-official

After that, in your ACP-spawned Claude Code thread, start the loop with the namespaced command:

  • /ralph-wiggum:ralph-loop "…your task…" --max-iterations 20 [other flags]

(Plugin commands are namespaced in Claude Code plugins, so the /ralph-wiggum:... form is the one to expect.)

Option B: make your agent do it automatically at spawn time

Put the bootstrap + loop invocation directly into the initial task you pass to sessions_spawn, e.g. conceptually:

  • first line: install plugin
  • second line: start ralph loop with your plan as the argument (or point it at PLAN.md)

This is the closest thing to “start in ralph mode” OpenClaw can do today: it’s just sending the right first messages into the Claude Code harness.

#

Two gotchas to watch for with Ralph + ACP

  1. Timeouts: a ralph loop can run a long time as a single harness turn. If you see it getting cut off, increase the ACP runtime timeout for that session (/acp timeout …) or set a higher default timeout in the acpx plugin config (timeoutSeconds) so long turns don’t get killed.

  2. Safety/cost control: always set --max-iterations, and ideally bake “run tests / lint” into the loop prompt so it converges on something verifiable.


Quick diagnostic

In the ACP-spawned Claude Code thread, try:

  • /plugin (should open/show plugin manager/help)
    If Claude Code responds “unknown command”, then your claude harness isn’t actually Claude Code-with-plugins (it’d be the wrong adapter), and we should look at what your acpx claude agent is mapped to.

If you paste the exact command you personally use to start Ralph in a normal Claude Code session (the working baseline), I can help you translate it into the exact “spawn task bootstrap text” you want your OpenClaw agent to send.

radiant oriole
#

is the agent that spawns the thread able to see all messages? in my specific example, i asked my agent to create a plan, then to spawn a claude code to review the plan. claude responded in the thread with changes and some open questions which my agent did not relay to me. is that an LLM limitation or an openclaw one?

exotic iglooBOT
#

It’s (mostly) an OpenClaw/session-routing thing, not an “LLM can’t do it” thing.

What’s happening

When you spawn thread-bound ACP (thread: true, mode: session), OpenClaw effectively creates/uses a new bound conversation:

  • The Claude Code ACP session is bound to the new Discord thread
  • All subsequent messages in that thread route to the ACP session
  • The agent/session that initiated the spawn does not automatically “subscribe” to that thread’s messages or relay them back to the parent channel/session

So your “planner” agent didn’t relay the open questions simply because it never saw them (they weren’t in its session context).

LLMs only respond to what’s in their current context; OpenClaw doesn’t currently do automatic cross-thread “fan-in” of messages back into the spawning session.


Best-practice patterns to avoid this

#

1) For “review this plan”, use one-shot (unbound) ACP

If you want the review to come back into the same place your planner is talking, don’t spawn a thread-bound persistent session for the review. Do something like:

  • /acp spawn claude --mode oneshot --thread off
  • and ask it to summarize + list questions in the same reply

That way, the result lands back in the same session/channel and your planner can immediately react to it.

2) If you do want a separate thread, make “relay back” explicit

You need an explicit step like:

  • you paste the review back to the planner, or
  • you ask the planner: “go read the Claude review thread and summarize it”

(That second one only works if your setup gives the planner tooling/permissions to read other thread messages; it’s not automatic.)

3) Keep everything in a single thread

Instead of “planner in parent + reviewer in child thread”, do the whole workflow in one thread and switch who’s bound via /focus (or just spawn “here” rather than creating a child thread).

radiant oriole
#

3

exotic iglooBOT
#

Cool — if you want everything in one Discord thread (so you never “lose” outputs in a side-thread), the trick is to treat the thread like a workbench and temporarily bind/unbind it to the ACP session as needed.

Single-thread workflow (recommended)

Phase 0: create/use one Discord thread for the whole task. Do not let /acp spawn … --thread auto create a child thread.

Phase 1 — Plan (main agent)

In the thread (no ACP bound yet), ask your main agent for the plan.

Phase 2 — Review the plan with Claude Code without stealing the thread

Run a one-shot ACP call that returns the review right into the same thread, but doesn’t bind the thread to ACP:

  • /acp spawn claude --mode oneshot --thread off
  • prompt: “Review PLAN.md, propose changes, list open questions, then summarize.”

This avoids the “now I’m stuck talking to Claude Code” problem.

Phase 3 — Build (persistent Claude Code + Ralph)

#

When you’re ready to actually implement, bind the same thread to a persistent Claude Code session:

  • /acp spawn claude --mode persistent --thread here --label claude-ralph

Now start Ralph inside Claude Code (once installed):

  • /plugin install ralph-wiggum@claude-plugins-official (only once per environment)
  • /ralph-wiggum:ralph-loop "Implement PLAN.md …" --max-iterations 20

Phase 4 — Switch back to your main agent

When you want the main agent back in control in the same thread:

  • /unfocus

That detaches the thread binding, so your next messages route back to the normal thread session (your main agent).


#

Quick ops commands you’ll actually use

  • /agents → see what’s currently bound/focused
  • /unfocus → return this thread to your normal agent
  • /acp sessions + /focus <label-or-session-key> → jump back to the same Claude Code session later

If you want, tell me whether you prefer (A) one-shot review + persistent build (what I described) or (B) persistent review+build in the same Claude session, and I’ll give you the exact command sequence/prompt template.

radiant oriole
#

A. Also, another question, what happens if the gateway restarts? any way to restart/resume the acp thread?

exotic iglooBOT
#

If the gateway restarts, two different things matter:

1) Does the thread binding survive?

Yes, normally. Discord thread bindings are persisted on disk in OpenClaw’s state dir (under $OPENCLAW_STATE_DIR, default ~/.openclaw), so after a restart OpenClaw can usually re-load “this Discord thread → that session key” and keep routing messages the same way.
Ref (where state lives): https://docs.openclaw.ai/start/faq (search “Where does OpenClaw store its data”)

So the happy path is simply:

  • restart gateway
  • go back to the same Discord thread
  • send a message → it should continue the same ACP session.

2) Does the in-flight ACP turn survive?

No guarantee. If the gateway restarts while Claude Code/acpx is mid-turn (especially during a long Ralph loop), that turn is very likely interrupted (because the gateway is the thing driving/waiting for the output).

After restart, you can usually continue the session, but you may need to re-issue “where were we / continue from current repo state”.


#

If it doesn’t resume automatically

You can rebind manually:

  1. In the thread, run /acp sessions (lists recent stored ACP sessions)
  2. Then /focus <session-key-or-label> to bind this thread back to the right one

Best practice: when spawning, always set a label (e.g. claude-ralph) so re-focusing is easy after restarts.

If /acp sessions shows nothing useful, or the backend can’t re-open the session, you’ll need to /acp spawn a new one (the code changes on disk remain; only the conversational runtime state is lost).

If you tell me whether you’re doing long Ralph loops (multi-minute) or short turns, I can suggest the right timeout/TTL settings so restarts hurt less.

radiant oriole
#

In the acp thread Claude is behaving strangely. First it does not seem to have the same tool access as Claude code, it tells me for example it cannot run uv, which it can when I point the correct PATH.

Then it does not want to complete all tasks because of time and token(!) concerns. I am quite far from the limit. I have to explicitly tell to keep working on it to continue.

Any ideas why that is the case for the acp bound Claude code but not the “normal” Claude code?

exotic iglooBOT
#

What you’re seeing is pretty typical when you run “Claude Code” via ACP vs running Claude Code in your own terminal, and it usually comes down to (1) environment and (2) runtime/session options, not the model.

1) “It can’t run uv” → almost always PATH / service env

ACP “Claude” is launched by the gateway service, so it inherits the gateway’s environment (often a minimal PATH, no shell rc files, no pyenv/nvm, etc.).

That’s why:

  • in your normal terminal Claude Code finds uv
  • in ACP it says it can’t… until you explicitly fix PATH

Fix (best practice): set PATH for the acpx plugin env, so every ACP turn gets the right PATH.

OpenClaw supports plugin-scoped env:

  • plugins.entries.<id>.env

So you’d do something like:

  • plugins.entries.acpx.env.PATH = "...:/path/to/uv/bin"
#

…and restart the gateway.

Docs (config key): https://docs.openclaw.ai/gateway/configuration-reference (search for plugins.entries.<id>.env)

Also: on macOS specifically, launchd services don’t inherit your shell env, so this shows up a lot.

2) “It stops early בגלל tokens/time concerns” → usually Claude Code heuristics or ACP runtime options

A few common causes:

A) You’re still in plan mode (or similar)

If you ever did /acp set-mode plan, the session may still be in that mode and will act cautious / avoid “doing everything”.

Run in that thread:

  • /acp status

If it shows mode=plan, switch:

  • /acp set-mode build
#

B) ACP turn timeout is low

Long tasks (especially Ralph loops) can look “time constrained” if the session has a short timeout.

Check in /acp status, then raise it:

  • /acp timeout 1800 (or higher for long loops)

C) It’s just being conservative because it’s not actually in Ralph loop

Claude Code (without Ralph) often stops at a “safe checkpoint” and asks to continue to manage cost/context. In ACP that feels more pronounced because you’re not sitting in the native UI.

If your intent is “keep going until done”, be very explicit in the prompt, or use Ralph:

  • /ralph-wiggum:ralph-loop "…" --max-iterations N …

Quick debugging question

In the “strange” thread, can you paste the output of:

  • /acp status

That will tell us immediately whether this is:

#
  • wrong mode (plan vs build),
  • timeout,
  • cwd,
  • permissions profile,
  • etc.

And separately: where is your uv installed (e.g. ~/.local/bin/uv, Homebrew path, etc.)? Then I can tell you the cleanest PATH value to inject via plugins.entries.acpx.env.PATH.

radiant oriole
#

ACP status:

session: agent:claude:acp:7a663a3a-55ad-473d-913a-e02c410f8445
backend: acpx
agent: claude
acpx session id: 9d254e8b-11da-4b3f-9a1e-3aea09438d24
acpx record id: 09708fcc-4067-4cd6-9468-e5a5b312b956
sessionMode: persistent
state: idle
runtimeOptions: cwd=/Users/telemachos/.openclaw/workspace
capabilities: session/set_config_option, session/set_mode, session/status
lastActivityAt: 2026-03-04T08:49:41.368Z
runtime: status=dead acpxRecordId=09708fcc-4067-4cd6-9468-e5a5b312b956 acpxSessionId=9d254e8b-11da-4b3f-9a1e-3aea09438d24 pid=47961
runtimeDetails: {"action":"status_snapshot","status":"dead","pid":47961,"summary":"queue owner unavailable","lastPromptTime":"2026-03-04T08:49:12.242Z","acpxRecordId":"09708fcc-4067-4cd6-9468-e5a5b312b956","acpxSessionId":"9d254e8b-11da-4b3f-9a1e-3aea09438d24"}

exotic iglooBOT
#

That /acp status is the smoking gun. Two things jump out:

1) Your ACP Claude is running with cwd=/Users/telemachos/.openclaw/workspace

If you don’t set --cwd (or /acp cwd …), ACP defaults to the OpenClaw workspace. That often explains “tool differences” vs your normal Claude Code run, because:

  • your normal terminal Claude Code is probably started inside the project dir
  • ACP Claude is started in a generic workspace dir, so it won’t see your project’s venv/.tooling, relative paths, etc.

Fix (per-session, easiest):

  • /acp cwd /path/to/your/repo

Or at spawn time:

  • /acp spawn claude --mode persistent --thread here --cwd /path/to/your/repo

That alone can change behavior a lot.

2) runtime: status=dead … summary="queue owner unavailable"

#

This is not a healthy “ready to work” status. It means acpx thinks the runtime/session owner process for that session is gone/unavailable (PID shown, status=dead).

When the backend is in this state, you can get exactly the kind of “weird” behavior you described: lost continuity, conservative stopping, etc., because the harness can end up effectively re-initializing / not holding onto state the way you expect.

What to do

In that same thread:

  1. Close it (cleans up binding + session metadata):
  • /acp close
  1. Spawn fresh with an explicit cwd and label:
  • /acp spawn claude --mode persistent --thread here --cwd /path/to/repo --label claude-repo
  1. Then check:
  • /acp status

If it still shows queue owner unavailable immediately after a fresh spawn, run:

#
  • /acp doctor

…and check gateway logs (there’s usually a concrete acpx error explaining why the runtime can’t stay up).


Why uv is missing in ACP but not in “normal” Claude Code

Even if the session is healthy, ACP runs under the gateway’s service environment, which often has a stripped PATH (especially on macOS services). So uv in ~/.cargo/bin or ~/.local/bin won’t be found unless PATH is set.

Best practice: set plugin-scoped PATH via:

  • plugins.entries.acpx.env.PATH (then restart gateway)

Config ref: https://docs.openclaw.ai/gateway/configuration-reference (search plugins.entries.<id>.env)


If you tell me where uv lives on your machine (which uv output) and what repo path you want as cwd, I can suggest the exact PATH string + the cleanest spawn command sequence.

radiant oriole
#

can i use something like plugins.entries.acpx.env.PATH: $PATH to get the contents of my system path variable?

exotic iglooBOT
#

Not with shell-style $PATH, no — OpenClaw doesn’t run a shell to expand $….

What it does support is config env-var substitution using ${VAR} (uppercase), applied when the gateway loads the JSON5 config.

So you can do:

plugins: {
  entries: {
    acpx: {
      env: {
        PATH: "${PATH}:/Users/telemachos/.cargo/bin:/opt/homebrew/bin",
      },
    },
  },
}
#

A couple important caveats:

  • ${PATH} expands to the PATH of the gateway process (service env), not your interactive terminal PATH. If the gateway is running under launchd/systemd with a minimal PATH, ${PATH} will be minimal too.
  • Setting plugins.entries.acpx.env.PATH will effectively set/override PATH for the acpx plugin runtime, so always prefix with ${PATH}:… unless you want to replace it entirely.
  • Plugin/env changes require a gateway restart.

If you tell me where uv is installed on your system (which uv), I can suggest the minimal PATH additions you need.

radiant oriole
#

Invalid config at /Users/telemachos/.openclaw/openclaw.json:\n- plugins.entries.acpx: Unrecognized key: "env"

753 │ "plugins": {
754 │ "entries": {
755 │ "telegram": {
756 │ "enabled": true
757 │ },
758 │ "discord": {
759 │ "enabled": true
760 │ },
761 │ "google-gemini-cli-auth": {
762 │ "enabled": true
763 │ },
764 │ "acpx": {
765 │ "enabled": true,
766 │ "config": {
767 │ "permissionMode": "approve-all",
768 │ "nonInteractivePermissions": "fail"
769 + │ },
770 + │ "env": {
771 + │ "PATH": "$PATH"
772 │ }
773 │ }
774 │ }
775 │ }

exotic iglooBOT
#

That error means your OpenClaw version doesn’t support plugins.entries.<id>.env yet (the docs/reference you’re looking at are newer than what your gateway is running).

Two separate notes:

  1. Even on versions that do support it, it would be ${PATH}, not $PATH (OpenClaw does config substitution with ${VAR}; it does not run a shell).
  2. On your version, plugins.entries.acpx.env is rejected entirely, so you need a workaround (or upgrade).

Workarounds you can use today

Option A (cleanest): wrap the acpx command and set PATH there

The acpx plugin already supports plugins.entries.acpx.config.command (you’re using config already), so you can point it at a tiny wrapper script that exports PATH and then execs the real acpx binary.

Example wrapper ~/bin/acpx-with-path:

#
#!/usr/bin/env bash
export PATH="/opt/homebrew/bin:/usr/local/bin:/Users/telemachos/.cargo/bin:/usr/bin:/bin"
exec "/Users/telemachos/.openclaw/extensions/acpx/node_modules/.bin/acpx" "$@"

Then in config:

plugins: {
  entries: {
    acpx: {
      enabled: true,
      config: {
        command: "/Users/telemachos/bin/acpx-with-path",
        permissionMode: "approve-all",
#
        nonInteractivePermissions: "fail",
      },
    },
  },
}

(You can keep expectedVersion as-is; or set "any" while testing.)

Option B: use absolute paths in Claude Code

Instead of relying on PATH, tell it to run /opt/homebrew/bin/uv (or wherever which uv points).

Option C: upgrade OpenClaw

If you want plugins.entries.acpx.env specifically, you’ll need to update to a build that includes it.


#

If you paste openclaw --version, I can tell you whether upgrading is the right move or whether the wrapper approach is the most stable for your setup.

radiant oriole
#

2026.3.2

new config, without path expansion, same issue:

753 │ "plugins": {
754 │ "entries": {
755 │ "telegram": {
756 │ "enabled": true
757 │ },
758 │ "discord": {
759 │ "enabled": true
760 │ },
761 │ "google-gemini-cli-auth": {
762 │ "enabled": true
763 │ },
764 │ "acpx": {
765 │ "enabled": true,
766 │ "config": {
767 │ "permissionMode": "approve-all",
768 │ "nonInteractivePermissions": "fail"
769 + │ },
770 + │ "env": {
771 + │ "PATH": "/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin"
772 │ }
773 │ }
774 │ }
775 │ },

exotic iglooBOT
#

Yeah — that’s expected on 2026.3.2.

In v2026.3.2, plugins.entries.<id> only allows:

  • enabled
  • config

There is no plugins.entries.<id>.env yet, so the validator correctly rejects it. (The config reference/docs mentioning plugins.entries.*.env are ahead of that release.)

What to do instead (works on 2026.3.2)

Option 1 (best): wrapper script via plugins.entries.acpx.config.command

Create a tiny wrapper that sets PATH, then execs the real bundled acpx:

/Users/telemachos/.openclaw/bin/acpx-with-path:

#
#!/usr/bin/env bash
export PATH="/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin:/Users/telemachos/.cargo/bin"
exec "/Users/telemachos/.openclaw/extensions/acpx/node_modules/.bin/acpx" "$@"

Then config:

plugins: {
  entries: {
    acpx: {
      enabled: true,
      config: {
        command: "/Users/telemachos/.openclaw/bin/acpx-with-path",
        permissionMode: "approve-all",
#
        nonInteractivePermissions: "fail",
      },
    },
  },
}

Restart gateway after.

Option 2: fix PATH at the gateway service level

If you’re running as a macOS service (launchd), set PATH in the LaunchAgent/Daemon EnvironmentVariables. That way everything spawned inherits it.


If you want, paste which uv and how you start the gateway (terminal vs launchd), and I’ll tell you the minimal PATH you need in that wrapper.

radiant oriole
#

ok, i will use the wrapper until env is in available. for which version is this planned?

exotic iglooBOT
#

Not in 2026.3.2, and (importantly) it’s not just “undocumented” — the config validator in 2026.3.2 literally doesn’t allow plugins.entries.*.env (only enabled + config), which is why you hit the “Unrecognized key: env” error.

I don’t have a reliable “this will be in vX.Y.Z” to give you, because there isn’t a tagged release in the repo that contains that schema change yet. Right now the docs/reference mention it, but the shipped schema doesn’t.

Best practical next steps:

  • Use the wrapper (what you’re doing) — that’s the correct workaround today.
  • If you want to track / accelerate it: file a GitHub issue noting the mismatch:

If you link me the issue after you create it, I can point you at the exact schema file/lines that need changing so it’s easy for someone to fix.

radiant oriole
#

exec "/Users/telemachos/.openclaw/extensions/acpx/node_modules/.bin/acpx" "$@"

this line seems to be wrong? it does not correspond to a valid path. there is no extensions folder in .openclaw. NB. acp works fine, so perhaps the folder is somewhere else?

exotic iglooBOT
#

Yep — that exec "/Users/telemachos/.openclaw/extensions/acpx/…" line was just an example path for the case where the acpx plugin was installed into the state dir.

In your setup, acpx is clearly installed/working, but it may be bundled inside the OpenClaw install/app (or installed under a different plugin root), so you won’t necessarily have:

~/.openclaw/extensions/acpx/…

How to find the actual acpx command path your gateway is using

Pick one of these:

  1. CLI (best):
openclaw plugins info acpx

This should show where it’s loaded from.

  1. Gateway log (also great): the acpx plugin logs the resolved command on startup. Look for a line like:
    acpx runtime backend registered (command: …)
#
rg -n "acpx runtime backend registered" ~/.openclaw/logs/gateway.log | tail -n 5

Whatever path it prints as command: is the one you should exec from your wrapper.

Alternative: force it into ~/.openclaw/extensions/

If you want it to exist there, you can install it explicitly via:

openclaw plugins install acpx

…but you don’t need to if it’s already working; you only need the correct binary path for your wrapper.

If you paste the output of openclaw plugins info acpx (or that one log line), I’ll tell you exactly what the wrapper’s exec ... line should be.

radiant oriole
#

❯ openclaw plugins info acpx

🦞 OpenClaw 2026.3.2 (85377a2) — Give me a workspace and I'll give you fewer tabs, fewer toggles, and more oxygen.

ACPX Runtime
id: acpx
ACP runtime backend powered by the acpx CLI.

Status: loaded
Source: /opt/homebrew/lib/node_modules/openclaw/extensions/acpx/index.ts
Origin: bundled
Version: 2026.3.2
Services: acpx-runtime

exotic iglooBOT
#

Perfect — that output explains it.

You’re using the bundled acpx plugin that ships inside your Homebrew-installed OpenClaw:

  • Plugin root (bundled):
    /opt/homebrew/lib/node_modules/openclaw/extensions/acpx/

So the pinned acpx CLI that the plugin uses by default will be at:

  • acpx binary:
    /opt/homebrew/lib/node_modules/openclaw/extensions/acpx/node_modules/.bin/acpx

That’s the path you want in your wrapper:

exec "/opt/homebrew/lib/node_modules/openclaw/extensions/acpx/node_modules/.bin/acpx" "$@"
#

Why you don’t see ~/.openclaw/extensions/: because you didn’t install it into the state dir — it’s coming from the OpenClaw install bundle (“Origin: bundled”).

Two quick notes:

  • If that .bin/acpx file isn’t there yet, restart the gateway once (the acpx plugin has an “ensure” step that installs the pinned acpx@0.1.15 into that extension folder).
  • When you upgrade OpenClaw via Homebrew, that path can change with the install, so you may need to update the wrapper (unless you choose to install acpx into ~/.openclaw/extensions and point to that instead).
radiant oriole
#

the acp doctor shows this error. the file path is correct

runtimeDoctor: error (spawn /Users/telemachos/.openclaw/acpx-with-path EACCES)

exotic iglooBOT
#

EACCES on spawn …acpx-with-path is almost always “the OS won’t execute this file”, i.e. missing execute bit (or a broken shebang/line-endings).

Do these checks/fixes:

  1. Make it executable
chmod 755 /Users/telemachos/.openclaw/acpx-with-path
ls -l /Users/telemachos/.openclaw/acpx-with-path

You want to see an x in the permissions, e.g. -rwxr-xr-x.

  1. Verify the shebang is clean (no CRLF)
    Check the first line:
head -n 1 /Users/telemachos/.openclaw/acpx-with-path

If you see something like #!/usr/bin/env bash^M, it’s Windows line endings. Fix with:

#
perl -pi -e 's/\r\n/\n/g' /Users/telemachos/.openclaw/acpx-with-path
  1. Try running it directly
/Users/telemachos/.openclaw/acpx-with-path --version

(or --help)

If that works, /acp doctor should stop complaining after a gateway restart.

If it still throws EACCES even with chmod +x, tell me what ls -l shows and whether the gateway is running as a LaunchAgent (your user) vs a LaunchDaemon (root/other user).

radiant oriole
#

that was dumb, i didn't make it executable...my nad.

exotic iglooBOT
#

All good — that exact EACCES is classic “forgot chmod +x”.

Next steps:

  1. Restart the gateway (so the acpx plugin reloads the command)
  2. Re-run /acp doctor
  3. Spawn a fresh Claude ACP session and try which uv / uv --version inside it

If this fixed it, please right‑click the solution message → Apps → ✅ Mark Solution so Answer Overflow picks it up for the next person.

radiant oriole
#

is there any way i can see the full agent process, its back and forth in the acp thread like i can see on claude code?

exotic iglooBOT
#

Not the full Claude Code TUI experience (panes, full transcript view, etc.) — ACP in Discord is basically a projection of the harness output, and OpenClaw intentionally hides a bunch of the ACP “session update” events by default to keep threads readable.

But you can get much closer by turning on live delivery and making more ACP tags visible.

What you can enable (OpenClaw 2026.3.2 supports this)

In openclaw.json:

{
  acp: {
    stream: {
      deliveryMode: "live", // instead of "final_only"
      tagVisibility: {
        tool_call: true,
        tool_call_update: true,
        current_mode_update: true,
#
        config_option_update: true,
        session_info_update: true,
        usage_update: true,
        plan: true,

        // optional + noisy + can leak “thought” text depending on harness:
        agent_thought_chunk: true,
      },
    },
  },
}

Then restart the gateway.

Caveats

#
  • Even with this, you’ll get more breadcrumbs (tool calls, mode changes, etc.), but it still won’t be identical to the Claude Code UI.
  • Turning on agent_thought_chunk can be very spammy and may reveal stuff you don’t want in chat. I’d start with tool_call/tool_call_update first.

If you want, tell me which parts you care about most (commands executed, file edits, token/usage, “thinking”), and I’ll suggest a minimal tagVisibility set that gives signal without turning the thread into a firehose.

radiant oriole
#

it seems the agent sometimes completes its tasks but does not post in the thread, unless i prompt it with something like "update", "done?" "???"

#

by agent i meant the claude code in the acp thread

exotic iglooBOT
#

That usually isn’t OpenClaw “dropping” a reply — it’s that Claude Code sometimes does a bunch of tool work and then emits little/no final agent_message_chunk text, so there’s literally nothing for OpenClaw to post.

When you type “update?/done???”, you’re forcing a new turn, and that turn produces visible output.

How to make it reliably talk without you poking it

Best fix: bake it into the prompt. For ACP sessions I often add something like:

  • “Post a short progress update after each major step.”
  • “Always end with a final message starting with DONE: (or BLOCKED:) + next steps.”
  • “If you need more time/turns, keep going; don’t ask for permission to continue.”

This ensures there’s always a visible final message.

Extra visibility (so silent tool work still shows up)

If you want to see tool/status breadcrumbs in the thread, switch ACP to live streaming and unhide tool call updates:

#
acp: {
  stream: {
    deliveryMode: "live",
    tagVisibility: {
      tool_call: true,
      tool_call_update: true,
      session_info_update: true,
      current_mode_update: true,
      plan: true,
    },
  },
}

Restart gateway after changing that.

#

Quick check when it “goes silent”

Run:

  • /acp status

If it says state: running, it’s still working. If it says idle and you got no message, it finished without emitting visible output → prompt/policy fix above is the right approach.

If you try the “always finish with DONE:” instruction and it still sometimes doesn’t post, that’s likely a bug worth filing (with a gateway.log snippet around that turn).

#

Got it — you mean the Claude Code harness inside the ACP-bound thread.

Yep, same root cause:

  • In the Claude Code TUI you “see activity” because the UI renders lots of internal events (tool execs, status, etc.).
  • In an ACP thread, OpenClaw mostly shows only what Claude Code emits as normal assistant text (agent_message_chunk). If Claude Code does work and doesn’t output a visible message at the end, the thread looks “silent” until you poke it.

So the fixes are still:

  1. Prompt it to always talk (best):

    • “Always post progress updates”
    • “Always end with DONE: / BLOCKED: + summary”
  2. Show more ACP events (so you see tool/status chatter even when it’s not writing prose):

    • acp.stream.deliveryMode: "live"
    • acp.stream.tagVisibility.tool_call(_update): true, etc.
#

If you want, paste a snippet of what you’re telling Claude Code (in the ACP thread) when it goes silent and I’ll suggest a “non-silent” template that works well with Ralph loops.