quvyta-code 0.1.22

Runs coding agent harnesses inside Podman or Docker containers, from the terminal (beta)
Documentation
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
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
# qcode

**Run Claude Code, opencode, Gemini CLI, Codex, Kimi Code CLI and Qwen Code side by side in Podman or Docker containers, with tabs like a browser, from one terminal app.**

![qcode with two real agents side by side: a Claude Code tab and an opencode tab start in their own containers on a local ollama model; Claude Code is asked to put a question to the opencode tab, sends it through qcode, opencode answers in its own tab and sends the answer back, and Claude Code reads it. Nothing in the recording is staged; the waits for the model are cut short](https://raw.githubusercontent.com/quvyta/code/main/docs/screenshots/qcode.gif)

**quvyta-code** runs coding harnesses inside containers, from the terminal. You set up a
profile once, sign it in, and from then on open that harness in any of your workspaces in a few
seconds, moving between harnesses and shells the way you move between tabs. Nothing the harness
runs ever runs on your machine itself. qcode is part of the Quvyta ecosystem of terminal
applications, is built on [quvyta-framework](https://github.com/quvyta/framework) and is open
source under the MIT licence.

> **Beta.** qcode is new. It works on Linux with both Podman and Docker, but the interface, the
> files it writes and the way it names images and containers may still change between releases.
> Please report anything that looks wrong at <https://github.com/quvyta/code/issues>.

## What it does

- **Harnesses in containers.** Each harness runs in a container of its own, built from an image
  made for it. The container sees the workspace folder and, if you allow it, the workspace's assets
  folder and the network. Because the container is what keeps the work apart from your machine,
  the harness is set up to work without stopping to ask for permission.
- **Profiles.** A profile is one harness with its settings: which harness, which template (the
  harness **as it comes**, **QCode recommended**, **QCode extra**, **Quvyta development** or, for
  opencode, **oh my opencode slim**), what it signs in with and what its containers may reach. Building a profile builds its image; the build can be stopped at any time and a
  half-made image is removed. **Edit** changes everything but the name later: a change to what the
  image holds builds it again (the old image stays until the new one is ready), a change of account
  asks you to sign in again, and a change to what the containers may reach needs no build at all.
  **QCode recommended** sets the harness up the way its makers and qcode recommend: update checks
  and usage reports off, its first questions answered, graphify (a map of the code the agent asks
  before it reads files) and the plugins recommended for that harness (Claude Code's starter
  plugins, oh-my-openagent for opencode); none of your workspace's files is changed. **QCode
  extra** adds, for Claude Code, every other plugin qcode's author works with, and writes graphify's
  and qcode's own instructions into the workspace's instruction file. **Quvyta development** is
  QCode extra plus what building the Quvyta apps needs, in the image: Rust's stable toolchain with
  clippy and rustfmt, rust-analyzer with its Claude Code plugin, a C compiler, pkg-config,
  OpenSSL's headers, git, ssh, curl, jq, uv and, as a part you can switch off, Chromium for tests
  that drive a browser. **oh my opencode slim** is
  opencode's alone: graphify and the same settings every QCode template writes, with
  [oh-my-opencode-slim]https://github.com/alvinunreal/oh-my-opencode-slim (about 220 MB) in
  place of oh-my-openagent (about 470 MB), which it does not install. Its seven agents
  (orchestrator, explorer, oracle, council, librarian, designer, fixer) all run on the model the
  profile signs in with; it asks for no key, sends no telemetry and never updates itself. The
  template step shows these ready-made sets above a switch for every part a profile can carry,
  each part's note under its row; changing a switch by hand makes the set **Custom**.
- **Signing in once.** A profile signs in through the harness's own sign-in flow, run in a
  terminal inside a container (Antigravity IDE: in its own window, with the page in your own
  browser). qcode then checks that the login is really there before it
  keeps it. Each workspace that uses the profile gets its own copy of the login, so chat history,
  memory and settings never leak from one workspace into another. A copy can be refreshed from the
  profile later, and the profile can be signed out. A profile that runs on one of your own
  providers has nothing to sign in to.
- **Providers of your own.** An ollama server on your network, an OpenRouter account, or a Xiaomi
  MiMo or Kimi Code subscription can stand in for a harness's own account, for every harness but
  Gemini CLI. A profile runs on one model or on a lineup: models tried in order, the next one
  asked when one is busy. The provider's key stays on your machine and never enters a container.
  See [Providers of your own]#providers-of-your-own.
- **Workspaces.** A workspace starts empty, from a folder, or from a git address (the clone
  runs inside a container, so git does not have to be installed on your machine). A folder is
  either copied into the QCode folder, links included, or used where it is: then nothing is
  copied, every container works in your real folder, and deleting the workspace leaves that
  folder as it is. The workspace
  screen has tabs for shells, harnesses and files, and a side panel with the workspace's files, its
  details and its containers, which can be stopped and restarted from there.
- **Built-in apps.** A file opened from the file tree opens in a tab of its own, in the workspace's
  base container, so the programs that open it live in that container and none of them has to be
  installed on your machine. See [Built-in apps]#built-in-apps.
- **Containers stop when you are done.** When the last QCode closes, the containers it started
  are stopped, and an optional background service does the same after a crash. See
  [When QCode closes]#when-qcode-closes.
- **Podman or Docker.** Either engine works. qcode finds it, tells you when it is missing or not
  running, and works out the command that installs or starts it. You either run that command
  yourself or let qcode run it on a terminal inside the setup, where you watch it. Either way it
  is the one command you chose, run with the rights you already have; qcode never raises its own.

The harnesses qcode knows today:

| Harness | Account types |
|---|---|
| Claude Code | subscription, API key, a provider of your own |
| opencode | free models, subscription, API key, a provider of your own |
| Gemini CLI | API key |
| Codex CLI | subscription, API key, a provider of your own |
| Kimi Code CLI | subscription (Kimi Code), a provider of your own |
| Qwen Code | a provider of your own |
| Antigravity IDE | Google account (a desktop window; see [Desktop harnesses]#desktop-harnesses) |

Google closed Gemini CLI's "Login with Google" to personal accounts (Code Assist for
individuals, Google AI Pro and Ultra) on 18 June 2026, so a new Gemini CLI profile signs in with
an API key. A profile made earlier with a Google sign-in still loads, with a note saying so: that
sign-in keeps working for Gemini Code Assist Standard and Enterprise.

Qwen Code's own sign-in ended on 15 April 2026, when Alibaba closed its free tier, and a key typed
into it is kept in its settings file beside everything else; so a Qwen Code profile runs on a
provider of your own, where the key stays on your machine. An Alibaba Cloud Coding Plan is an
OpenAI-compatible service and can be added on the **Providers** page like any other. Kimi Code
CLI's sign-in is Kimi Code's own (mainland `kimi.com`, the default); a sign-in to the global
region is not carried yet, and a Kimi Code key goes through the **Providers** page instead.

Each harness is installed from its own published package when a profile's image is built; qcode
does not ship or change any of them. The interface follows your system language, in English,
Turkish, German, Spanish, French, Japanese, Brazilian Portuguese, Russian or Simplified Chinese,
and uses the ecosystem's themes, icons, keys and mouse behaviour.

## Why qcode, and what else there is

qcode is an everyday place to work with several harnesses, not a security product. What it adds
is the whole working surface around the containers: profiles you sign in once, a login copy per
workspace, tabs, going back to an earlier conversation, a file tree and the containers' state in
one screen. Other good tools solve neighbouring problems:

| Tool | What it does | How qcode differs |
|---|---|---|
| Docker Sandboxes (`docker sandbox`) | Runs harnesses in Docker's microVMs | Its isolation is stronger than a container's; it needs Docker Desktop and is a command-line tool, without tabs, a workspace screen or a list of earlier conversations |
| Dagger container-use | Gives each task of a harness its own container and git branch, over MCP | A base for running tasks in parallel, with no interface of its own; needs Dagger and git |
| Dev containers | The route the Claude Code documentation suggests | Tied to an editor such as VS Code and set up by hand for each workspace |
| claude-squad and similar | Several harnesses side by side with tmux and git worktrees | No container: the harness still runs on your machine |
| Single-image scripts (claudebox and others) | One harness in one Docker image | No interface, profiles, per-workspace logins or conversations to go back to |

If what you need is the strongest possible wall between a harness and your machine, a microVM is
the better tool. If you want to use several harnesses every day without handing them your
machine, qcode is made for that.

## What the container protects, and what it does not

The harnesses run in an unattended mode, without asking before each command, because the
container is what keeps them away from your machine. qcode also answers their trust and approval
questions ahead of time, under every template and in Antigravity IDE too: its terminal commands,
file edits, browser actions and permission requests go ahead without a question. It helps to know
exactly where that wall is.

The container keeps the harness away from:

- your home folder, your other workspaces and every file qcode did not mount: a container sees
  only its workspace's `Work/` folder (or, for a workspace that uses a folder where it is, that
  folder), its `Assets/` folder (read-only unless the profile allows writing) and its own home;
- other workspaces' logins and conversations: each workspace has its own copy of a profile's home;
- the container engine itself: its socket is never mounted, containers are not privileged, and
  processes inside run as your own user id, never as root on your machine.

It does not protect:

- **the workspace folder.** The harness can change or delete anything in `Work/`, and it is
  mounted straight from your disk. For a workspace that uses a folder where it is, that is your
  real folder, not a copy. Keep your work in git and push it somewhere.
- **your data from leaving over the network.** A profile with network access (the default) can
  send anything it can read, the workspace included, anywhere. A profile can be set to have no
  network, but most harnesses need it to reach their model. A profile that runs on one of your
  providers can work with no network at all, and even then what the harness puts in its requests,
  which can be anything in the workspace, goes to that provider: qcode carries those requests
  there, and to nowhere else.
- **your machine's loopback during a sign-in.** While a desktop harness signs in, qcode listens
  on one port of `127.0.0.1` (and `[::1]`), the one the sign-in comes back to, and carries every
  connection to that port to the application inside the container. Any program on your machine
  can connect to it in that time, as it could to the application's own port if it ran outside a
  container. qcode stops listening as soon as the sign-in has arrived, when the window closes, or
  after ten minutes.
- **the logins.** A profile's login lives in the engine's volumes. Anyone who can use your
  container engine can read them.
- **against the engine or the kernel.** A container shares your machine's kernel; a flaw there
  or in the engine is a way out that a virtual machine would not have.

## No telemetry, and what goes over the network

qcode collects no statistics and sends nothing about you, your machine or your work anywhere.

It asks one question of its own accord: whether a newer qcode is out. When qcode starts, at most
once a day, it reads the list of published versions of `quvyta-code` from crates.io, the same file
`cargo install` reads: one HTTPS `GET` of `https://index.crates.io/qu/vy/quvyta-code`. The request
carries no cookie and no identifier; its headers are `Host: index.crates.io`, `User-Agent:
quvyta-code/<the version you run>`, `Accept: */*` and `Accept-Encoding: gzip`. crates.io sees, as with any connection, the
address it comes from. When a newer version is out, a notice says which one and how to update. When
there is no network, or crates.io does not answer within ten seconds, nothing is said and the next
day asks again. Nothing is asked while the first-run setup is open. The time of the last question is
kept in `~/.local/state/quvyta/code/update-check` on Linux.

To turn it off, switch off **Say when an update is out** in **Settings**. The switch belongs to the
whole Quvyta ecosystem: it is `update-notice = false` in `~/.config/quvyta/quvyta.conf`, and turning it
off stops the question in every Quvyta application. While it is off, qcode asks nothing at all.

Apart from that question, qcode connects to the network itself only after you have added a
provider on the **Providers** page, and then only to that provider's address:

- **When you ask on the Providers page.** **Try the connection**, **Ask what it offers** and
  **Measure the real window** each send their requests at the moment you press them, never
  because the page was opened or qcode started. Measuring sends up to four prompts, of 400 to
  48 000 words, and reads back how many tokens the provider counted; a provider that charges by
  the token charges for them like for any other prompt.
- **While a tab of a profile that runs on that provider is open.** The harness's requests for an
  answer and for the list of models are carried from its container to the provider by qcode,
  which adds the key on the way out. Nothing else the container asks for is carried, and the key
  never enters the container.

Two providers are ready-made: picking one fills in its address and you paste only your key. They
are the only addresses qcode knows of its own, and nothing is sent to either until you have added
it: **Xiaomi MiMo Token Plan** at `token-plan-ams.xiaomimimo.com`, `token-plan-sgp.xiaomimimo.com`
or `token-plan-cn.xiaomimimo.com`, whichever your subscription names, and **Kimi Code** at
`api.kimi.com` or `api.kimi.ai`. For them, **Try the connection** is a `GET` of the service's
model list with your key, which spends nothing.

A version of qcode before 0.1.13 said here that it had no network code at all. That stopped being
true in 0.1.12, which added providers, and the sentence was not changed with it.

All other traffic comes from programs you can see qcode start: your container engine, when it
builds an image (the base image, the harness packages and, for the QCode templates, what they
add) or clones a workspace from a git address; the command that installs a container engine,
when you let qcode run it in the setup; and your own browser, when a sign-in page is handed to
it. That hand-over is the one time qcode listens for a connection itself: on your machine's own
loopback, on the port the sign-in comes back to, until it has (see Desktop harnesses). Inside the
containers, the harnesses keep their own behaviour, including any telemetry of their own; their
documentation says what that is. Some are switched off by qcode's templates: QCode recommended turns off
Claude Code's telemetry, error reports and updater, opencode's updater, Kimi Code CLI's telemetry
and updater, Gemini CLI's usage statistics and update checks, Codex's update check and analytics,
Qwen Code's usage statistics (which Qwen Code otherwise sends to Alibaba Cloud) and updater, and
Antigravity's telemetry and updater, and turns off that of oh-my-openagent, which it adds. The
**As it comes** template leaves all of them as their makers ship them. A profile without
the network sends none of this anywhere.

## Screens

![A workspace open in qcode: a shell tab beside a Claude Code and an opencode tab, and the panel with the file tree, the workspace and its containers](https://raw.githubusercontent.com/quvyta/code/main/docs/screenshots/workspace.png)

<p>
  <img src="https://raw.githubusercontent.com/quvyta/code/main/docs/screenshots/new-tab.png" width="49%" alt="A new tab offering a shell and each profile's earlier conversations">
  <img src="https://raw.githubusercontent.com/quvyta/code/main/docs/screenshots/panel.png" width="49%" alt="The panel with the workspace's details and its containers">
</p>
<p>
  <img src="https://raw.githubusercontent.com/quvyta/code/main/docs/screenshots/home.png" width="49%" alt="The home screen, ready to continue with the workspaces left open">
  <img src="https://raw.githubusercontent.com/quvyta/code/main/docs/screenshots/workspaces.png" width="49%" alt="The list of workspaces">
</p>
<p>
  <img src="https://raw.githubusercontent.com/quvyta/code/main/docs/screenshots/apps.png" width="49%" alt="The workspace's README read in a Markdown tab, opened from the file tree">
  <img src="https://raw.githubusercontent.com/quvyta/code/main/docs/screenshots/files.png" width="49%" alt="Three files selected in the file tree, with the menu that cuts or deletes them">
</p>

## Requirements

- **A container engine:** [Podman]https://podman.io/docs/installation (recommended: rootless,
  with no background service) or [Docker]https://docs.docker.com/engine/install/ with its daemon
  running.
- **An account** with the harness you want to use: a subscription or an API key from its provider,
  or, for every harness but Gemini CLI, a model service of your own (an ollama server, OpenRouter,
  Xiaomi MiMo or Kimi Code).
- **Disk space and a network connection** for the first images. The base image is Debian with
  Node.js (about 520 MB); each profile adds its harness on top of it. A profile can instead be
  built on Arch Linux (about 810 MB), Ubuntu 24.04 LTS (about 510 MB) or Alpine (about 310 MB,
  not recommended: Gemini CLI, Qwen Code and Antigravity IDE do not run on it). A profile image a
  rebuild replaced is removed once no container is made from it, and nothing else on the engine is.
- Rust 1.95 or later to install from source.

qcode is developed and tested on Linux. The paths, engine checks and container settings for macOS
and Windows are written in, but have not yet been tried on those systems.

## Install

```sh
curl -fsSL https://raw.githubusercontent.com/quvyta/quvyta/main/install.sh | sh -s -- code
```

Or with Cargo:

```sh
cargo install quvyta-code
qcode
```

If the shell cannot find `qcode`, add `~/.cargo/bin` to your `PATH` (fish: `fish_add_path ~/.cargo/bin`).

The program is installed as `qcode` and also as `quvyta-code`.

## Using it

1. **Setup.** The first time qcode opens it asks three things: the language, the container engine
   and where the QCode folder goes. It checks each answer before going on, and checks them
   again every time it starts.
2. **A profile.** Open **Profiles** and make a new profile: pick the harness, the template, the
   account type and the permissions, then build the image. When it is built, sign in in the
   terminal that opens and press **I have signed in** (for Antigravity IDE, sign in in the window
   that opens, then press **I have signed in**: the application writes its sign-in down only when
   it closes, so qcode closes the window first and then stores it).
3. **A workspace.** Open **Workspaces** and make a new workspace, empty, from a folder or from a git
   address. For a folder, choose **Copy it into QCode** or **Use it where it is**.
4. **Tabs.** In the workspace, the `+` after the last tab (or `ctrl+t`) opens a new tab at once.
   It lists the shell of the workspace's own container and, for every profile, **New chat** and
   the profile's latest conversations in this workspace, newest first, with when each was last
   used. Choosing a conversation opens the harness on it again, where it left off; every harness
   that draws in a terminal resumes a conversation this way. A profile the workspace
   does not have yet is added to it the moment you open it, and is written into the workspace's
   `workspace.qcode`. With no profile at all, the list offers **New profile**, which leads to the
   profiles screen. A conversation already open in another tab is not offered until that tab
   closes. While a tab's harness is working, a thin turning mark stands before its name on the
   tab strip (a still dot with reduced motion).
5. **Continue.** The rail on the left holds the workspaces you have open, like the windows of a
   browser: `+` at its end adds another one, and each can be closed. **Continue** on the home
   screen brings back the open workspaces with their tabs as you left them, even after qcode was
   closed; a tab's container starts when you first switch to that tab.

| Key | What it does |
|---|---|
| `ctrl+pgdn` `ctrl+pgup` | Go to the next or previous tab, with the keyboard in it |
| `alt+1` … `alt+9` | Go straight to that tab, with the keyboard in it |
| `←` `→` (or `h` `l`) | Move between tabs, while the tab strip has the keyboard; `enter` or `↓` steps into the tab |
| `f2` | Name the open tab (or right-click a tab and choose **Rename**) |
| `ctrl+shift+←` `ctrl+shift+→` | Move the open tab left or right |
| `ctrl+t` | Open a new tab |
| `ctrl+w` | Close the tab |
| `ctrl+alt+space` | Inside a harness or shell tab: leave it for the tab strip. Anywhere else on the workspace screen: go back into the open tab's terminal |
| `alt+b` | Show or hide the side panel |
| `tab` `shift+tab` | Move to the next or previous control; `shift+tab` also leaves a terminal |
| `ctrl+p` | Command palette |
| `esc` | Leave the screen that is open |
| `?` or `f1` | The list of every key |
| `ctrl+q` | Quit |

In the file tree of the side panel:

| Key | What it does |
|---|---|
| `↑` `↓` | Move between entries |
| `→` (or `l`) | Open a folder, or step into it when it is open |
| `←` (or `h`) | Close a folder, or step up to the folder above |
| `enter` | Open a folder, or open a file in a tab |
| `space` | Add the entry to the selection, or take it out |
| `shift+↑` `shift+↓` | Select a range of entries |
| `shift+f10` or the menu key | The entry's context menu |

While a harness or shell tab has the keyboard, keys go to it, `esc` and `?` included; `f1`, `f2`, `alt+b`, `ctrl+alt+space`, `ctrl+pgup`, `ctrl+pgdn`, `alt+1` … `alt+9`, `shift+tab` and `ctrl+q` still reach qcode. The mouse reaches a harness that uses it.

## Built-in apps

Every workspace has one small container of its own, the base container, which is also where the
shell tab runs. It starts the first time something needs it, so a session that opens no shell and
no file starts nothing. A file chosen in the file tree (`enter` or a click) opens in a new
tab named after the file; choosing it again goes back to that tab.

| File | Opens in |
|---|---|
| Text: `txt`, source code, configuration and data files, and files such as `README`, `LICENSE`, `Makefile` or `Dockerfile` | The editor chosen in **Settings**, **Built-in apps**: `nano` (the default) or `vim`. When the editor exits, the tab offers to open the file again |
| Markdown: `md`, `markdown` | qcode itself, as a formatted page with no container at all. **Edit** above the page opens the same file in the editor |
| Pictures: `png`, `jpg`, `jpeg`, `gif`, `webp` | `chafa`, which draws the picture in the tab with full colour. **Redraw** draws it again at the tab's new size |
| PDF: `pdf` | Its text, taken out by `pdftotext` and shown by qcode like a document. **Page picture** draws a page with `chafa`, and **Previous** and **Next** walk through the pages; **Text** goes back. A PDF with no text, such as a scan, opens on its first page picture |
| Word and OpenDocument text: `docx`, `odt` | Its text, taken out by `docx2txt` or `odt2txt` and shown by qcode like a document |
| Sound: `mp3`, `ogg`, `oga`, `opus`, `flac`, `wav` | `sox`, which plays it in the tab and shows how far it has got; **Play again** plays it once more. `m4a` and `aac` are not supported |

A sound does not play in the base container. Each time it plays, qcode starts a container of its
own for it, which sees the workspace read-only, has no network and reaches only this machine's
sound server (PulseAudio, or PipeWire through its PulseAudio socket). On a machine with no such
server, which includes macOS and Windows, the tab shows the sound's details (length, rate,
channels) instead. If no container should ever reach the sound server, set **Sounds** to
**Details only** in **Settings**, **Built-in apps**: every sound then shows its details and
nothing plays. The default is **Play**.

![The manual of a rain gauge read as text in a PDF tab, opened from the file tree](https://raw.githubusercontent.com/quvyta/code/main/docs/screenshots/pdf.png)

Other kinds of files (spreadsheets, presentations, archives) have no app yet; choosing one says
so. The base image also carries `unzip`, `zip`, `xz`, `bzip2` and `7z`, ready in the shell tab.
Every program runs inside the container, on the file's path there, and is handed that path as one
word, never through a shell. Open file tabs come back with **Continue** like any other tab; a file
that has gone since is reported in its tab.

## Desktop harnesses

Most harnesses draw in the tab's terminal. One does not: **Antigravity IDE** is a desktop
application, and qcode runs it the same way it runs the others — in a container, on your workspace
and nothing else — except that its window opens on your own screen instead of in a tab.

A profile for it is made like any other, in **Profiles**. The image is built on qcode's base image
and downloads the application from Google's own address while it builds; nothing of the
application is carried inside qcode. That download is about 230 MiB and the finished image about
1.4 GiB, so it is much larger than a command-line harness's, and the application uses well over a
gigabyte of memory while its window is open. The version is fixed in qcode, so updating the
application means building the image again.

The tab is one line of status with two things you can do to the window:

| The tab says | What it means |
|---|---|
| **Opening the window…** | The container is starting. The first time takes a few seconds longer |
| **Window open** | The window is on your screen. **Ask it to come forward** asks it to show itself (under Wayland your desktop decides whether it does), **Close the window** closes it |
| **Window closed** | It is not open. **Open the window** opens it again. Nothing was lost: the settings, the history and the sign-in are in the workspace's home volume |
| **The window did not open** | Why, in the engine's own words |

The window is a container's, which is what makes it worth having and also where its limits come
from:

- **It needs Wayland.** The container is given the one socket file of your compositor and nothing
  else that lives beside it — not the engine's own socket, not the session bus, not the keyring.
  Because it is that one file, a compositor that restarts cuts an open window off; opening the tab
  again is the way back. X11 is not offered: there every program on the screen could read this
  one's windows and keypresses.
- **Graphics.** If your machine has `/dev/dri`, the container is given it and the application may
  use the card; without it, and on cards the application does not trust, it draws in software,
  which works and is not noticeably slower in the editor. NVIDIA's closed driver is not supported
  yet.
- **The application's own sandbox stays on.** qcode never passes `--no-sandbox`. Podman needs
  nothing extra for that. Docker's default seccomp profile refuses the calls the sandbox is built
  from, so qcode hands docker a profile of its own: docker's default plus `clone`, `setns` and
  `unshare`. It is in `assets/seccomp/desktop.json` with a comment saying what it costs.
- **Signing in happens in your own browser.** The application cannot be used without a Google
  account. When it asks to sign in, qcode opens the page in your own browser, where you may be
  signed in to Google already. Google then sends the browser back to `http://localhost:<port>/…`,
  where the application waits inside its container; qcode listens on that port on your machine's
  `127.0.0.1` (and `[::1]`) and carries what arrives to the application, through the engine, so
  the container needs no network for it. The tab says which port and for how long. You sign in
  once, in the profile's sign-in step, and qcode keeps that login with the profile: every
  workspace's window opens with it, and a workspace whose window already has a login of its own
  keeps that one. A profile made before qcode 0.1.18 shows **Sign in** on the Profiles screen for
  the same step. If another program already uses the
  port, the page is not opened and the tab says so: the sign-in has to come back to exactly that
  port. The page can also be shown in a small sign-in window inside the container (**Use the
  sign-in window here** on the tab), but Google refuses its own sign-in there with "This browser
  or app may not be secure", so it is only the fallback. A profile made with qcode 0.1.13 or
  earlier has no such window in its image: **Rebuild image** on the Profiles screen gives it one.
- **A profile with the network off makes no sense here.** The application does all its work on its
  maker's servers; the tab says so if you try.

Closing the tab closes the window and removes its container; closing the window ends the
container, which the tab notices and offers to open again. qcode quitting stops any open window,
like every other container it started. A container left over from a crash is found by name the
next time and either taken over, if its window is still up, or cleared away.

## File manager

The file tree of the side panel is also a file manager. It works on the workspace's own folder
directly on your machine, so it needs no container and works with no engine running.

- **Context menu.** Right-click an entry, or select it and press `shift+f10` or the menu key. On a
  folder: **New file**, **New folder**, **Rename**, **Cut**, **Copy**, **Paste here** (once something
  is cut or copied) and **Delete**. On a file: **Rename**, **Cut**, **Copy** and **Delete**. The
  first row of the tree is the workspace folder itself; its menu has **New file**, **New folder**,
  **Paste here** and **Refresh**. With several entries selected, the menu cuts, copies or deletes
  all of them. Folders and files also offer **Don't back up** (or **Back up again**), and files
  **Earlier versions**; see [Backups]#backups.
- **Names** are asked for in a small dialog and checked as you type: not empty, no `/`, not `.` or
  `..`, and not a name the folder already has. A rename opens with the name before its extension
  selected.
- **Moving and copying.** **Cut** or **Copy**, then **Paste here** on the folder it goes to; or
  `ctrl+x` or `ctrl+c` on the entry under the cursor and `ctrl+v` on the folder it goes to. A cut
  entry is drawn faded until it is pasted; `esc`, **Cancel the move** or **Cancel the copy** lets it
  stay. A copy shows how far it has come above the tree, with **Stop**; what was copied before you
  stop stays. A folder cannot go into itself, and nothing is ever written over: when the target
  already has that name, nothing moves and the reason is shown.
- **Deleting** asks first and cannot be undone; for a folder the question says that everything in
  it goes too.
- **Several entries.** `ctrl`+click adds or removes an entry, `shift`+click selects a range, and
  `shift` with the arrow keys extends it; `space` adds or removes the entry under the cursor,
  `ctrl+a` selects every entry shown and `esc` goes back to one. Cut, copy, paste, delete and
  dragging act on all of them.
- **Dragging** entries onto a folder, or onto the workspace folder's row, moves them there; with
  `ctrl` held when you let go, it copies them instead.
- **Hidden entries**, the ones whose name starts with a dot, are shown like any other.
- **Live.** The tree follows the disk: the folders on screen are watched, and when something
  changes in one, by qcode, a harness or any other program, only that folder is read again.
  Nothing is read on a timer. Where the system has no watch to give (so far, anywhere but Linux),
  the tree is read again after qcode's own changes, when you come back to the screen, and on
  **Refresh**.

A link inside the workspace is handled as an entry of its own: deleting or moving it touches the
link, never what it points to.

## Backups

qcode keeps copies of every open workspace's `Work/` folder in `Backup/`, next to it in the
workspace's own folder, so the backup moves and is copied with the workspace. For a workspace
that uses a folder where it is, that folder is what is backed up, into the same `Backup/`.

- **How often.** Every 15 minutes while the workspace is open, once more when you close it from the
  rail, and once more when qcode quits. **Settings**, **Back up open workspaces** chooses **Off**,
  **5 min**, **15 min** or **1 hour**. A round in which nothing changed writes nothing.
- **How.** Each backup is a git commit in `Backup/Code.git`, made by git in a short-lived
  container of the base image, so nothing runs on your machine and your machine needs no git.
  Your own repository inside `Work/`, if you have one, is never touched, and what your
  `.gitignore` leaves out stays out.
- **Leaving things out.** Right-click a folder or a file in the file tree and choose
  **Don't back up**; **Back up again** takes it back in. The list is kept in `workspace.qcode`.
  Left-out entries are drawn faded with a coloured icon, and the **Workspace** part of the side panel
  names them.
- **Assets.** `Assets/` is left out unless you turn on **Back up Assets too** in the **Workspace**
  part of the side panel. It is then backed up in every round into `Backup/Assets.git`, apart
  from the workspace, and the choice is kept in `workspace.qcode`.
- **Conversations.** Each round also backs up the conversations of every profile whose tab was
  open since the round before, each profile into `Backup/Conversations/<profile>.git`. Only the
  harness's conversation files are taken, never its login. opencode keeps its conversations in a
  database, so they are backed up only while its container is stopped: when qcode quits and
  stops it.
- **Bringing things back.** **Backups** in the **Workspace** part of the side panel lists every
  backup with its time and how many files it changed; choose one to bring the workspace back to it.
  **Earlier versions** in a file's menu does the same for that one file. The choice at the top of
  the list switches it to the assets, when they are backed up, or to a profile's conversations.
  qcode asks first, then backs up how things are now, so bringing something back can be undone
  the same way. Nothing is deleted: a file made after that backup stays where it is.
  Conversations are brought back only while the profile's container is stopped; if it runs, qcode
  offers to stop it first.
- **What it is for.** A backup protects against a wrong delete, a change that breaks things or a
  harness scattering files. It sits on the same disk as the workspace, so it does not protect
  against losing the disk.

The **Workspace** part of the side panel also shows when the last backup was made and how much
`Backup/` holds. If a backup fails, qcode says so once for that workspace, not at every round.

![The list of a workspace's backups, the newest taken just before a restore, with the choice of the workspace's files, its assets or a profile's conversations above it](https://raw.githubusercontent.com/quvyta/code/main/docs/screenshots/backups.png)

## Many opencode tabs, one opencode

opencode is a server and an interface in one program, and one of them costs most of a gigabyte.
So the opencode tabs of a profile made with a QCode template share one opencode server in the
profile's container, and each tab runs only opencode's interface, attached to it. Measured on a
Raspberry Pi 5 with oh-my-openagent: seven tabs took 5.0 GB and 241 threads each on its own, and
take 2.4 GB and 112 threads sharing one server. From the second tab on, sharing is the smaller;
one tab alone costs about 140 MB more.

Each tab still shows a conversation of its own, opens it again next time, and is told apart from
the others when its agent sends a message. Closing a tab stops what its agent was doing, as it
did before, and removes its conversation if nothing was said in it. If the server stops, it is
started again and every tab attaches to its conversation again. A while after the last tab is
closed, the server stops too. A profile made with **base** is opencode as it comes: every tab runs
its own. A profile without the network opens its first tab in seconds: the packages opencode
would fetch for its plugins are put in the image while it is built.

## Tabs talking to each other

The agents in a workspace's harness tabs can hand each other work: the one in a Claude Code tab can
ask the one in a Codex tab to write a test, and hear back. qcode gives every harness three tools for
this, `list_tabs`, `send_message` and `check_inbox`, through a small MCP server it registers in each harness's
own settings, next to anything you added there. The server runs inside the container and talks
to qcode through a socket in the workspace's `Containers/MCP/` folder, so it works in a profile
without the network too. opencode tabs that share a server get the same three tools from a
plugin qcode loads into that server instead, since an MCP server started once for all of them could
not tell which tab is asking.

Every tab has an id within its workspace (1, 2, 3 and on) that is never given to another tab while
the workspace is open, and a name: the one you give it with `f2`, or else the title its harness gave
the conversation, or the profile's name. `list_tabs` first tells the asking agent which tab it is,
then lists the other agent tabs of the same workspace; tabs of another workspace are never listed.
A message has a kind: `info` (no answer needed), `question` (the sender waits for the answer) or
`report` (a task whose result the sender waits for), and it arrives under two short lines naming the
sending tab's id and name, the kind and how to answer. A message sent to `all` goes to every other
agent tab of the workspace, each by the same rules as a message to it alone.

Messages go from tab to tab without asking you: you set the agents to work, and handing it to
each other is part of that work. Two rules hold for every message all the same, and no setting
turns them off:

- **A tab without the network never sends to a tab with it.** The second tab could carry out what
  it is given, which is what taking the network away was meant to prevent. The other way round is
  allowed.
- **Loops stop.** An exchange between tabs ends after 6 messages, and one tab sends at most 5
  messages a minute, so two agents cannot keep each other busy, and spend your balance, forever.
  The sending agent is told why its message was refused. When an exchange is ended, both tabs say
  so in a line under their terminal until you press **Got it**, and a notice tells you once,
  whichever tab you are looking at. What you type into a tab yourself starts a new exchange for it:
  the agents you keep giving work are counted from your latest task, not from the first one.

If you would rather approve the first message between two tabs, turn on **Ask before the first
message** under **Messages between tabs** in **Settings**. The first message from one tab to
another then asks you, with the message shown. Your answer holds for those two tabs, in that
direction, until qcode closes, and is never written to disk. Esc denies. A pair you denied stays
denied until qcode closes, even if you turn asking off again.

A message that is taken is typed into the receiving harness's own prompt, on a line that says
which tab sent it, as soon as that tab is quiet: its program has stopped writing and you have no
line in it that you started and have not sent, so a line you paused over, however long, is never
sent off with the message joined to it. Until then, or while the harness is not running, it waits in the tab, where
**Read** shows it and **Discard** throws it away. The sending agent is told whether its message
went in or still waits. A desktop window (Antigravity) has no prompt to type into, so a message to
it waits in its tab until the agent inside the window calls `check_inbox`, which hands over every
message waiting for it. A small extension qcode puts inside the window's application tells the
agent when one arrives, with a prompt in its agent panel, and qcode's instructions tell it to check
at the start and the end of every task too. The line under the window's tab says how many are
waiting.

## Providers of your own

**Providers**, beside **Profiles**, lists the model services you already have: an ollama server
on your own network, or OpenRouter. Each gets a tag of your choosing, and a key where the service
needs one, pasted into the dialog. The page shows, in a line of its own, which file the keys are
kept in and that a backup of your home folder carries them in plain text.

For each provider the page can try the connection, ask which models it offers and what window
each one claims, and measure the window the server really gives: a model may say 262 144 tokens
while the server quietly keeps three thousand and drops the front of everything larger. The page
shows both numbers. Each of these goes out only when you press its button; see
[what goes over the network](#no-telemetry-and-what-goes-over-the-network).

The list of models scrolls and has a filter above it, so a provider with hundreds of models stays
usable on a small terminal. For OpenRouter each row says whether the model is free or costs money,
read from the prices OpenRouter publishes.

**Lineups…** under a provider makes, edits and deletes its lineups. A lineup is a named order of
that provider's models, for example `coder`: first `z-ai/glm-4.6:free`, then
`qwen/qwen3.8-27b:free` when the first is busy. A lineup or a chosen model with a step that costs
money gets a warning line saying which steps those are. Deleting a lineup asks first and says that
the profiles using it will not start; such a tab says which lineup of which provider is missing.

A Claude Code, opencode, Codex, Kimi Code CLI or Qwen Code profile can then sign in with **a
provider of your own** and one of its lineups or models, chosen from one list with the lineups on
top. Its tab talks to a small relay that runs inside the
container, on the container's own loopback address; the relay hands each request to qcode through
a socket in the workspace's `Containers/MCP/` folder, and qcode sends it on to the provider with
the key added. So the container never holds the key and needs no network of its own, and the
harness is told the window that was measured, so it does not assume room the server does not give
(Qwen Code has no way to be told one that its tabs do not share, so it is not). Only two kinds of
request are carried: a message and the list of models. Codex speaks OpenAI's newer Responses
shape to a provider, which ollama, OpenRouter, Xiaomi MiMo and Kimi Code all answer; its web
search, which only OpenAI's own servers run, is turned off in such a tab. Gemini CLI does not offer
a provider yet, because pointing it at another address has not been checked.

Every message a tab sends goes to the model the profile chose, or to the current step of its
lineup, whatever model the harness asks for: a harness that asks for a small model of its own for
background work cannot reach one you did not pick, perhaps one that costs money. When a step
answers busy (429), out of credit (402), with a server error, not at all, or that the model does
not exist, qcode asks the next step of the lineup with the same message and skips the failed one
for a minute; a line under the tab says so for ten seconds, for example
`coder: z-ai/glm-4.6:free answered 429, now qwen/qwen3.8-27b:free`. A refused key or a malformed
message is not carried on, since every step would answer the same.

## When QCode closes

**Settings**, **When QCode closes** decides what the containers QCode started do once no QCode is
open any more: **Stop** (the default) or **Keep running**. Stopped containers are not removed;
the same container starts again the next time it is needed. Only containers QCode itself started
are ever stopped; one you run by hand, or another program's, is never touched.

Several QCodes can be open at once. When the last one closes normally, it stops the containers
itself and says which on the terminal it was started from.

**The background service** covers what a normal close cannot: a QCode that crashed or whose
terminal was killed. It is optional, and **Settings**, **Background service** installs and removes
it. On Linux it is a systemd user path unit that watches the list of the containers QCode
started; on macOS it is a launchd job that watches the same file. It runs only when that list
changes, then waits, without polling and without using the processor, until no QCode is open,
applies the setting, and exits. On a day QCode is never opened it never runs.

On Windows there is no background service, and the last QCode to close cannot tell it is the last,
so the containers keep running; Settings says so.

## Where things live

| What | Where |
|---|---|
| Settings | `code.conf` in the Quvyta folder of the platform's configuration folder: `~/.config/quvyta/code.conf` on Linux, `~/Library/Application Support/Quvyta/code.conf` on macOS, `%APPDATA%\Quvyta\code.conf` on Windows. Settings from before (`~/.config/quvyta/code/settings.toml`) move there once, at start |
| Open workspaces and tabs | `session.toml` in the platform's data folder, `~/.local/share/quvyta/code` on Linux |
| Containers QCode started | `containers.toml` in the same data folder, with `instances.lock`, which every open QCode holds |
| Background service | `~/.config/systemd/user/qcode-reaper.service` and `qcode-reaper.path` on Linux, `~/Library/LaunchAgents/io.quvyta.code.reaper.plist` on macOS, while it is installed |
| QCode folder | `Quvyta/Code` in your Documents folder by default (`~/Documents/Quvyta/Code`, or `~/Belgeler/Quvyta/Code` where the desktop names it so), or the folder you chose; a folder chosen before stays where it is |
| Profiles | `Profiles/<profile>.toml` in the QCode folder |
| Workspaces | `Workspaces/<workspace>/` in the QCode folder: `workspace.qcode`, the code in `Work/` (or the path of the folder it uses where it is), your material in `Assets/` |
| Tabs talking to each other | `Workspaces/<workspace>/Containers/MCP/` in the QCode folder: the server the harnesses start and, while the workspace is open, the socket qcode listens on; each harness's own settings in `qcode-home-<workspace>-<profile>` hold the entry `qcode` |
| Providers | `providers.toml` in the data folder, `~/.local/share/quvyta/code` on Linux, readable only by you in a folder only you can open. It holds the keys in plain text; they are never written to the settings file, the QCode folder or a container |
| The relay to a provider | `relay.sock` and `qcode-relay.mjs` in `Workspaces/<workspace>/Containers/MCP/`, beside the bridge's socket and server |
| Backups | `Workspaces/<workspace>/Backup/` in the QCode folder: `Code.git`, the backups of `Work/`; `Assets.git`, those of `Assets/` when it is backed up; `Conversations/<profile>.git`, each profile's conversations; and the lock files that keep two QCodes from backing up the same thing at once |
| Images | `qcode/base` and `qcode/profile/<profile>`, in the engine |
| Logins | engine volumes: `qcode-cred-<profile>` for the profile, `qcode-home-<workspace>-<profile>` for each workspace's copy |

Files are plain TOML. A file qcode cannot read is reported with its line and column instead of
stopping the program, and a broken workspace stays on the list so it can be repaired.

## Building from source

The toolchain is pinned by `rust-toolchain.toml`.

```sh
git clone https://github.com/quvyta/code
cd code
cargo run --bin qcode
```

`cargo test` needs no container engine. The tests that build images and run containers are
ignored by default; run them with a working engine:

```sh
QCODE_CONTAINER_TESTS=1 cargo test -- --ignored
```

Before your first commit, enable the checks (formatting, clippy, tests and docs):

```sh
git config core.hooksPath .githooks
```

[CONTRIBUTING.md](CONTRIBUTING.md) says more about tests and pull requests, and
[CHANGELOG.md](CHANGELOG.md) lists what changed in each release.

## Licence

MIT. See [LICENSE](LICENSE).