| 🛟 Updated. 28d5985 k33g 13h ago | 1 | # Handoff — 2026-09-15 — ACP agent windows, and the jsonrpc extraction |
| 2 | |
| 3 | ## State |
| 4 | |
| 5 | **Done and working end to end.** Branch `feature/acp` in **both** turbo-core and turbo-go, created locally, **not pushed and not committed**. |
| 6 | |
| 7 | The feature was driven against the user's own stack — `docker agent` v1.139.0 talking to a llama.cpp serving JetBrains Mellum2 — by running the real `turbo-go` binary in a pty. What was seen: `Alt-A` opens a menu listing the agent from `.turbo-go/acp.toml`; the window opens and its rule says `Enter sends` once the handshake finishes; a prompt gets a streamed reply; asking for a shell command raises the permission dialog with the agent's own three options; answering it runs the tool and folds its output onto the tool line; and a ```go fence comes back coloured span by span (keywords, identifiers, strings and punctuation each their own colour, read off the wire). |
| 8 | |
| 9 | Both suites green. Both quality gates PASS. Docs EN+FR done. `.memory/` updated in both repositories. |
| 10 | |
| 11 | ## In flight |
| 12 | |
| 13 | Nothing. |
| 14 | |
| 15 | ## Next steps |
| 16 | |
| 17 | 1. **Read the diff.** It is large: two new packages in turbo-core, a refactor of `lsp`, and the `app` wiring. |
| 18 | 2. **Use it with your hands.** Everything so far was driven from a script. See "never touched by a human" below. |
| 19 | 3. **Commit and publish, in this order** — turbo-core first, because turbo-go cannot build against an unreleased library from a clean clone: |
| 20 | - turbo-core: commit, tag, release (`01-release.tag.sh`, `02-release.publish.sh`) |
| 21 | - turbo-go: `go get codeberg.org/turbo-editors/turbo-core@vX.Y.Z`, then **`GOWORK=off make check`** — the proof it builds without the workspace |
| 22 | - turbo-go: commit, tag, release |
| 23 | 4. **Roll out to the other editors.** Each needs exactly one thing: an `acp.toml.tmpl` beside its other starter files, and `Agents:` in its `profile.Templates`. Nothing else. `turbo-rust`, `turbo-python`, `turbo-moonbit`, `turbo-golo`, `turbo-js`, `turbo-spectrum`. |
| 24 | |
| 25 | ## Watch out for |
| 26 | |
| 27 | - **Every write to an agent must leave the caller's goroutine.** `Session.Cancel`, `Permission.Answer` and `Permission.Cancel` all write on a goroutine of their own. Writing blocks until the agent reads, and an agent that has stopped reading would otherwise freeze the editor on the very keystroke meant to get away from it. This was found by a **test hanging**, not by reasoning — twice, in the same session. If you add another call made from the event loop, do the same. |
| 28 | - **`net.Pipe` is synchronous, and that is what makes these tests honest.** A write blocks until somebody reads. Any test helper that reads on demand deadlocks against a reply made from the test goroutine; the peer in `jsonrpc_test.go` and `fakeagent_test.go` reads continuously into a buffered channel for exactly this reason. Do not "simplify" it back. |
| 29 | - **`t.Fatalf` outside the test goroutine hangs the run instead of failing it.** Cost two debugging rounds here. The peers return errors as values. |
| 30 | - **`Painter.Sub` takes absolute coordinates; every drawing call takes local ones.** The first version of `view_draw.go` drew at absolute coordinates and the input box simply did not appear, with no error anywhere. The memory warned about this and it still happened — check it against `filetree/view_draw.go` when adding any widget. |
| 31 | - **A ui.Result is an outcome, not a button index.** The permission dialog has one button per option the agent offered, so which one was pressed is recorded into a captured pointer. `ResultOK` cannot say which of four. |
| 32 | - **Never back a file up with `cp` here.** The corruption below destroyed `acp/view_events.go` *and* the backup taken seconds earlier with `cp`, in one command; the file had to be rewritten from scratch. Use `cat a > b`. |
| 33 | - **The sandbox's filesystem corrupts `cp` for some inodes.** A file reads correctly through `cat`, `git`, `gofmt` and `go build`, and comes out of `cp` as the right number of **NUL bytes**. It is sticky to the inode; recreating the file at a fresh one fixes it, content unchanged. `release_test.go` copies the module with `cp -r`, so it fails spuriously after new files are written. **It is not a code defect and git stores the right bytes** — verified by comparing the on-disk SHA against the blob's. If `release_test.go` fails with `unexpected NUL in input`, this is why. |
| 34 | - **The permission request arrives *before* the `tool_call` update.** `Transcript.addToolCall` therefore has to tolerate an id it has already seen. Recorded from the real agent; the specification does not say so. |
| 35 | - **`content` is two shapes under one name** — an object on a message chunk, an array on a tool call. It is kept as `json.RawMessage` on purpose. Decoding it eagerly breaks only once an agent uses a tool, which is late. |
| 36 | |
| 37 | ## Never touched by a human |
| 38 | |
| 39 | Everything below was verified by driving the binary through a pty from a script, which is not the same as using it: |
| 40 | |
| 41 | - the mouse anywhere in an agent window — the wheel, clicking between the panes |
| 42 | - `Tab` between the transcript and the box on a real keyboard |
| 43 | - resizing the window, or the terminal, while a turn is running |
| 44 | - two agent windows side by side under `Window ▸ Tile` |
| 45 | - `fs/read_text_file` / `fs/write_text_file` from a **real** agent — `docker agent` never calls them, because its filesystem toolset is server-side |
| 46 | - a conversation long enough to matter: the transcript is re-wrapped every frame and nothing has been profiled |
| 47 | |
| 48 | ## Open questions |
| 49 | |
| 50 | - **Should the input box keep a history?** Up-arrow on an empty box currently scrolls the conversation. Nobody has asked for one yet. |
| 51 | - **Is `agent.*` in the themes worth it?** The decision was to reuse the syntax styles, which costs a theme the ability to make thoughts quiet without making comments quiet. Only use will say. |