md

package module
v0.5.2 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Feb 17, 2026 License: Apache-2.0 Imports: 32 Imported by: 0

README

md: Branch-locked Development Containers for AI Coding Agents

Each container is locked to a repository-branch pair. No confusion. Safe parallel work.

Safe parallel work with multiple AI coding agents. Run Claude Code, Codex, Amp CLI, Gemini CLI, Kilo CLI, Pi, and other tools in isolated containers without branch conflicts, file interference, or environmental headaches.

The Problem

AI coding agents work best when given full command execution (YOLO mode). But running them locally is risky:

  • Branch conflicts - Agent changes on one branch interfere with your local checkout
  • Test conflicts - Running tests simultaneously causes failures and race conditions
  • Environment pollution - Dependencies and state accumulate, causing hidden bugs
  • Context switching - Switching branches while an agent is working loses progress

The Solution

md gives each AI agent a complete, isolated container with a full git clone. You can:

  • Run agents on multiple branches simultaneously
  • Switch local branches without affecting running agents
  • Run tests in parallel without conflicts
  • Keep your local checkout clean
  • Delete containers cleanly when done
  • Accessible over Tailscale

Quick Start

# Start container for your current branch
git checkout -b wip origin/main
md start --display  # Add --display if you need VNC support

# SSH in and run your coding agent, where "wip" is the branch associated with this container
ssh md-<repo>-wip
amp
# Exit from the ssh session into the container (or use a separate terminal)
exit

# Pull changes back when done
md pull
# Verify changes
git log -1

# Clean up the container
md kill

Installation

go install github.com/maruel/md/cmd/md@latest

Recommended: Also install git-maruel for the git squash and git rb helpers.

Harnesses

For coding agents: The container includes /home/user/src/AGENTS.md which provides information about preinstalled tools (list available in ~/src/tool_versions.md).

Harnesses preinstalled:

  • amp: ~/.config/amp/AGENTS.md
  • claude: ~/.claude/CLAUDE.md
  • codex: ~/.codex/AGENTS.md
  • gemini: ~/.gemini/GEMINI.md
    • Recommended in ~/.qwen/settings.json to change "context" / "fileName" to AGENTS.md
  • kilo: ~/.kilocode/rules/*.md
  • opencode: ~/.config/opencode/AGENTS.md
  • pi: ~/.pi/agent/AGENTS.md
  • qwen: ~/.qwen/QWEN.md
    • Recommended in ~/.qwen/settings.json to change "context" / "fileName" to AGENTS.md
Readme for agents (https://agents.md) and Skills (https://agentskills.io)

FYI, here's locations of AGENTS.md for each harness:

mkdir -p ~/.config/agents ~/.config/amp ~/.claude ~/.codex ~/.gemini ~/.kilocode/rules ~/.config/opencode ~/.pi/agent ~/.qwen
echo "Read ~/AGENTS.md if present." >> ~/.config/agents/AGENTS.md
ln -s ../../.config/agents/AGENTS.md ~/.config/amp/AGENTS.md
ln -s ../.config/agents/AGENTS.md ~/.claude/CLAUDE.md
ln -s ../.config/agents/AGENTS.md ~/.codex/AGENTS.md
ln -s ../.config/agents/AGENTS.md ~/.gemini/AGENTS.md
ln -s ../../.config/agents/AGENTS.md ~/.kilocode/rules/AGENTS.md
ln -s ../../.config/agents/AGENTS.md ~/.config/opencode/AGENTS.md
ln -s ../../.config/agents/AGENTS.md ~/.pi/agent/AGENTS.md
ln -s ../.config/agents/AGENTS.md ~/.qwen/AGENTS.md

FYI, here's locations of skills for each harness:

  • amp: ~/.config/agents/skills/**/SKILL.md (recursive)
    • Fallbacks to ~/.claude/skills/
  • antigravity: ~/.gemini/antigravity/skills/<name>/SKILL.md
  • claude: ~/.claude/skills/<name>/SKILL.md
  • codex: ~/.codex/skills/**/SKILL.md (recursive)
  • cursor: ~/.cursor/skills/<name>/SKILL.md
    • Fallbacks to ~/.claude/skills/
  • gemini: ~/.gemini/skills/<name>/SKILL.md
  • kilo: ~/.kilocode/skills/<name>/SKILL.md
  • opencode: ~/.config/opencode/skill/<name>/SKILL.md
    • Fallbacks to ~/.claude/skills/
  • pi: ~/.pi/agent/skills/**/SKILL.md (recursive)
    • Fallbacks to ~/.claude/skills/, ~/.codex/skills/ (recursive)
  • qwen: ~/.qwen/skills/<name>/SKILL.md

Centralize your skills with symlinks:

mkdir -p ~/.config/agents/skills ~/.claude ~/.codex ~/.cursor ~/.gemini/antigravity ~/.kilocode ~/.config/opencode ~/.pi/agent ~/.qwen
ln -s ../.config/agents/skills/ ~/.claude/skills
ln -s ../.config/agents/skills/ ~/.codex/skills
ln -s ../.config/agents/skills/ ~/.cursor/skills
ln -s ../../.config/agents/skills/ ~/.gemini/antigravity/skills
ln -s ../.config/agents/skills/ ~/.gemini/skills
ln -s ../.config/agents/skills/ ~/.kilocode/skills
ln -s ../../.config/agents/skills/ ~/.config/opencode/skill
ln -s ../../.config/agents/skills/ ~/.pi/agent/skills
ln -s ../.config/agents/skills/ ~/.qwen/skills

How It Works

Container Setup

Each container is named md-<repo-name>-<branch-name> with:

  • Isolated git clone - ~/src/<repo-name> inside the container is a git clone of your local repository. It tracks branch base which matches your local branch. This is useful for commit-happy agents like Codex to track pending changes.
  • User-mapped permissions - Container runs as your local user ID for proper file permissions
  • SSH access - Connect via ssh md-<repo>-<branch>
  • Remote GUI (VNC) - Optional full desktop environment (via --display) accessible via VNC on a dynamic port
  • Minimal overhead - Only sshd runs by default; no unnecessary background services
Preinstalled Tools
Configuration

Agent configurations and credentials are automatically mounted:

  • Agent configurations: ~/.amp, ~/.claude, ~/.codex, ~/.gemini, ~/.kilocode, ~/.pi, ~/.qwen, ~/.config/agents, ~/.config/opencode, ~/.local/state/opencode, ~/.local/share/opencode
  • Android ADB keys: ~/.android
  • Gradle cache: ~/.gradle

Environment variables can be passed via:

  1. .env file in your repository (auto-mapped)
  2. ~/.config/md/env on your local machine (applies to all containers)

For example:

# ~/.config/md/env
ANTHROPIC_API_KEY=your_key
OPENAI_API_KEY=your_key
GitHub Authentication

The container doesn't have access to your GitHub credentials. To enable git credentials and access to GitHub (e.g. to create PRs or issues), authenticate inside the container via:

gh auth login

Commands

Command Purpose
md start Create and start a container for the current branch
md start --display Start container with X11/VNC desktop environment enabled
md start --tailscale Start container with Tailscale networking for remote SSH access
md start --usb Start container with USB device passthrough (for ADB, etc.)
md run <cmd> Start a temporary container, run a command, then clean up
md list List all md containers
ssh md-<repo>-<branch> Access the container via SSH
md vnc Open VNC connection to the container
md diff Show changes (runs git diff base). Arguments are passed through, e.g. md diff --stat
md pull Pull changes from container back to local branch
md push Push local changes to the container
md kill Stop and remove the container

Remote GUI Access (VNC)

Each container can include a full XFCE4 desktop environment. It must be enabled at startup:

md start --display
md vnc

md vnc opens the VNC connection in your default VNC client.

Recommended VNC clients by OS:

The DISPLAY environment variable is automatically set in SSH sessions, so X11 applications launched from SSH will appear on the VNC desktop.

Contributing

Made with ❤️ by Marc-Antoine Ruel. Contributions are very appreciated! Thanks in advance! 🙏

Documentation

Overview

Package md manages isolated Docker development containers for AI coding agents.

It provides programmatic access to create, manage, and tear down containers that each get a full git clone of your repository with SSH access.

Index

Constants

View Source
const DefaultBaseImage = "ghcr.io/maruel/md"

DefaultBaseImage is the base image used when none is specified.

Variables

This section is empty.

Functions

func GitCurrentBranch

func GitCurrentBranch(ctx context.Context, wd string) (string, error)

GitCurrentBranch returns the current branch name for the given working directory.

func GitRootDir

func GitRootDir(ctx context.Context, wd string) (string, error)

GitRootDir returns the git repository root for the given working directory.

Types

type Client

type Client struct {
	// W is the writer for progress and status messages.
	W io.Writer

	// Paths.
	Home          string
	XDGConfigHome string
	XDGDataHome   string
	XDGStateHome  string

	// SSH key paths.
	HostKeyPath string // ~/.config/md/ssh_host_ed25519_key (generated)
	UserKeyPath string // ~/.ssh/md

	// Docker.
	ImageName string

	// Tokens.
	GithubToken     string // GitHub API token for Docker build secrets.
	TailscaleAPIKey string // Tailscale API key for auth key generation and device deletion.
	// contains filtered or unexported fields
}

Client holds global MD tool state (paths, image config, SSH keys).

func New

func New() (*Client, error)

New creates a Client with global MD tool config.

func (*Client) BuildBase

func (c *Client) BuildBase(ctx context.Context, serialSetup bool) (retErr error)

BuildBase builds the base Docker image locally.

func (*Client) Container

func (c *Client) Container(gitRoot, branch string) *Container

Container returns a Container handle for the given git root and branch.

It doesn't start it, it is just a reference.

func (*Client) List

func (c *Client) List(ctx context.Context) ([]*Container, error)

List returns running md containers sorted by name.

func (*Client) Prepare

func (c *Client) Prepare() error

Prepare ensures all directories and keys exist.

type Container

type Container struct {
	*Client
	GitRoot   string
	RepoName  string
	Branch    string
	Name      string
	State     string
	CreatedAt time.Time
}

Container holds state for a single container instance.

func (*Container) Diff

func (c *Container) Diff(ctx context.Context, stdout, stderr io.Writer, extraArgs []string) error

Diff writes the diff between base and current in the container. When stdout is a terminal, a TTY is allocated so git's pager and colors work.

func (*Container) Fetch

func (c *Container) Fetch(ctx context.Context, provider, model string) error

Fetch commits any uncommitted changes in the container and fetches them locally, updating the remote-tracking ref without integrating.

provider and model control AI commit message generation. See https://github.com/maruel/genai for valid names. If provider is empty, a default message is used.

func (*Container) GetHostPort

func (c *Container) GetHostPort(ctx context.Context, containerPort string) (string, error)

GetHostPort returns the host port mapped to a container port (e.g. "5901/tcp"). Returns empty string if the port is not mapped.

func (*Container) Kill

func (c *Container) Kill(ctx context.Context) error

Kill stops and removes the container.

func (*Container) Pull

func (c *Container) Pull(ctx context.Context, provider, model string) error

Pull fetches changes from the container and integrates them into the local branch.

provider and model control AI commit message generation. See https://github.com/maruel/genai for valid names. If provider is empty, a default message is used.

func (*Container) Push

func (c *Container) Push(ctx context.Context) error

Push force-pushes local state into the container.

func (*Container) Run

func (c *Container) Run(ctx context.Context, baseImage string, command []string) (_ int, retErr error)

Run starts a temporary container, runs a command, then cleans up. baseImage is the full Docker image reference; if empty, DefaultBaseImage is used.

func (*Container) Start

func (c *Container) Start(ctx context.Context, opts *StartOpts) (retErr error)

Start creates and starts a container.

type StartOpts

type StartOpts struct {
	// BaseImage is the full Docker image reference (e.g.
	// "ghcr.io/maruel/md:v1.0" or "myregistry/custom:tag"). When empty,
	// DefaultBaseImage is used.
	BaseImage          string
	Display            bool
	Tailscale          bool
	TailscaleAuthKey   string
	TailscaleEphemeral bool
	USB                bool
	Labels             []string
	NoSSH              bool
	Quiet              bool
}

StartOpts configures container startup.

Directories

Path Synopsis
cmd
md command
md (my devenv) manages isolated Docker development containers for AI coding agents.
md (my devenv) manages isolated Docker development containers for AI coding agents.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL