Skip to main content
Version: 2.0.0

ACP Drive Path

The ClaudusCode terminal is the interactive face of ClaudusBridge. The ACP drive path is its programmatic counterpart: a native C++ client, FClaudusAcpClient, that drives the very same embedded agent without a terminal, PTY, or TUI — straight C++ to agent, over the Agent Client Protocol.

Use it when you want to script the agent from your own editor tooling: kick off a prompt from a button, react to streamed assistant text and tool calls in C++, gate the agent's actions behind your own permission UI, or run the agent headless as part of an automated workflow.

ACP controls the embedded Node agent from an Unreal host. It is separate from the native claudus executable and its stdio MCP connection. For shell commands and editor-closed asset workflows, start with Claudus CLI; the CLI can launch a hidden Unreal backend without using ACP or Node.js.

How it differs from the terminal

ClaudusCode terminalACP drive path
TransportOS pseudo-terminal (ConPTY / forkpty)node cli-node.js --acp, JSON-RPC over stdio
SurfaceRendered TUI in a Slate tabDelegates + console commands; no UI
InputKeystrokes you typeSendPrompt(...) calls
OutputPainted terminal cellsOnAgentText / OnToolCall delegates
Use caseA human chatting with the agentC++ code driving the agent

Both launch the same bundled agent (see the Embedded Agent Bundle page) — the ACP path simply speaks a structured protocol instead of a terminal.

What FClaudusAcpClient does

FClaudusAcpClient (in the ClaudusCode module) spawns node "<Resources/Agent/cli-node.js>" --acp and:

  1. Speaks JSON-RPC 2.0, newline-delimited (ndjson), over the child process's stdio pipes.
  2. Performs the initialize + session/new handshake.
  3. Forwards your prompts as ACP prompt turns.
  4. Streams the agent's reply back through delegates: assistant text chunks (agent_message_chunk) and tool calls (tool_call).
  5. Answers the agent's permission requests according to the configured permission mode (or your own hook).

It is created, owned, and ticked on the game thread via the core ticker, and is not thread-safe — drive it from the game thread.

Configuration

Start() takes an FConfig:

FieldMeaningEmpty default
NodeExePathAbsolute path to the node executable.Resolve node from PATH.
AgentScriptPathAbsolute path to cli-node.js.The plugin's Resources/Agent/cli-node.js.
WorkingDirSession working directory.The Unreal project directory.
PermissionModeACP permission-mode seed (see below).The agent's own default.
ModelModel id supported by the configured agent and provider account.The agent's default model.

Permission modes

The permission mode controls whether — and how — the agent is allowed to take actions (run tools, edit files, call MCP tools on the Unreal backend):

  • default — uses the agent's normal permission checks. If an action reaches the host as a permission request and no handler is bound, the client cancels that request. This host behavior does not make the agent a general-purpose read-only sandbox.
  • acceptEdits — auto-accept file edits.
  • bypassPermissions — let the agent act autonomously without prompting.
  • Additional seeds (dontAsk, plan, auto) are passed through to the agent.

For finer control, bind the PermissionRequest delegate. It receives the tool title and the offered ACP option ids (e.g. allow_always, allow, reject) and returns the chosen option id — or an empty string to cancel. If you do not bind it, the client returns a cancelled outcome. This is the hook to wire up your own in-editor "Allow / Deny" dialog. The agent can approve actions under its configured mode before they reach this hook; choose that mode deliberately.

Delegates

Subscribe to these multicast delegates on the client to react to the agent:

DelegateFires when
OnSessionReadyThe session is established and prompts run immediately.
OnAgentText(Text)A streamed chunk of assistant text arrives.
OnToolCall(Title, RawInputJson)The agent invokes a tool (human title + condensed JSON input).
OnTurnComplete(StopReason)The current prompt turn finished (end_turn, cancelled, error, …).
OnStopped(Reason)The client stopped (process exit, error, or Stop()).
PermissionRequest(Title, OptionIds)(return-value) The agent asks permission; you choose the option.

Prompts sent before OnSessionReady are queued and flushed once the session is up, so you can call SendPrompt immediately after Start without racing the handshake. CancelTurn() cancels the in-flight turn via an ACP session/cancel notification.

Console commands

The subsystem (UClaudusCodeSubsystem) exposes the ACP path through console commands, handy for trying it out from the editor's command line or for a quick test harness:

ClaudusCode.Acp.Start [permissionMode]
ClaudusCode.Acp.Prompt <your prompt text…>
ClaudusCode.Acp.Stop
  • ClaudusCode.Acp.Start — starts the agent. The optional argument is the permission mode; it defaults to default. Permission requests are cancelled when no handler is bound. Pass bypassPermissions or acceptEdits only when the corresponding automatic permissions fit your workflow.
  • ClaudusCode.Acp.Prompt — sends a prompt; remaining args are joined with spaces. Queued until the session is ready.
  • ClaudusCode.Acp.Stop — stops the agent and releases the process.

While running through these commands, streamed text, tool calls, turn completion, and stop events are surfaced to the Output Log (prefixed [ClaudusAcp]), so you can watch the agent work without any UI. A richer in-engine UI can subscribe to the same delegates.

Driving it from C++

UClaudusCodeSubsystem* Sub = GEditor->GetEditorSubsystem<UClaudusCodeSubsystem>();

// Start the agent with autonomous permissions.
Sub->StartAcpAgent(TEXT("bypassPermissions"));

// React to streamed output.
if (FClaudusAcpClient* Acp = Sub->GetAcpClient())
{
Acp->OnAgentText.AddLambda([](const FString& Text)
{
UE_LOG(LogTemp, Display, TEXT("Agent: %s"), *Text);
});
Acp->OnTurnComplete.AddLambda([](const FString& StopReason)
{
UE_LOG(LogTemp, Display, TEXT("Turn done: %s"), *StopReason);
});
}

// Prompt it (queued until the session is ready).
Sub->SendAcpPrompt(TEXT("List the actors in the current level and tidy up their names."));

Stop() (or StopAcpAgent()) cancels any active turn, closes the session, terminates the process, and releases resources; it is idempotent and safe to call on shutdown.

Verifying the agent responds to ACP

You can sanity-check the bundle's ACP support directly with your system Node, exactly as the plugin invokes it:

echo '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":1,"clientCapabilities":{}}}' | node cli-node.js --acp

A successful run returns a JSON-RPC initialize result with the agent's capabilities. See the Embedded Agent Bundle page for where cli-node.js lives and what it requires.