Workspace Mode - Multi-Repository Support¶
Workspace mode allows a single Nyia Keeper session to work across multiple related repositories simultaneously.
Quick Start¶
-
Add your repositories with
--workspace-edit: -
Run any assistant - workspace mode is auto-detected:
The File Is Always There¶
| Conf content | Mode | Confirmation |
|---|---|---|
| Absent | normal | none; the launch creates the template |
| Exact Nyia template | normal | none |
Comments or sync_branches= only |
normal | once per change |
| At least one repository line | workspace | once per change |
| A line that does not parse | launch refused | not asked |
- Created in the project root and in each rw repository.
- Bound read-only in every container. A session reads it and cannot write it.
- Shows as
??ingit status, likeexclusions.conf. An untracked exact template does not count as an uncommitted change for Nyia.
Confirmation¶
The launch prints the real paths, modes and other changed lines, then asks y/N.
| Situation | Result |
|---|---|
Saved with --workspace-edit |
no prompt |
| Hand edit, or a conf from a clone | asks once |
| A listed symlink now points elsewhere | asks once |
| Project folder moved | asks once |
| No terminal (CI, scripts) | launch refused; run --workspace-edit once in a terminal |
| Conf or repo path changes during launch | launch refused; run again |
nyia exclusions and nyia git-history never prompt: an unapproved conf means a plain project, with a notice.
| Command | Effect |
|---|---|
--workspace-edit |
Opens a private copy in $VISUAL, $EDITOR or vi, validates, writes in place, records approval. Needs a terminal. |
--workspace-init |
Writes the template. Accepts a comment-only file; refuses repository lines or directives. |
Configuration File Format¶
The .nyiakeeper/workspace.conf file uses a simple line-based format. Each line has a path followed by a mandatory access mode (ro or rw):
# Comments start with #
# Directives (optional, key=value format)
sync_branches=true
# Repository entries: <path> <ro|rw>
~/projects/repo1 rw # Read-write — full git guards
~/projects/api-specs ro # Read-only — no git needed
/absolute/path/to/docs ro # Non-git dir OK for ro
"~/projects/path with spaces" rw # Quoted paths supported
# Empty lines are ignored
Access Modes¶
| Mode | Mount | Git Required? | Branch Sync? | Use For |
|---|---|---|---|---|
rw |
Read-write | Yes (must be git repo) | When enabled | Repos you edit |
ro |
Read-only | No | No | Reference material, docs, specs |
Directives¶
Directives use key=value format and control workspace behavior:
| Directive | Values | Default | Effect |
|---|---|---|---|
sync_branches |
true / false |
false |
Auto-sync RW repo branches to work branch |
consolidated |
true / false |
false |
The root holds the members' Nyia state (Consolidated Mode). Another value refuses the launch |
Directives can appear anywhere in the file (before, after, or between repo entries).
Parsing Rules¶
- Unquoted paths: last whitespace-delimited token is the mode; everything before it is the path
- Quoted paths: wrap the path in
"double quotes", mode follows after the closing quote - Directives:
key=valuelines (only known directives likesync_branches=are intercepted) - Mode is case-insensitive (
RO,ro,Roall work) - Inline comments are NOT supported — use comment-only lines instead
- Old-format lines (path only, no
ro/rw) produce a clear error
Requirements¶
- Each path must be a valid directory
- RW repos must be git repositories (contains
.git/) - In each RW repo,
.git/config,.git/hooksand.git/commondirare read-only in the container, also where the repo appears inside another mount. Example:git remote addfails there; see THREAT_MODEL.md. - RO repos just need to exist (git not required)
- The workspace cannot list itself (self-reference check)
- Symlinks are automatically resolved to canonical paths
- Paths are absolute after
~expansion, with no.., control character,,or:
Refused Paths¶
$HOME, its ancestors, the Nyia home, and any directory containing or inside it- The workspace root and its ancestors
~/.ssh,~/.gnupg,~/.aws,~/.config,~/.local,~/.docker,~/.kube,~/.password-store, and anything below them- The Nyia config home and run directories, and any directory containing them
How It Works¶
Container Mount Structure¶
When workspace mode is active:
/project/{workspace-hash}/ # Main workspace (your current directory)
/workspace-repos/ # Additional repositories
├── shared-library-a1b2c3d4/ # RW repo (with hash suffix, :rw mount)
├── api-client-e5f6g7h8/ # RW repo (with hash suffix, :rw mount)
└── reference-docs-i9j0k1l2/ # RO repo (with hash suffix, :ro mount)
The hash suffix prevents collisions when multiple repos have the same basename.
Each assistant is allowed these directories and finds them listed in its prompt. They are outside the working directory, so Claude Code does not load a member's nested CLAUDE.md or .claude/ on its own. Old launches left an empty root-owned repos/ in the workspace root; check that it holds only empty directories, then sudo rm -r <root>/repos.
Consolidated Mode¶
One project holds the Nyia state of its read-write members. A member gets no Nyia files.
consolidated=true |
absent or false |
|
|---|---|---|
| Member state | <root>/.nyiakeeper/projects/<id>/ |
the member's own .nyiakeeper/ |
| Plans | the root's | the member's |
| Files in an rw member | .nyiakeeper-workspace-member only |
control files |
| Member mount | /workspace-repos/<id> |
/workspace-repos/<name>-<hash> |
| Direct launch in an rw member | refused | allowed |
- Id: the directory name, lowercased, other characters as hyphens, 1 to 40 characters. An invalid id or two members with one id refuse the launch.
- Nesting: a member inside the root, the root inside a member, or a member inside a member refuses.
- Marker:
.nyiakeeper-workspace-member, written at first launch in each rw member and excluded through.git/info/exclude. To leave: remove the member from the root'sworkspace.confand delete the marker. - Hidden: if present in a member, rw or ro, these are covered by a read-only placeholder, and the launch prints one line per member:
.nyiakeeper,.claude,.codex,.opencode,.vibe,.mcp.json,CLAUDE.md,AGENTS.md,OPENCODE.md,VIBE.md,GEMINI.md. A team'sCLAUDE.mdis therefore not read in the session. A symlink at one of these paths refuses the launch. - Exclusions:
<root>/.nyiakeeper/projects/<id>/exclusions.conf, created at first launch, a placeholder in the box. A!line refuses the launch: a member's list can only hide more for now. A member's old.nyiakeeper/exclusions.confno longer applies, and each launch says so. Nothing is moved or deleted. - Notes:
<root>/.nyiakeeper/projects/<id>/context.md. - Sharing: the root's
.gitignoresharesprojects/*/exclusions.confandprojects/*/context.md. - Git history cutoff: the root's setting applies to members.
- Git in the box: placeholders and the marker stay out of
git add -A, through a container-onlycore.excludesFile(this replaces anycore.excludesFileof your own, in that member, for the session). Tracked placeholders get skip-worktree, cleared at exit. After a crash clear them yourself:git update-index --no-skip-worktree -- CLAUDE.md. - ro member: no marker, nothing written. It can still be opened as its own project, with its own state.
Direct launch in an rw member:
Nyia refuses to start: this project is a member of a consolidated workspace. Its Nyia state lives in the parent project. Open the parent, or remove this project from the parent's workspace.conf and delete .nyiakeeper-workspace-member.
Branch Naming¶
Branch names follow the standard format: {assistant}-{timestamp}
- Example: claude-2025-01-15-143025
The same branch name is used across all RW repositories in the workspace. RO repos are not touched.
Exclusions¶
Each repository can have its own exclusions:
repo1/.nyiakeeper/exclusions.conf # Applied to repo1 mount
repo2/.nyiakeeper/exclusions.conf # Applied to repo2 mount
workspace/.nyiakeeper/exclusions.conf # Applied to main workspace
Built-in security exclusions (.env, .key, .pem, etc.) are applied to all mounts (both RO and RW). Edit each exclusions.conf on the host: inside the container it is a placeholder. Each launch creates <repo>/.nyiakeeper/exclusions.conf (commented header) in every RW repository that has none, so a session cannot plant one for the next launch; list a repository that is not for assistant work as RO, and nothing is written into it (unless it lies inside an RW mount, such as the workspace root: there it is writable for the assistant, so it gets the conf too). A created .nyiakeeper/ is also added to the repository's .git/info/exclude. An RW repository whose directory you cannot write refuses the launch: make it writable, or list it as RO. Check the result with nyia exclusions list. For full details, see MOUNT_EXCLUSIONS.md.
RO vs RW Behavior Summary¶
| Operation | RW repo | RO repo |
|---|---|---|
| Must be git repo | Yes | No |
| Docker mount mode | :rw |
:ro |
| Branch sync | Yes | Skipped |
| Branch rollback | Yes | Skipped |
| Uncommitted changes check | Yes | Skipped |
| Exclusions applied | Yes | Yes |
Limitations¶
RAG (Codebase Search)¶
RAG is automatically disabled in workspace mode. Multi-repository indexing is not yet supported. You'll see a warning:
Warning: RAG disabled in workspace mode (multi-repo indexing not yet supported)
Workspace contains 3 additional repositories
Git Operations¶
- Git operations in the container target the main workspace only
- Each additional RW repo maintains its own git state
- Commits made by the assistant go to the workspace repo
Branch Synchronization¶
By default, Nyia Keeper warns when RW workspace repos are on different branches but does not automatically sync them. This lets you intentionally work with repos on different branches.
Default Behavior (Warn Only)¶
When you start a workspace session, Nyia Keeper checks all RW repos. If any are on a different branch than the work branch, you'll see:
Warning: Workspace repos on different branches than 'feature/my-feature':
shared-library (/home/user/projects/shared-library): main
api-client (/home/user/projects/api-client): develop
To auto-sync branches, set workspace_sync=true in config or workspace.conf
Enabling Auto-Sync¶
To have Nyia Keeper automatically create/switch branches on all RW repos:
Per-workspace — add a directive to workspace.conf:
Globally — set the config key:
Precedence: workspace.conf directive > global config > default (false).
To disable sync for a specific workspace even when global is true:
Atomic Rollback (Sync Mode Only)¶
When sync is enabled, if branch creation fails on any RW repository: - All RW repositories are rolled back to their original branches - The work branch is deleted from any repos where it was created - You'll see an error message indicating which repo failed and why
Common failure reasons: - Uncommitted changes in an RW workspace repo (commit or stash first) - Inaccessible repository path - Branch already exists with conflicts
Explicit Branch Names¶
Use --work-branch to specify the branch name:
Status Display¶
When workspace mode is active, the status display shows:
Git Status
Branch: claude-2025-01-15-143025
Commits: 42
Changes: Clean working directory
Mode: Workspace (3 repos: 2 rw, 1 ro)
Use Cases¶
Monorepo Alternative¶
Work on related packages without a monorepo structure:
Cross-Project Refactoring¶
Make coordinated changes across multiple projects:
Code + Reference Documentation¶
Edit code while referencing read-only docs/specs:
Mixed: Editable + Reference¶
# workspace.conf
~/services/my-service rw # This is what I'm working on
~/services/upstream-api ro # Just for reference
~/docs/architecture ro # Read-only architecture docs
Troubleshooting¶
"Missing access mode (ro/rw) for: ..."¶
Your workspace.conf uses the old format (path only). Add ro or rw at the end of each line.
"Workspace repo not found"¶
The specified path doesn't exist. Check the path in workspace.conf.
"Workspace repo is not a git repository"¶
An RW directory exists but isn't a git repo. Either run git init or change it to ro.
"Workspace cannot include itself"¶
Remove the current directory from workspace.conf - it's already the main workspace.
Repos with Same Name¶
If you have multiple repos named app/:
"Failed to sync branches across workspace repositories"¶
Branch synchronization failed on an RW repo. Common causes: - Uncommitted changes: Commit or stash changes in the failing repo - Inaccessible repo: Check the path exists and is readable - Permission issues: Ensure you have write access to create branches
All RW repos will be rolled back to their original branches when this happens. RO repos are never affected.
Workspace Root as a Project¶
The directory you launch Nyia from is the workspace root — the main workspace itself. You do not need a separate, empty directory to host a workspace.
Starting from an existing repo is fully supported. If the workspace root contains a .git/ directory, Nyia automatically treats it as a full project — with branch safety (assistant branch creation, cleanup, and sync) — alongside the additional repos you list. Those additional repos are mounted under repos/; the root project is the workspace root itself and is not placed in repos/.
Because the root is already the main workspace, do not list the root (current) directory in workspace.conf — Nyia rejects it with an error. Listing only the additional repos is correct; "the root project is not under repos/" is by design.
A fresh, non-git root also works. If the root has no .git/, it's treated as an orchestration directory only — branch operations are skipped for the root and applied solely to the workspace repos.
This is automatic — no extra flags needed. In short: launch from a repo or from a plain directory; either is valid.
Unsupported: Subdirectories of Git Repositories¶
Adding a subdirectory of a git repository as a workspace entry (e.g., ~/monorepo/packages/my-service rw) is not supported.
Why: Inside the Docker container, only the specified directory is mounted. The parent repository's .git/ directory is not included, which means:
- git status, git diff, git blame, git log — all fail
- No assistant branch creation or cleanup (no safety net)
- The AI assistant loses all git context for that directory
Workaround: Mount the full repository and use nyia's exclusion system to control what the AI can access:
1. Add the full repo: ~/monorepo rw
2. On the host, create ~/monorepo/.nyiakeeper/exclusions.conf and list the directories you don't want exposed (e.g., other-packages/, infrastructure/)
3. Edit it on the host only (inside the container it is a placeholder)
4. Verify: nyia exclusions list ~/monorepo
This gives you scoped access WITH full git functionality.
Alternatively, add the subdirectory as read-only if you don't need git operations:
Best Practices¶
- Keep workspace.conf in version control - Share the workspace setup with your team
- Use absolute or home-relative paths - More reliable than relative paths
- Mark reference repos as
ro- Prevents accidental writes, skips git overhead - Group related repos only - Don't include unrelated projects
- Consider security - Excluded files in one repo don't affect another
- Document the workspace - Add comments explaining why each repo is included