In v0.6.0, --mode acp was added, running prime-agent as an Agent Client Protocol agent over NDJSON on stdio, driving an AgentConnection in-process rather than shelling out to RPC mode.[1] ACP mode intentionally avoids a translating adapter because prime-agent's differentiators — IPython-only tools, subagents, and autonomous gates — would be flattened away by such an adapter; instead, these appear as first-class ACP events.[2] Capabilities ACP has no native concept for (e.g. autonomous-gate state, subagents, heartbeats, compaction, goals, rich IPython output) travel in a reverse-domain _meta envelope keyed "ai.primeintellect.prime-agent", which vanilla ACP clients safely ignore; nothing non-standard is added to an ACP object root.[2][3] NDJSON (Newline-Delimited JSON) is a transport format in which each JSON value occupies exactly one newline-terminated line, allowing a stream reader to parse complete messages incrementally without buffering the entire stream. A kernel-owned MCP runtime (packages/coding-agent/src/core/kernel/) runs MCP servers inside the kernel process, with server lifecycles tied to that kernel.
One JSON-RPC message is written per line on stdout; requests are read from stdin, and diagnostics go to stderr — nothing else may be written to stdout, which belongs entirely to the protocol.[3] At startup, ACP mode calls takeOverStdout(), which redirects process.stdout.write to stderr so stray logging cannot corrupt the NDJSON stream; ACP frames are then written via writeRawStdout(), the same raw escape hatch used by RPC mode.[2] The rawStdoutSink() function returns a WritableStream<Uint8Array> that routes all bytes through writeRawStdout, bypassing the stdout takeover that would otherwise misdirect the ACP stream to stderr.[2] The default transport is acp.ndJsonStream(rawStdoutSink(), Readable.toWeb(process.stdin)) — NDJSON framing over stdin/stdout — used when no stream override is provided in AcpModeOptions.[2]
runAcpMode is the top-level entry point; it instantiates an InProcessAgentConnection around the provided AgentSessionRuntime and delegates to runAcpModeWithConnection.[2] The AcpModeOptions interface accepts a stream override (replaces the default NDJSON-over-stdio transport) and an ownStdout flag (skips claiming stdout when the caller supplies its own transport), both used by tests to run the protocol in-memory without a subprocess — see Testing and harness.[2] agent-connection/snapshot.ts supports capturing resident session state as a snapshot of an active AgentConnection.
ACP mode supports five methods: initialize (returns protocol version, capabilities, and agent info), session/new (creates the session), session/prompt (runs one turn and resolves with a stop reason), session/cancel (notification; aborts the addressed session's turn), and session/close (releases the session and frees the connection for a new one).[3] ACP mode advertises image: true and embeddedContext: true prompt capabilities and sessionCapabilities: { close: {} } in its initialize response, so clients can release the single-session slot by closing rather than dropping the connection.[2] session/prompt resolves with one of four stop reasons: end_turn (normal), cancelled (session/cancel called), max_tokens (autonomous token budget exhausted), or max_turn_requests (autonomous turn, continuation, or wall-clock limit hit). Autonomous quality gates run inside a single prompt turn and do not produce an intermediate stop reason.[3] In ACP mode, IPython is the model-facing tool: a cell execution appears as a tool_call of kind execute whose rawInput carries the cell source.[3] ACP mode supports declaring MCP programs in its configuration; the agent resolves them through the MCP manager and surfaces the resulting tools to the system prompt. ACP mode gates prompt dispatch on a turn-admission signal from agent-session.ts, coordinated through daemon-protocol.ts, preventing a race condition where a prompt could be answered before the session was ready to accept the next turn. openai-completions.ts preserves the reasoning_details field on Chat-format responses during replay, maintaining the model's reasoning chain across multi-turn conversations. acp-events.ts preserves assistant message boundaries when converting ACP events into the internal message representation, preventing consecutive assistant messages from being collapsed or merged incorrectly.
ACP mode maintains a single-session invariant: one ACP connection drives exactly one AgentConnection, and a second session/new request is refused rather than silently sharing conversation state, cwd, and queues. For a second concurrent session, start another process.[2][3] session/prompt refuses a concurrent turn while one is already running, and the working directory cannot be changed after startup — a client-supplied cwd that differs from the agent's real one is reported back in _meta rather than silently ignored.[3]
promptContent() splits ACP prompt blocks into plain text and images: text blocks are joined with newlines, image blocks become ImageContent objects, embedded resource blocks with .text are prepended with their URI and appended to the text corpus, and resource_link blocks contribute only their URI string.[2] Image and embedded-resource blocks must not be silently dropped: the client has been told via initialize that they are supported, so dropping them would let a client believe a pasted screenshot was accepted when it was not.[2]
The TurnBoundary mechanism tracks pre-turn transcript state using both object identity (a WeakSet) and a content key (a Set<string>) to survive auto-compaction, which can rebuild state.messages and re-materialize entries at lower indices, making a simple pre-turn message count unreliable — see Compaction.[2] The messageKey() function produces a stable identity string from [role, timestamp, stopReason, errorMessage]; because compaction drops messages without rewriting them, these fields are preserved on kept messages and the key survives compaction.[2] turnFailure() detects a failed turn by scanning the transcript in reverse for the newest assistant message added after the TurnBoundary; if that message has stopReason === "error" it returns errorMessage (falling back to "the model request failed"). Only post-turn messages are examined to avoid reporting stale errors from earlier turns.[2] In v0.6.1, ACP mode was fixed to report a failed turn as an error rather than a clean end_turn. Previously, a provider error, expired auth, or unusable model left session/prompt resolving with no updates at all, which appeared to clients as a successful but empty turn.[4] acp-mode.ts awaits terminal quiescence before signalling turn completion, preventing the next agent step from observing stale tool output.
sameCwd() compares two working-directory paths by canonicalizing them with realpathSync and then cross-checking dev+ino inode identity via statSync; the inode check is skipped when either dev or ino is zero, because a zero dev on Windows can produce false inode matches across different volumes.[2] normalizeWindowsDriveLetter() lowercases the drive letter of Windows paths (e.g. C: → c:) before path comparison, preventing false mismatches from mixed-case drive letters that Windows APIs can return inconsistently.[2] canonicalCwd() falls back to a lexical path when realpathSync throws (missing or inaccessible path), preserving the previous comparison behavior rather than crashing.[2]
autonomousMeta() returns undefined when autonomous mode is not enabled, and otherwise wraps autonomous status — continuations used, turns used, tokens used, gate attempt, and gate-failure exit text — in a primeAgentMeta envelope for the ACP _meta field.[2]
src/core/semantic-edges.ts contains a provenance producer that emits semantic-edges-v1 records during agent-session runtime, capturing compaction events, RLM calls, side-questions, and SDK interactions. agent-traces.ts is the single delivery path for both span traces and semantic-edges records; it owns the disk-cursor outbox and schedules uploads durably rather than fire-and-forget. Late compaction slices arriving after a session closes are settled before the outbox flushes; daemon-spawned subagents forward their parent lineage so the provenance graph remains fully connected. Provenance tracing is the mechanism by which agent-session runtime events — compaction, model calls, subagent interactions — are recorded as linked records, enabling a fully connected audit graph across sessions and subagents.
Sources