Targets
A runtime is a named execution template under [targets.<id>], and it names
the machine it runs on under [machines.<id>]. A session combines one
profile, one target, and either a configured
bundle or an existing
project directory. The target decides where work runs, what isolation
contains unrestricted agent actions, how the workspace is created, and what is
removed after a verified stop.
Local target choices are supplied automatically. Add SSH and AWS connections or customize runtime defaults in prefix+s Settings → Machines and prefix+s Settings → Runtimes. No setup command is required.
Mjolnir checks what is there when the dashboard starts and each time you open
the new-session or resume picker, in the background. It looks at this
computer directly and at each SSH machine with one SSH call, and adds what is
new: a runtime for every engine a machine newly runs (Podman, Docker, Apple
container, or a bare worker), on every machine. The pickers list only what the
latest check found there. Nothing is shown as “checking”: until an answer
arrives the picker shows the last one, and the list changes only when an
answer does. A runtime that is gone, such as Docker that stopped or a host that
did not answer, is left out and named under the list with the reason and the
fix; a standard local runtime whose engine was never installed is left out
without a word. A machine that still has no runtime says why in Settings, in
the picker and in mj doctor. Launching still checks the runtime, so one that
disappears after the last check is refused with its reason.
A runtime you do not want stays off with enabled = false; detection never
adds back a kind the machine already has configured, enabled or not:
[targets.builder-docker]enabled = falsekind = "docker"machine = "builder"Settings → Runtimes has the same Enabled checkbox. A disabled runtime is left
out of every picker and refused by launch, resume and move; sessions already
on it keep running. Dashboard prefix+shift+r refreshes host capacity and
checks again what is there. So does Refresh in the menu
under the dashboard’s Targets title (click the title, or press . while the
pane has focus). The same menu’s Runtimes… opens Settings → Runtimes, and
its Machines… opens Settings → Machines. The optional mj doctor --smoke
command exercises container creation and removal.
Capability matrix
Section titled “Capability matrix”| Runtime | Machine | Isolation boundary | New-session project | Supplemental directories | Resource choice | Target lifecycle |
|---|---|---|---|---|---|---|
bare | local (Linux or macOS controller machine) | none | Existing local directory, optionally an isolated Git clone | no | host-owned | Machine and user-selected directory persist; a managed clone is archived and retired on suspend. |
bare | an ssh machine (named remote Linux host) | none beyond host/account | Existing remote Git directory, optionally an isolated clone | no | host-owned | Host and user-selected directory persist; the managed clone and staging areas are retired on suspend. |
podman | local (Linux/WSL2, or macOS with a rootless podman machine) | rootless container | Bundle | copy-on-write or read-only mounts | CPU and memory | Container and workspace storage are removed after verified stop. |
podman | an ssh machine | rootless container | Bundle | remote-host copy-on-write or read-only mounts | CPU and memory | Remote container and workspace storage are removed after verified stop. |
docker | local with a Linux Docker daemon (including a VM on macOS), or an ssh machine | Docker container | Bundle | copy-on-write or read-only OverlayFS views | CPU and memory | Container and managed workspace volume are removed after verified stop. |
apple-container | local (Apple-silicon macOS 26+) | Apple container VM | Bundle | read-only mounts | CPU and memory | Container is removed after verified stop. |
apple-container | an ssh machine (Apple-silicon macOS 26+) | Apple container VM on the remote Mac | Bundle | remote-host read-only mounts | CPU and memory | Remote container is removed after verified stop. |
“Verified stop” means Mjolnir has created a recovery archive and checked its SHA-256 on the controller before tearing the resource down. See Durability and recovery.
Harness versions
Section titled “Harness versions”Mjolnir installs and caches its pinned harness runtimes for every bare session,
on this machine, on an SSH machine, and on an EC2 instance. Every session uses
a staged copy of the selected profile’s allowlisted files and credentials,
while its runtime is managed independently
of commands such as codex installed for native terminal use. Containers use
the runtimes supplied by their image.
New workers use the versions shipped with your Mjolnir build. Upgrade an existing session’s worker to adopt those versions; running workers retain their current runtime until upgraded.
Execution policy
Section titled “Execution policy”Mjolnir selects approval behavior from the runtime, then translates it into the chosen harness’s controls:
| Runtime | Effective policy |
|---|---|
bare on local | Preserve the profile and harness’s configured approvals. |
bare on an SSH machine, permissions = "guardian" | Preserve configured approvals. |
bare on an SSH machine, permissions = "yolo" | Unconstrained. |
| Every container runtime | Unconstrained inside the isolation boundary. |
The unconstrained translation is Codex agent-full-access, Claude Code
bypassPermissions with its sandbox disabled, Kimi Code auto, Grok Build
always-approve with its sandbox disabled, and OpenCode
"permission": "allow" in opencode.json. DSH selects dsh-acp’s
permission_mode allow. Those all approve every action;
Kimi’s mode happens to be named auto but is not a risk-selective guardian.
Muse instead stages :ask-me, selects allowAll approvals, turns
muse-acp’s auto-review off, and disables only the sandbox
(--disable-sandbox), so it runs without permission checks like the others.
A Muse profile with muse_auto_review = true opts back into the reviewer
(promptUnmatched approvals with auto-review on), which answers each
approval so critical actions can still be denied. Muse’s guardian on raw
targets is that same review with the sandbox kept on.
Codex, Claude Code, Grok Build, and Muse Code can preserve guardian approvals
on raw targets. Kimi Code cannot. OpenCode asks before tools
according to its permission rules, and DSH, with dsh-acp’s permission_mode
ask, asks before every write, command, and MCP tool call. Neither has a
sandbox, so a prompt is the only thing between the agent and the host.
The dashboard’s new-session and resume wizards display a prominent warning
when any of these is selected without an isolation boundary; mj new, the
API, and the web viewer do not warn. Read Security boundaries
before choosing a raw or yolo target.
Bare runtimes
Section titled “Bare runtimes”A bare runtime selects an existing absolute project directory rather than a bundle. A local bare runtime accepts a plain folder; an SSH bare runtime requires a Git project. Bare runtimes do not accept supplemental directory attachments or container resource sizing.
On local, that directory need not be a Git checkout, Git need not be
installed, and a new repository does not need its first commit. Such a folder
is a project in its own right, identified by its path, and the whole session
works in it. Nothing about it is captured in a checkpoint and no restore touches
its files, because the folder belongs to you and not to a managed checkout. A
committed Git checkout is still required wherever Mjolnir has to clone or move
the project into a bundle workspace, and on SSH targets, which address a
project by the repository it shares with its sessions.
Review captures changes from Git history, so a session in a local directory
without a committed checkout is created with review off. The New Session
review page says so before you create it, mj new prints the same notice, and
/review status in the session gives the reason. /review reports that it has
no Git changes to capture.
Git 2.39.5 or later is required on the controller and on SSH bare targets.
mj doctor checks both locations. Before-push review cannot review merged
work when Git is older.
Bare on this machine
Section titled “Bare on this machine”[targets.localhost]kind = "bare"machine defaults to local, so it is omitted.
The directory must exist locally. For a Git project, the final review offers
Create isolated checkout. It is checked by default for a primary checkout
and unchecked for an existing linked worktree; you can change either choice.
When checked, Mjolnir creates a separate checkout under
.mj/clones/<session-id> as an independent clone, starting on the remote default
branch (or the source’s current branch without a remote) and preserving the selected
subdirectory. Its commits and branch changes do not move refs in the source checkout.
Uncommitted source changes stay in the source checkout.
Uncheck it to use the selected directory directly. The choice survives stop and resume. Plain directories are used directly with the checkbox disabled.
Local bare runs on a Linux or macOS controller. It uses the native
mj-worker installed beside mj, so on macOS no Linux worker is needed for
it. Container and remote targets still need a static Linux worker; see
Install.
There is no process, filesystem, or network isolation between the harness and your controller account. The harness runs from a staged copy of the configured profile home, as on every target, but that copy isolates only its own state: the harness can still read your whole home directory, the profile home included. Use this target only when you trust both the agent and its approval configuration.
Bare on an SSH machine
Section titled “Bare on an SSH machine”[machines.builder]kind = "ssh"host = "builder.example.com"user = "ubuntu"workspace_prefix = ".local/share/hel/workspaces"
[targets.builder]kind = "bare"machine = "builder"permissions = "guardian"The wizard validates an existing Git directory on the remote host. The same
Create isolated checkout choice and defaults apply on that host. The remote
machine persists across sessions. Mjolnir-created clones and worker/profile staging
areas are lifecycle-managed; a linked worktree you selected yourself remains
yours. workspace_prefix controls a separate per-session lifecycle/cleanup
path, not the selected project or its linked-worktree location.
The host does not need a preinstalled harness bridge, except DSH. Its worker
installs and reuses the exact harness version pinned by Mjolnir in the remote
user’s cache; DSH instead runs the host’s own dsh-acp when it is 0.4.0 or
newer, or the pinned npm package. Node-based harnesses reuse Node.js 22 or newer
with npm and npx when already on the host; otherwise the worker downloads a
checksum-verified pinned Node.js into the user cache. Kimi, Grok, Muse, and
OpenCode also install from checksum-verified artifacts downloaded by Mjolnir;
they do not require curl, tar, or a shell-based vendor installer. DSH needs
a current dsh-acp on PATH or the managed Node fallback. Mjolnir never uses
sudo to add these prerequisites and does not fall back to another harness
executable from the remote PATH.
permissions accepts guardian or yolo and defaults to guardian. The
machine’s SSH connection fields include identity_file and extra_args. See
SSH and SSH Podman for host prerequisites, connection checks, and
workspace cleanup.
Container targets
Section titled “Container targets”All container targets use an image and accept optional pull_policy,
platform, cpus, memory, and target environment. If image is omitted,
they use Mjolnir’s published multi-architecture agent-dev image. An installed
release names the immutable image published for its own version, such as
ghcr.io/brokkai/mjolnir/agent-dev:2.36.0; a development build names
ghcr.io/brokkai/mjolnir/agent-dev:latest from master. Both keep the default
pull_policy = "auto", which for an immutable version tag pulls only when the
host has no copy.
It carries the supported bridges and common development tools. A plain image can work, but every new session may need to install Git, GitHub CLI, or a harness bridge; a missing Node.js is installed into the session user’s cache when a Node-based harness needs it. See Container targets and Custom images.
Podman
Section titled “Podman”[targets.podman]kind = "podman"Rootless Podman 4.0 or newer runs on Linux, WSL2, or macOS with a rootless
podman machine. It is the only runtime with configurable workspace backing:
a named volume by default, the container layer, or a host path managed through
an operator-supplied helper. A rootful machine and a manually configured remote
connection are not supported. See Podman.
Docker
Section titled “Docker”[targets.docker]kind = "docker"Docker requires a reachable Linux daemon. Mjolnir owns a managed volume for the
session workspace and uses managed OverlayFS volumes for writable supplemental
directories. Podman’s workspace_storage override is not accepted. See
Docker.
Apple container
Section titled “Apple container”[targets.apple]kind = "apple-container"platform = "linux/arm64"Apple’s container CLI requires Apple silicon and macOS 26 or newer.
Supplemental directories are mounted read-only; writable OverlayFS views and
Podman workspace-storage overrides are unavailable. See
Apple container.
An apple-container runtime on an ssh machine runs on that Mac. This lets a
Linux controller, or an Intel Mac, reach the Apple-silicon Mac beside it:
[machines.mac]kind = "ssh"host = "mac.example.com"user = "dev"
[targets.mac-apple]kind = "apple-container"machine = "mac"Settings › Runtimes offers that runtime when the probe finds Apple-silicon
macOS 26 or newer with a running Apple container daemon; mj doctor checks it
the way it checks a local one.
Podman on an SSH machine
Section titled “Podman on an SSH machine”[machines.builder]kind = "ssh"host = "builder.example.com"user = "ubuntu"
[targets.remote-podman]kind = "podman"machine = "builder"The machine carries the SSH connection; the runtime carries every Podman container field. The container and any workspace volume live on the remote host. Supplemental directory sources are therefore paths on that remote host, not paths on the controller. See SSH and SSH Podman.
The machine can be a Mac running a rootless podman machine, or Docker through
Docker Desktop or Colima. Mjolnir runs each command on a Mac with the
directories from /etc/paths, /etc/paths.d and Homebrew’s /opt/homebrew
added after the SSH session’s own PATH, so Homebrew’s podman and docker
are found without editing shell startup files.
AWS EC2
Section titled “AWS EC2”Create a persistent Podman host with mj ec2 create c8id.16xlarge --zone us-east-1a.
It adds an SSH machine and Podman target. Sessions share that host and its build
cache; closing a session removes its container. mj ec2 list --region us-east-1
lists managed hosts and mj ec2 destroy <name> --region us-east-1 terminates one.
See AWS EC2 for storage, ZFS, networking, and lifecycle options.
Bundles, clones, and workspace roots
Section titled “Bundles, clones, and workspace roots”Managed targets—Podman, Docker, Apple Container, SSH Podman, SSH Docker, Apple
Container over SSH—clone a configured bundle into a new session
workspace. primary_repo is the agent’s working directory and every other
repository is an additional ACP workspace root. Every repository clones its
network fetch remote’s default branch. A controller-side local path supplies
only its configured network fetch and push destinations; no Git connection
back to this checkout is created.
Local and SSH bare sessions instead choose an existing project directory. A single-repository isolated session can move into a raw local worktree when its source repository is available and contains the archive’s prerequisite history.
A local session can move or resume the other way, into an isolated target, when
its checkout is a whole Git checkout on this machine with a network remote
(https or ssh). The target clones that remote and the session’s unpushed
commits, staged, unstaged, and untracked files are restored over the clone. A
checkout with no network remote stays bare-only: add a remote (git remote add origin <url>) or keep resuming on a bare target. A subdirectory of a checkout,
an SSH-hosted checkout, and a multi-repository bundle cannot become one
checkout either way. A plain local folder has no repository to clone from, so
it also stays bare-only.
Muse Code supports one ACP workspace root, so it requires a single-repository bundle or one existing bare project directory, with no supplemental directories. See Workspaces and bundles for repository validation, dirty state, Git caching, and project memory.
Supplemental directories
Section titled “Supplemental directories”The terminal new-session and resume flows can attach directories that are not
part of the bundle. Each attachment has an absolute source, a unique safe
absolute destination, and a read-only choice. The default destination is
/mnt/<source-name>.
| Target | Source location | Writable request |
|---|---|---|
| Local Podman | Controller host | Podman’s isolated overlay view. |
| Local Docker | Controller host | Managed OverlayFS volume. |
| Apple Container | Controller host | Downgraded to read-only. |
| SSH Podman | Remote container host | Podman’s isolated overlay view. |
| Local/SSH bare | — | Unsupported. |
For Podman and Docker, NFS, SMB, FUSE, FAT-family, and existing overlay filesystems cannot safely host the requested overlay. Mjolnir detects these and forces the attachment read-only, reporting the reason. Sources must be absolute directory paths, and destinations must be unique safe absolute paths without parent traversal.
Attachments are per-session state in mj.sqlite3, not target fields in
config.toml. They can be changed for the next explicitly created environment through the
command palette (prefix+:) → Container settings where supported. See Session lifecycle.
CPU, memory, and instance sizing
Section titled “CPU, memory, and instance sizing”Container target cpus and memory values are backend fallbacks for launch
requests that do not supply a per-session allocation. The terminal wizard does
not seed from them: it starts with the remembered size for that physical host
or a baseline of 4 CPUs and 8 GiB. Checking Save as default sizing on a new
session makes that session’s choice the remembered size for the host. The
minimum accepted size is 2 CPUs and 4 GiB. The per-session choice overrides the
fallback. Later Container settings overrides win when that session’s
container is next created.
The EC2 host instance type is chosen when the host is created. Bare targets use the host directly and have no Mjolnir resource limit.
These choices are capacity controls. Mjolnir sums the CPU and memory limits of
live containers that share a physical host, reserving one fifth of physical
RAM (at least 2 GiB) for the operating system. CPU overcommit always produces
a warning and the launch proceeds. RAM overcommit also proceeds with a warning
for Docker and Podman, including on macOS: cgroup limits are ceilings that an
idle container does not use. Only Apple container targets treat RAM as a hard
commitment, because their VMs do not return freed memory to macOS. For those
targets, compression, swap, and reclaimable memory do not increase the budget;
an unavailable or incomplete budget refuses the launch. Create, resume, and
move share this policy. Mjolnir never stops or suspends sessions automatically.
Image pull policy
Section titled “Image pull policy”Container pull_policy accepts:
| Value | Launch behavior |
|---|---|
auto | Use the existing image. A missing image is downloaded when the daemon starts; eligible moving tags are also refreshed hourly. Apple resolves it during provisioning. |
always | Refresh during launch. |
newer | Refresh when the runtime supports a newer-only check; Docker treats it as always. |
missing | Pull only if absent. |
never | Never pull; fail if absent. |
Refreshing an image leaves running containers unchanged. New sessions use the
refreshed image; existing sessions require Upgrade container and restart…
and explicit acknowledgment of writable-layer loss. Restart retains the current
container and filesystem. Digest references remain pinned and
versioned tags remain cached under auto: the daemon downloads either one once
if the host lacks it, then only checks that it is still there. A localhost/
or local/ image cannot be downloaded from a registry, so a missing one is
reported once as a failure rather than retried into a notice every hour. Only
never keeps an image out of the daemon’s startup download.
Target environment versus profile environment
Section titled “Target environment versus profile environment”For container targets, [targets.<id>.environment] becomes environment inside
the target container. [profiles.<id>.environment] configures the harness and
ACP bridge. AWS and bare target variants do not accept a target environment
table, but profile environment still applies to their workers.
Do not write secrets into either table as plain strings: config.toml is
copied and read as ordinary configuration. Write { from_secret = "NAME" } to
read secrets.toml beside it, or { from_env = "NAME" } to read the daemon’s
environment; see Secrets. Use the
harness login flow for provider credentials and the controller’s GitHub token
flow for GitHub access. See Profiles and harnesses.
Full field reference
Section titled “Full field reference”The examples above intentionally omit optional connection and storage fields. Use the Configuration reference for every accepted field, exact defaults, and validation constraints. Use Troubleshooting when a target appears configured but fails its doctor check or launch.