AgentCore is a universal Docker container for coding agents. One image runs Claude Code, OpenCode, or Aider — selected at runtime via AGENT_TYPE. It handles SSH, credential injection, MCP tool auto-discovery, optional desktop (Xvfb/VNC/noVNC/Chrome), git repo sync, plugin loading, and a lightweight HTTP control API. Orchestrators and swarm projects use it as a building block.
Three image tiers:
Dockerfile.minimal— CLI-only, ~500 MB (Ubuntu 24.04)Dockerfile.ubuntu— Full desktop + Chrome + Playwright, ~3.5 GBDockerfile.kali— Kali Linux + security tools, ~5 GB
# Build images
docker build -f dockerfiles/Dockerfile.minimal -t agentcore:minimal .
docker build -f dockerfiles/Dockerfile.ubuntu -t agentcore:ubuntu .
docker build -f dockerfiles/Dockerfile.kali -t agentcore:kali .
# Run
docker run -d -e AGENT_TYPE=claude -e ANTHROPIC_API_KEY=xxx -p 2222:22 -p 8080:8080 agentcore:minimal
docker run -d -e AGENT_TYPE=claude -e ENABLE_DESKTOP=true -p 2222:22 -p 6080:6080 -p 8080:8080 agentcore:ubuntu
# SSH in
ssh agent@localhost -p 2222
# Health check
curl http://localhost:8080/health
# Docker Compose (single agent)
cd examples && cp .env.example .env && docker compose up -d
# Docker Compose (multi-agent swarm)
cd examples && docker compose -f docker-compose.swarm.yml up -d
# Run Python tests
pip install -r tests/requirements-dev.txt
pytest tests/ -v
# Run shell tests (requires bats-core)
bats tests/shell/*.batsAgentCore/
├── dockerfiles/
│ ├── Dockerfile.minimal Ubuntu 24.04 CLI-only
│ ├── Dockerfile.ubuntu Ubuntu + desktop + Chrome + Playwright
│ └── Dockerfile.kali Kali + security tools
│
├── base/ Build-time install scripts (COPY'd, then deleted)
│ ├── packages-core.sh apt: git, curl, python3, tmux, openssh, sudo, jq
│ ├── packages-desktop.sh apt: xvfb, x11vnc, novnc, openbox, xdotool
│ ├── packages-dev.sh Optional: .NET, Rust, Docker (via build args)
│ ├── install-node.sh Node.js 22.x + global npm packages
│ ├── install-chrome.sh Google Chrome stable
│ ├── install-playwright.sh Playwright chromium + firefox browsers
│ ├── create-agent-user.sh agent:agent user (uid 1000, sudo NOPASSWD)
│ └── install-agents.sh Claude Code + Aider + MCP Python deps
│
├── config/
│ ├── sshd_config SSH: PermitRootLogin no, AllowUsers agent
│ ├── openbox-rc.xml Window manager: Chrome auto-maximize
│ └── chrome-policies.json Chrome: disable telemetry, signin, autofill
│
├── hooks/
│ └── hivemind/ HiveMindDB hooks + task watcher
│ ├── session-start.sh SessionStart: register agent + recall memories
│ ├── prompt-search.sh UserPromptSubmit: semantic RAG search
│ ├── track-changes.sh PostToolUse (Edit|Write): log file changes (async)
│ ├── session-stop.sh Stop: heartbeat (async)
│ ├── task-watcher.js WebSocket daemon: subscribe to HiveMindDB tasks
│ └── task-inbox.sh UserPromptSubmit: inject pending tasks as context
│
├── entrypoint/
│ ├── entrypoint.sh Main orchestrator: sources lib/ then modules/*
│ ├── lib/
│ │ ├── env.sh All env var defaults (: "${VAR:=default}" pattern)
│ │ └── log.sh log_info/warn/error/debug with [LEVEL] [module] format
│ └── modules/ Numbered modules, sourced in order
│ ├── 00-init.sh Directories, permissions, env validation
│ ├── 10-ssh.sh Start sshd, configure keys/passwords
│ ├── 20-desktop.sh Xvfb + VNC + noVNC (if ENABLE_DESKTOP=true)
│ ├── 30-credentials.sh Copy from /credentials mount into agent home
│ ├── 40-agent-setup.sh Claude onboarding bypass, settings.json, teams
│ ├── 50-mcp-tools.sh Auto-discover MCP tools from library.json
│ ├── 52-memory-hooks.sh Install HiveMindDB auto-memory hooks + task watcher
│ ├── 55-plugins.sh Clone PLUGIN_REPOS, symlink into agent
│ ├── 60-llm-config.sh Wire CodeGate/proxy/direct API keys
│ ├── 65-repos.sh Clone REPOS, start background sync daemon
│ ├── 70-agent-start.sh Launch agent in tmux session (as agent user)
│ ├── 80-api-server.sh Start control API (if ENABLE_API=true)
│ ├── 90-auto-update.sh Start auto-update daemon (if ENABLE_AUTO_UPDATE=true)
│ └── 99-cred-refresh.sh Background credential refresh + optional adblock
│
├── api/
│ └── server.py Python stdlib HTTP API on port 8080
│
├── mcp-tools/
│ ├── library.json 12-tool registry (mcpServers dict)
│ ├── desktop-control/
│ │ ├── server.py 17-tool MCP: screen, mouse, keyboard, window, clipboard
│ │ └── requirements.txt
│ └── agent-memory/
│ ├── server.py 5-tool MCP: remember, recall, forget, search, list_topics
│ └── requirements.txt
│
├── auto-update/
│ ├── updater.sh Main daemon loop
│ └── agents/
│ ├── claude-code.sh Claude Code update via native installer
│ ├── opencode.sh OpenCode update via GitHub releases
│ └── aider.sh Aider update via pip
│
├── repo-sync/
│ └── sync.sh Git sync daemon (pull mode + push mode)
│
├── plugins/
│ └── .gitkeep Plugin mount point
│
├── examples/
│ ├── .env.example Complete env var reference
│ ├── docker-compose.yml Single agent quick start
│ └── docker-compose.swarm.yml Multi-agent + CodeGate + Qdrant
│
├── tests/
│ ├── requirements-dev.txt pytest + pytest-mock + pytest-asyncio
│ ├── conftest.py Shared fixtures
│ ├── test_api_server.py 52 tests: API handler, auth, routing, shell helpers
│ ├── test_agent_memory.py 32 tests: MCP memory tool operations
│ ├── test_mcp_filter.py 26 tests: MCP library filtering logic
│ ├── test_configs.py 32 tests: library.json, chrome policies, sshd config
│ ├── test_llm_config.py 28 tests: LLM settings merge logic
│ ├── test-minimal.sh Docker smoke test (minimal image)
│ ├── test-ubuntu.sh Docker smoke test (ubuntu image)
│ ├── test-api.sh Docker smoke test (API endpoints)
│ └── shell/
│ ├── run_tests.sh bats-core runner
│ ├── test_log.bats log.sh function tests
│ ├── test_env.bats env.sh variable defaults
│ ├── test_repo_sync.bats repo-sync daemon tests
│ └── test_updater.bats auto-update daemon tests
│
├── .claude/
│ └── commands/ Claude Code slash commands
│ ├── build.md /build — build Docker images
│ ├── test.md /test — run test suites
│ ├── add-mcp-tool.md /add-mcp-tool — add a new MCP server
│ ├── add-module.md /add-module — add a new entrypoint module
│ ├── add-agent.md /add-agent — add a new agent type
│ └── validate.md /validate — validate configs and scripts
│
├── .gitattributes Force LF line endings for Docker
├── .gitignore
├── .dockerignore
├── CLAUDE.md This file
├── README.md
└── LICENSE MIT
entrypoint.sh runs as root:
1. source lib/env.sh set all env var defaults, export
2. source lib/log.sh load logging functions
3. for each modules/*.sh: source in numeric order
00-init → create dirs, fix permissions
10-ssh → start sshd on :22
20-desktop → [conditional] start Xvfb+VNC+noVNC
30-credentials→ copy from /credentials mount
40-agent-setup→ Claude onboarding bypass, settings files
50-mcp-tools → filter library.json → register via `claude mcp add-json`
52-memory-hooks→ [conditional] install HiveMindDB auto-memory hooks
55-plugins → clone PLUGIN_REPOS, symlink plugins
60-llm-config → write ~/.claude/settings.json with LLM endpoint
65-repos → clone REPOS, start sync daemon in background
70-agent-start→ launch agent in tmux as agent user
80-api-server → [conditional] start api/server.py on :8080
90-auto-update→ [conditional] start updater.sh in background
99-cred-refresh→ start credential refresh loop in background
4. exec tail -f /dev/null keep container alive
/opt/agentcore/
entrypoint/ entrypoint.sh + lib/ + modules/
hooks/ HiveMindDB auto-memory hooks + task watcher
api/ server.py
auto-update/ updater.sh + agents/
repo-sync/ sync.sh
/opt/mcp-tools/
library.json MCP tool registry
desktop-control/ built-in MCP server
agent-memory/ built-in MCP server
custom/ mount point for user MCP tools
/home/agent/
.claude/ settings.json, apiKeyHelper.sh, teams/, tasks/
.ssh/ injected SSH keys
/workspace/
projects/ working directory (volume mount)
.state/ runtime state
.agent-memory/ local memory store
/credentials/ read-only mount: claude/, ssh/, git/, api-keys/
The module:
- Reads
mcp-tools/library.json— a dict ofmcpServers - For each tool, checks:
requiresDesktop(skip if desktop off),requiredEnv(skip if missing) - Includes tools that are
default: trueOR have allrequiredEnvvars set - Scans
/opt/mcp-tools/custom/for additional tools withmanifest.json - For Claude: registers each tool via
claude mcp add-json(writes to~/.claude.json) - For non-Claude agents: writes
mcpServersdirectly tosettings.json
library.json format:
{
"mcpServers": {
"tool-name": {
"command": "npx",
"args": ["@scope/mcp-server"],
"default": true,
"requiresDesktop": false,
"requiredEnv": ["SOME_KEY"],
"category": "development"
}
}
}When HIVEMINDDB_URL is set, installs Claude Code hooks that automatically manage shared memory:
| Hook Event | Script | Behavior |
|---|---|---|
SessionStart |
session-start.sh |
Registers agent with HiveMindDB, recalls recent memories, injects as context |
UserPromptSubmit |
prompt-search.sh |
Semantic search on user's prompt → injects relevant memories (RAG) |
PostToolUse |
track-changes.sh |
Logs file edits/writes as memories (async, non-blocking) |
Stop |
session-stop.sh |
Sends heartbeat (async, non-blocking) |
For Aider/OpenCode (no hooks API): writes a system prompt reminder about memory MCP tools.
Hook scripts live at /opt/agentcore/hooks/hivemind/ and are copied to ~/.claude/hooks/hivemind/ at startup. All hooks are no-ops when HIVEMINDDB_URL is unset.
CODEGATE_URLset → proxy mode, writesapiKeyHelper+ANTHROPIC_BASE_URLLLM_PROXY_URLset → same as aboveANTHROPIC_API_KEYset → direct mode, writes key tosettings.json- Nothing set → warns, still writes
skipDangerousModePermissionPrompt
Reads from /credentials mount:
claude/→.credentials.json,settings.json,settings.local.json,statsig/ssh/→ copied to~/.ssh/, permissions fixed (700/600)git/→.gitconfigapi-keys/→ sources all*.envfiles
Python stdlib http.server, port 8080. Auth via API_AUTH_TOKEN Bearer token.
| Endpoint | Method | Auth | Description |
|---|---|---|---|
/health |
GET | No | Agent type, ID, tmux session status |
/ready |
GET | No | Readiness probe |
/instances |
GET | Yes | List tmux windows |
/instances |
POST | Yes | Create tmux window + start agent |
/instances/:id |
DELETE | Yes | Kill tmux window |
/exec |
POST | Yes | Run shell command (stdout, stderr, code) |
/logs |
GET | Yes | Last N lines of tmux pane output |
All tmux commands run as the agent user via su - agent -c.
Creates a tmux session named agent as the agent user. The selected agent starts in window 0. AGENT_TYPE=all creates three windows (Claude, OpenCode, Aider). Sends fallback Enter keypresses after 5s to dismiss any startup prompts.
REPOS format: url|local_path|branch|mode (newline-separated)
pullmode:git fetch && git reset --hard origin/branchpushmode:git add -A && git commit && git push- Auth:
GITHUB_TOKENinjected into remote URLs - Interval:
REPO_SYNC_INTERVAL(default 300s)
All defaults are in entrypoint/lib/env.sh. Full reference with comments in examples/.env.example.
| Variable | Default | Description |
|---|---|---|
AGENT_TYPE |
claude |
claude, opencode, aider, all, none |
AGENT_ID |
default |
Unique ID for orchestrators |
AGENT_NAME |
$AGENT_ID |
Human-readable name |
AGENT_ROLE |
(empty) | Role hint (e.g. backend, reviewer) |
| Variable | Default | Description |
|---|---|---|
ENABLE_DESKTOP |
false |
Xvfb + VNC + noVNC + Chrome |
ENABLE_API |
true |
HTTP control API on :8080 |
ENABLE_AUTO_UPDATE |
true |
Background agent updater |
ENABLE_DIND |
false |
Docker-in-Docker |
ENABLE_ADBLOCK |
false |
StevenBlack ad-blocking hosts |
| Variable | Default | Description |
|---|---|---|
CODEGATE_URL |
(empty) | CodeGate proxy URL |
LLM_PROXY_URL |
(empty) | Generic LLM proxy URL |
PROXY_API_KEY |
(empty) | Proxy auth key |
ANTHROPIC_API_KEY |
(empty) | Direct Anthropic key |
OPENAI_API_KEY |
(empty) | Direct OpenAI key |
| Variable | Default | Description |
|---|---|---|
SSH_PASSWORD |
agent |
SSH password (empty = disable) |
SSH_AUTHORIZED_KEYS |
(empty) | Public keys (newline-separated) |
API_AUTH_TOKEN |
(empty) | Bearer token for control API |
VNC_PASSWORD |
agentpwd |
VNC password |
VNC_RESOLUTION |
1920x1080x24 |
Virtual display resolution |
| Variable | Default | Description |
|---|---|---|
REPOS |
(empty) | url|path|branch|mode per line |
GITHUB_TOKEN |
(empty) | Git auth token |
REPO_SYNC_INTERVAL |
300 |
Sync interval (seconds) |
PLUGIN_REPOS |
(empty) | Plugin git URLs (newline-separated) |
PLUGIN_SYNC_INTERVAL |
3600 |
Plugin sync interval (seconds) |
| Variable | Default | Description |
|---|---|---|
MEMORY_PROVIDER |
local |
local, mem0, qdrant, hiveminddb |
MEM0_API_KEY |
(empty) | Enables mem0 MCP |
QDRANT_URL |
(empty) | Enables Qdrant MCP |
HIVEMINDDB_URL |
(empty) | Enables HiveMindDB MCP + auto-memory hooks |
- Add entry to
mcp-tools/library.jsonundermcpServers - Set
default: truefor always-on, orrequiredEnv: ["VAR"]for opt-in - The
50-mcp-tools.shmodule auto-discovers it — no script changes needed
- Create
entrypoint/modules/NN-name.sh(pick a number between existing ones) - Use
log_info,log_warn,log_errorfromlib/log.sh $CURRENT_MODULEis set automatically from the filename- Use
return 0to skip (notexit 0— modules are sourced, not executed)
- Add install logic in
base/install-agents.sh(or a newbase/install-<name>.sh) - Add a case branch in
entrypoint/modules/40-agent-setup.shfor config - Add a case branch in
entrypoint/modules/70-agent-start.shfor launch - Add the agent command in
api/server.pycreate_tmux_window()dict - Add an update script in
auto-update/agents/<name>.sh
- Copy closest existing Dockerfile
- Add/remove
RUN /tmp/base/*.shlines for the packages needed - All Dockerfiles must: COPY
base/,config/,hooks/,mcp-tools/,api/,auto-update/,repo-sync/,entrypoint/ - All must set
ENTRYPOINT ["/opt/agentcore/entrypoint/entrypoint.sh"]
- Add the service to
docker-compose.ymlordocker-compose.swarm.yml - Pass the env var to the agent container (e.g.
QDRANT_URL=http://qdrant:6333) - Add an MCP entry in
mcp-tools/library.jsonwith"requiredEnv": ["QDRANT_URL"] - The filtering module auto-enables it — no code changes needed
# Python unit tests (170 tests)
pip install -r tests/requirements-dev.txt
pytest tests/ -v
# Shell unit tests (requires bats-core)
bats tests/shell/*.bats
# Docker smoke tests (build + run + verify)
bash tests/test-minimal.sh # minimal image
bash tests/test-ubuntu.sh # ubuntu image + desktop
bash tests/test-api.sh # API endpoint checksTest files test against real config files (library.json, sshd_config, chrome-policies.json) and replicate the Python logic from shell modules to test in isolation.
- Line endings: Windows creates CRLF.
.gitattributesforces LF. Dockerfiles also runsed -i 's/\r$//'as a safety net. - tmux must run as agent user: Entrypoint runs as root, but all tmux commands use
su - agent -c. The API server also runs tmux commands as agent. - Modules are sourced, not executed: Use
return 0to skip a module, notexit 0(which would kill the entrypoint). - MCP library.json is a dict:
{"mcpServers": {"name": {...}}}, not a list. The filtering script iterates.items(). - Claude onboarding bypass:
40-agent-setup.shsetshasCompletedOnboarding,hasCompletedAuthFlow, and enables experimental teams. Without this, Claude Code prompts interactively on first run. - apiKeyHelper pattern: When using a proxy, Claude Code reads the API key from a helper script (
apiKeyHelper.sh) instead of an env var. This is how proxy auth works. - Shell quote helper:
api/server.pyhas_shell_quote()for safe command embedding. All user-provided strings must go through it.