Workspaces and bundles
Workspaces and bundles solve different problems:
- A workspace is a named control-plane view. It groups live sessions, drafts, and read state for one operator context.
- A bundle is a configured project shape. It tells a managed target which repositories to check out and which one is the agent’s primary working directory.
They are deliberately independent. The same bundle can be used by sessions in several workspaces, and a workspace can hold sessions from several bundles. Persistent project memory follows the project identity, not the workspace name.
Workspaces organize the dashboard
Section titled “Workspaces organize the dashboard”Run mj to open a workspace. If none exists, Mjolnir creates one using the
current directory name, then leaves the dashboard ready for an explicit new
session. Otherwise it opens the requested workspace or the most recently
opened workspace. Use either of these forms when you want to choose explicitly:
mj workspacesmj --workspace "Release work"--workspace matches names case-insensitively. In the terminal surface, a
bordered three-row Workspaces pane sits above Sessions. Its right-hand ≡
button opens the workspace manager. From the keyboard, focus the Workspaces
pane, move from the tabs to ≡ with Tab or Right, then press Enter;
Shift-Tab from the Sessions pane also lands on it. The command palette also
lists Workspaces (prefix+shift+n). Selecting a tab, or
pressing an arrow while the workspace tabs have focus, changes the live-session
filter immediately. Tabs are local
views, so sessions in other workspaces continue running independently. The web
viewer shows each workspace as a separate tab. The terminal surface needs at
least 60 columns.
The manager can create, rename, and delete workspaces and recover drafts. Names
are trimmed, must be 1–64 Unicode characters, cannot contain control characters,
and are unique case-insensitively. Release work and release work therefore
name the same workspace. Deleting the last workspace leaves the manager open;
Mjolnir does not create a session automatically.
Workspaces without a terminal
Section titled “Workspaces without a terminal”A script does not need the dashboard. mj workspaces list prints the
workspaces, and mj workspaces create <name> creates one — or selects the one
that already carries the name, so it is safe to run before every session. Both
are thin clients for GET and POST /api/v1/workspaces; see the
HTTP API reference.
mj new requires --workspace <name> or --workspace-id <id>, even when
there is only one workspace. A fresh instance has none; create one first:
mj workspaces create agentsmj new --workspace agents --project-directory . "Review this project"The HTTP create-session endpoint can infer the workspace only when exactly one
exists. Neither interface creates an implicit default workspace for a new
session. Plain mj still creates its first dashboard workspace from the current
directory name.
What belongs to a workspace
Section titled “What belongs to a workspace”A workspace owns the active presentation of:
- live sessions and their selected order;
- per-client read frontiers;
- detached terminal drafts; and
- per-browser conversation drafts.
Read state and browser drafts are client-specific, so opening a session on your phone does not consume another terminal’s unread marker or steal its unsent text. The daemon remains the owner of the actual sessions.
Suspended histories are global resume candidates. Resuming one moves it into the workspace from which you resume, even if its former workspace was deleted. This is why deleting an otherwise empty workspace does not erase stopped session history.
Delete safely
Section titled “Delete safely”Deleting a workspace first suspends its active sessions, preserving verified recovery copies and stopping their Mjolnir sub-agents. The confirmation also discards detached drafts and unsent composer text. Only after every suspension succeeds does Mjolnir remove the workspace and its drafts.
If a suspension fails or deletion is cancelled, the workspace and drafts stay; sessions already suspended remain suspended. Retry after resolving the failure. Retained session histories remain available in the global resume picker and can be resumed into another workspace. See Durability and recovery for session-level destruction and recovery guarantees.
Workspaces are stored in mj.sqlite3, not config.toml. Do not add a
[workspaces] table to the configuration file.
Bundles define managed projects
Section titled “Bundles define managed projects”Container, SSH Podman, and EC2 targets start from a bundle. A bundle can contain
one repository or assemble several repositories into a virtual monorepo. Its
primary_repo becomes the ACP session working directory; every other member is
provided to the harness as an additional workspace root.
[bundles.product]primary_repo = "app"
[[bundles.product.repositories]]id = "app"github = "acme/app"destination = "app"
[[bundles.product.repositories]]id = "shared"local = "/home/me/src/shared"destination = "shared"This produces a target workspace conceptually like:
<workspace-root>/├── app/ ← primary working directory└── shared/ ← additional workspace rootBundle and repository IDs use 1–64 ASCII letters, digits, ., -, or _;
. and .. are invalid. Repository IDs must be unique within the bundle, and
primary_repo must name one of them.
Each repository declares exactly one source:
githubacceptsowner/repository, a GitHub HTTPS URL, or a supported GitHub SSH URL.localnames an absolute controller-side Git repository whose network remote configuration is resolved at session creation. Its local commits and working tree are never copied into an isolated session.
destination is a non-empty relative path beneath the bundle root. It cannot
contain . or .., and two destinations cannot overlap. For example, api
and api/generated cannot coexist because one would contain the other.
See the Configuration reference for every accepted source form and validation rule.
Choose project files in the wizard
Section titled “Choose project files in the wizard”In a new terminal or web session, choose a saved project or browse for a repository. In the terminal, Add project opens the browser for a project that is not saved. You do not need to create or name a bundle first. The project picker offers:
- Recent: pick a project you have worked on before.
- GitHub: browse repositories available to the GitHub account already signed in on the computer running Mjolnir, or search by name. If needed, the picker explains how to install or sign in to GitHub CLI on that computer.
- Folders: open folders, go up a level, and choose a Git repository without typing its path. These folders belong to the computer running Mjolnir, not the phone or browser device.
- Paste URL: enter a GitHub URL,
owner/repository, or an existing local repository path.
Choose a repository to continue directly to the session review. Mjolnir saves
it for next time. In the terminal, Use several repositories together lets you
select several projects. In the browser, choose Select multiple repositories,
add repositories from GitHub, folders, or a pasted URL, then choose Use project.
The first repository is primary, where the agent starts; removing it makes the
next repository primary. Existing multi-repository configurations remain available
as saved projects. Edit config.toml for a custom repository layout.
Projects saves the complete repository set, primary repository, and layout. A subdirectory, symlink, linked worktree, and matching GitHub URL resolve to the same repository. Identity follows the fetch remote tracked by the default branch, so switching feature branches or using a separate push remote does not change the project. Selecting a single repository never adds other members of a larger saved project.
On first discovery, Mjolnir reads project directories from the ten newest native sessions in each configured harness home. It does not import those conversations. Later openings of Projects or the raw directory picker refresh from new Mjolnir session records. Unavailable directories are reported, other homes continue, and Retry discovery retries failed candidates. Local resolution uses Git; GitHub CLI remains necessary only for the existing GitHub browser/authentication integration.
Equivalent saved definitions are consolidated. Existing sessions retain their accepted repository IDs, layout, and fetch/push settings, and their history stays available through the old project IDs. Custom layouts and different primary repositories remain distinct saved projects.
Isolated sessions begin at the fetch remote’s default branch. Local unpublished
commits and working-tree changes are excluded; the review shows the source before
launch. git_ref is obsolete and is rejected with migration guidance.
Bare runtimes work differently. A new bare session, on this machine or on an
SSH machine, selects an existing absolute Git project directory instead of a
configured bundle. Its directory is represented internally as a project with
one repository; the selected directory remains the session’s working directory.
Its history and memory share repository identity with a managed project of one.
The final review offers Create isolated checkout, on by default for a
primary checkout and off for an existing linked worktree. When checked,
Mjolnir creates a session-specific independent clone under .mj/clones/ and
leaves source working-tree changes behind. When unchecked, the session uses the
selected directory directly. See
Targets.
GitHub repositories
Section titled “GitHub repositories”GitHub members clone the default branch of their configured source. For private HTTPS
repositories, Mjolnir looks for a token in GH_TOKEN, then GITHUB_TOKEN, then
the authenticated GitHub CLI. The active token is injected into managed
non-local sessions and kept out of checkpoints and recovery archives.
Local Podman, Docker, SSH Podman, SSH Docker, and Apple Container targets maintain a
read-only Git object cache on the container host under
~/.cache/mjolnir/git. Mjolnir refreshes a bare mirror, makes an isolated
per-session snapshot with hardlinked immutable objects, and lets the target
clone borrow from it. If cache setup is unavailable, the launch falls back to a
normal network clone.
The cache can contain objects from private repositories and is created with user-only permissions. Unused mirrors are pruned after 30 days and the mirror set has a 20 GiB least-recently-used soft cap. Session snapshots are removed with their containers.
Local paths and network remotes
Section titled “Local paths and network remotes”A local bundle member is a controller-side path used to resolve its network
fetch and push configuration. Mjolnir creates an independent target clone on
the fetch remote’s advertised default branch. The target’s origin keeps the fetch URL and the configured
push destination(s), including a separate push repository when one is set.
The source checkout is never mounted or copied into the target, and Mjolnir does not provide a Git service backed by it. Unpublished commits, staged changes, unstaged changes, and untracked files stay on the host. If the source has no usable network remote, isolated creation fails before provisioning; a raw local session is the only no-remote exception. Suspending an isolated session saves its checkpoint and does not publish a branch to the host checkout. New network-backed sessions can resume from their saved checkpoint, including work made after the clone.
Managed targets inherit a small allowlist of useful controller Git settings,
including identity, pull/rebase behavior, conflict style, rerere, and pruning.
Each new clone uses push.default=current so a normal push publishes its session
branch to the configured push destination(s). Targets do not inherit arbitrary
Git configuration or credential helpers.
Multi-root behavior
Section titled “Multi-root behavior”The primary repository is the session cwd. Other bundle repositories are
sent through ACP as additional workspace directories, so a capable harness can
reason across the set without pretending the repositories are one Git tree.
Each repository keeps its own .git, origin, session branch, dirty state, and
archive material.
Muse Code and MiMo Code ACP support one workspace root only. Pair either with a single-repository bundle or one bare project directory, and do not add attached directories. The other six supported harnesses, DSH included, accept multi-root bundles.
Attached directories are not bundle members. They are per-session supplemental resources selected in the creation or resume wizard, do not acquire Git origins, and have target-specific copy-on-write or snapshot behavior. See Session lifecycle and Targets.
Persistent project memory
Section titled “Persistent project memory”Mjolnir gives new sessions a small persistent knowledge store scoped to the project. It is shared across harnesses and sessions that resolve to the same project identity:
- a GitHub repository is identified by lowercased owner and repository;
- another network repository keeps its server’s case-sensitive path;
- a repository without a network remote uses its canonical repository root, qualified by host for SSH directories;
- raw and managed single-repository projects share repository memory; and
- a multi-repository project includes its primary repository, members, and layout, independent of member order.
The workspace name is not part of that identity. Two workspaces using the same project therefore share project memory; two different bundles do not merely because their display names happen to match.
What the agent sees
Section titled “What the agent sees”Each session gets a private project-memory directory in its staged harness
profile. Claude Code uses its native project-memory integration. Codex, Kimi,
and Grok receive the directory as a readable and writable workspace root and
use their own file tools to edit it. Muse can access it directly because its
sandbox is disabled. The startup context gives non-Claude agents its path and
supplies the first 200 lines or 25 KiB of MEMORY.md, so keep that index short
and link to focused documents there. The mj-memory MCP server provides only
session-history and code-provenance tools.
For each non-Claude memory file, keep one fact in a Markdown document with
frontmatter for name, description, and metadata.type. Add a one-line
pointer to each file in MEMORY.md. Update an existing note instead of
duplicating it, and delete notes that are wrong. Claude Code keeps its native
memory conventions. See Harness limitations.
A session can start without any of this. mj new --no-project-memory (or
"no_project_memory": true in the session-creation API) adds no startup
context, stages no memory directory, turns off Claude Code’s auto-memory, and
leaves out the mj-memory history tools. The session’s sub-agents inherit the
choice, and its memory is neither read from nor synchronized with the project’s
canonical copy. The choice is stored with the session and kept on resume and
Move. Other sessions are unaffected.
For a multi-root bundle, bundle-wide material lives at the virtual root and
repository-specific material may live below /roots/<repository-id>/.
The startup context also tells the agent which repository ID maps to each
workspace root.
Writes, limits, and synchronization
Section titled “Writes, limits, and synchronization”Project-memory files are synchronized at checkpoints and have these limits:
- 100 KiB per file;
- 1 MiB per synchronized snapshot;
- 1024 bytes per path.
Empty files, unsafe path components, hidden or reserved path segments, and symbolic-link traversal are not synchronized. Delete a file from the session replica to remove it from project memory at the next checkpoint.
Every session works on a private replica. At checkpoints, Mjolnir combines it with the canonical controller copy using a line-by-line three-way merge. Additions and deletions sync too. When two sessions change the same lines, an available utility model merges their edits; if none is available, the session being merged wins. Mjolnir does not create a conflicts folder.
The canonical copy lives below:
<MJ_DATA_DIR or platform data directory>/projects/<project-key>/memory/Memory is background context, not authoritative project state. Agents are told to verify it against the working tree. Do not store credentials or other secrets there; it is deliberately available to every session for that project and syncs at checkpoints.
Choose the right scope
Section titled “Choose the right scope”Use a workspace for “which sessions and drafts do I want in this view?” Use a bundle for “which repositories make up this project?” Use project memory for small durable facts the agent should carry between sessions. Use the repository itself for source code, design documents, and anything that belongs under version control.