Mjolnir (mj) is a terminal control plane for coding agents. It runs many long-lived
agent sessions — Codex, Claude Code, Kimi Code, Grok Build, and DeepSeek Harness — in disposable
isolated environments, keeps them working while you are away, and gives you one
dashboard for their sessions, quotas, and credentials. Agents connect through
the Agent Client Protocol (ACP).
Mjolnir 2.0 is a new product generation: the session control plane replaces the 1.x interactive client. The last 1.x release remains available at the v1.17.0 tag.
Running one coding agent in one terminal works. Running six of them across two Codex accounts and a Claude account, on three machines, overnight, does not. Mjolnir exists for the second case.
- Sessions survive everything. Prompts queue durably on the target and keep executing in order while your terminal is closed or your laptop is off. Every session records a hash-chained event journal. Recovery archives are verified end to end before Mjolnir tears anything down, and crashed or wedged workers are detected and restarted automatically.
- Full-access mode without fear. Isolated targets run the agent in its unrestricted mode — no permission prompts — because the blast radius is a disposable container or instance, not your machine.
- Your credentials stay canonical. Each profile keeps one credential set on your machine. Mjolnir copies a minimal allowlist into each target, reconciles rotating OAuth tokens across every live session within about a minute, and structurally excludes credentials from event streams and recovery archives.
- One view of capacity. Sessions, per-profile quota and usage, and host capacity in one dashboard — and on your phone through the persistent Mjolnir daemon.
- Agents can operate it.
mj doctor --jsonandmj setup instructionsare designed so your coding agent can converge a host to session-ready by looping on machine-readable checks.
- Run many concurrent, long-lived agent sessions and make them durable: detached execution, verified recovery archives, resume onto a fresh target.
- Make unrestricted agent modes safe by pairing them with disposable, isolated environments.
- Keep provisioning minimal and deterministic: per-harness allowlists, SHA-256-verified workers and archives, no snowflake state in targets.
- Give one control plane across harnesses and profiles: sessions, quotas, credentials, and remote control in one place.
- Fail loudly. A failed checkpoint leaves the session usable and says so; retired formats are rejected, never half-converted.
- Stay operable by both humans (TUI, web) and coding agents (JSON output, scriptable CLI).
- Mjolnir is not an agent. It does not write code, plan, or pick models. It manages harnesses that do.
- No privileged host setup. Mjolnir will not install container runtimes, edit
subuid/subgid, create AWS launch templates or security groups, or make SSH hosts reachable. You (or your agent, with your credentials) do that;mj doctorverifies it and prescribes the exact remediation. - No wholesale environment transfer. SSH and GPG keys, shell dotfiles, editor configuration, package-registry credentials, cloud configuration, and toolchain state are never copied into targets.
- Not a team server. One controller process owns a session store, enforced by an OS-backed lock. The web server is a personal remote control with one viewer credential, not a multi-user service.
- Not an orchestration platform. Containers are unnamed disposable templates, rebuilt from checkpoints rather than upgraded in place. There is no scheduler and no load-based admission; overcommit is your call.
- No compatibility shims. Old relay protocols and archive schemas are rejected with a clear error instead of being partially converted.
| Harness | Credentials & quota | Checkpoint/restore of native state |
|---|---|---|
| Codex | yes | yes |
| Claude Code | yes | yes |
| Kimi Code | yes | yes |
| Grok Build | yes | yes |
| DeepSeek Harness | credentials yes; usage-priced, no subscription quota | yes |
The set is extensible by design: these five are reference integrations, not a closed list. A new ACP-speaking harness needs a launch recipe or bridge, its credential file shapes and login command, its home environment variable, a checkpoint allowlist for native session state, and optionally a quota reader. Issues and pull requests for new harnesses are welcome.
| Target | Kind | Where it runs | Agent mode |
|---|---|---|---|
| Local Git worktree | local-bare |
your machine | your configured approvals |
| Podman container | local-podman |
Linux, WSL2 | unrestricted |
| Docker container | local-docker |
Linux, WSL2 | unrestricted |
| Apple container | apple-container |
macOS 26+, Apple silicon | unrestricted |
| SSH machine | ssh-bare |
a Linux host you name | guardian or unrestricted |
| Podman over SSH | ssh-podman |
a Linux host you name | unrestricted |
| EC2 instance | aws-ec2 |
your AWS account | unrestricted |
The controller (the mj binary you run) supports Linux and macOS. Windows is
not supported; use WSL2.
curl -fsSL https://raw.githubusercontent.com/BrokkAi/mjolnir/master/install.sh | shThis downloads a verified release into ~/.local/bin — no Rust toolchain
needed. Each release ships mj, the voice worker, and the static musl session
workers that Mjolnir uploads into disposable targets. Run mj doctor next.
The installer also supports --prefix and --version; see --help.
npm works too:
npm install -g @brokkai/mjolnirAs does building from source:
cargo build --release
./target/release/mjFor container targets, pull the published multi-arch agent image (public, no authentication):
podman pull ghcr.io/brokkai/mjolnir/agent-dev:latest
# or
docker pull ghcr.io/brokkai/mjolnir/agent-dev:latestIt includes Rust, cargo-nextest, Node, OpenJDK 25, Git, GitHub CLI, the Codex
and Claude ACP bridges, and pinned DeepSeek Harness plus dsh-acp-server
packages.
It also bakes in Playwright's Chromium system libraries and the Chromium
headless shell (in PLAYWRIGHT_BROWSERS_PATH=/ms-playwright), so headless
browser tests run without a privileged install or a run-time download,
and the profiling tools perf, cargo-flamegraph, samply, and heaptrack
(perf also needs the host's kernel.perf_event_paranoid to be 1 or lower, or
--cap-add SYS_ADMIN on the container).
Coverage runs in a session too: the image carries the llvm-tools-preview
component, cargo-llvm-cov at the version .github/workflows/coverage.yml
pins, and lcov for genhtml.
See docs/src/content/docs/custom-images.md
to build your own.
- Run
mj. The first run creates a named workspace and opens a plain-terminal setup dialog: it finds your local harness homes, checks that credentials look present, detects the current GitHub repository, configures each usable local container runtime as its own target, and writesconfig.tomlafter you confirm. - Run
mj doctor(ormj doctor --json) and fix what it reports, until it is clean. Log in to any profile that needs it withmj login --profile <id>. - Press
Tabto focus Sessions, thennto create a session: pick a profile, a repository bundle, and a target. Focus returns to the prompt; send your first message. - Detach whenever you like (
Ctrl+Q). The session keeps running and your queued prompts keep executing. Reattach by runningmjagain, or open the daemon-owned web viewer shown bymj daemon status.
Mjolnir's TUI is one screen. From top to bottom: Sessions, the transcript of the conversation you are in, the Prompt composer, Targets, Quota, and a footer that names the keys for whatever has focus. Nothing is behind a navigation step, so you can read an agent's output while seeing what your other agents are doing and how loaded your machines are.
Mjolnir opens on the session whose agent spoke most recently, with the cursor in Prompt.
Tab moves the keyboard down the layout — Sessions, Prompt, Targets, Quota —
and Shift+Tab reverses it. Once the support panes are collapsed the ring is
the two panes that are still lists. The transcript is not a Tab stop: read it
with the mouse wheel or PageUp/PageDown from wherever you are.
Ctrl+G is a two-position dial: panes open, or panes collapsed for the
conversation. Collapsed, Targets and Quota become one summary row each — host
names with CPU load, EC2 fleets with how many machines they are running,
profile names with weekly quota remaining — and the session list shrinks to a
fixed grid, one line per session, unless your terminal is taller than it is
wide, in which case the list stays a list.
Ctrl+G always leaves the keyboard in Prompt: asking for room around the
conversation and asking to work in it are the same gesture.
Tab leaves the dial where you set it.
F2 opens the workspace picker, F3 the web viewer, and Ctrl+Q detaches.
The panes take plain keys, because the composer is a separate focus and never
sees them. On Sessions: Enter opens the selection, n creates a session, s
opens Resume, e edits, a marks everything read, x cancels an operation in
flight, Space and 1–9 collapse and expand projects. On Targets and Quota:
r refreshes and Enter or e opens that row's actions. Every list also takes
the arrow keys, j/k, Ctrl+N/Ctrl+P, and Home/End.
Escape belongs to the conversation: it cancels a running turn or a shell
command, and closes a dialog. It never quits.
In an attached TUI or the phone viewer, start a message with ! to run the
rest as bash -lc inside that session's target. Shell commands run in the
session workspace without blocking an active agent turn. Their bounded live
output is saved in the transcript and included once as hidden context on the
next prompt submitted after the command finishes. Press Escape in the TUI, or
use the shell's Cancel button in the viewer, to stop it.
Configuration lives at ~/.config/mjolnir/config.toml (the platform-equivalent
directory elsewhere). The first-run dialog writes a working single-target
setup; everything beyond that is edited in TOML. A minimal example:
version = 1
[profiles.codex-1]
kind = "codex"
home = "/home/me/.codex"
[profiles.claude-1]
kind = "claude"
home = "/home/me/.claude"
[bundles.myapp]
primary_repo = "myapp"
[[bundles.myapp.repositories]]
id = "myapp"
github = "your-org/myapp" # or: local = "/home/me/src/myapp"
destination = "myapp"
[targets.podman]
kind = "local-podman"
image = "ghcr.io/brokkai/mjolnir/agent-dev:latest"
# Optional: auto (default), always, newer, missing, or never. Auto refreshes
# remote latest tags, keeps versioned tags cached, and pins digest references.
# pull_policy = "auto"
# Docker uses the same fields:
# [targets.docker]
# kind = "local-docker"
# image = "ghcr.io/brokkai/mjolnir/agent-dev:latest"version is the config schema version. A file written by a newer Mjolnir still
loads: the settings this build understands keep working, and the config becomes
read-only, so the older build refuses to save and never downgrades the file.
mj doctor reports that state. Update Mjolnir, or change settings with the newer
build, to make it writable again.
Profiles point at harness home directories on your machine — run as many
profiles per harness as you have accounts. Bundles describe the repositories a
session checks out (multi-repository bundles give agents a virtual monorepo).
Mjolnir-owned worker and bridge commands use non-login shells. On raw local, SSH,
and EC2 targets, Mjolnir makes one bounded login-shell probe when each worker starts
and carries only its discovered PATH into the non-login runtime; an explicit
environment.PATH in the profile takes precedence. Later profile changes take
effect after the worker restarts or the session resumes. Agent-requested shell
commands still run as bash -lc and intentionally use the session user's login
environment. If automatic discovery is insufficient, set a target-side ACP
bridge path with the profile's executable key or set an explicit search path
under [profiles.<id>.environment] with PATH = "...".
Target prerequisites and full option lists are covered in
docs/PODMAN.md, docs/DOCKER.md,
docs/SSH.md, and
docs/AWS.md.
The daemon starts the authenticated web viewer by default. Run
mj daemon status for its URL and six-digit login code. Without Tailscale it
serves HTTP only on 127.0.0.1:3765.
When the local Tailscale node has MagicDNS and HTTPS Certificates enabled, Mjolnir
automatically requests the node's trusted ts.net certificate and serves HTTPS
on all interfaces at the same port. Certificate issuance runs in the background
and may take about 30 seconds the first time; certificates renew daily without a
daemon restart. If HTTPS Certificates are unavailable, the status output keeps
the viewer loopback-only and explains how to enable them. After changing the
tailnet setting, run mj daemon restart.
The historical configuration section remains [phone]. Explicit certificate
configuration takes precedence over automatic Tailscale detection:
[phone]
# Set false to disable the web viewer entirely.
enabled = true
bind = "127.0.0.1:3765"
# Set false to keep the viewer loopback-only without probing Tailscale.
tailscale_detect = true
# tls_cert = "/path/to/cert.pem"
# tls_key = "/path/to/key.pem"- Execution policy is selected by target, then translated into each harness's
own controls. Containers and EC2 targets run unconstrained. Named raw SSH
targets (
ssh-bare) explicitly selectpermissions = "guardian"to preserve configured approvals orpermissions = "yolo"for unconstrained execution. A local worktree (local-bare) also preserves the profile and harness's configured approval behavior. Codex, Claude Code, and Grok Build expose guardian modes; Kimi Code and DeepSeek Harness do not, so Mjolnir shows a prominent warning when guardian permissions cannot be enforced on a target. - Harness homes are copied by allowlist, not wholesale. For Claude Code, for
example: credentials, settings,
CLAUDE.md,skills/, andplugins/— no transcripts, history, or caches. Mjolnir setsCODEX_HOME,CLAUDE_CONFIG_DIR,KIMI_CODE_HOME, orGROK_HOMEin the target. Skill edits on your machine propagate to live sessions within about a minute. - Credentials travel only between the controller and a session's worker. They
are never written to the event journal or recovery archives. When the
controller's
ghis authenticated, Mjolnir continuously pushes its active GitHub token to every live non-local session, including raw SSH targets. The token is not stored in archives. - A repository configured with
localis served to workers through a per-session Git protocol bridge over the session's own transport:git fetchand fast-forwardgit push originoperate on your checkout with no inbound port and no writable mount. Force pushes, ref deletion, and receive hooks are disabled; pushes to a dirty checked-out branch are rejected. Git LFS is not supported through the bridge. - Attached directories reject symbolic links, so an attachment cannot escape its source or destination tree.
- The daemon's web viewer requires a six-digit code exchanged for a signed
session cookie. It binds only to loopback unless explicit TLS is configured
or automatic Tailscale detection obtains a trusted
ts.netcertificate.
Mjolnir saves a recovery copy automatically after completed turns when the session
is idle (at most every ten minutes), and mj checkpoint --session <id>
forces one. Recovery archives are verified end to end; a normal Stop writes
and verifies the archive before any teardown, and refuses teardown if
verification fails. Explicit force-destroy is the data-loss escape hatch.
A stopped session resumes by provisioning a fresh target from its archive, with its pending prompt queue intact (resume asks whether to keep or discard it). A session recorded under one harness can be resumed under another; Mjolnir condenses the transcript into a size-bounded handoff for the new harness.
If Mjolnir or its host crashes, workers and their queued prompts keep running.
mj recover scan finds managed containers and instances that are no longer
tracked; mj recover adopt reconnects one as a tracked session.
Mjolnir is licensed under GPL-3.0-only.