Unix user and group permissions have been battle-tested for over fifty years. When you bring on a new collaborator you give them their own account, set group read on shared files, and let the filesystem enforce the boundaries. Why should an AI coding agent be any different?
sucoder treats an LLM agent (Codex, Claude, Gemini) as a collaborator with its own unix account. The human’s canonical repository is group-readable but not group-writable; the agent works in a sandboxed mirror clone where it has full write access. No custom container runtimes, no bespoke sandboxing daemons—just chown, chmod, and git.
git clone https://github.com/ligon/sucoder && cd sucoder
make quick-start
cd ~/Projects/my-project # any git repo
sucoder collaborate -a claudeNo config file needed. sucoder detects the git repo, mirrors it under /var/tmp/coder-mirrors/, and launches Claude (the default agent). Use -a to pick a different agent (codex, gemini); omit it and sucoder defaults to claude or auto-detects from PATH. Add --task fix-login to start on a dedicated branch.
For multi-repo setups, skills, and system prompts, see Configuration below. make env-setup does the full host provisioning (make help for all targets).
- Linux (user/group sandboxing relies on
useradd,groupadd, and setgid) - Python >= 3.9 with pip
- git
- sudo
- make (optional; you can run the setup commands by hand)
- bash
- At least one supported agent CLI on PATH:
claude,codex, orgemini
This README is .org, the default system prompt is .org, and the skills repo leans heavily on Org-mode. Why not Markdown?
Org-mode is a plain-text format that every LLM reads fluently—but it also has structured features (TODO states, properties, tags, executable source blocks) that Markdown lacks. For a project about human-agent collaboration, those features pull their weight: a system prompt can carry metadata an agent can act on, a skill file can embed runnable examples, and a handoff note can be a living checklist rather than a static document.
You do not need Emacs to read or edit these files. Any text editor works; GitHub renders .org natively. But if you do use Emacs, the integration is a bonus, not a requirement.
Running sucoder collaborate -a claude (or with --task) triggers a multi-step workflow. Steps marked auto happen without intervention; steps marked human need you at the keyboard.
If ~/.sucoder/config.yaml exists, load it. Otherwise, derive everything from the environment: $USER becomes the human identity, the git repo root becomes the canonical repo, and /var/tmp/coder-mirrors/ is the mirror root. The agent CLI is resolved from -a flag > $SUCODER_AGENT > ~/.sucoder/agent > PATH auto-detect > interactive prompt.
Set group-read permissions on your repo so the coder user can traverse it. Adds a coder git remote pointing at the mirror (for easy fetching later) and writes a helper script scripts/fetch-agent-branches.sh. Your repo stays read-only to the agent—only the group bits and the remote config change.
If the mirror does not exist under /var/tmp/coder-mirrors/<repo>/, clone it from the canonical repo as the coder user. Push access back to canonical is disabled (no_push). If the mirror already exists, verify the remote and enforce permissions.
- Without
--task: fetch the latest commits from canonical into the mirror. - With
--task fix-login: create a branchcoder/fix-login-<timestamp>in the mirror, based on the default base branch (or--base).
Detect the agent type (Claude, Codex, Gemini) from the command name and inject the appropriate flags: write-access (--dangerously-skip-permissions, --sandbox danger-full-access, --yolo), writable directories, skills paths, and system prompt. If a system prompt file exists, it is passed via the agent’s native flag or appended as trailing text.
The agent process starts inside the mirror working tree. From here, you are talking to the agent. It has full write access to the mirror but cannot write to your canonical repo. The agent commits its work to the task branch.
When the agent session ends (you quit it, or it finishes), control returns to your shell.
Fetch the agent’s branch into your canonical repo and review it:
git fetch /var/tmp/coder-mirrors/my-project coder/fix-login-20250215180000
git checkout -b review/fix-login FETCH_HEAD
# ... review, test, iterate ...
git checkout main && git merge --ff-only review/fix-loginOr pull directly:
git pull --ff-only /var/tmp/coder-mirrors/my-project coder/fix-login-20250215180000The canonical repository remains unwritable by the agent throughout.
collaborate bundles steps 2–6. You can also run them separately:
| Command | Step |
|---|---|
prepare-canonical | Set permissions and add agent remote (step 2) |
agents-clone | Clone the mirror (step 3) |
sync | Fetch latest into the mirror (step 4) |
start-task | Create a task branch (step 4) |
agents-run | Launch the agent (steps 5–6) |
worktrees | List active worktrees with status |
attach | Reconnect to a remote tmux session |
- agents-clone
- Clone a canonical repository into an agent-owned mirror with strict permissions.
- sync
- Fetch updates from the canonical repository into the mirror without granting write access.
- start-task
- Create a new agent task branch based on a human branch while preserving branch naming conventions.
- status
- Summarize the state of a mirror, including permissions and git remotes.
- collaborate
- Prepare canonical, ensure mirror, and launch agent in one step.
- worktrees
- List active git worktrees in a mirror with per-worktree status (branch, commits ahead, dirty state). Supports
--watchfor live monitoring and--difffor file-level changes. - attach
- Reconnect to a remote agent session via tmux after an SSH disconnect.
- –target/-T
- Select a named remote execution target (e.g.,
--target savio) to run the agent on a remote host. - version
- Print the installed sucoder version.
- –dry-run
- Available on modifying commands to review logged operations without executing.
# Clone the canonical repository into the configured mirror.
sucoder agents-clone project
# Refresh the mirror with the latest human commits.
sucoder sync project
# Create a task branch for the agent starting from ligon/main.
sucoder start-task project rewrite-parser --base main
# Inspect current branch and outstanding changes.
sucoder status projectSkip this section if zero-config mode works for you.
Configuration is stored in YAML (default ~/.sucoder/config.yaml). To set it up:
- Copy the sample configuration and adjust paths, users, and mirror names.
install -d -m 750 ~/.sucoder cp config.example.yaml ~/.sucoder/config.yaml
- Update
~/.sucoder/config.yamlso that:human_usermatches your login (for exampleligon).agent_userandagent_groupmatch the agent Unix account (defaultcoder). This is a shared account used by any supported agent (Codex, Claude Code, Gemini CLI, etc.)—it is not tied to a specific agent binary.mirror_rootpoints to the directory where agent mirrors will live.mirrors.<name>.canonical_repopoints at the human-owned repository.
- Ensure the agent can read (but not write) the configuration directory and file.
sudo chgrp coder ~/.sucoder sudo chmod 750 ~/.sucoder sudo chgrp coder ~/.sucoder/config.yaml sudo chmod 640 ~/.sucoder/config.yaml
- (Optional) Install the skills repository for agent capabilities.
git clone https://github.com/ligon/sucoder-skills ~/Projects/sucoder-skills ln -s ~/Projects/sucoder-skills ~/.sucoder/skills sudo chgrp -R coder ~/Projects/sucoder-skills sudo chmod -R g+r,g-w ~/Projects/sucoder-skills
Note: The skills repository is separate from the tool repository for security isolation. Skills influence agent behavior and are kept in a dedicated repository to prevent agents from modifying skills and tool code in the same commit. The tool enforces semantic versioning compatibility between the tool and skills repository.
- Warm up the shared skills catalog so launches confirm access before work starts.
ls ~/.sucoder/skills sucoder skills-list sucoder mirrors-listThe listings should succeed without permission errors. The CLI helper highlights any unreadable paths and
sucoder mirrors-listprints the configured mirrors (with their canonical and mirror directories). To preload the catalog into a session, use the agent’s file-read command (e.g.,codex read ~/.sucoder/skills/SKILLS.mdfor Codex).
Enable tab completion for sucoder commands and mirror names by running:
sucoder --install-completionBy default, sucoder uses sudo to run commands as the agent user (coder).
The human user must have sudo access to impersonate the agent user. If you are
running as the agent user directly (for example inside a container), pass
--no-agent-sudo to skip sudo:
sucoder --no-agent-sudo agents-clone projectcollaborateis the all-in-one command: it runsprepare-canonical+agents-clone(ensure mirror) +agents-run(launch agent) in a single step.agents-runonly launches the agent, assuming the mirror already exists. Use this when the mirror is already prepared and you just want to start a session.
- Prepare the canonical repository for collaboration.
sucoder prepare-canonical projectNote: this command modifies the canonical repository — it sets the group to
coder, grants group read permissions, removes group write bits, and optionally adds an agent remote and fetch helper script. Elevate with--sudowhen root privileges are required. By default it also adds acoderremote pointing at the agent mirror and writesscripts/fetch-agent-branches.shto simplify fetching agent branches (--no-agent-remotedisables this). - Clone the mirror as the human operator; the tool will invoke git as the agent.
sucoder agents-clone project --verboseMirror arguments support shell completion, so pressing Tab after
sucoder <command>suggests configured mirror names.
- Refresh the mirror with the latest human commits.
sucoder sync project - Create a task branch for the agent based on the desired human branch.
sucoder start-task project add-metrics --base mainThe command prints the full branch name (for example
coder/add-metrics-20251107164028).
- The agent works directly inside the mirror directory (for example
/var/tmp/coder-mirrors/project) on the branch created above. - The agent commits work to
coder/<task>-<timestamp>without pushing to the canonical repository. - To run the full workflow in one step (prepare canonical, ensure mirror, launch agent), use:
sucoder collaborate project --task add-metrics
- To experiment with a different agent binary temporarily, supply
--agent-command(and optional--agent-env):sucoder agents-run project --agent-command "foo --flag" --agent-env TOKEN=abc123 - The CLI automatically injects the appropriate write-access flag for the detected agent (e.g.,
--yolofor Codex,--dangerously-skip-permissionsfor Claude Code; see the flag template table below). When invoking an agent manually, include the equivalent flag to avoid read-only failures. The helper will also warn if it detects a read-only sandbox and still attempts a launch. - Org authoring playground (documentation example): load
docs/examples/org-authoring-demo.orgusing your agent’s file-read command. Demonstrates the in-repo Org skills—structure, markup, tables, capture, citations, exporting, agenda integration—so humans and agents have a quick reference.
Inside the mirror the agent can use git worktrees to work on multiple
tasks in parallel. Claude supports this natively via claude --worktree NAME;
other agents can achieve the same result with git worktree add.
Each worktree gets its own checkout while sharing the same git object store (no duplicate clone). This is useful for:
- Parallel feature development
- Fix a bug in one worktree while building a feature in another.
- Comparative computation
- Run the same estimation with different parameters in separate worktrees, then consolidate results.
- Review isolation
- Keep exploratory changes out of the main branch until they are ready.
The human can monitor all worktrees from upstream:
sucoder worktrees project # snapshot
sucoder worktrees project --watch 10 # live, refresh every 10s
sucoder worktrees project --diff # include file-level changes
sucoder worktrees project --main # include the main worktreeExample output:
Worktrees for mirror 'MyModel' (/var/tmp/coder-mirrors/MyModel):
.claude/worktrees/instruments-A
Branch: worktree-instruments-A @ a1b2c3d
Ahead: 2 commits (vs main)
Last: a1b2c3d GMM estimation with instrument set A (1 minute ago)
Status: clean
.claude/worktrees/instruments-B
Branch: worktree-instruments-B @ e4f5g6h
Ahead: 2 commits (vs main)
Last: e4f5g6h GMM estimation with instrument set B (30 seconds ago)
Status: dirty (1 modified)
The worktrees command uses git worktree list --porcelain for discovery,
so it works regardless of which agent created the worktrees or where they
are located.
When the agent consolidates worktree branches back to the mirror’s main
branch, prefer git merge --no-ff to preserve the individual branch histories.
This keeps the worktree commits individually cherry-pickable from upstream.
Poetry caches virtual environments by project path, so each worktree
would normally create its own .venv. To share a single environment
across all worktrees, pin VIRTUAL_ENV in .envrc:
_git_common="$(git rev-parse --git-common-dir 2>/dev/null)"
if [ "${_git_common}" = ".git" ]; then
_main_tree="$(pwd)"
else
_main_tree="$(dirname "${_git_common}")"
fi
VIRTUAL_ENV="${_main_tree}/.venv"
export VIRTUAL_ENV
layout poetry
Mirrors can also live on a remote host (e.g., an HPC cluster) where the local machine cannot run heavyweight computation. The privilege separation shifts from “different Unix users on the same box” to “same user on different machines”—the network boundary enforces isolation.
Define a named target in ~/.sucoder/config.yaml:
targets:
savio:
gateway: brc.berkeley.edu
transfer_host: dtn.brc.berkeley.edu
mirror_root: ~/mirrors
control_persist: 12hNo mirrors: entry is needed—zero-config repo detection works with
targets. The target can be used with any repo.
Use --target (-T) as a global option before the subcommand:
sucoder -T savio collaborate # launch agent on cluster
sucoder -T savio worktrees --watch 30 # monitor from local
sucoder -T savio attach # reconnect via tmux- Authenticate once —
sucoderestablishes an SSH ControlMaster connection to the gateway. For clusters with OTP/two-factor auth, this is the only password prompt; all subsequent SSH commands multiplex through the socket. - Pin a login node — SSH to the gateway, run
hostname, store the result (e.g.,ln002.brc) in~/.sucoder/sessions/<mirror>.yaml. Subsequent connections jump to the same node via-J. - Open a tunnel — A local port forward to the data transfer node for fast git transport.
- Push canonical state —
git pushthrough the tunnel to the remote mirror. - Launch the agent — SSH to the pinned login node,
cdinto the remote mirror, start the agent inside atmuxsession. - Add a git remote — A remote named after the target (e.g.,
savio) is added to the canonical repo so the human cangit fetch savioto pull back agent work.
If the SSH connection drops, the tmux session on the login node survives. Reconnect with:
sucoder -T savio attachIf the ControlMaster socket expires (default 12 hours), sucoder
detects the stale socket and re-prompts for authentication.
Fetch the agent branch back into the canonical repository:
git fetch coder # uses the coder remote added by prepare-canonical
git log --oneline coder/coder/add-metrics-20251107164028 -5
git checkout -b review/add-metrics FETCH_HEAD
# ... review, test, iterate ...
git checkout main && git merge --ff-only review/add-metricsOr pull directly:
git pull --ff-only /var/tmp/coder-mirrors/project coder/add-metrics-20251107164028When using --target, sucoder automatically adds a git remote
named after the target. Fetch agent work back over SSH:
git fetch savio # fetches from brc.berkeley.edu:~/mirrors/project
git log --oneline savio/master -5
git merge --ff-only savio/masterThe canonical repository remains unwritable by the agent throughout both local and remote flows.
Configuration is stored in YAML (default path is ~/.sucoder/config.yaml for the human operator). Grant the agent group read access to this file while keeping it group-writable disabled.
human_user: ligon
agent_user: coder
agent_group: coder
mirror_root: /var/tmp/coder-mirrors
log_dir: ~/.sucoder/logs
skills:
- ~/.sucoder/skills # optional global default for all mirrors (overridable per-mirror)
system_prompt: ~/.sucoder/system_prompt.org
# Named remote execution targets (optional).
# Use sucoder -T <name> to select one.
targets:
savio:
gateway: brc.berkeley.edu
transfer_host: dtn.brc.berkeley.edu
mirror_root: ~/mirrors
control_persist: 12h
# Mirrors are optional when using zero-config repo detection.
mirrors:
project:
canonical_repo: ~/src/project.git
mirror_name: project
default_base_branch: main
task_branch_prefix: task
branch_prefixes:
human: ligon
agent: coder
# Optional: override or extend global skills for this mirror
skills:
- ~/.sucoder/skills/org-style
- ~/.sucoder/skills/document-skill
agent_launcher:
# Optional: map generic intents to agent-specific flags
needs_yolo: true
writable_dirs:
- "~"
flags:
skills: "--skills {path}" # Uses mirror skills or ~/.sucoder/skills by defaultYou can also maintain a global catalog at ~/.sucoder/skills/SKILLS.md, which can
point to additional skill files or directories using file: links or bullet-listed paths. Any
catalog discovered this way is loaded automatically before launching the agent.
The agent_launcher.flags mapping lets you translate generic intents (write access, writable
directories, workdir, default flags, skills paths) into agent-specific switches. If a skills flag
template is provided, sucoder will pass any configured skills plus the default
~/.sucoder/skills directory when it exists.
The table below documents the current default flag templates used when the agent type is detected.
Templates can be overridden per-mirror or globally via agent_launcher.flags, with precedence:
per-mirror > global > profile > UNKNOWN.
| Template | Intent | Codex default | Claude default | Gemini default | Notes |
|---|---|---|---|---|---|
| yolo | Unsafe/full access mode | –sandbox danger-full-access –ask-for-approval never | –dangerously-skip-permissions | –yolo | Injected when needs_yolo is true (or defaulted). |
| writable_dir | Allow extra writable paths | (none) | –add-dir {path} | –include-directories {path} | Codex uses sandbox policy instead of per-dir flags. |
| workdir | Set agent working directory | (none) | (none) | (none) | Only applied if you set a template. |
| default_flag | Pass through default flags | {flag} | {flag} | {flag} | Each default flag is rendered through this template. |
| skills | Expose skills paths | (none) | (none) | (none) | Use “–skills {path}” or similar if your CLI supports it. |
| system_prompt | Inject system prompt | (none) | –system-prompt | –prompt-interactive | If unset, sucoder appends the prompt as trailing text when inline prompts are supported. |
If you change any defaults, update AGENT_PROFILES in sucoder/config.py alongside the
README so the documentation stays in sync.
The core sucoder workflow (mirror, sync, permissions, branch management) is
fully agent-agnostic. The table below summarises where agents differ in
features that sucoder can exploit.
| Capability | Claude | Codex | Gemini |
|---|---|---|---|
Native --worktree flag | Yes (claude --worktree NAME) | No | No |
| Subagent worktree isolation | Yes (isolation: worktree) | No | No |
| Session resume | Yes (claude --resume) | No | No |
| System prompt flag | --system-prompt FILE | Trailing text | --prompt-interactive |
| Write-access flag | --dangerously-skip-permissions=| =--sandbox danger-full-access | --yolo | |
| TTY requirement | Works with subprocess | Works with subprocess | Requires exec (TTY) |
| Default agent | Yes (sucoder default) | Supported | Supported |
Agents without native --worktree support can still use git worktrees
manually. The agent just needs to run git worktree add inside the mirror;
sucoder worktrees will discover and display them regardless of which agent
created them.
Similarly, the remote execution support (--target, SSH tunnels,
ControlMaster) is entirely agent-agnostic—any agent CLI can be launched
over SSH.
When adding a new agent, implement its profile in AGENT_PROFILES in
sucoder/config.py and add a row to these tables.
Some agents (e.g., Codex) bundle their own Node.js runtime. sucoder can wrap the
launch so that the agent runs under a newer nvm-managed runtime instead (for example Node 22).
- Install/activate the desired version for the agent user:
sudo -u coder bash -lc 'export NVM_DIR=/home/coder/.nvm; . "$NVM_DIR/nvm.sh"; nvm install 22.11.0' sudo -u coder bash -lc 'export NVM_DIR=/home/coder/.nvm; . "$NVM_DIR/nvm.sh"; nvm alias default 22.11.0'
Replace the version string with the release you need (direct versions,
lts/*aliases, etc.). - Update the mirror configuration so launches always source nvm before running the agent:
mirrors: project: canonical_repo: ~/src/project.git agent_launcher: command: - codex nvm: version: "22.11.0" # Any version/alias `nvm use` understands dir: /home/coder/.nvm # Optional; defaults to <agent home>/.nvm
When the nvm block is present, the launcher injects:
export NVM_DIR=…; source "$NVM_DIR/nvm.sh"nvm use <version>(fails fast if the version is missing)exec <agent> …so the rest of the CLI arguments (including any injected flags and context prelude) are preserved.
If dir is omitted the helper assumes the agent’s home directory contains ~.nvm.
Any existing agent_launcher.command and agent_launcher.env settings continue to work.
Workspace skills follow Anthropic’s Agent Skills Spec:
- Each skill directory must contain a
SKILL.mdfile with YAML frontmatter (`name`, `description`, and `license`; add `allowed-tools`/`metadata` as needed). If you copy a third-party skill, keep its license value and include the upstream notice in the directory.
- Generate a tool-agnostic catalog of local skills (from the configured skills directory) plus the openai/skills curated list:
python scripts/generate_unified_skills_catalog.py \ --output ../Skills/UNIFIED_SKILLS_CATALOG.md # choose any writable pathThe script reads
../Skills/trusted_skill_sources.yamlto determine which sources to include (local catalogs plus curated repos; defaults includeopenai/skillsandanthropics/skills). If a target is not writable (for example/home/ligon/.sucoder/skills), use--outputto pick a writable path. - The generated catalog shows entries per source and flags conflicts when the same skill name appears in multiple sources.
- Any LLM can read the catalog and follow linked
SKILL.mdfiles. Curated entries are documentation-first; helper scripts are optional. - To install a curated skill manually, download
skills/.curated/<name>from https://github.com/openai/skills and place it under your skills directory (e.g.,~/.sucoder/skills). - The frontmatter `name` must match the directory name exactly.
- Long-form references, scripts, and assets live under
references/,scripts/, andassets/to keep the entrypoint concise. - The shared catalog (
~/.sucoder/skills/SKILLS.md) in the skills repository lists every bundled skill so agents discover them automatically. - A reusable system prompt template (`default_system_prompt.org`) mirrors these expectations, including a reminder to confirm today’s date at session start.
- The upstream workflow is available in the
sucoder-skillsrepository underskill-creator/; use it alongsidedocument-skillwhen you need initialization or packaging scripts. - A starter configuration (`default_config.yaml`) points at the bundled skills directory and default prompt; copy or merge it into
~/.sucoder/config.yamlas needed.
Resource directories are summarized automatically when present:
references/— documentation to load on demand (each file includes a suggested load command).scripts/— helper executables (never run automatically; humans can execute after review).assets/— supporting files such as templates or images that may be referenced in outputs.
Skills are maintained in a separate repository (sucoder-skills) for security isolation.
This separation prevents agents from modifying skills and tool code in the same commit, reducing
the attack surface and simplifying security reviews.
- Security Isolation — An agent working on tool code cannot simultaneously modify skills that influence agent behavior.
- Review Clarity — Code reviews and content reviews use different security mindsets and can be performed separately.
- Blast Radius Reduction — Malicious skill instructions cannot be hidden among tool code changes.
The tool enforces semantic versioning compatibility between itself and the skills repository:
- Tool validates skills repository
VERSIONfile on startup - Compatible: Tool requires
1.0.0, skills has1.x.x✓ - Incompatible: Tool requires
1.x.x, skills has2.0.0✗
Skills repository: https://github.com/ligon/sucoder-skills
To update skills:
cd ~/.sucoder/skills # Or ~/Projects/sucoder-skills
git pullTo skip version checking (not recommended):
export SUCODER_SKIP_SKILLS_VERSION=1PATCH (1.0.0 → 1.0.1): Typo fixes, clarifications, examples MINOR (1.0.0 → 1.1.0): New skills, new optional features, backward-compatible improvements MAJOR (1.0.0 → 2.0.0): Breaking changes, requires tool update
Run the automated tests with pytest.
pytest