Skip to content

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 = false
kind = "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.

RuntimeMachineIsolation boundaryNew-session projectSupplemental directoriesResource choiceTarget lifecycle
barelocal (Linux or macOS controller machine)noneExisting local directory, optionally an isolated Git clonenohost-ownedMachine and user-selected directory persist; a managed clone is archived and retired on suspend.
barean ssh machine (named remote Linux host)none beyond host/accountExisting remote Git directory, optionally an isolated clonenohost-ownedHost and user-selected directory persist; the managed clone and staging areas are retired on suspend.
podmanlocal (Linux/WSL2, or macOS with a rootless podman machine)rootless containerBundlecopy-on-write or read-only mountsCPU and memoryContainer and workspace storage are removed after verified stop.
podmanan ssh machinerootless containerBundleremote-host copy-on-write or read-only mountsCPU and memoryRemote container and workspace storage are removed after verified stop.
dockerlocal with a Linux Docker daemon (including a VM on macOS), or an ssh machineDocker containerBundlecopy-on-write or read-only OverlayFS viewsCPU and memoryContainer and managed workspace volume are removed after verified stop.
apple-containerlocal (Apple-silicon macOS 26+)Apple container VMBundleread-only mountsCPU and memoryContainer is removed after verified stop.
apple-containeran ssh machine (Apple-silicon macOS 26+)Apple container VM on the remote MacBundleremote-host read-only mountsCPU and memoryRemote 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.

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.

Mjolnir selects approval behavior from the runtime, then translates it into the chosen harness’s controls:

RuntimeEffective policy
bare on localPreserve 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 runtimeUnconstrained 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.

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.

[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.

[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.

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.

[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.

[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.

[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.

[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.

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.

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.

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>.

TargetSource locationWritable request
Local PodmanController hostPodman’s isolated overlay view.
Local DockerController hostManaged OverlayFS volume.
Apple ContainerController hostDowngraded to read-only.
SSH PodmanRemote container hostPodman’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.

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.

Container pull_policy accepts:

ValueLaunch behavior
autoUse the existing image. A missing image is downloaded when the daemon starts; eligible moving tags are also refreshed hourly. Apple resolves it during provisioning.
alwaysRefresh during launch.
newerRefresh when the runtime supports a newer-only check; Docker treats it as always.
missingPull only if absent.
neverNever 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.

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.