Skip to content

ligon/sucoder

Repository files navigation

sucoder

Project Overview

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.

Quick Start

git clone https://github.com/ligon/sucoder && cd sucoder
make quick-start
cd ~/Projects/my-project  # any git repo
sucoder collaborate -a claude

No 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).

Requirements

  • 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, or gemini

Why Org-mode?

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.

What sucoder collaborate does

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.

1. Resolve configuration auto

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.

2. Prepare the canonical repository auto

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.

3. Clone (or verify) the mirror auto

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.

4. Sync or create a task branch auto

  • Without --task: fetch the latest commits from canonical into the mirror.
  • With --task fix-login: create a branch coder/fix-login-<timestamp> in the mirror, based on the default base branch (or --base).

5. Compose the agent launch command auto

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.

6. Launch the agent human

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.

7. Review and integrate human

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-login

Or pull directly:

git pull --ff-only /var/tmp/coder-mirrors/my-project coder/fix-login-20250215180000

The canonical repository remains unwritable by the agent throughout.

Running the steps individually

collaborate bundles steps 2–6. You can also run them separately:

CommandStep
prepare-canonicalSet permissions and add agent remote (step 2)
agents-cloneClone the mirror (step 3)
syncFetch latest into the mirror (step 4)
start-taskCreate a task branch (step 4)
agents-runLaunch the agent (steps 5–6)
worktreesList active worktrees with status
attachReconnect to a remote tmux session

Key Features

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 --watch for live monitoring and --diff for 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.

Usage

# 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 project

Configuration

Skip this section if zero-config mode works for you.

Configuration is stored in YAML (default ~/.sucoder/config.yaml). To set it up:

  1. Copy the sample configuration and adjust paths, users, and mirror names.
    install -d -m 750 ~/.sucoder
    cp config.example.yaml ~/.sucoder/config.yaml
        
  2. Update ~/.sucoder/config.yaml so that:
    • human_user matches your login (for example ligon).
    • agent_user and agent_group match the agent Unix account (default coder). 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_root points to the directory where agent mirrors will live.
    • mirrors.<name>.canonical_repo points at the human-owned repository.
  3. 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
        
  4. (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.

  5. Warm up the shared skills catalog so launches confirm access before work starts.
    ls ~/.sucoder/skills
    sucoder skills-list
    sucoder mirrors-list
        

    The listings should succeed without permission errors. The CLI helper highlights any unreadable paths and sucoder mirrors-list prints 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.md for Codex).

Shell Completion

Enable tab completion for sucoder commands and mirror names by running:

sucoder --install-completion

Sudo Requirement

By 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 project

Command Overview

  • collaborate is the all-in-one command: it runs prepare-canonical + agents-clone (ensure mirror) + agents-run (launch agent) in a single step.
  • agents-run only launches the agent, assuming the mirror already exists. Use this when the mirror is already prepared and you just want to start a session.

Provision the Mirror

  1. Prepare the canonical repository for collaboration.
    sucoder prepare-canonical project
        

    Note: 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 --sudo when root privileges are required. By default it also adds a coder remote pointing at the agent mirror and writes scripts/fetch-agent-branches.sh to simplify fetching agent branches (--no-agent-remote disables this).

  2. Clone the mirror as the human operator; the tool will invoke git as the agent.
    sucoder agents-clone project --verbose
        

    Mirror arguments support shell completion, so pressing Tab after sucoder <command> suggests configured mirror names.

Sync Before Handing Off

  1. Refresh the mirror with the latest human commits.
    sucoder sync project
        
  2. Create a task branch for the agent based on the desired human branch.
    sucoder start-task project add-metrics --base main
        

    The command prints the full branch name (for example coder/add-metrics-20251107164028).

Agent Workflow

  • 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., --yolo for Codex, --dangerously-skip-permissions for 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.org using 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.

Parallel Work with Worktrees

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 worktree

Example 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.

Shared virtual environment for worktrees

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

Remote Execution

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.

Configuration

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: 12h

No mirrors: entry is needed—zero-config repo detection works with targets. The target can be used with any repo.

Usage

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

What happens under the hood

  1. Authenticate oncesucoder establishes 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.
  2. 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.
  3. Open a tunnel — A local port forward to the data transfer node for fast git transport.
  4. Push canonical stategit push through the tunnel to the remote mirror.
  5. Launch the agent — SSH to the pinned login node, cd into the remote mirror, start the agent inside a tmux session.
  6. Add a git remote — A remote named after the target (e.g., savio) is added to the canonical repo so the human can git fetch savio to pull back agent work.

Reconnecting

If the SSH connection drops, the tmux session on the login node survives. Reconnect with:

sucoder -T savio attach

If the ControlMaster socket expires (default 12 hours), sucoder detects the stale socket and re-prompts for authentication.

Human: Review and Integrate Agent Work

Local mirrors

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-metrics

Or pull directly:

git pull --ff-only /var/tmp/coder-mirrors/project coder/add-metrics-20251107164028

Remote mirrors

When 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/master

The canonical repository remains unwritable by the agent throughout both local and remote flows.

Configuration

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 default

You 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.

Agent flag templates by CLI

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.

TemplateIntentCodex defaultClaude defaultGemini defaultNotes
yoloUnsafe/full access mode–sandbox danger-full-access –ask-for-approval never–dangerously-skip-permissions–yoloInjected when needs_yolo is true (or defaulted).
writable_dirAllow extra writable paths(none)–add-dir {path}–include-directories {path}Codex uses sandbox policy instead of per-dir flags.
workdirSet agent working directory(none)(none)(none)Only applied if you set a template.
default_flagPass through default flags{flag}{flag}{flag}Each default flag is rendered through this template.
skillsExpose skills paths(none)(none)(none)Use “–skills {path}” or similar if your CLI supports it.
system_promptInject system prompt(none)–system-prompt–prompt-interactiveIf 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.

Agent capability comparison

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.

CapabilityClaudeCodexGemini
Native --worktree flagYes (claude --worktree NAME)NoNo
Subagent worktree isolationYes (isolation: worktree)NoNo
Session resumeYes (claude --resume)NoNo
System prompt flag--system-prompt FILETrailing text--prompt-interactive
Write-access flag--dangerously-skip-permissions=| =--sandbox danger-full-access--yolo
TTY requirementWorks with subprocessWorks with subprocessRequires exec (TTY)
Default agentYes (sucoder default)SupportedSupported

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.

Pin agent Node version with nvm

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).

  1. 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.).

  2. 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.md file 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.

Unified skills catalog (local + curated)

  • 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 path
        

    The script reads ../Skills/trusted_skill_sources.yaml to determine which sources to include (local catalogs plus curated repos; defaults include openai/skills and anthropics/skills). If a target is not writable (for example /home/ligon/.sucoder/skills), use --output to 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.md files. 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/, and assets/ 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-skills repository under skill-creator/; use it alongside document-skill when 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.yaml as 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 Repository

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.

Why Skills Are Separate

  1. Security Isolation — An agent working on tool code cannot simultaneously modify skills that influence agent behavior.
  2. Review Clarity — Code reviews and content reviews use different security mindsets and can be performed separately.
  3. Blast Radius Reduction — Malicious skill instructions cannot be hidden among tool code changes.

Version Compatibility

The tool enforces semantic versioning compatibility between itself and the skills repository:

  • Tool validates skills repository VERSION file on startup
  • Compatible: Tool requires 1.0.0, skills has 1.x.x
  • Incompatible: Tool requires 1.x.x, skills has 2.0.0

Skills repository: https://github.com/ligon/sucoder-skills

To update skills:

cd ~/.sucoder/skills  # Or ~/Projects/sucoder-skills
git pull

To skip version checking (not recommended):

export SUCODER_SKIP_SKILLS_VERSION=1

Version Bumping Strategy

PATCH (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

Testing

Run the automated tests with pytest.

pytest

About

Sandbox for coding agents based on unix filesystem permissions

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors