… runs isolated AI coding agent instances in parallel.
The Micronaut Project gets hundreds of issue reports from users. Many of them are easy to solve, but still take time to understand, debug and fix. AI agents can solve many of these issues on their own. multicode speeds up and parallelizes this work.
Warning
This project is (obviously) heavily vibe-coded. Read the code at your own risk.
cargo run --bin multicode-tui -- config.tomlTo run with the Codex-focused example config in this repository:
cargo run --bin multicode-tui -- config.codex.ymlThe e shortcut in the TUI opens the selected workspace or issue repository in a configured IDE.
This is configurable in config.toml with the [compare] section.
By default, multicode uses VS Code:
[compare]
tool = "vscode"Supported values are:
vscodeintellij
You can also override the launcher command explicitly:
[compare]
tool = "intellij"
command = "~/Library/Application Support/JetBrains/Toolbox/scripts/idea"Notes:
- If
commandis omitted, multicode tries common launcher names and install locations for the selected tool. - For VS Code, that includes
codeand common macOS app bundle paths. - For IntelliJ IDEA, that includes
idea, common app bundle paths, and the JetBrains Toolbox shell script location at~/Library/Application Support/JetBrains/Toolbox/scripts/idea. - If you manage IntelliJ via JetBrains Toolbox, using the Toolbox-generated
ideascript is the most stable option. If you have not enabled Toolbox shell scripts, you can instead pointcommanddirectly at the app binary inside the.appbundle. - The
cshortcut opens an in-terminalgit diffview for the selected repository and returns to the TUI when you exit.
multicode parallelizes work in workspaces. They are short-lived and isolated. Typically, a workspace is used for a single issue report. A workspace gets its own working directory and agent session, so you can work from a blank slate.
The workspace root is configurable with workspace-directory. If you omit it, multicode defaults
to ~/dev/multicode-workspaces.
The main TUI table shows one row per workspace and, when autonomous queueing is active, nested task rows for queued or running issues.
| Column | Meaning |
|---|---|
Workspace |
Workspace key. Task rows are shown underneath as > owner/repo#number. |
Server |
Workspace or task state, such as Starting, Idle, Busy, Question, Waiting on VM, Needs Rebase, Review Wait, Assign Wait, or Merge Ready. |
CPU |
Current workspace CPU usage. |
RAM |
Current workspace memory usage. Turns warning-colored near the configured memory limit. |
Cost |
Agent usage cost when available, otherwise compact token usage. Workspace rows sum task tokens when task usage exists. |
RE |
Review repository link. Selecting it opens the configured git review or diff tool. |
IS |
GitHub issue link and status. Open issues are green; closed issues are magenta. |
T |
Issue type for the active task or task row. Types include bug, docs, enhancement, improvement, regression, and dependency upgrade. |
PR |
Pull request link and state. Open PRs are green, drafts are dark gray, rejected PRs are red, and merged PRs are magenta. |
Base |
Pull request target branch, shortened when needed. |
G |
Git status for task worktrees. Yellow means uncommitted changes, cyan means unpushed commits, red means both, and blank means clean and pushed. |
C |
Copilot review state for open PRs. N means no Copilot review, ? means requested, and C means completed. |
B |
Build/check status for open PRs. Yellow means running, green means succeeded, and red means failed. |
S |
Sonar status for open PRs. Yellow means running, green means succeeded, and red means failed. |
R |
Open PR review-thread count. Green means no unresolved review threads; red means at least one unresolved thread. |
Description |
User description, automation status, session title, or task progress text. |
Blank status cells mean multicode does not currently have that link or status for the row.
When a queued issue or task row is focused, the help line only shows shortcuts that are usable for that row's current state.
| Shortcut | Meaning |
|---|---|
↑ / ↓ |
Move between selectable rows. |
← / → / Tab |
Move focus from the row to one of its issue or PR link cells. |
Enter |
Attach to the task session when the workspace is running, or start the workspace and attach when it is stopped. |
o |
Open the task's GitHub issue. |
p |
Open the task's pull request. Shown only when the task has a PR link. |
c |
Open the task checkout in the configured terminal diff/compare view. Shown only when a task checkout is available. |
e |
Open the task checkout in the configured editor. Shown only when a task checkout and editor integration are available. |
a |
Approve publishing local task work. For Codex tasks this asks the agent to create or update the PR, or publish already prepared review/Sonar fixes. |
r |
Ask Codex to rebase the task PR. Shown when the task has an open or refreshable PR and rebase is allowed. |
v |
Ask Codex to address unresolved PR review feedback. Shown when the open PR has unresolved review threads. |
f |
Ask Codex to fix failing CI for the task PR. Shown when the PR exists and CI is not known to have succeeded. |
z |
Ask Codex to fix Sonar failures for the task PR. Shown when the open PR has a failing Sonar status. |
m |
Ask Codex to merge the PR. Shown only when the PR is open, green, approved, has no unresolved review threads, and has no local unpublished task work. |
y |
Request a Copilot PR review. Shown when the task has a PR link. |
| configured tool key | Run a configured exec tool against the task checkout. Shown only for usable task-scoped tools. |
x |
Remove the issue from the workspace queue. The confirmation dialog lets you remove it, remove and ignore it, or cancel. |
q |
Quit the TUI. |
When an issue or PR link cell inside the issue row is focused, the shortcuts change:
| Shortcut | Meaning |
|---|---|
↑ / ↓ |
Move between matching link targets when more than one exists. |
Enter |
Open the focused link with the configured handler. |
o |
Open the focused GitHub issue or PR in the browser. |
Esc |
Return focus to the issue row. |
q |
Quit the TUI. |
When a workspace is assigned to a GitHub repository, multicode can scan for issues and queue multiple issue tasks in that workspace.
Queueing is controlled in config.toml with the [autonomous] section:
[autonomous]
max-parallel-issues = 5
issue-scan-delay-seconds = 900
scan-on-startup = true
idle-runtime-cleanup = false
idle-runtime-cleanup-delay-seconds = 300
idle-runtime-cleanup-interval-seconds = 900
idle-runtime-restart = falseNotes:
max-parallel-issuescontrols how many issue tasks a workspace may queue at once.- The default
max-parallel-issuesvalue is5. - If a workspace already has that many queued tasks, it will not scan in additional issues until you remove, finish, or otherwise clear some of the existing tasks.
issue-scan-delay-secondscontrols how often background issue scans are retried. The default is900seconds (15 minutes).scan-on-startupcontrols whether an assigned workspace immediately begins autonomous issue scanning when it starts with an empty queue. The default istrue.idle-runtime-cleanupcontrols whether multicode periodically runsgradle --stopand then terminates any remaining Gradle daemon/worker processes inside Apple container workspaces that have stayed idle. The default isfalse.idle-runtime-cleanup-delay-secondscontrols how long a workspace must remain idle before the first Gradle cleanup attempt. The default is300seconds.idle-runtime-cleanup-interval-secondscontrols how often multicode repeats that cleanup while the workspace stays idle. The default is900seconds.idle-runtime-restartcontrols whether multicode also recycles an idle Apple container runtime after that cleanup. The default isfalse.- Set
scan-on-startup = falseif you want to start a workspace without it auto-populating issues. In that mode you can still queue work manually withior usento queue the next available issue on demand.
The agent used inside workspaces is configured globally in config.toml with the [agent] section.
OpenCode remains the default:
[agent]
provider = "opencode"
# Backwards-compatible top-level command list.
opencode = ["opencode-cli", "opencode"]
[agent.opencode]
commands = ["opencode-cli", "opencode"]To use Codex instead:
[agent]
provider = "codex"
[agent.codex]
commands = ["codex"]
model = "gpt-5-codex"
model-provider = "openai"
approval-policy = "never"
sandbox-mode = "external-sandbox"
network-access = "enabled"Notes:
provideris global for the whole multicode instance. A single TUI session uses either OpenCode or Codex for all workspaces.commandsis the host-side command resolution order. The first installed command is used.- OpenCode keeps the existing top-level
opencode = [...]setting for backwards compatibility. If[agent.opencode].commandsis omitted, multicode falls back to that list. - Codex workspaces use
codex app-serverinside the isolate/container andcodex resume --remote ...when attaching from the TUI. profileis optional for Codex. If you set it, it must name a real profile from your host~/.codex/config.toml.- For Codex,
approval-policy = "never"suppresses approval prompts,sandbox-mode = "workspace-write"keeps Codex's own sandbox active, andsandbox-mode = "external-sandbox"tells Codex to trust the outer multicode sandbox such as the Apple container runtime. network-access = "enabled"is the practical setting for issue fixing workflows that need GitHub, dependency downloads, or web access. Withexternal-sandbox, this is sent as Codex app-serversandboxPolicy.networkAccess.runtime.imageis still the global image override. If you want separate images, useruntime.opencode-imageandruntime.codex-image.
Workspaces are isolated from each other. This isolation is for safety and convenience, it does not provide
security. SSH keys to clone git repos and GitHub CLI credentials to create PRs remain available to each workspace,
but are read-only to reduce potential impact when an AI agent randomly goes haywire. Only the workspace directory is
writable, and some shared directories (e.g. ~/.gradle).
Workspaces also have get limited in the amount of RAM and CPU they can use. This prevents OOM conditions in one workspace from affecting other workspaces or even your desktop.
Isolation is implemented using systemd-run (for resource constraints) and
bwrap (for read/write isolation). These tools are Linux only, so
multicode will not work on other operating systems.
On newer Apple Silicon Macs, there is also an experimental Apple container runtime backend. It
reuses the existing [isolation] configuration for readable, writable, isolated, and tmpfs
paths, and maps CPU / memory limits onto container allocation settings. It also supports a
per-workspace file descriptor ceiling via [isolation].nofile.
If you want to run multicode on macOS with backend = "apple-container", the practical
requirements are:
- An Apple Silicon Mac. The Apple container backend is intended for newer Apple Silicon systems.
- The Apple
containerCLI installed and working on the host.multicodeshells out tocontainer run,container exec,container list, andcontainer build. - A working Rust toolchain on the host so you can run
cargo run --bin multicode-tui .... tmuxon the host. The TUI uses it for interactive attach sessions.- The host-side agent CLI installed for the provider you choose:
- OpenCode:
opencode-clioropencode - Codex:
codex
- OpenCode:
- A local Apple container image for the selected provider.
- GitHub authentication on the host if you want issue scanning, PR creation, builds, review status, or authenticated git pushes.
There are also image-level requirements. The container image must contain the tools the agent uses inside the isolated workspace, not just on the host:
gitgh- the provider CLI you selected:
- OpenCode image:
opencode - Codex image:
codex
- OpenCode image:
- the language/toolchain your repositories need, for example Java / Gradle for Micronaut work
This repository already contains an Apple-container build recipe. To build the local images:
./apple-container/build-local.shThat script expects the host container CLI to be available and produces:
multicode-java25:latestandmulticode-opencode-java25:latestfor OpenCodemulticode-codex-java25:latestfor Codex
Host configuration also matters because Apple-container workspaces deliberately reuse some host state:
- OpenCode reuses host config from
~/.config/opencodeand authentication from~/.local/share/opencode/auth.jsonwhen you mount them. - Codex workspaces synthesize a per-workspace
CODEX_HOMEfrom the host~/.codexconfiguration, includingconfig.toml,auth.json,AGENTS.md, andskills. - Git uses your host
~/.gitconfig, which multicode exposes automatically inside Apple containers viaGIT_CONFIG_GLOBAL. - If you want GitHub integration or MCP access inside the container, configure
[github]and pass through any required token env vars with[isolation].inherit-env.
The minimum setup flow on macOS is therefore:
- Install and verify the Apple
containerCLI on the host. - Install Rust and
tmuxon the host. - Install the host agent CLI you want to use (
opencodeorcodex). - Authenticate that agent on the host so the reused host config files actually contain valid credentials.
- Build an Apple container image, or provide your own compatible image.
- Set
[runtime].backend = "apple-container"and pointimage,opencode-image, orcodex-imageat that image. - Configure
[isolation]mounts for caches and credentials you want the workspace to reuse, such as~/.m2/repository,~/.config/gh, and provider-specific config paths. On macOS Apple containers, prefer isolating~/.gradleper workspace instead of bind-mounting the host cache tree.
If container is missing, the image does not include the selected agent CLI, or the host does not
have the matching CLI for TUI attach, Apple-container workspaces will start or attach incorrectly.
OpenCode example:
[agent]
provider = "opencode"
[runtime]
backend = "apple-container"
opencode-image = "ghcr.io/example/multicode-opencode-java25:latest"
[autonomous]
idle-runtime-cleanup = true
[isolation]
writable = ["~/.m2/repository", "~/.config/gh"]
readable = ["~/.config/opencode", "~/.local/share/opencode/auth.json"]
isolated = ["~/.gradle", "~/.local/share/opencode", "~/.local/state/opencode"]
tmpfs = ["/tmp"]
inherit-env = ["HOME", "PATH", "XDG_RUNTIME_DIR"]
memory-max = "16 GiB"
cpu = "300%"
nofile = 16384Mounting ~/.config/opencode read-only lets the container see the same profiles, models,
skills, and other OpenCode configuration as the host. This is useful if you manage local
profiles with tools like ocp. Keep ~/.local/share/opencode and ~/.local/state/opencode
isolated so session state remains per-workspace.
Codex example:
[agent]
provider = "codex"
[agent.codex]
commands = ["codex"]
model = "gpt-5-codex"
model-provider = "openai"
approval-policy = "never"
sandbox-mode = "external-sandbox"
network-access = "enabled"
[runtime]
backend = "apple-container"
codex-image = "ghcr.io/example/multicode-codex-java25:latest"
[autonomous]
idle-runtime-cleanup = true
[isolation]
add-skills-from = ["./workspace-skills"]
writable = ["~/.m2/repository", "~/.config/gh"]
isolated = ["~/.gradle"]
inherit-env = ["HOME", "PATH", "XDG_RUNTIME_DIR"]
memory-max = "16 GiB"
cpu = "300%"
nofile = 16384For Codex, multicode prepares a synthetic per-workspace CODEX_HOME inside the isolate/container.
It copies the host ~/.codex/config.toml, ~/.codex/auth.json, ~/.codex/AGENTS.md, and
~/.codex/skills, then merges in any add-skills-from mounts. This keeps Codex session state
isolated per workspace while still reusing your host configuration and credentials.
If you want Codex to behave more autonomously inside an Apple container, prefer:
[agent.codex]
approval-policy = "never"
sandbox-mode = "external-sandbox"
network-access = "enabled"That combination keeps multicode's Apple container as the real isolation boundary while avoiding repeated Codex approval prompts for normal shell execution.
If you maintain two images, the practical split is:
[runtime]
backend = "apple-container"
opencode-image = "ghcr.io/example/multicode-opencode-java25:latest"
codex-image = "ghcr.io/example/multicode-codex-java25:latest"Use the OpenCode image for the existing OpenCode workflow and a Codex image that includes the codex CLI and any Codex-specific bootstrap you need.
To build both local Apple-container images from this repository:
./apple-container/build-local.shThe Codex image build pins the installed Codex CLI to the version declared in
apple-container/Containerfile
via CODEX_VERSION so container behavior stays reproducible across rebuilds. Update that build arg
when you intentionally want to move the image to a newer Codex release.
You can also override the pinned version at build time without editing the file:
CODEX_VERSION=0.120 ./apple-container/build-local.shThat script produces:
multicode-java25:latestandmulticode-opencode-java25:latestfor the OpenCode workflowmulticode-codex-java25:latestfor the Codex workflow
The split keeps the existing OpenCode image compatible while allowing the Codex image to install Codex-specific tooling without changing the OpenCode bootstrap path.
Both Apple-container images also set GRADLE_OPTS=-Dorg.gradle.daemon=false so Gradle defaults to
non-daemon execution inside workspace containers.
On macOS Apple-container backends, isolating ~/.gradle per workspace is strongly recommended.
The host Virtualization VM process can retain very large numbers of FDs for ~/.gradle/caches
entries that the guest has traversed, which can create host-wide FD pressure even when no Gradle
daemon is left running inside the container.
Apple workspaces also expose the host ~/.gitconfig automatically. The runtime mounts it through
an internal read-only path and sets GIT_CONFIG_GLOBAL so git can use your host global identity
and defaults without requiring a direct file bind.
If your Apple-container workspaces need Docker, for example to run docker ps or use
Testcontainers, the host needs a working Docker-compatible engine first. multicode does not start
or provision that engine for you.
Practical requirements:
- Install a Docker-compatible host runtime such as Docker Desktop or Rancher Desktop.
- Make sure the host
dockerCLI is installed and onPATH. - Make sure the active Docker context points at a local Unix socket.
- If you use Rancher Desktop, enable its admin/rootful networking access and restart it. Without that, Apple-container guests may be able to use the mounted Docker socket but still fail to reach Docker-published host ports, which breaks Testcontainers and Ryuk callbacks.
You can verify the host setup with:
docker context inspect --format '{{.Endpoints.docker.Host}}'
docker psThe context host should resolve to a local Unix socket such as:
unix:///var/run/docker.sockunix://~/.rd/docker.sock
When that is true, Apple-container workspaces auto-detect the active host Docker context, mount
the host socket into the container as unix:///var/run/docker.sock, and export DOCKER_HOST so
docker ps works inside the workspace by default.
If you use Testcontainers inside Apple-container workspaces, you also need a host DNS alias so the
guest can reach host-published ports. multicode uses TESTCONTAINERS_HOST_OVERRIDE=host.multicode.test
for that path.
You can check whether the alias already exists:
container system dns listIf host.multicode.test is missing, create it once on the host as an administrator:
sudo container system dns create --localhost 192.0.2.123 host.multicode.testNotes:
- The
--localhostvalue must be a non-loopback IP. Do not use127.0.0.1. 192.0.2.123is a fixed example alias IP, not your machine's real LAN address.container system dns listonly reports the domain name, not the configured redirect target.- Once the alias exists, Apple-container workspaces can reach host services and Docker-published
ports through
host.multicode.test:<port>. - If the alias is missing, Docker can still work inside the workspace via the mounted socket, but Testcontainers host-port access will fail.
- With Rancher Desktop, host-port callback reachability may still fail until admin access is enabled and Rancher Desktop has been restarted.
With the GitHub integration you can see progress at a glance in the overview screen, and navigate to the issue or PR without copying links.
When an agent emits a <multicode:repo>/path/to/repository</multicode:repo> message, that repository will show up in
the overview panel. If you select the repo icon with your arrow or tab keys and press enter, the repository will be
opened in a configurable git viewer (e.g. sublime merge).
An issue link can be emitted as <multicode:issue>https://github.com/example-org/example/issues/1234</multicode:issue>.
The up-to-date issue status (open/closed) will be shown in the UI. Interacting with the icon opens it in the browser.
A pull request link can be emitted as <multicode:pr>https://github.com/example-org/example/pull/1234</multicode:pr>.
The current status will show (draft/open/merged/rejected) in the UI. There are also icons for build status
(running/success/fail) and review status (no reviewers/pending/changes requested/approved).
It is recommended to use a skill that instructs the agent to emit these tags while working.
Alternatively, you can add entries manually in the TUI, but this is tedious.
To authenticate with GitHub, you need to configure a personal access token (PAT)
(required scopes: public_repo, read:user). In config.toml, there are three approaches to configuring this token:
[github]
# Token from macOS Keychain
token = {keychain-service = "multicode.github", keychain-account = "github-mcp-token"}
# Token from environment variable
token = {env = "GITHUB_MCP_TOKEN"}
# Token from command (GitHub CLI)
token = {command = "gh auth token"}On macOS, the Keychain approach is recommended because the token is not left in shell startup files or other plaintext config. The env variable and command approaches remain available as fallbacks. It is prudent to use a token that has more limited access than the GitHub CLI.
To store the token in Keychain and configure multicode to use it:
security add-generic-password -U \
-a github-mcp-token \
-s multicode.github \
-w 'YOUR_GITHUB_PAT'You can verify the stored token can be read:
security find-generic-password -a github-mcp-token -s multicode.github -wThen configure:
[github]
token = {keychain-service = "multicode.github", keychain-account = "github-mcp-token"}
populate-git-credentials = trueTo give agents access to GitHub, there are two options to set. populate-git-credentials will set environment
variables that authenticate git instances inside the isolate with GitHub. This allows the agents to push changes to
repos they have cloned with HTTPS. It also provides GH_TOKEN and GITHUB_TOKEN inside the isolate for GitHub-aware
tools. The inherit_env option is only needed if you explicitly want to pass through additional host environment
variables; it is not required when using the Keychain-backed GitHub token source.
[github]
token = ...
populate-git-credentials = trueYou can enter a custom description for each workspace to be shown in the overview panel. This is useful for keeping notes.
When you're done with a workspace, you can archive it. The workspace files will be compressed, and the workspace will be moved to the bottom of the UI. You can unarchive it again when needed.
multicode-remote is a helper tool to run a multicode instance on a remote machine. Features:
- Dependency installation (bubblewrap, opencode, codex, ...)
- Synchronization of local agent configuration (including authentication details)
- Synchronization of GitHub credentials
- Bi-directional synchronization of the agent workspace
- Opening links in the local browser or git diff viewer
# cross compilation of micronaut-tui
cargo install cross --git https://github.com/cross-rs/cross
make multicode-remote
target/debug/multicode-remote --ssh opc@192.168.0.1 config.toml




