Durability and recovery
Mjolnir separates a logical session from the target currently running it. The target-side worker makes current activity durable; a verified recovery archive lets the controller recreate the session after that target is stopped or lost.
These are complementary layers. A live worker has the newest journal and queue. A recovery archive is the latest controller-held point from which a new target can be built. Work newer than that archive is recoverable only while the original target and its worker storage still exist.
The target-side journal
Section titled “The target-side journal”Every session worker owns an append-only, ordered event journal and a compact operational snapshot on its target. Commands and observations become durable there before the worker acknowledges them. That includes queued prompts, so a prompt accepted behind an active turn does not depend on an open terminal or web connection.
Journal records carry an ordinal and content digest. On startup, the worker loads its durable snapshot, validates and replays only the bounded crash tail that follows it, and serves older history from journal segments when the controller asks. Mjolnir 2’s current records are self-describing: each record’s digest verifies that record, while ordered frontiers prove which exact prefix a controller or checkpoint has accepted. Legacy chained records remain readable, but new documentation should not treat the current journal as a single hash chain.
The controller materializes journal events into its local session database and acknowledges a verified frontier. Acknowledgement alone does not permit the worker to discard history. Journal data is eligible for collection only after it is both durably acknowledged by the controller and covered by a verified checkpoint.
Queued work continues in order
Section titled “Queued work continues in order”The worker promotes queued prompts one at a time, in the order they were
accepted. Closing the TUI with prefix+q, closing a browser tab, or stopping the
controller daemon does not turn those accepted prompts back into client-side
drafts.
The queue waits behind an active prompt, configuration change, checkpoint barrier, or close operation. Agent-started work and background commands also count as live work; Mjolnir does not declare a session quiet merely because the last user prompt appears complete.
Codex goals across restarts
Section titled “Codex goals across restarts”An active /goal keeps the session busy between native turns, even after the
initial prompt finishes. Automatic worker upgrades and routine checkpoints wait
until the goal is paused or ends. Older workers that cannot report goal state
are not automatically replaced.
After an unavoidable recovery restart, Mjolnir observes any native continuation and resumes a quiescent active goal without resetting its budget or usage. Paused, blocked, limited, completed, and cleared goals stay inactive.
An explicit Restart or Resume pauses an active goal before opening the native session. Use the ! question indicator to answer Resume this goal? in the prompt area. Choosing Keep paused or cancelling leaves it paused. If a saved model needs replacement, choose that model before resuming the goal.
What a recovery checkpoint contains
Section titled “What a recovery checkpoint contains”A checkpoint barrier waits for earlier effectful commands and any harness- started turn to finish. Once admitted, it briefly freezes new ACP dispatch at a specific event frontier while Mjolnir captures the state needed for a coherent archive.
The archive includes:
- the canonical transcript and session state at the captured frontier;
- queued prompts and queued configuration changes;
- each repository’s committed delta, staged and unstaged changes, and untracked files;
- an allowlisted set of native session artifacts for the selected harness, including the selected Codex goal and its budget/accounting; and
- manifests describing the session, bundle, target provenance, versions, and payload hashes.
Harness credentials and configuration are excluded from native checkpoint artifacts. They are supplied again from the selected controller-side profile when a target is resumed. Repository origins containing embedded credentials are also rejected or redacted at the archive boundary.
Checkpointing protects project workspaces. Installed packages, the rest of the
target user’s home, /tmp, container layers, and other files outside the
declared project workspace are ephemeral. Directory attachments are not a
substitute for project workspace storage; copy durable results into a project
repository or push them to a remote.
Verification before teardown
Section titled “Verification before teardown”The target builds the archive and reports its SHA-256. After transfer, the controller compares the received bytes with that digest, validates the archive structure and each payload hash, and records the exact journal frontier and frontier digest it covers. Restore performs the same structural and payload verification before changing a destination.
Only a successfully installed, verified archive advances the worker’s recovery floor. If export, transfer, verification, or persistence fails, the previous archive remains in place and the live target is left usable. Mjolnir does not remove a target merely because checkpointing was attempted.
Automatic and manual checkpoints
Section titled “Automatic and manual checkpoints”The recovery coordinator observes completed turns. When a session is idle and its newest checkpoint is at least ten minutes old, it captures the newest completed turn. The first eligible completed turn can be captured without waiting for an older checkpoint to age.
A failed automatic checkpoint is reported and retried after a backoff that starts at ten minutes and doubles to a two-hour ceiling. An automatic attempt has a 15-minute deadline so a wedged transfer cannot own the lifecycle gate forever.
Request a fresh checkpoint for an active session with:
mj checkpoint --session <session-id>The barrier still waits for a safe capture point. A session with an active turn, harness-started work, terminal, background command, queued prompt, or another checkpoint operation is not quiet and may defer the copy. See Troubleshooting if a checkpoint never becomes eligible.
Detach, suspend, destroy, and resume are different
Section titled “Detach, suspend, destroy, and resume are different”Detach closes only the current client. The daemon, worker, harness, and
queue keep running. In the TUI use prefix+q; running mj later reattaches.
Suspend session… (prefix+shift+x) preserves the logical session. A
normal suspension records its intent,
creates or safely reuses a verified archive, closes and seals the relay at the
captured frontier, and only then removes the exact managed target. If a
checkpoint fails, suspension fails non-destructively and can be retried. A controller
restart can continue a previously recorded closing transition.
Resume provisions a fresh selected target, verifies and restores the archive, restages current credentials, and reconnects the logical session. You may select a different compatible target and profile. The resume flow asks whether archived queued work should be started or discarded.
Move combines a verified interruption, checkpoint, source teardown, and resume under one daemon-owned operation. It is available for a live session when changing its target, profile, or both. Preparation checks compatibility before the active turn is interrupted, and confirmation explicitly acknowledges that interruption.
A move that keeps the same target, the same attached directories, and the same resource allocation replaces only the harness, in place. The container or bare worker root, the workspace, untracked files, and build caches are kept; the old worker daemon and profile home are removed inside the existing environment, the new profile is staged there, and the harness state is restored from the checkpoint. Running process memory is still lost, because the old harness process is stopped.
A move that changes the target still rebuilds the environment from the checkpoint. Such a move does not preserve running process memory, installed packages, container layers, or arbitrary files outside the declared workspace.
If an in-place swap fails, or the daemon restarts while it runs, Mjolnir does not retry in place: it releases the environment and leaves the session stopped with its verified checkpoint, so you can retry the move or resume with the previous settings.
The same target/profile compatibility rules used by resume still apply.
The move confirmation lists queued prompts and configuration commands. Discard is the default and restores the destination idle. If you choose Start, the daemon waits for verified destination readiness before admitting the retained commands in order. An interrupted active prompt is never replayed.
Resource sizing is inherited from the source unless the caller explicitly clears it; attached directories remain fixed to the workspace. A cancelled or failed Move that retains a checkpoint is shown with Retry move and Resume with previous settings. If queue admission already began, recovery keeps the same durable destination and queue choice, and refuses replay on a replacement relay.
When the same harness is selected, Mjolnir restores its allowlisted native session state and verifies that the expected native session opened. When the harness changes, Mjolnir starts a new native session and generates a size-bounded, tool-free handoff from the canonical transcript. Queued work that you choose to retain is replayed into the new relay.
Cross-harness handoff requirements.
The handoff is direct, tool-free inference. Mjolnir selects a configured profile
with a usable utility model and considers its quota before provider preference.
Within the same quota class, the preference is Codex GPT Luna, Muse Spark, Grok,
Kimi, then a DeepSeek profile running its newest DeepSeek Flash model: a
DeepSeek-on-Codex profile, or a DSH profile whose base URL is DeepSeek’s own
api.deepseek.com and whose DEEPSEEK_API_KEY is set.
A Z.ai (GLM) Codex profile is not a utility model. Model versions are resolved
from live provider catalogs. Claude profiles are not utility models: a Claude-only configuration
needs another supported profile before cross-harness handoff can work.
The visible conversation and declared repository state survive a harness change; harness-private state is not translated into another harness’s native history. Move restores the harness from the checkpoint rather than transferring live processes, whether or not it keeps the environment.
Destroy session… permanently removes the session record, environment, and
recovery archive, without taking a new checkpoint. Mjolnir indexes the
conversation in SessionWiki before removing the record. The archived
conversation remains searchable and can seed a new session through
mj resume --wiki <id>, but it cannot restore the deleted checkout or files
that were never pushed or exported.
An independent managed clone owns its own branches. Suspension verifies a checkpoint before deleting the clone, and Resume recreates it from the source and saved Git work. Destroy removes that recovery copy and can discard work which has not been pushed. Older linked-worktree sessions retain their branch in the source repository; their destroy dialog can also delete that branch.
When a suspension fails, Discard changes since checkpoint… is narrower: it requires an existing verified archive, skips the fresh checkpoint, tears down the current target, and leaves the session resumable from that older archive. It asks a second time before it runs. Any work after that archive can be lost.
Session commands and confirmation screens are covered in Session lifecycle.
What survives a failure
Section titled “What survives a failure”| Failure or action | What happens |
|---|---|
| TUI detaches or browser closes | Worker, harness, and durable queue continue |
mj daemon stop or controller restart | Detached target workers continue; a new daemon reconnects and catches up from their journals |
| Controller host is unavailable | Remote workers can continue already queued work; local targets still depend on that host staying up |
| Worker process dies or becomes unresponsive | Mjolnir diagnoses it, restarts a confirmed dead or unresponsive worker, and recovers relay state from its on-target snapshot and journal |
| Target is lost | Resume can recover only through the newest verified controller-side archive |
| Controller state loses track of a still-running managed target | mj recover scan can find the orphan and mj recover adopt can add it back |
Mjolnir is conservative about live restarts: it does not kill a worker merely because one connection failed. It distinguishes a dead worker, an unresponsive handshake, a worker still recovering its journal, and a live process whose transport is temporarily unavailable.
To find untracked managed resources:
mj recover scan --jsonmj recover adopt --session <session-id> --target <target-id>Older resources without current ownership metadata may additionally require
--profile and --bundle. Adoption is preferable to destruction when the
resource may contain work not present in a recovery archive. See the recovery
flow in Troubleshooting.
Upgrades wait for a quiet worker
Section titled “Upgrades wait for a quiet worker”At launch, the daemon captures the available worker binaries. It uses those same builds for new sessions, recovery, and upgrades until it restarts; rebuilding or replacing files on disk does not change a running daemon’s workers. Restart the daemon to adopt newly installed worker builds.
The daemon compares each connected worker with its captured build. An outdated worker is replaced in place only at a quiet moment: no active prompt, autonomous harness turn, queued prompt, foreground tool, user shell, agent terminal, background command, or checkpoint barrier.
Replacing a worker also ends its ACP bridge, so a session that never becomes quiet keeps its current worker rather than sacrificing live work. Failed upgrades retry with bounded backoff. The next stopped-session resume always provisions a fresh target and installs the daemon’s captured worker.
Relay protocols and archive schemas are versioned. A build that cannot safely understand a stored format reports an explicit compatibility error instead of attempting a partial conversion. Keep the controller current before relying on a long-lived archive for disaster recovery.