turbo-editors/turbo-jspublic Fork 0
v1.0.0
Commits
Clone
git clone https://git.rickub.com/turbo-editors/turbo-js.git
git clone ssh://git@rickub.com/turbo-editors/turbo-js.git

Host key fingerprint (ed25519): SHA256:iycHnxEyq0Q7uyVpB7JlznP0G7JrTPXLYRcAU5CSLhc — verify it before your first connect.

📦 Turbo JS 91999d1 · on v1.0.0 · k33g · 11h ago
talk-to-an-agent.md · 177 lines · 10.9 KBmarkdown
Blame HistoryOpen raw

How to talk to a coding agent from the editor

This guide shows how to point Turbo JS at an agent that speaks the Agent Client Protocol, open a window onto it, and hold a conversation about the code you are editing. It assumes you already have Turbo JS running in a project.

Turbo JS is an ACP client. It starts the agent as a child process and talks JSON-RPC to it over its standard input and output — the same arrangement Zed uses, so an agent that works there works here.

Tell the editor about an agent

Agents are listed in acp.toml. Choose Agent ▸ Create agents file and the editor writes a starter one into .turbo-js/acp.toml and opens it.

An agent is one [[agent]] block:

[[agent]]
name    = "Bob (llama.cpp)"
command = "docker"
args    = ["agent", "serve", "acp", ".turbo-js/agent.yaml"]
env     = { TELEMETRY_ENABLED = "false" }

name is what the Agent menu shows and what the window is called. command and args are how the agent is started. That is the whole of it — the file is read again every time you open a window, so you never restart the editor to try a change.

List as many as you like. Each becomes its own line in the menu, and each window you open from it is a separate process with a conversation of its own.

Put the agent's own configuration beside it

Most agents have a configuration file of their own, and .turbo-js/ is a reasonable place to keep it so that it travels with the project. For docker agent, saving this as .turbo-js/agent.yaml is what the args above point at:

providers:
  llamacpp:
    api_type: openai_chatcompletions
    base_url: http://localhost:8080/v1

models:
  mellum2:
    provider: llamacpp
    model: JetBrains/Mellum2-12B-A2.5B-Instruct-GGUF-Q4_K_M:Q4_K_M
    temperature: 0.7
    provider_opts:
      context_size: 262144

agents:
  root:
    model: mellum2
    description: A helpful AI assistant running on a local llama.cpp server
    instruction: |
      You name is Bob 🤓, you are a knowledgeable code assistant.
      Be helpful, accurate, and concise in your responses.
      You have access to the local filesystem and shell: use these tools
    toolsets:
      - type: filesystem
      - type: shell

Open a window on it

Press Alt-A, or choose Agent from the menu bar, and pick the agent by name.

A window opens, split in two: the conversation above, and a box to type in below. The agent is started when the window opens and stopped when it closes.

┌ Bob (llama.cpp) ───────────────────────────────[■]┐
│ ‣ You                                             │
│   What does buildMenus do?                        │
│                                                   │
│ ‣ Shell  ls -1 internal/                 ✓ done   │
│   golang                                          │
│                                                   │
│ ‣ Bob                                             │
│   It assembles the menu bar. Here is the shape:    │
│                                                   │
│   ```javascript                                   │
│   buildMenus() {                                  │
│     return new MenuBar(...this.allMenus());       │
│   }                                               │
│   ```                                             │
├───────────────────────────────────────────────────┤
│ > _                                               │
└───────────────────────────────────────────────────┘

Code the agent sends inside a fenced block is coloured by the same scanners the editor uses for files, so a JavaScript answer is coloured as JavaScript and a shell answer as shell. A fence naming a language the editor does not colour is left plain rather than guessed at.

Hold the conversation

Key Effect
Enter Send what you have typed
Alt-Enter Start a new line instead of sending
Tab Move between the conversation and the input box
PgUp PgDn Scroll the conversation a screenful at a time
Esc Stop the turn in progress
Ctrl-W Close the window, and stop the agent with it

While the agent is answering, its reply appears as it is written rather than all at once, and the rule between the two panes turns a spinner beside the word thinking. Esc interrupts it — the agent is told to stop, and what it had already said stays in the window.

Use the agent's own commands

Some agents answer to commands — /compact, /web, /plan — and tell the editor which ones. Type / as the first character of the box and the list opens over the conversation: each command, what it does, and in angle brackets what it wants after its name.

Type on to narrow it, to move, then Tab to complete. A command that takes something is completed with a space after it, ready for you to type the rest; press Enter when the line is what you mean. If nothing appears when you type /, the agent announced no commands — Agent ▸ Agent status says so — and / is only a character.

Point the agent at a file

Type @ anywhere in the box and the project's files appear. Type a few letters of the file's name to narrow the list, Tab to take the highlighted one:

> explain what @internal/scanner.go does

When you press Enter, the agent is given the file, not merely its name: its text when the agent accepts embedded context, a link to it otherwise. If the file is open in the editor with unsaved changes, it is your unsaved version that goes. The line stays in the conversation as you typed it.

Several files in one prompt is several @. A word that begins with @ but is not a file — an e-mail address — is left as text.

Take something out of the conversation

Press Tab to put the cursor in the conversation. The rule changes to say what the keys now do.

Key Effect
PgUp PgDn Move the cursor through what was said
Shift-↑ Shift-↓ Select whole lines
Drag with the mouse The same, by hand
Ctrl-C Copy
Esc Drop the selection
Tab Back to the box

With nothing selected, Ctrl-C copies the block the cursor is on — one fenced code block, one paragraph, one tool's output — without the speaker's label above it and without the sentence after it. That is almost always what you wanted, and it saves selecting it by hand.

What is copied goes to two clipboards: this editor's, so Shift-Ins pastes it into a file you have open, and your system's, so Ctrl-V pastes it anywhere else. The indentation the conversation is drawn with is taken off, so pasted code lands flush against the margin.

The system half travels through your terminal (an escape sequence called OSC 52). Most terminals do it; a few refuse it for security, and some need it turned on. If Ctrl-V elsewhere gives you nothing, that is where to look — the editor's own clipboard has the text either way.

Answer the agent when it asks permission

An agent with a shell or a filesystem toolset asks before it uses one. A dialog names the tool and the exact command, and offers the choices the agent itself proposed — normally Allow this action, Allow and remember my choice, and Skip this action.

┌───────────── Bob (llama.cpp) wants to run ─────────────┐
│                                                        │
│  Shell                                                 │
│    ls -1                                               │
│                                                        │
│    [ Allow ]  [ Allow always ]  [ Skip ]               │
└────────────────────────────────────────────────────────┘

Allow always is remembered by the agent, not by the editor, so what it covers and how long it lasts are the agent's business. Escape is the same answer as Skip.

Nothing runs before you answer. An agent waiting on a permission dialog is simply blocked, which is the point.

Let the agent see what you have not saved yet

The editor offers the agent its own filesystem: when the agent reads a file you have open with unsaved changes, it is given the text in the buffer, not the older text on disk. That is usually what you want — you are asking about the edit you just made.

When the agent writes a file, the change lands in the buffer and the window is marked modified, so you can read it, undo it with Ctrl-Z, or save it with F2. A file you do not have open is read from and written to disk directly.

Run several agents at once

Each window is its own process and its own conversation. Opening the same agent twice gives two independent sessions, and opening two different agents lets you put a fast local model and a slower careful one side by side — Window ▸ Tile arranges them.

Leaving the editor stops every agent.

Variants

  • You want the agent to run somewhere other than the project root. Add cwd = "backend" to its block. The path is relative to the project, and is both where the process starts and what the agent is told the working directory is.
  • The agent needs a credential. Put it in env, or rely on it being in the environment you started the editor from — the agent inherits it.
  • The agent's commands do not appear when you type /. Open Agent ▸ Agent status with the window in front. If it lists no commands, the agent announced none — or announced them in a shape this editor could not read, in which case the dialog names the update and the decoding error. To see exactly what went over the wire, start the editor with TURBO_ACP_TRACE=/tmp/acp.log and read the file: -> is what the editor sent, <- what the agent answered.
  • The agent will not start. Agent ▸ Agent status lists what was read from acp.toml, what each agent's command line came out as, and the error from anything that failed to start. Whatever the agent writes to its standard error is shown there too, which is where a misconfigured model endpoint reports itself.
  • You keep the same agent in every project. Put the [[agent]] block in ~/.config/turbo-js/acp.toml instead. A project's own file is read afterwards and an agent with the same name in it replaces yours.

See also

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
# How to talk to a coding agent from the editor

This guide shows how to point Turbo JS at an agent that speaks the [Agent Client Protocol](https://agentclientprotocol.com), open a window onto it, and hold a conversation about the code you are editing. It assumes you already have Turbo JS running in a project.

Turbo JS is an ACP **client**. It starts the agent as a child process and talks JSON-RPC to it over its standard input and output — the same arrangement Zed uses, so an agent that works there works here.

## Tell the editor about an agent

Agents are listed in `acp.toml`. Choose **Agent ▸ Create agents file** and the editor writes a starter one into `.turbo-js/acp.toml` and opens it.

An agent is one `[[agent]]` block:

```toml
[[agent]]
name    = "Bob (llama.cpp)"
command = "docker"
args    = ["agent", "serve", "acp", ".turbo-js/agent.yaml"]
env     = { TELEMETRY_ENABLED = "false" }
```

`name` is what the Agent menu shows and what the window is called. `command` and `args` are how the agent is started. That is the whole of it — the file is read again every time you open a window, so you never restart the editor to try a change.

List as many as you like. Each becomes its own line in the menu, and each window you open from it is a separate process with a conversation of its own.

## Put the agent's own configuration beside it

Most agents have a configuration file of their own, and `.turbo-js/` is a reasonable place to keep it so that it travels with the project. For `docker agent`, saving this as `.turbo-js/agent.yaml` is what the `args` above point at:

```yaml
providers:
  llamacpp:
    api_type: openai_chatcompletions
    base_url: http://localhost:8080/v1

models:
  mellum2:
    provider: llamacpp
    model: JetBrains/Mellum2-12B-A2.5B-Instruct-GGUF-Q4_K_M:Q4_K_M
    temperature: 0.7
    provider_opts:
      context_size: 262144

agents:
  root:
    model: mellum2
    description: A helpful AI assistant running on a local llama.cpp server
    instruction: |
      You name is Bob 🤓, you are a knowledgeable code assistant.
      Be helpful, accurate, and concise in your responses.
      You have access to the local filesystem and shell: use these tools
    toolsets:
      - type: filesystem
      - type: shell
```

## Open a window on it

Press `Alt-A`, or choose **Agent** from the menu bar, and pick the agent by name.

A window opens, split in two: the conversation above, and a box to type in below. The agent is started when the window opens and stopped when it closes.

```
┌ Bob (llama.cpp) ───────────────────────────────[■]┐
│ ‣ You                                             │
│   What does buildMenus do?                        │
│                                                   │
│ ‣ Shell  ls -1 internal/                 ✓ done   │
│   golang                                          │
│                                                   │
│ ‣ Bob                                             │
│   It assembles the menu bar. Here is the shape:    │
│                                                   │
│   ```javascript                                   │
│   buildMenus() {                                  │
│     return new MenuBar(...this.allMenus());       │
│   }                                               │
│   ```                                             │
├───────────────────────────────────────────────────┤
│ > _                                               │
└───────────────────────────────────────────────────┘
```

Code the agent sends inside a fenced block is coloured by the same scanners the editor uses for files, so a JavaScript answer is coloured as JavaScript and a shell answer as shell. A fence naming a language the editor does not colour is left plain rather than guessed at.

## Hold the conversation

| Key | Effect |
| --- | --- |
| `Enter` | Send what you have typed |
| `Alt-Enter` | Start a new line instead of sending |
| `Tab` | Move between the conversation and the input box |
| `PgUp` `PgDn` | Scroll the conversation a screenful at a time |
| `Esc` | Stop the turn in progress |
| `Ctrl-W` | Close the window, and stop the agent with it |

While the agent is answering, its reply appears as it is written rather than all at once, and the rule between the two panes turns a spinner beside the word *thinking*. `Esc` interrupts it — the agent is told to stop, and what it had already said stays in the window.

## Use the agent's own commands

Some agents answer to commands — `/compact`, `/web`, `/plan` — and tell the editor which ones. Type `/` as the first character of the box and the list opens over the conversation: each command, what it does, and in angle brackets what it wants after its name.

Type on to narrow it, `↑` `↓` to move, then `Tab` to complete. A command that takes something is completed with a space after it, ready for you to type the rest; press `Enter` when the line is what you mean. If nothing appears when you type `/`, the agent announced no commands — **Agent ▸ Agent status** says so — and `/` is only a character.

## Point the agent at a file

Type `@` anywhere in the box and the project's files appear. Type a few letters of the file's name to narrow the list, `Tab` to take the highlighted one:

```
> explain what @internal/scanner.go does
```

When you press `Enter`, the agent is given the **file**, not merely its name: its text when the agent accepts embedded context, a link to it otherwise. If the file is open in the editor with unsaved changes, it is your unsaved version that goes. The line stays in the conversation as you typed it.

Several files in one prompt is several `@`. A word that begins with `@` but is not a file — an e-mail address — is left as text.

## Take something out of the conversation

Press `Tab` to put the cursor in the conversation. The rule changes to say what the keys now do.

| Key | Effect |
| --- | --- |
| `↑` `↓` `PgUp` `PgDn` | Move the cursor through what was said |
| `Shift-↑` `Shift-↓` | Select whole lines |
| Drag with the mouse | The same, by hand |
| `Ctrl-C` | Copy |
| `Esc` | Drop the selection |
| `Tab` | Back to the box |

**With nothing selected, `Ctrl-C` copies the block the cursor is on** — one fenced code block, one paragraph, one tool's output — without the speaker's label above it and without the sentence after it. That is almost always what you wanted, and it saves selecting it by hand.

What is copied goes to **two** clipboards: this editor's, so `Shift-Ins` pastes it into a file you have open, and your system's, so `Ctrl-V` pastes it anywhere else. The indentation the conversation is drawn with is taken off, so pasted code lands flush against the margin.

The system half travels through your terminal (an escape sequence called OSC 52). Most terminals do it; a few refuse it for security, and some need it turned on. If `Ctrl-V` elsewhere gives you nothing, that is where to look — the editor's own clipboard has the text either way.

## Answer the agent when it asks permission

An agent with a shell or a filesystem toolset asks before it uses one. A dialog names the tool and the exact command, and offers the choices the agent itself proposed — normally *Allow this action*, *Allow and remember my choice*, and *Skip this action*.

```
┌───────────── Bob (llama.cpp) wants to run ─────────────┐
│                                                        │
│  Shell                                                 │
│    ls -1                                               │
│                                                        │
│    [ Allow ]  [ Allow always ]  [ Skip ]               │
└────────────────────────────────────────────────────────┘
```

*Allow always* is remembered by the agent, not by the editor, so what it covers and how long it lasts are the agent's business. Escape is the same answer as *Skip*.

Nothing runs before you answer. An agent waiting on a permission dialog is simply blocked, which is the point.

## Let the agent see what you have not saved yet

The editor offers the agent its own filesystem: when the agent reads a file you have open with unsaved changes, it is given **the text in the buffer**, not the older text on disk. That is usually what you want — you are asking about the edit you just made.

When the agent writes a file, the change lands in the buffer and the window is marked modified, so you can read it, undo it with `Ctrl-Z`, or save it with `F2`. A file you do not have open is read from and written to disk directly.

## Run several agents at once

Each window is its own process and its own conversation. Opening the same agent twice gives two independent sessions, and opening two different agents lets you put a fast local model and a slower careful one side by side — **Window ▸ Tile** arranges them.

Leaving the editor stops every agent.

## Variants

- **You want the agent to run somewhere other than the project root.** Add `cwd = "backend"` to its block. The path is relative to the project, and is both where the process starts and what the agent is told the working directory is.
- **The agent needs a credential.** Put it in `env`, or rely on it being in the environment you started the editor from — the agent inherits it.
- **The agent's commands do not appear when you type `/`.** Open **Agent ▸ Agent status** with the window in front. If it lists no commands, the agent announced none — or announced them in a shape this editor could not read, in which case the dialog names the update and the decoding error. To see exactly what went over the wire, start the editor with `TURBO_ACP_TRACE=/tmp/acp.log` and read the file: `->` is what the editor sent, `<-` what the agent answered.
- **The agent will not start.** **Agent ▸ Agent status** lists what was read from `acp.toml`, what each agent's command line came out as, and the error from anything that failed to start. Whatever the agent writes to its standard error is shown there too, which is where a misconfigured model endpoint reports itself.
- **You keep the same agent in every project.** Put the `[[agent]]` block in `~/.config/turbo-js/acp.toml` instead. A project's own file is read afterwards and an agent with the same `name` in it replaces yours.

## See also

- Every key of the file, and exactly how much of the protocol is implemented: [Agents and ACP reference](../reference/acp.md)
- Why an agent is a window rather than a panel, and why permissions are modal: [Agent windows](../explanation/agent-windows.md)
- The protocol itself: [agentclientprotocol.com](https://agentclientprotocol.com)