Container wrappers for agentic coding tools that sandbox execution to the selected workspace directory.
Isolate coding agents from sensitive files
Each agent runs inside a container with access limited to the current project directory, mounted as /workspace. Host credential files (SSH keys, API tokens, cloud configs) are never mounted into the container — they are copied once into an isolated per-workspace state directory on first run and never written back to the host. The container HOME is fully isolated, preventing any host configuration from leaking in.
Allow selected SSH connections without sharing keys with the agent
Contagent can forward SSH authentication into the container without ever exposing your private keys. An isolated per-workspace ssh-agent is started on the host, loaded only with the keys you explicitly approve via contagent ssh add. The container receives the agent socket — not the keys themselves. The corresponding public keys are copied into the container's ~/.ssh/ so IdentitiesOnly works correctly with agent forwarding. Selected Host blocks from your ~/.ssh/config are injected into the container so SSH aliases work transparently inside.
Load preferred modules from the CVMFS software stack
For CVMFS-enabled clusters (e.g. DRAC/Alliance), contagent tracks which lmod modules are loaded in your shell and makes them available inside the container. On first run, the current module list is saved to .contagent/<variant>/modules. On subsequent runs, if the loaded modules differ from the saved list, you are prompted to reconcile — keeping your container environment consistent across sessions without manual intervention.
- Claude Code
- Cursor CLI
- OpenCode
- Apptainer with/without CVMFS support (DRAC/Alliance clusters)
- Docker
- The target agentic tool configured on the host (the initial configuration and credentials are copied to each local workspace of the coding agent, but
claude login,agent login, oropencode auth loginmust first be executed on the host) - Docker (for Docker variants) or Apptainer (for Apptainer variants)
python3(for OpenCode variants — used to merge context intoopencode.json; available by default on all supported platforms)
cd /path/to/contagent
./contagent buildcontagent build shows the current container state and prompts you to select a variant:
| Choice | Type | Image |
|---|---|---|
| 1 (default) | apptainer-cvmfs |
~/.contagent/apptainer-cvmfs.sif |
| 2 | apptainer |
~/.contagent/apptainer.sif |
| 3 | docker |
docklaude Docker image |
The selected type is saved to ~/.contagent/settings and used by all subsequent contagent commands.
cd /path/to/project
contagent ssh add # select SSH hosts to forward into the container
contagent mount add # add extra bind mounts (e.g. /data, ~/models)These are optional and workspace-local. SSH and mounts can be configured at any time; a session restart is required for changes to take effect.
cd /path/to/project
/path/to/contagent/contagent claude [...] # Claude Code
/path/to/contagent/contagent agent [...] # Cursor CLI
/path/to/contagent/contagent opencode [...] # OpenCode
/path/to/contagent/contagent bash # Interactive shellAll arguments are passed through to the underlying tool.
Each tool maps to a wrapper script selected automatically based on the container type set during contagent build:
| Command | Docker | Apptainer | Apptainer + CVMFS |
|---|---|---|---|
contagent claude |
docklaude |
applaude |
applaude-cvmfs |
contagent agent |
docksur |
appsur |
appsur-cvmfs |
contagent opencode |
dockopen |
appopen |
appopen-cvmfs |
contagent bash |
dockbash |
appbash |
appbash-cvmfs |
Workspace state is stored under .contagent/<variant>/ in the project directory. Credentials are copied from the host on first run; host files are never modified.
contagent bash launches an interactive shell in the same container, with the same workspace mount and state isolation as the agent commands. This is useful for debugging or running one-off commands in the container environment.
Before each run, contagent writes .contagent/context.md in the workspace describing the container's filesystem layout, and injects it into the agent's session:
| Agent | Injection mechanism |
|---|---|
| Claude Code | --append-system-prompt-file /workspace/.contagent/context.md |
| OpenCode | Added to the instructions array in opencode.json |
| Cursor | Embedded in .cursor/rules/contagent.mdc (alwaysApply: true) |
The context file lists every mounted path the agent can see, with host↔container mappings and access modes. It also explains how to request additional mounts (via .contagent/mounts) and, for CVMFS variants, which modules are loaded.
Claude Code additionally receives two skills installed into the container home on first run (upgraded automatically when the skill version changes):
add-mount— guides Claude to propose and write a new.contagent/mountsentry with user consentcontagent-status— invokable as/contagent-status; summarises current mounts, modules, and how to expand access
.cursor/rules/contagent.mdc is automatically added to .gitignore so it is not committed.
contagent mount add # interactively add a mount
contagent mount list # show current mounts for this workspacecontagent mount add prompts for a host path (absolute or ~/…), a container
path, and an access mode (ro/rw). The entry is appended to
.contagent/mounts.
You can also edit .contagent/mounts directly:
# host_path:container_path[:mode] (mode: ro or rw, default: ro)
/data/shared:/data:ro
~/models:/models:ro
Lines starting with # are ignored. Tilde (~) is expanded to $HOME.
Paths that do not exist on the host are skipped with a warning. A session
restart is required for mount changes to take effect.
The -cvmfs variants track which lmod modules are loaded in your shell, storing them in .contagent/<variant>/modules and prompting you to reconcile on subsequent runs.
Load the modules you need before invoking the agent:
module load python/3.11.5 scipy-stack/2023b
cd /path/to/project
/path/to/contagent/contagent claude # or agent, or opencodeFirst run: the currently loaded modules are saved to the modules file automatically.
Subsequent runs: if the loaded modules differ from the saved list, you are prompted:
Loaded modules differ from .contagent/applaude/modules:
Currently loaded: python/3.11.5
In modules file: python/3.11.5 scipy-stack/2023b
(C)hange loaded modules to match file, (U)pdate file with current modules, (I)gnore? [C/U/I]:
| Choice | Effect |
|---|---|
Change |
Purge current modules and reload from the modules file |
Update |
Overwrite the modules file with the currently loaded modules |
Ignore |
Proceed as-is, leaving both the environment and the file unchanged |
The CVMFS variants also bind /cvmfs:/cvmfs into the container and propagate the full post-load environment (PATH, LD_LIBRARY_PATH, PYTHONPATH, etc.).
Contagent can forward an SSH agent into the container so agents can push to private repos or authenticate against remote hosts — without exposing your private keys.
Private keys never enter the container. An isolated per-workspace
ssh-agent is started on the host with only the approved key(s) loaded; the
container only receives the agent socket. Selected Host blocks from
~/.ssh/config are injected into the container's ~/.ssh/config so SSH
aliases work inside the container without any manual setup.
cd /path/to/project
contagent ssh addAn interactive menu lists every named Host block from your ~/.ssh/config
plus a "Default key" option that forwards your existing agent as-is. Toggle
entries by number; press Enter on a blank line when done.
SSH host configuration for: /path/to/project
1) Default key (forward agent as-is)
2) github github.com IdentityFile: ~/.ssh/id_github
3) work-server work.example.com (uses any key in agent)
4) * (Host *) IdentityFile: ~/.ssh/id_default
5) Done / Skip
Choice (toggle number, blank = done):
The selected Host blocks are written to .contagent/ssh-config and injected
into the container's ~/.ssh/config before each run. The private key paths are
recorded in .contagent/ssh-allowed-keys and loaded into an isolated
per-workspace ssh-agent at launch time.
IdentityFile injection for keyless hosts: if you select a named host that
has no IdentityFile in its block, and your ~/.ssh/config contains a
Host * block with an IdentityFile, that key is automatically injected into
the written host block. This gives precise control without relying on SSH's
implicit Host * fallback inside the container.
contagent ssh listSSH_AUTH_SOCK is set; test with:
ssh -T git@github.comIf a key has a passphrase, load it into the host agent once before launching:
ssh-add ~/.ssh/id_yourkey # enter passphrase onceContagent detects that all configured keys are already in the host agent and forwards it directly, without starting a new agent or prompting for the passphrase again.
SSH config is managed on the host via contagent ssh add. An agent inside the
container cannot modify it — a restart is required for any changes to take effect.
All variants:
- Copy credentials from the host into a per-workspace state directory on first run — host credential files are never modified
- Mount the current directory as
/workspace - Isolate container HOME to prevent host config leaking in
- Pass all command-line arguments through to the underlying tool
Warning: Do not run
contagentfrom your home directory (~/). This mounts your entire home as the workspace. If you do, you will be prompted to confirm, with the option to disable the warning permanently.
- Host credential files are never mounted into the container
- Credentials are copied to the workspace state directory before the container starts
- Container cannot modify host credential files
- All container state is confined to
.contagent/in each workspace
The test suite requires only bash — no Docker or Apptainer installation needed.
bash tests/run-all.shPass --verbose (or -v) to print a 2–3 line description of each test scenario:
bash tests/run-all.sh --verbose
bash tests/test-context-generation.sh -vIndividual test files can be run directly:
bash tests/test-common-mounts.sh
bash tests/test-common-lmod.sh
bash tests/test-contagent-settings.sh
bash tests/test-wrapper-preflight.sh
bash tests/test-credential-cleanup.sh
bash tests/test-context-generation.sh
bash tests/test-ssh-config-select.sh
bash tests/test-ssh-agent.sh| File | What it covers |
|---|---|
tests/test-applaude-cvmfs-modules.sh |
reconcile_cvmfs_modules() |
tests/test-common-mounts.sh |
init_mounts_file, parse_mounts_apptainer, parse_mounts_docker |
tests/test-common-lmod.sh |
ensure_module, load_apptainer_module, load_modules_from_file |
tests/test-contagent-settings.sh |
read_setting, set_setting, check_home_dir |
tests/test-wrapper-preflight.sh |
Pre-flight checks for all 12 wrapper scripts |
tests/test-credential-cleanup.sh |
Credential isolation for all Apptainer and Docker wrappers |
tests/test-context-generation.sh |
generate_context_file, generate_opencode_config, install_cursor_rules, install_claude_skills |
tests/test-ssh-config-select.sh |
parse_ssh_config_hosts, _parse_ssh_catchall_blocks, _collect_wildcard_ssh_blocks, prompt_ssh_host_selection, _inject_ssh_config |
tests/test-ssh-agent.sh |
forward_ssh_agent_apptainer, forward_ssh_agent_docker, _start_workspace_ssh_agent, contagent_ssh_agent_cleanup |