# Selesai in T3 Code (ACP)

Selesai ships an Agent Client Protocol (ACP) server, `selesai-acp`. ACP hosts can start it as a local command, and T3 Code does exactly that through its **Local ACP command** provider: T3 speaks ACP to `selesai-acp`, and `selesai-acp` owns a normal Selesai RPC session. Models, providers, credentials, tools, extensions, MCP servers and transcripts all stay in Selesai.

**Evidence levels.** This page keeps three kinds of evidence apart:

- **Real T3 runs:** T3 Code v0.1.6 (source commit `2359643c`), with Selesai built from this repository and a local fake model. Everything described as observed on that build was exercised in that app, except the items under [Checks that were not run](#checks-that-were-not-run).
- **Inspected host source:** the installed T3 Code Beta v0.1.7, build-embedded commit `322f6544dfad`. Its ACP consumer code was read, not run. Source support for a frame is not proof that this version draws it.
- **Offline protocol runs:** the built `dist/acp-entry.js`, the real Selesai RPC child it starts, an isolated loopback model and a plain ACP v1 client. These prove the frames and values on the wire, not a T3 window.

No row added in this pass was rendered in a T3 UI on any version, so nothing below claims full parity with T3's native Pi adapter. [Compared with the native Pi adapter](#compared-with-the-native-pi-adapter) lists what was translated and what stays a host-side limitation.

## Requirements

- Selesai installed **on the machine that runs the T3 server**, under the same user account T3 runs as. T3 starts the command on the server, not in the browser.
- Node.js 22.19.0 or newer for the ACP process and for the Selesai child process it starts. The command runs with the T3 server's environment, and Selesai starts its child with `node` resolved from `PATH` (`src/modes/rpc/rpc-client.ts`). If the server's `node` is older, either put a newer Node first on the instance's `PATH` environment row, or set the command to a Node 22.19.0-or-newer binary and pass the absolute `dist/acp-entry.js` path as the first argument. The integration harness used the second form.
- Selesai provider credentials on that machine. Run `selesai` and use `/login`, or configure an API key through the environment or the auth file. See [Providers](providers.md).
- The T3 provider dialog has environment rows; use them when the T3 server's environment does not already contain the variables your Selesai install needs (for example `HOME`, a provider API key, or `SELESAI_CODING_AGENT_DIR`).

## Add Selesai to T3 Code

1. In T3 Code, open **Settings → Providers → Add provider**.
2. Choose **Show more**, then **Local ACP command**, then **Next**.
3. **Command:** `selesai-acp`, or the absolute path to `dist/acp-entry.js` inside your Selesai installation or checkout.
4. **Arguments:** add one row per literal argument, in order. For a pinned provider and model, four rows:

   | Row | Value |
   | --- | --- |
   | 1 | `--provider` |
   | 2 | `<provider>` |
   | 3 | `--model` |
   | 4 | `<model>` |

   `--session-dir <path>` is optional. Each flag and its value are separate argument rows; nothing is shell-split or quoted.
5. **Environment:** add rows only if the server needs them.
6. **Next**, set the display name to **Selesai**, then **Add instance**.
7. Open a thread, pick Selesai in the composer, and send a message.

**Windows:** T3 launches Local ACP commands without a shell, so it cannot run npm's `selesai-acp.cmd` or `.ps1` shims, and a bare `selesai-acp` is not found. Use `node` (a real executable) as the **Command** and the entry script as the **first argument row**. Run this once in a terminal and copy the rows it prints:

```
selesai-acp --print-t3-config
```

```
Command:
  node

Arguments (one row each, in order):
  1. C:\Users\<you>\AppData\Roaming\npm\node_modules\@selesai\code\dist\acp-entry.js
```

Add `--provider`/`--model` before `--print-t3-config` to include them in the printed rows.

Do **not** put `selesai --mode rpc` in the ACP command field. That is Selesai's own RPC mode and it is not an ACP server, so T3 cannot initialize it. `selesai-acp` is the ACP entry point, and it starts the RPC child itself.

With no `--provider`/`--model` arguments, the child uses Selesai's configured default model, and the model picker can switch it for the session. `selesai-acp --help` lists the accepted flags.

Selesai advertises a terminal-login authentication option to ACP clients. Hosts that support terminal authentication can launch `selesai-acp --terminal-login`, which opens the interactive Selesai TUI. Run `/login`, then exit the TUI and retry or restart the agent session. Credentials remain in Selesai's auth storage; the ACP authenticate acknowledgement does not itself validate credentials. The terminal-launch UI has not been verified in a fresh T3 browser run.

## Streaming and Stop

T3 v0.1.6 streams assistant text **by paragraph**: its `responseStreamingMode` setting defaults to `paragraph`, and its server publishes a streaming message only once the text contains a completed paragraph (a blank line, a list item, a heading, or a closing fence). As a result:

- A long single paragraph appears only when it is finished, or when the turn ends.
- An answer made of several paragraphs appears paragraph by paragraph while the model is still running.

This is T3's rendering rule, not a Selesai delay. A counter pass on that build showed live text rendering as soon as the text crossed a paragraph boundary.

**Stop generation** sends `session/cancel`. Selesai aborts the child's run and answers the ACP prompt with `cancelled`. In the real-T3 runs the ACP cancellation response was sent 57–109 ms after the click, T3 left the running state after 295–333 ms, and the next prompt in the same thread worked. After an interrupted turn T3 shows **Resume thread** instead of the normal submit button.

## Models and thinking

- The model picker lists Selesai's available models with provider-qualified labels, such as `GPT-6.1 Sol (openai)`, so the same model from different providers is distinguishable. Selection values remain `provider/model-id`. It sends `session/set_config_option` when you choose one. The next turn runs on the new model, and T3 receives the full option list again.
- The choice applies to **that Selesai session** and is recorded in its transcript. It does not rewrite Selesai's saved default model (`defaultModel`/`defaultProvider` in settings). A new T3 thread starts with the model from the instance's `--provider`/`--model` arguments, or with Selesai's configured default when those are omitted. (`src/modes/acp/adapter.ts` sends RPC `set_model`; `src/core/agent-session.ts` persists a default only when a caller asks it to, which only the TUI does.)
- **The thinking-level picker is not usable in T3 v0.1.6.** Selesai advertises the model's thinking levels (for the test run's reasoning model: `off`, `minimal`, `low`, `medium`, `high`, with the current value), but T3's UI drops those descriptors. With a reasoning default model T3 rendered no thinking control at all; with a non-reasoning default the only choice was `Off`. T3 never sent a `thinking_level` change, so Selesai's own configured default level applies. This is documented, not worked around.
- Configuration changes are refused while a prompt is running. T3 only changes them between turns.

## Plan and build mode

Selesai advertises two session modes, `build` (default) and `plan`. T3's composer shows a Plan toggle once you enable plan mode in T3's settings; switching it sends `session/set_mode`.

- **Plan mode is enforced inside the Selesai child.** It declares only the readers (`read`, `grep`, `find`, `ls`), `todo_write`, and MCP tools whose server marks them read-only, and it blocks every other tool call — `write`, `edit`, `bash`, PowerShell, unproven MCP tools, and custom or extension tools. A blocked call returns a "Plan mode is read-only" result to the model. In the real-T3 run a write was blocked with **no** permission request.
- **Build mode** restores the tool set the session started with. Tools run without approval by default; enable the approval flow below with `SELESAI_ACP_REQUIRE_APPROVAL=1`.
- Plan mode follows T3's interaction mode. T3 re-applies it after a session starts or loads, and every new Selesai child starts in build mode.

## Approvals

**By default Selesai does not ask for approval**, just like the TUI: build-mode tools run without a prompt, whatever T3's runtime mode is. Plan mode still blocks mutating tools. Everything below applies only if you opt in by setting the environment variable `SELESAI_ACP_REQUIRE_APPROVAL=1` for the ACP agent.

With approvals enabled, Selesai asks T3 before running any tool that plan mode does not allow: file writes and edits, shell commands, MCP tools that are not marked read-only, and custom or extension tools. The read-only tools listed above run without asking.

T3's runtime mode answers the request. The policy applies per turn, from the turn after you change it:

| T3 runtime mode | Edits (file writes) | Execute (commands) |
| --- | --- | --- |
| Supervised | asks | asks |
| Auto-accept edits | allowed, no prompt | asks |
| Full access | Selesai still sends the permission request; T3 answers `allow_once` with no UI | same |

Supervised, Auto-accept edits and Full access were exercised with a file write. The Execute column comes from T3's own policy code (`clientPolicy.ts`) and was **not** exercised with an execute tool by a Selesai run. The execute approval path itself is exercised offline through the real child: the request carries the same bounded command text, and a denied or cancelled answer runs nothing.

- In Supervised, the approval shows **Approve** and **Decline**; **Cancel** is under **More approval options**. Approve runs the tool. Decline and Cancel do not, and the model receives a "Tool approval was not granted" result.
- **Always allow this tool this session** (`allow_always`) is remembered for that tool name in the running Selesai child. Later calls to that tool do not ask again; other tools still need approval. **Allow once** is never remembered. Plan mode still blocks mutating tools even when granted. Grants are not saved to disk: restart the agent session to clear them (including before switching back to stricter supervision).
- For no approval UI across tools, select T3's **Full access** runtime mode; Selesai still sends ungranted calls through T3's policy. This permits commands and writes with your local user's access, not sandboxed execution.
- Approvals fail closed. Decline, Cancel, Stop, a closed connection, a child exit, a timeout, a handler error, or an option that was not offered all leave the tool unrun.
- The approval channel is ACP-only and cannot be forged by an ordinary extension: the child checks the caller against the shipped fixed extension, and the channel is disabled outside ACP sessions. TUI, print and plain RPC behavior is unchanged.

## Extension dialogs

When an extension asks a question during a turn, T3 shows it in its **Input requested** panel. Selesai maps the dialog methods T3 can represent:

- `select` becomes a single-choice list.
- `confirm` becomes true/false.
- `input` and `editor` become a text answer.

All four forms were answered in the real-T3 runs.

- A `confirm` answer must be `true` or `false`. Free text is refused and treated as a cancel, and the extension sees the cancellation.
- Dialogs T3 cannot represent (`multiselect`, secret inputs, empty forms) and answers that do not match the offered schema are cancelled, so the extension never hangs.
- Extension dialogs are only available during a turn. A dialog raised while no turn is active is cancelled.
- A slash command's own handler runs before its prompt is acknowledged, so a dialog it raises has no started turn and is cancelled too. A command that needs an answer must report a fixed instruction instead of waiting for one.
- There is **no dismiss button** for ACP questions in T3 v0.1.6; its ACP questions are live requests without a dismiss capability. To abandon a pending question, press **Stop**: Selesai cancels the dialog and the turn ends.

## Extension output and commands

Extension **tools** work like any other tool. Extension **slash commands** are advertised with the other commands, and a command's own feedback has one bounded channel instead of a free-form one.

- A command that publishes `pi.sendMessage({ display: true, content: <text> })` reaches T3 as transcript text labelled `Extension output:`, once per completed message, in the order it was produced. Hidden messages (`display` absent or `false`), `details`, images and resources are never forwarded, and ANSI/control sequences are removed.
- One message is bounded to 8 KiB including the label and a visible truncation marker. A prompt may publish 32 KiB of this text; after that one fixed request-limit marker is sent. Live delivery and transcript replay share the 8 KiB per-message projection; replay has separate transcript-wide caps, not the live per-request budget.
- Output is tied to the prompt that produced it. A handled command's completed output is published before that prompt returns `end_turn`, with no model request. Buffered output from a rejected, cancelled or disconnected prompt is discarded; cancellation does not retract text already delivered. Text stamped with another request's id is dropped; uncorrelated text from an older child keeps its previous behavior and is accepted only once a turn has started.
- A command that calls `pi.sendUserMessage` or `sendMessage(message, { triggerTurn: true })` starts a model run, and its answer arrives as ordinary assistant text. A pure command ends with `end_turn` and no model request.
- Notifications keep the privacy rule above: only the fixed severity title is sent. A command that reports through `ctx.ui.notify` therefore has no visible result, even though its handler ran.
- Commands marked `interactiveOnly` (currently `/zentui` and `/tool-display`) are left out of the advertised command list. Typing one anyway returns one fixed line — `/<name> is available only in the terminal UI. Use an interactive session instead.` — before its handler runs, so it cannot mutate settings and no model request is made. The refusal covers every argument branch, including text-only ones.
- Clipboard commands such as `/cp` act on the machine that runs Selesai. They never touch the browser clipboard.
- Commands that replace the session (`/handoff-new`) or navigate its branch (`/undo`) change state the ACP session owns and are unsupported here. A changed session identity fails closed; branch-only mutation is not guaranteed to be detected or rolled back. Use the TUI for them.

The complete per-extension, per-command lookup, with its evidence levels, is in [Extension GUI compatibility](extensions-gui-compat.md).

## Thinking, plan and usage display

- Reasoning reaches T3 as `agent_thought_chunk` updates. The real-T3 run recorded them on the wire; how T3 renders a thinking row was not asserted separately.
- The todo list reaches T3 as a `session/update` plan frame whose entries are `{content, status, priority}`. T3 decides how to display it; the real-T3 run asserted the plan frame itself, not a particular plan widget.
- The usage/context meter comes from `usage_update` frames and appears as T3's **Context window N% used** button after you enable T3's context-window meter setting (it is off by default). The display run measured `Context window 18% used`.
- Each frame carries the child's own context estimate for the active window plus its cumulative session cost. Billed token totals and per-message streaming arithmetic are never substituted, and the sample is an incidental background read: a slow or failed sample cannot delay the turn result.
- A sample requested before a new turn, a compaction, a model change or a session failure is dropped when it arrives, so it cannot refill a meter that a completed compaction already cleared. After a compaction the child reports no estimate until the next assistant message carries usage; nothing is sent in between, and the following turn supplies the fresh value.
- In plan mode T3 also receives `current_mode_update`, so its mode toggle follows the session.

### Usage page: saved token totals and costs

The **Usage page is separate from the context meter**. In the inspected T3 build, it scans built-in providers' transcript directories; Local ACP instances are not Usage sources. Sending more ACP `usage_update` frames does not add historical usage there.

Selesai already saves Pi-compatible token and cost records. Reuse T3's existing Pi transcript reader:

1. Keep your **Selesai Local ACP** instance for chats. Add a separate **Pi** provider instance under **Settings → Providers**, named **Selesai usage**. No Pi installation or chat with that instance is required for transcript scanning; its executable-health warning does not gate the Usage reader.
2. On the **Pi instance**, add an Environment row with name `PI_CODING_AGENT_SESSION_DIR` and the **absolute path on the T3 server** to your Selesai sessions root. The default is `~/.selesai/agent/sessions` (expand `~` to your home directory). With a custom agent directory, use its `sessions` subdirectory.
3. If `--session-dir`, `SELESAI_CODING_AGENT_SESSION_DIR`, or Selesai's `sessionDir` setting overrides storage, point the reader at that directory instead. Relative overrides are resolved against the thread's project directory, not the terminal where setup is printed. Add a Pi instance for each distinct root you want included.
4. Refresh **Usage**, choose a date range covering your sessions, and include **Pi** in the provider filter.

`selesai-acp --print-t3-config` now prints this setup alongside the ACP command rows. Run it with the same flags and environment as the ACP instance; check any settings-based storage override separately. It only prints instructions—it does not modify T3 settings.

**Limits:** T3 labels these records **Pi**, even when the provider instance is named Selesai usage. The root includes saved Selesai CLI and ACP sessions, not just T3 chats. Existing history can be scanned without replaying prompts, exporting duplicate files, or changing the active chat provider. A dedicated **Selesai** label/source requires a change in T3 itself. This path was checked against the installed reader implementation, not through a browser UI run.

## Tool cards

Tool calls reach the client as ordinary ACP tool cards. Extra metadata is bounded, and nothing outside the fields below is forwarded.

- **Shell commands** (`bash`, `shell`, `powershell`) are `execute` cards. The card carries the command as display text (`rawInput.command`), at most 8 KiB of UTF-8, with control characters and line breaks collapsed to one line. The command is display-only: nothing is re-read from ACP to decide what runs.
- A streamed card can open before the child knows the arguments; the command is attached when the call actually starts.
- The final update carries the command's output and exit status (`rawOutput.output`, `rawOutput.exitCode`). Output keeps its line breaks and is bounded to 8 KiB; the exit status is the shell result's own `exit_code` and is omitted when the child reports none. No truncation paths, timing fields, images, resource links or arbitrary structured content are copied.
- Read, edit and write keep their existing text, diff and file-location projection, and repeated identical snapshots are still deduplicated.

## Slash commands

Selesai advertises its prompt templates, commands and skill commands when a session starts or resumes. T3 keeps that list for the provider instance; type `/` in the composer to open the menu. Skills are advertised as `$name`, so they appear in T3's `$` menu; T3 sends a picked skill as plain `$name` text and Selesai rewrites a leading `$name` of a known skill to `/skill:name`. In the real-T3 run an advertised command ran with its argument.

The list is sent once per session start because Selesai has no change event for it. New or edited prompt templates, commands or skills appear in a new session or on resume, not in a running one.

**`/compact`** is the one built-in Selesai advertises as runnable from prompt text; it is listed as `compact` without the interactive-only marker. Sending `/compact` or `/compact <instructions>` runs manual compaction: the optional text becomes the summary focus, and the ACP prompt's `end_turn` response is sent only after compaction finishes. No assistant summary message is added to the transcript; the summary is stored as a compaction entry and the next turn continues from it. A failed compaction rejects that prompt with Selesai's generic rejected-prompt error; the detailed compaction reason is not copied into the ACP error, and the same session accepts the next prompt. Cancelling while the summary request is still pending answers `cancelled`, records no incomplete summary and leaves the session usable; cancellation is not a rollback, so a summary that already completed and persisted before the cancel arrived remains. Terminal-only built-ins (`/settings`, `/model`, `/tree`, `/quit` and the rest) keep `interactiveOnly: true` and are filtered out of the ACP command list, so they do not appear as dead entries. Extension commands can carry the same marker: `/zentui` and `/tool-display` do, so they are absent from the list and refused with one fixed line when typed. This was exercised against the built ACP server with a real RPC child and a local loopback model, not a fresh T3 UI run (see [Checks that were not run](#checks-that-were-not-run)).

**Compaction card.** Compaction is projected as an ordinary tool call titled **Compact context** (`acp-compaction:<session>:<n>`, `kind: think`): `in_progress`, then `completed` with the stored summary (at most 8 KiB). An announced card closes as `failed` on cancellation or failure, with fixed text rather than summarizer/provider errors. A completed result without usable summary text gets the fixed fallback “Context compacted.” The inspected T3 v0.1.7 connection does not advertise `session.compaction`, so Selesai uses no dedicated `compaction_update` or `compaction_summary_chunk`; other hosts receive an ordinary tool card.

Manual `/compact` acknowledges only after compaction finishes. Selesai buffers its start/end events (compaction only, under the existing event ceiling) and replays them after acceptance. Rejected preflights, including cancellation that rejects the preflight, discard those events without opening a card. Finalization resets any open card and attempts to deliver its terminal frame before the result. Rejection waits at most one second for delivery, logging possible nondelivery on timeout; a closing connection resets state without promising delivery. This is an adapter-side bound: a stalled ordered writable can also delay the response reaching the client. No retry or post-result attachment is relied upon. A settled turn still drains queued updates without a timeout, so a client that stops reading can hold the bridge there.

Project prompt templates such as `.selesai/prompts/deploy.md` appear as `/deploy` only when the session runs in that project and the folder is trusted. T3 caches the command list per provider instance, not per project, so a session in another project can replace the menu. If a project command is missing, restart that project's agent session and send a normal message to reconnect before checking `/` again; refreshing provider status alone may use a probe outside the project. A known template can still be typed directly, but sending it executes its instructions—do not submit a release or deployment prompt just to test discovery.

**File changes.** File locations are resolved against the session's project directory, including relative tool paths. A completed `edit` call sends the tool's exact unified patch as ACP v1 text content, which T3 uses for its inline diff. This preserves hunk line numbers and gaps without pretending hunk excerpts are whole-file contents; v2-only `patch`/`changes` fields are not used. A `write` call carries a v1 diff with the new file text and T3 shows it as an added file; an overwrite is not told apart from a new file. Failed calls and other tools carry no diff, and a patch larger than 64 KiB is left out (T3's own change summary still lists the file). Implemented in `src/modes/acp/mapping.ts` (`diffContent`).

## Images

Attach images in T3 as usual. Selesai accepts `image/png`, `image/jpeg`, `image/gif` and `image/webp`, up to 4 MiB per image and 8 MiB per prompt after decoding; anything larger or another type is rejected as an invalid prompt (`src/modes/acp/mapping.ts`). A png attachment was verified end to end.

An image-only message reaches Selesai as T3's placeholder text plus the image, so the model sees the image with a short "user attached …" text. That is expected.

## History: reopen, list and import

**Reopening** a thread that T3 created sends `session/load`. Selesai replays that session's transcript (user, assistant and thought chunks, in order, on the active branch) before it answers the load, and the thread then continues with its Selesai context and its model and mode restored.

Replay is display-only and bounded: at most 512 KiB of text, 128 KiB per message and 512 updates; a longer transcript keeps its newest messages. Tool calls, plans and mode changes are not replayed; explicitly displayed extension output on the active branch is, under the same caps and the per-message 8 KiB projection (`src/modes/acp/history.ts`).

**List and import:** open **Settings → the Selesai provider card → Native sessions → List sessions**. Choose the project in **Project for ACP sessions**; the list is limited to that project's directory and to 256 rows. **Import** creates a T3 thread bound to the chosen Selesai session.

- If the **Native sessions** section is missing, click **Refresh provider status** on the card. T3 does not re-probe the provider by itself after an interrupted status check, so the section stays hidden until a refresh completes.
- In the tested run the imported thread did not appear in the sidebar and T3 showed no link to it; it was reachable only at its own thread URL.
- T3 does not render the imported session's earlier turns, before or after the thread's first turn, so the imported thread starts visually empty. Its first turn still runs `session/load`, restores the Selesai context, and continues the conversation. Rendering imported history is best-effort in T3 v0.1.6.

A missing or mismatched session fails instead of creating a new transcript. If T3 reports `Resource not found` for a deleted Selesai session, run **Restart agent session** from the command palette and send the next prompt; on that fresh provider session T3 falls back to a new Selesai session.

## Fork

Selesai does not advertise ACP fork. **Fork from this response** in T3 still works: T3 uses its portable context handoff, which opens a new Selesai session and sends the earlier turns as handoff context together with your new message. The original session is untouched.

## MCP tools

T3 passes its own MCP servers to the Selesai session as stdio servers (command, arguments, environment), so T3's tools appear to the model as `mcp__<server>__<tool>` and run inside Selesai's child. In plan mode only MCP tools that the server marks read-only stay available. The real-T3 run saw T3's `t3-code` server advertised and the model called one of its tools.

Selesai does not advertise ACP MCP support, so T3 uses its stdio path. HTTP or SSE MCP entries are rejected as an invalid configuration.

## Stop, disconnects and failures

- **Stop** is described under [Streaming and Stop](#streaming-and-stop).
- **Model error:** T3 shows a sanitized failure reason and the next turn works. Recognized rate limits, usage/billing limits and context limits receive specific messages instead of a generic mapping error. Unknown provider errors stay generic; raw provider bodies are never forwarded. A retry that succeeds does not fail the turn; its lifecycle is reported only through the notices below.
- **Child failure:** if the Selesai process exits, T3 shows a safe error instead of a partial answer, and no Selesai process is left behind.
- **Disconnect, or T3 stops the agent session:** the ACP connection closes, Selesai releases the session atomically, refuses new commands, cancels pending dialogs and approvals (denying the tool), and stops the child it owns. Background work is not continued for a closed connection; if Selesai's atomic release is refused by a blocker, it logs that background work may be lost on disconnect.
- T3 restarts the provider session in some situations. A transport restart inside the same provider session surfaces as an error until the provider session itself is restarted.

### Retry and extension notices

Retry lifecycle and extension notifications are sent as ACP `notice` frames — short system events, not conversation text. They are sent only when the client advertised `session.notices` during initialize; T3 v0.1.7 always does. A client that does not advertise it receives silence, never a substitute assistant message.

- A retry announces that the model request is being retried, with the attempt count and, when known, the delay; a retry that succeeds announces that. Wording is fixed and only validated attempt and delay numbers are inserted.
- An extension notification becomes a fixed title for its severity (`info`, `warning` or `error`). The extension's own message is not forwarded: it can hold secrets, provider errors or private paths.
- An exhausted retry produces no notice. The sanitized terminal error stays the only report of a failed turn, so a failure is never shown twice.
- Notices are dropped with no active turn, after the turn settled, after cancellation, or without the capability. Unsupported status/widget/chrome methods and deferred `extension_error` events are not emulated.

## Trust model

- The ACP server is a **local process** that T3 starts on the server machine with the permissions of the user running T3. It is not a sandbox: the Selesai child runs with that user's credentials, filesystem access and shell. See [Security](security.md).
- Build-mode tools run without approval by default, regardless of T3's runtime mode. With `SELESAI_ACP_REQUIRE_APPROVAL=1`, gated tools wait for T3's answer unless the user explicitly granted that tool for the running session with `allow_always`. Every non-allow outcome denies the requested call.
- When approvals are enabled, T3's runtime mode answers new approval requests. Full access answers automatically with no UI. Existing per-tool session grants skip new requests; restart the agent session to revoke them. Changing T3's runtime mode alone does not revoke a grant in Selesai.
- The approval channel cannot be raised by another extension, and it exists only in ACP sessions.
- Plan mode is a guardrail for the child's tools, not a sandbox; reads stay allowed.

## Compared with the native Pi adapter

T3's built-in Pi provider drives a Pi process it launches itself. Selesai is not that provider and is not upstream Pi; the connection stays `T3 → ACP → selesai-acp → Selesai RPC child`, so only behavior representable in ACP v1 can be translated. Emitting a frame the inspected host source reads is not the same as rendering it, and neither is the same as native behavior.

Translated and verified offline through the built bridge, the real RPC child and a loopback model:

| Native Pi behavior | What Selesai sends |
| --- | --- |
| shell command text, output and exit code | `execute` card with bounded `rawInput.command` and final `rawOutput.{output, exitCode}` |
| compaction timeline item | ordinary `acp-compaction:` tool call with a bounded summary and fixed failure text |
| context usage and session cost | authoritative `contextUsage` estimate plus cumulative cost after each assistant message |
| retry lifecycle and extension notifications | capability-gated `notice` frames with fixed wording |
| extension command feedback | labelled extension text when the extension marks a message `display: true`, bounded to 8 KiB per message |

Not translated in this bridge (host-only, deliberately deferred, or unsupported by the negotiated contract):

- Dedicated ACP compaction frames: the inspected T3 v0.1.7 connection does not advertise `session.compaction`; an agent must not send them unnegotiated.
- Images, arbitrary structured tool results, resource links, audio, and parent/nested tool links inside tool cards.
- Streaming per-token context estimates. Native Pi derives live tokens from its own stream; Selesai reports the child's authoritative estimate and makes no live-token parity claim.
- Native provider fork/rollback semantics, active steering, background-work adoption, subagent UI, additional MCP transports and a dedicated historical Usage source. These stay host-side or separately scoped; the existing portable handoff and Pi-reader workaround remain available. See [Fork](#fork), [MCP tools](#mcp-tools) and [Usage page](#usage-page-saved-token-totals-and-costs).
- Extension components and TUI chrome (settings modals, status widgets, footers, overlays), multiselect and secret input forms, and commands that replace the session or navigate its branch. Commands that report only through notifications also stay invisible, because notify bodies are not forwarded.

## Checks that were not run

- Thinking-level selection in T3: the picker is unusable in v0.1.6, so a chosen level's effect on the next turn was not measured.
- Dialog dismiss in T3: no dismiss control exists; only Stop was tested.
- How T3 renders ACP thinking rows and the plan list: the frames were asserted, the widgets were not.
- Rendered imported history in T3: not rendered; recorded as best-effort.
- The Execute column of the approval matrix: from T3's policy code, not exercised with an execute tool.
- Disconnect as a dedicated real-T3 scenario; the code path and the ACP tests cover it.
- A fresh T3 v0.1.6 UI run of `/compact` (the **Compact context** control). The command is verified over the built ACP server with a real RPC child and a loopback model, not in a browser.
- A rendered diff in T3 for `edit` and `write` calls: payloads are checked through an ACP v1 client and against the installed T3 projection code, not drawn in a fresh T3 UI run.
- The compaction card, shell execute rows, context-meter clear and notices in a T3 window: the frames and values are proven offline, and the installed v0.1.7 consumer source reads them, but no host run was made for those rows.
- Extension output in a T3 window: the labelled text and its bounds are proven over the built bridge, and the displayed reports of bundled commands other than Ponytail were not re-run end to end.
- T3 v0.1.7 as an app: only its ACP consumer source was inspected, so its pickers, approvals, history and notice rendering are unmeasured there.
- Interactive T3 behavior on v0.1.7, and any T3 version other than v0.1.6 for the rows exercised in that app.

## See also

- [CLI Integration](cli-integration.md) for Selesai's own modes.
- [RPC Mode](rpc.md) for the protocol `selesai-acp` uses to drive its child.
- [Extension GUI compatibility](extensions-gui-compat.md) for the per-extension, per-command lookup.
- [T3 Code provider setup](https://github.com/pingdotgg/t3code) — T3's own repository.
