repoman — Vision Doc
Status: v0.4.4 shipped 2026-05-11
Origin: Bash prototype at ~/.local/bin/repoman (~200 LOC), April 2026. Productized rewrite in reef-lang began v0.1 (2026-04-29); current release is v0.4.4.
Vision sentence
repoman is a safety harness for AI-assisted development: it isolates each project inside its own Incus (LXC) container, bind-mounts the repo into it, and mirrors everything to NFS — so AI coding agents can work with broad permissions inside the container while the host OS, sibling projects, and the source repository on disk stay protected.
Why this exists
AI coding agents — Claude Code, Aider, Cline, Continue, Gemini CLI, and the wave of others — are dramatically more productive when granted broad permissions. They are also dramatically more capable of "fixing" things you did not ask them to touch: editing the wrong file tree, running install scripts against the host, mass-replacing across a repo, deleting paths they misread, pushing stray commits.
The original problem (which seeded the bash prototype): run Claude Code with --dangerously-skip-permissions without taking the host down with it. The solution: per-project Incus containers + bind-mounted repos + NFS backup + ZFS snapshots on the file server. Doing it manually with raw incus and rsync works but accretes ad-hoc shell scripts. repoman makes the workflow first-class and turns the safety properties into invariants of the tool, not habits of the operator.
What "safety harness" actually means
- Host OS sealed off. Containers run with their own root filesystem, init, package manager, services. An agent can
apt install, mangle/etc, scribble into/usr, install systemd units — none of it crosses the boundary. Worst case is "throw away the container." - Projects isolated from each other. Every project gets its own container in the
repomanIncus project namespace. An agent in container A cannot read or write container B's files. No accidental cross-edits between sibling repos, no sharednode_modulescorruption, no rogue file watchers. - Source code preserved. The repo lives on the host at
~/repos/<name>and is exposed to the container via a uid-shifted bind mount (shift=true, since v0.4.2).repoman removedeletes the container — never the repo. The confirmation prompt spells out exactly what gets removed (v0.4.3). - Backup as recoverability.
repoman syncrsyncs every repo to NFS. Run that NFS on ZFS and snapshots give you point-in-time history. If an agent does mangle the working tree, you have last night's copy. - Authenticated agent state shared safely. The vendor profile library lets you bind-mount things like
~/.claude(auth + history + plugins) into every container — one login, every project — without exposing SSH keys, browser cookies, or shell config.
Audience
Homelab developer running:
- Incus for containerization (Linux-only).
- Local SSD/NVMe for active project working copies (latency matters for the agent's iteration loop).
- NFS-mounted remote storage for durable backup (ideally ZFS-backed, for snapshot history).
- One or more AI coding agents (Claude Code, etc.) that they want to grant generous permissions to.
One developer, multiple projects. Not multi-user, not multi-host coordination.
Realistic audience size: small. Niche tool serving its narrow audience well > generic tool nobody loves.
Mission (re-anchored in v0.4)
repoman has exactly four jobs:
- Provision per-project Incus containers that bind-mount the user's git/hg repos (
repoman new). - Backup repos to NFS via rsync (
repoman sync). - Share host-side resources (configs, agents, runtimes) into containers via Incus profiles (
repoman profile). - Operate the above with quality-of-life subcommands (
list,status,remove,shell,setup).
What repoman is NOT:
- A host-configuration tool. It does not install or configure host services (ollama, hermes, kernel modules).
- A container-image builder. It uses upstream Incus images; image curation is somebody else's tool's job.
- An arbitrary install-script runner inside containers. Per-container provisioning is the user's shell script in their project repo.
The v0.3 attempt to bind-mount hermes from the host failed because hermes pins to a uv-vendored host-only Python. The v0.4 brainstorm explored image management to fix that, then rejected it as ongoing maintenance overhead. The conclusion: agents fall into two categories — bind-mountable (claude works) and non-shareable (hermes needs per-container install). repoman does the bind-mountable case via profiles; the rest is shell-script territory in the user's project repo.
Differentiators (vs rolling your own scripts)
- Incus
projectfor ownership. Allrepoman-managed containers live in theincus project create repomannamespace — clean scoping, no leakage into the user's other Incus work, easy to nuke the entire estate. - Single tool covering container lifecycle + project conventions + backup + profile management. Existing tools each cover one slice (
incusfor containers,restic/borgfor backup,distroboxfor dev shells); none do all four with conventions tuned for "AI-agent-in-a-Linux-VPS on a homelab." - Opinionated backup story. rsync mirror + autofs-aware mount checks + ZFS snapshots assumed on the file server. No bespoke history layer — ZFS is the time machine.
- Profile library with vendor/user split. Shareable host-side things (auth state, runtime binaries, dotfiles) are first-class via Incus profiles. Vendor ships sensible defaults; user shadows them in
~/.config/repoman/profiles.d/.${HOST_LAN_IP},${USER},${HOME}substituted at install time. - Both interactive and flag-driven. Same code path serves a guided wizard and a shell-scriptable CLI.
- Native single-binary distribution via reef compiling to native code. No runtime install for users.
Subcommands as shipped (v0.4.4)
| Subcommand | Purpose |
|---|---|
repoman setup [--non-interactive] |
First-time host bootstrap. Creates Incus project repoman, detects host LAN IP, writes initial registry, ensures ~/.config/repoman/profiles.d/ exists. Idempotent. |
repoman profile {list,install,diff,remove,show} |
Manage Incus profiles from /usr/local/share/repoman/profiles/ (vendor) and ~/.config/repoman/profiles.d/ (user shadow). Renders ${HOST_LAN_IP} / ${USER} / ${HOME} at install time. |
repoman new <name> [--repo <dirname>] [--image <img>] |
Create a managed container. Pre-validates profiles are installed, bind-mounts the repo with shift=true, applies profiles, restarts. Interactive form prompts for unset values. |
repoman list |
Print a table of managed containers: name, repo path, image, created, last sync, backup flag. Reads from registry. |
repoman shell <name> [--cwd <path>] |
Drop into the container as the local user, in the repo's directory. Replaces the verbose incus shell --user 1000 --cwd ... invocation. |
repoman sync [name] [--no-delete] [--dry-run] |
rsync mirror local → NFS, all projects or one. NFS auto-mount aware. ZFS snapshots on the file server provide history. |
repoman remove <name> [--yes | -y] [--keep-incus] |
Destroy the container; the repo on host is never touched. Prompts for confirmation by default; --yes skips the prompt. |
repoman status [name] |
Health check: container state, mounts, NFS reachability, last sync. |
Out of scope
Brainstormed and rejected during the v0.4 design conversation (see memory: project_repoman.md for full rationale):
- Other container runtimes (Docker, Podman). Incus only — we depend on its full-Linux-VPS shape.
- Other backup destinations (S3, restic, borg). NFS only.
- macOS/Windows. Linux only.
- Multi-host coordination, replication.
- ZFS snapshot management (file-server-side; user runs sanoid/syncoid themselves).
- Cron installation (provide a doc snippet, not a subcommand).
- IDE integration.
- Image building / curation (
repoman image build/refresh/list/prune). Rejected: maintenance burden outweighs benefit for homelab usage volumes. [install]block in override files (per-project install commands run in container at create). Rejected: users putscripts/install.shin their project repo and run it afterrepoman shell. Not worth a TOML schema + lifecycle + security model.- Setup wizard configuring host services (ollama auto-install, systemd overrides, model pulls). Rejected: scope creep across the host-config boundary.
--hermes/ per-container hermes provisioning. Hard-rejected after v0.3 iterations failed on uv-vendored Python portability.
Configuration
Central registry: ~/.config/repoman/repoman.toml (schema 3 as of v0.4)
schema = 3
[repoman]
output = "quiet"
[host]
lan_ip = "192.168.1.42"
[defaults]
repos_root = "~/repos"
backup_root = "/nfs/repos"
logdir = "~/.local/state/repoman"
incus_project = "repoman"
default_image = "images:ubuntu/26.04/cloud"
profiles = ["default", "claude-share"]
[[project]]
name = "isurus"
repo = "isurus-project"
image = "images:ubuntu/26.04/cloud"
profiles = ["default", "claude-share"]
created = "2026-04-28T15:00:00Z"
last_sync = "2026-04-28T20:17:03Z"
backup = true
Per-project override (optional): ~/.config/repoman/repos.d/<name>.toml
[container]
image = "images:debian/12/cloud"
profiles = ["default", "claude-share", "node-dev"]
[[mount]]
source = "~/.npm"
path = "/home/ctusa/.npm"
[env]
NODE_ENV = "development"
Implementation: reef-lang
Reef has the right shape for this:
- Native compilation → C → GCC/Clang → static binary. Single-file distribution.
- Stdlib coverage in use:
encoding.toml— central registry + per-project overrides + profile YAML helperssys.args/sys.flag— CLI parsingsys.env— env var readssys.process— argv-listprocess_run/process_spawnfor rsync/incus shell-outs (noprocess_spawn_shell: user-derived names cannot trigger shell injection)io.console— interactive prompts (confirm,confirm_default_no)io.file,io.dir,io.path,io.stream— local FS opscore.result_generic,core.option— error handling primitives
- Multi-platform reef = future macOS/BSD/illumos support comes for free if/when needed.
- Currently pins reef-lang 0.5.20 (source at
~/reef-lang-0.5.20-source/).
Resolved implementation questions
- Subprocess execution.
sys.processprovides the POSIX surface needed. Rule: argv-listprocess_run/process_spawnexclusively; neverprocess_spawn_shellfor user-derived inputs. - Incus integration. Decision: CLI shell-out (
incusinvoked with argv-list,--format jsonwhere useful). REST-via-unix-socket migration deferred until a concrete need arises. - Interactive UX. Line-based prompts using
io.console. Rich TUI was out of scope and stayed out. - uid mapping for bind mounts. Resolved in v0.4.2: every
cmd_newrepo bind is created withshift=trueso host uid 1000 maps to container uid 1000.
Versions shipped
- v0.1 (2026-04-29):
new+syncsubcommands, Incusproject repomannamespace, central registry. - v0.2 (2026-05-05): added
list,status,remove,shell. - v0.3 (2026-05-08):
setupwizard,llm-shareprofile (ollama-over-LAN), schema-2 migration.--hermesexperiment reverted before tag. - v0.4 (2026-05-09):
profilesubcommand family, vendor profile library with user-shadow override, schema-3 migration (drops[defaults].llm, adds[host].lan_ip), setup wizard trimmed (no more--with-llm). - v0.4.2 (2026-05-11):
cmd_newbind-mounts withshift=trueso host uid maps into the container — agents in containers can actually write to the repo as the in-container user. - v0.4.3 (2026-05-11):
cmd_removeconfirmation prompt spells out exactly what gets removed; reassures the user that the source repo on host is not touched. - v0.4.4 (2026-05-11):
--versionreports the actual version; build-warning cleanup.
Success criteria for v0.x
- Builds clean from a fresh reef-lang install on Linux x86_64.
-
repoman setupruns idempotently and produces a working baseline on a fresh host. -
repoman newcreates a container, matches the bash prototype's behavior, and persists in the registry with per-project override support. -
repoman syncmatches the bash prototype's behavior with autofs check + mirror semantics. - All subcommands have both interactive and flag-driven invocation paths.
- Bind mounts respect host UID/GID so containers can write to the repo.
- License chosen (CDDL 1.0, Leafscale, LLC as steward/Initial Developer).
- Single static binary distributable via tarball or per-distro package.
- README + VISION + per-feature doc.
- Public repo on a forge with a contribution guide.
Open product questions
- Forge. GitHub, Codeberg, self-hosted? Distribution implications (binaries, releases).
- Versioning toward 1.0. Current cadence is rapid patch releases. What's the 1.0 bar? Probably "stable schema + public forge + tarball releases."
- Logging / observability. Stderr + per-invocation log files today. Structured (JSONL) output mode for automation — needed yet?
- Configuration migration policy. Schema 1 → 2 → 3 worked as implicit lossless migration on parse. Same approach for the next bump?
Naming convention
- Container name =
<name>(positional, required). - Repo dirname =
<name>by default, overridable with--repo <dirname>. - Repo absolute path =
<repos_root>/<repo-dirname>. - No magic suffix logic. Explicit > implicit.
Differentiation reality check
Why someone would pick repoman:
- They want to run AI agents with
--dangerously-skip-permissions(or its equivalent) without taking the host down. - They want the Incus-project-as-namespace pattern but don't want to wire it themselves.
- They want a guided onboarding into "this style of homelab dev workflow."
- They want a unified registry of "what projects do I have, where are they, when did they last back up."
Why they wouldn't:
- They already have ansible/nix/etc. covering this.
- They use Docker, not Incus.
- They don't separate local working copies from backup destinations.
- They run their agents under tighter constraints (microVMs, gVisor, restricted unix users) and don't need a container-shaped harness.
That's fine. The audience is small and that's the intentional design.