ACP local development
August 12, 2026 · View on GitHub
How to run a source change through a real ACP client on your machine. The protocol contract lives in External control readiness; this page is only the build/run/verify loop.
The commands below assume macOS or Linux with a POSIX shell. The Paseo examples were verified with Paseo 0.2.5; confirm command and status names when using a newer release.
The loop
bun run build:native # only when crates/ changed
bun run install:dev:bin # compile dist/gjc and point `gjc` on PATH at it
bun run restart:sdk-broker -- --close-session-hosts # REQUIRED — see below
Then drive it from a client, or from a bare stdio handshake:
printf '%s\n' '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":1,"clientCapabilities":{"fs":{"readTextFile":true,"writeTextFile":true},"terminal":true}}}' | gjc acp
Why the broker restart is not optional
gjc acp is a thin stdio front end. It does not run the agent loop — it attaches
to the SDK broker published for the agent directory, and the broker spawns a
sdk session-host-internal child per session. That broker is long-lived and
holds the entrypoint it was started from, so rebuilding the binary changes
nothing for ACP until the broker is replaced: the new gjc acp process talks
to an old broker, which spawns session hosts from the old build, and your change
appears to have no effect.
The symptom is indistinguishable from a broken fix. Check the broker's entrypoint before concluding anything about a change:
ps -eo pid,etime,command | grep '[b]roker-internal'
A stale broker is obvious once you look — the path is a different checkout, or the elapsed time predates your build:
19466 08:25:00 /Users/you/git/gajae-code/packages/coding-agent/dist/gjc sdk broker-internal --agent-dir /Users/you/.gjc/agent
After bun run restart:sdk-broker -- --close-session-hosts from your checkout,
the broker is replaced by one running that checkout's source:
79955 00:09 bun --config=.../src/sdk/broker/internal-source.bunfig.toml .../src/cli.ts sdk broker-internal --agent-dir /Users/you/.gjc/agent
The restart asks the published broker to shut down over its authenticated
loopback channel and starts a replacement. --close-session-hosts first closes
every broker-spawned host in that agent directory, so it can interrupt active
ACP work; it never closes interactive gjc TUI sessions. Without the flag,
live hosts keep their old entrypoint. A broker-only restart is safe only when
you will create a fresh ACP session instead of loading or reusing an existing
one.
Working against a scratch agent directory instead:
bun run restart:sdk-broker -- --agent-dir /tmp/gjc-acp-agent --close-session-hosts
GJC_CODING_AGENT_DIR=/tmp/gjc-acp-agent gjc acp
A fresh agent directory carries no credentials. Stored local credentials live
in agent.db, not models.db; do not copy a live SQLite database. Authenticate
inside the scratch agent directory, use provider environment variables or an
auth broker, or copy agent.db only while no process is using either database.
For Paseo, put GJC_CODING_AGENT_DIR in the provider's env entry and restart
the Paseo daemon, or pass it with paseo run --env so the provider process
receives the override.
Confirming what is actually live
| Question | Command |
|---|---|
Which binary is gjc? | readlink $(which gjc) |
| When was it built? | ls -l packages/coding-agent/dist/gjc |
| Which broker is serving? | ps -eo pid,etime,command | grep '[b]roker-internal' |
| Which hosts are running? | ps -eo pid,etime,command | grep 'session-host-internal' |
bun run install:dev:bin prints the symlink it wrote and runs a smoke test, so
its output already answers the first question.
Driving it from Paseo
Register GJC as a custom ACP provider in ~/.paseo/config.json (full example in
External control readiness),
then:
paseo daemon restart # after editing config.json
paseo provider ls # gjc must read `available`, not `error`
paseo run --provider gjc --cwd /tmp/gjc-acp-test --wait-timeout 3m "your prompt"
paseo logs <agent-id> # rendered transcript
paseo ls # lifecycle: running / idle / error
paseo stop <agent-id> # exercises session/cancel
paseo delete <agent-id>
paseo stop <agent-id> sends an ACP session/cancel, which by default stops only
the current turn (matching the SDK turn.abort default); owned background work
(subagents, background jobs) keeps running. To keep owned cancels that also stop
exact owned work, set GJC_ACP_ABORT_SCOPE=owned in the provider's env entry
(shown in the full example in
External control readiness)
and restart the Paseo daemon.
Paseo runs its daemon as a separate long-lived process, so it needs its own
restart after a config change — but not after a GJC rebuild, since it spawns
gjc per session. --wait-timeout 3m stops the CLI from waiting; it does not
cancel the agent, which may remain running. That timeout is separate from
GJC's sdk.promptDeadlineMs, which defaults to 30 minutes and settles as
prompt_deadline_exceeded.
Errors surface in the daemon log with the JSON-RPC payload intact, which is
where to look when the CLI prints something opaque like
Failed to create agent: [object Object]:
grep -i 'failed to create agent' ~/.paseo/daemon.log | tail -1
What to smoke-test
Unit tests cover the individual terminal and cancellation contracts, but not the complete client/daemon/process lifecycle. At minimum:
- A configured continuation path. Exercise a deterministic todo reminder,
TTSR resume, or auto-continue setup and verify that the same
session/prompteventually settles instead of remainingrunning. Different continuation mechanisms may start another agent run or continue within a managed loop, so do not use a fixedagent_startcount as the invariant. - A follow-up turn on the same session, including a tool call that touches the filesystem.
- Cancel mid-turn. The pending prompt must settle as
cancelled, and the agent must land onidlerather than surfacing a transport error. - A non-default mode, if the client offers one.
initializeagainst the bare stdio handshake above, to eyeball the advertised capabilities.
Verification references
packages/coding-agent/test/acp-*.test.tspackages/coding-agent/test/acp/packages/coding-agent/test/sdk-acp-*.test.tsbun run conformance:run— pinnedacp-core-v1corpus