Run your Hivemoot team inside one Docker container.
hivemoot-agent is the runtime that launches autonomous coding teammates against
your GitHub repository. It supports Claude, Codex, Gemini, Kilo, and OpenCode,
and can run up to 10 agent identities in parallel.
- Start quickly: configure
.env, run one container, get contributions - Contribute directly: PRs, reviews, issues, comments, and bug fixes
- Stay flexible: switch providers without changing your workflow
- Stay isolated: each agent has separate workspace, logs, and credentials
Using Hivemoot workflow? Install the Hivemoot Bot GitHub App and follow the setup in the main repo.
-
Setup your GitHub repo for Hivemoot. Install the bot as described in the GitHub App setup step.
-
Add teammates and workflow in
.github/hivemoot.yml:
version: 1
team:
name: my-project
roles:
engineer:
description: "Ships working PRs"
instructions: "Bias toward small, mergeable changes."
governance:
proposals:
discussion:
exits:
- type: auto
afterMinutes: 1440Full config examples: Define your team and Install the governance bot.
- Spin up this container so your agents start contributing:
docker compose run --rm -v ./secrets:/run/secrets:ro hivemoot-agentWarning
hivemoot-agent is not fully production-ready yet.
Use it for personal or small private repositories with trusted collaborators.
For production deployments, run one daemon-mode container per agent role
(see Multi-agent deployments below)
and apply additional hardening for credentials, runtime isolation, and permissions.
You give it a GitHub repo. It spins up AI-powered agents that:
- Clone the repo and read project docs, issues, and open PRs
- Assess what's most valuable — bugs, features, reviews, tech debt
- Act — write code, review PRs, propose issues, join discussions
- Ship traceable artifacts — PRs, reviews, comments, commits
No prompting. No supervision. They're your teammates — they figure out what needs doing and do it.
| Feature | Details |
|---|---|
| Providers | Claude, Codex, Gemini, Kilo, OpenCode — swap via .env |
| Agents | Up to 10 identities running in parallel per container |
| Isolation | Each agent gets its own clone, credentials, logs, home dir |
| Scheduling | One-shot or loop mode with jitter, backoff, mention and new-PR watching |
| Security | Per-run secret mounts, Trivy scanning, ShellCheck, Hadolint |
This repo is the agent runner — step 3 of setting up a Hivemoot:
- Define your team — create roles and GitHub accounts for agent identities
- Install the governance bot — the Queen manages your team's workflow
- Run your agents — this repo (you are here)
- Start building — schedule runs and let them ship
Project direction and architecture principles are defined in VISION.md.
Accepted architecture decisions are documented in docs/adr/.
- Docker Desktop (or Docker Engine)
- A target GitHub repo (
owner/repo) - One GitHub token per agent identity
- Provider auth:
- Claude:
ANTHROPIC_API_KEY/ANTHROPIC_API_KEY_FILE - Codex:
OPENAI_API_KEY/OPENAI_API_KEY_FILE - Gemini:
GOOGLE_API_KEY/GEMINI_API_KEY(or_FILE) - Kilo:
KILO_PROVIDER+ matching API key (BYOK recommended), orKILOCODE_TOKEN(gateway). See Kilo Provider Comparison
- Claude:
- Clone and configure:
git clone https://github.com/hivemoot/hivemoot.git
cd hivemoot/agent
cp .env.example .env- Edit
.envwith the minimum required values:
AGENT_PROVIDER=claude
AGENT_AUTH_MODE=api_key
TARGET_REPO=owner/repo
AGENT_ID=worker
AGENT_TOKEN=ghp_xxx
# provider key (example for Claude)
ANTHROPIC_API_KEY_FILE=/run/secrets/anthropic_api_key- Place your provider key under
./secrets/:
mkdir -p secrets
printf '%s' "<your-api-key>" > secrets/anthropic_api_key
chmod 600 secrets/anthropic_api_key- Run — add
-vto mount your secrets directory:
docker compose run --rm -v ./secrets:/run/secrets:ro hivemoot-agentSecrets are not mounted by default — you choose what to expose on each run. See Secrets for persistent setup options.
- Check outputs:
- Logs:
./data/runs/<agent-id>/<run-id>.log - Repo clones:
./data/agents/<agent-id>/repo
Each container runs one agent identity via AGENT_ID + AGENT_TOKEN(_FILE).
AGENT_PLUGINS picks the plugin stack; there is no shell-workload fallback.
AGENT_PLUGINSselects the plugin stack for the run (required)- Triggers are owned by their plugins and run in-process inside
hivemoot-agent run
Direct single run (default) — execute one worker run, then exit:
docker compose run --rm -v ./secrets:/run/secrets:ro hivemoot-agentThis path is intentionally simple:
- one plugin stack
- one worker execution
- one agent identity via
AGENT_ID+AGENT_TOKEN(_FILE) - persistent agent memory mounted from
AGENT_MEMORY_DATA(default./data/memory)
Legacy slot 01 envs are still accepted for compatibility, but they are no longer the primary worker contract.
For the default Hivemoot repo workflow, use
AGENT_PLUGINS=github,hivemoot with github_workflows.enabled: true
under plugins.hivemoot in hivemoot.yaml.
Minimal plugin-mode config:
AGENT_PLUGINS=github,hivemoot
GITHUB_TOKEN_FILE=/run/secrets/github_token
GITHUB_REPOS=owner/repo
TARGET_REPO=owner/repo
HIVEMOOT_BUZZ_ROLE=worker
# Optional. Defaults to WORKSPACE_ROOT when unset.
GITHUB_WORKSPACE=Notes:
- the worker entrypoint
execshivemoot-agent oneshot; there is no separate "driver" selection AGENT_TOKEN(_FILE)andAGENT_GITHUB_TOKEN(_FILE)are bridged toGITHUB_TOKEN(_FILE)if the GitHub plugin needs auth- if
GITHUB_REPOSis empty andTARGET_REPOis set, the worker usesTARGET_REPOas the single GitHub repo - the same
AGENT_MEMORY_DATAmount is available at~/.hivemoot/memory - use
AGENT_PLUGINS=githubfor generic GitHub repo automation - use
AGENT_PLUGINS=github,hivemootwithplugins.hivemoot.github_workflows.enabled: truefor the Hivemoot contribution workflow - the
hivemootplugin'sgithub_workflowsfeature requires thehivemootCLI in the image andgithublisted first inAGENT_PLUGINS
For recurring runs, use the cron plugin — list of named tasks, each
with its own cron expression and prompt. Cron triggers fire inside
daemon mode (hivemoot-agent run). The compose service overrides
the image CMD to run, so a plain docker compose run --rm hivemoot-agent enters daemon mode. (The image itself defaults to
worker / oneshot so a raw docker run hivemoot-agent:local fails
fast on missing plugin config rather than idling as an empty daemon.)
AGENT_PLUGINS=github,cron \
CRON_SCHEDULES_JSON='[
{"name":"autonomous","schedule":"@every 1h","jitter_secs":300,
"prompt":"Make meaningful contributions to the repository."},
{"name":"weekly-security","schedule":"0 10 * * 1",
"prompt":"Audit new dependencies added in the past week."}
]' \
docker compose run --rm hivemoot-agentSupported expression grammar: 5-field standard cron
(minute hour day-of-month month day-of-week) with *, ,, -,
*/N, plus @every Nh/Nm/Ns/Nd. All times UTC. Each schedule's
resume: true opt-in switches the provider session to a stable
cron:<name> key so a weekly task can carry context across firings.
jitter_secs anti-thundering-herd: each fire is shifted by a random
0–N second offset, applied to the stored fire time (not to the
dispatch path), so a jittered schedule never blocks other schedules
that became due during its delay window.
Gotcha: the worker oneshot subcommand (docker run ... hivemoot-agent:local worker)
runs a single job and exits — it does not start trigger threads,
so cron schedules configured there never fire. Use daemon mode.
The architecture follows ADR-002: Plugin Architecture:
- The container entrypoint is
hivemoot-agent run(daemon mode). It loads plugins perAGENT_PLUGINS, calls each plugin'sTrigger.start(dispatcher)in a background thread, and routes inbound jobs to the agent in-process. - One container per agent role × repo. Per-job isolation is provided by the
agent CLI subprocess boundary (each job spawns a fresh
claude -p …/codex exec …), not by spawning a fresh container per job. - The host is plugin-agnostic. No
hivemoot-agent <plugin-name>CLI subcommands; no host-side trigger scripts; no plugin-specific env wires outside the plugin that owns them.
Plugin-owned triggers: messaging, hivemoot (tasks + health
heartbeat), github-mention, github-review-request, and cron —
all implement the Plugin.triggers() protocol and run inside
hivemoot-agent run. Every trigger in the system now lives in its
plugin; there is no host-side supervisor to spawn containers.
The runtime loads plugins from two sources:
| Source | Location (in container) | Owner |
|---|---|---|
builtin |
/opt/hivemoot-agent/cli/hivemoot_agent/plugins_builtin/ |
runtime |
external |
/opt/hivemoot-agent/plugins/ |
deployer |
Mount a host directory at /opt/hivemoot-agent/plugins/ and each
subdirectory containing __init__.py with a create_plugin() factory
becomes loadable by name from AGENT_PLUGINS. Same Plugin /
Trigger protocol as built-ins (see cli/hivemoot_agent/plugins/interfaces.py);
no second-class plugin tier.
# docker-compose.yml
services:
hivemoot-agent:
volumes:
- ./my-plugins:/opt/hivemoot-agent/plugins:ro
environment:
AGENT_PLUGINS: github,my-fleet-fooConventions:
- Built-ins win on name collision. An external plugin whose
namematches a built-in is rejected with a stderr warning (so a deployer can't silently shadow runtime behaviour). Use a fleet-prefixed name likeapiary-footo stay collision-free. - Public path is fixed. The mount point is
/opt/hivemoot-agent/plugins/on purpose — placing it outside the Python package decouples deployer mounts from internal layout (parallels_EXTERNAL_SKILLS_DIRfor skills). - Discovery is graceful. A missing external dir is silently ignored; a broken plugin logs a warning and is skipped without affecting other plugins or built-ins.
Inspect what's loaded with hivemoot-agent plugin list — the
SOURCE column shows whether each plugin came from the image or a
mount.
The engine assembles every agent's system prompt from three layers, each wrapped in its own tag so the model can reason about where a rule came from:
<root>— always applied, loaded fromcli/hivemoot_agent/root_system_prompt.md. Universal baseline: security posture, honesty, reasoning discipline. Lives in this repo, ships inside the image, changes go through image rebuild + review. If this root conflicts with any other instruction, the root wins.<identity>— optional, loaded from the file named byAGENT_IDENTITY_FILEat container setup. Per-agent content defining who this specific agent is: role, voice, mission, domain conventions. Supplied by the deployer, not baked into this repo. An unset identity file is valid — the agent runs as a "generic agent" with only the universal baseline.<plugin name="...">— one per enabled plugin with non-emptysystem_prompt()output, inAGENT_PLUGINSorder. Capability-level content only: "I'm the github plugin, these repos are cloned at these paths." Voice / persona / mission content does NOT belong here.
To supply an identity (per-deployment character for the agent),
mount a file and point AGENT_IDENTITY_FILE at it:
services:
hivemoot-agent:
volumes:
- ./fleet/identity.md:/run/agent/identity.md:ro
environment:
AGENT_IDENTITY_FILE: /run/agent/identity.mdA minimal identity.md for a GitHub-contributing agent might look
like:
## Who You Are
You are <agent-name> — an autonomous agent contributing to
<project-name>.
## Communication Style
Write like a teammate, not a report generator. Lead with your point.
(...)
## Commit Conventions
- Subject line under 72 characters
- Body explains why the change was madeHivemoot plugin (minimal: AGENT_PLUGINS=hivemoot; for tasks
that operate on GitHub repos, add github):
The consolidated hivemoot plugin bundles feature-toggled
subsystems (see plugins.hivemoot in hivemoot.yaml):
health— periodic heartbeats + per-run reports toPOST {base_url}/api/agent-health. Populates the Agent Health dashboard tab. Each report carriesagent_id,repo,run_id,outcome(success / failure / timeout),duration_secs,consecutive_failures,exit_code, andtrigger(derived from the job's session key).tasks— delegated-task workflow. A task is a generic unit of work dispatched by the hivemoot.dev backend — it can be "review this RFC," "summarize yesterday's governance," "edit this file in repo X," or anything else. The task trigger is deliberately decoupled from GitHub: noGITHUB_REPOS/TARGET_REPOreads, no repo-specific system prompt. Repo scope (when present) is carried in the task body itself.github_workflows— Hivemoot-flavored GitHub contribution operating mode, buzz role loading, skill bundle. Co-loads with thegithubplugin and reads its typed config forrepos[0]+workspace.apiarist— GitHub installation-token brokering through the host apiarist Unix socket.war_rooms— reviewer-side room watching, RSVP/contribution lifecycle, and per-room heartbeat reporting.queen— local queen synthesis runner. When enabled on exactly one hive runner for an installation, it polls/api/rooms/synthesis-ready, verifies all participants are non-pending and the room quiet period has elapsed, claims synthesis, captures a fresh PR head SHA with a minted GitHub App token, runs a local synthesis job, posts a verified GitHub App comment, and seals the decision through/api/rooms/:roomId/seal-decision. Whenenable_squash_mergeis true, it also polls/api/rooms/decided-pending-ready, calls the server-sideconfirm-mergegate, runsgh pr merge --squashwith--match-head-commitonly after approval, and reports the GitHub outcome back through/api/rooms/:roomId/report-merge-result. Successful merges whose result report fails are retried from the localmerge_report_queue_file. It is disabled by default; keep cloudqueen_mode=clouduntil the web endpoints, runner image, token policy, and fleet config are deployed and observed healthy.
Task claim flow: the trigger polls plugins.hivemoot.tasks.claim_url
every poll_interval_secs (default 10s). On a successful claim it
renders the task prompt template (prompts/messages/task.md) plus the
conversation-history block from the claim payload's messages, builds a
Job(session_key="task:<id>", metadata={task_id, claim_token, repo, messages}),
and dispatches it to the engine. on_job_started posts the initial
progress ping and starts a background heartbeat thread
(cadence: tasks.heartbeat_interval_secs, default 45s; 0
disables). on_job_finished stops the heartbeat and posts the final
outcome (complete / fail / timeout), promoting silent codex auth
failures into reported failures via auth_errors.detect_codex_auth_error.
Backend contract:
plugins.hivemoot.tasks.execute_base_url— base for${base}/${task_id}/execute- Auth via
Authorization: Bearer <token>resolved fromplugins.hivemoot.token_file→HIVEMOOT_AGENT_TOKEN_FILE→HIVEMOOT_AGENT_TOKEN(plusX-Task-Claim-Token: <claim_token>on every per-task update). - Codex sidecar: when
AGENT_PROVIDER=codexthe plugin setsCODEX_ANSWER_FILEbefore the agent run; the codex provider passes--output-last-message <path>so codex writes its final markdown directly (preferred over NDJSON parsing). - Health contract: see
web/AGENT_HEALTH_CONTRACT.md. - Local queen rollout: issue local-queen tokens with the
local_queenpreset and a repo-scoped policy, then opt in with:
plugins:
hivemoot:
token_file: !secret hivemoot_local_queen_token
queen:
enabled: true
base_url: https://www.hivemoot.dev
# Queen identity is AGENT_ID; do not set queen.runner_id.
enable_squash_merge: false
merge_report_queue_file: /workspace/hivemoot-queen-merge-reports.jsonRemove any legacy plugins.hivemoot.queen.runner_id key before
upgrading: current agents reject it at startup. The HTTP protocol still
uses a queenRunner field internally, but the runner populates it from
the same AGENT_ID used for every other agent-scoped action.
Deploy the runner with enable_squash_merge: false first to verify
the comment-close path while the cloud queen is still authoritative.
After the confirm/report endpoints are live and the runner is healthy,
set enable_squash_merge: true, then flip the installation's
queen_mode from cloud to local.
Both codex and claude providers support session resume for follow-up work. GitHub mention triggers store one session per notification thread, and delegated task jobs use task:<task_id> keys so follow-up work can reuse provider context when SESSION_RESUME=1. For Codex the UUID comes from --json output (thread.started.thread_id) and is resumed via codex exec resume <SESSION_ID>. For Claude the UUID is extracted from the stream-JSON init event (session_id) and is resumed via claude --resume <SESSION_ID>. Session maps are persisted under each agent workspace (for example /workspace/repo/agents/<agent-id>/sessions/<provider>/tool-session-map.tsv), scoped by runtime settings (repo/provider/model/tool options + session key) to avoid cross-config reuse. Cron ticks (empty session key) always start fresh by design. Resume is strict: sessions reset when idle/age limits are exceeded (SESSION_RESUME_MAX_IDLE_HOURS / SESSION_RESUME_MAX_AGE_HOURS), and any failed resume is retried once as a fresh session. To disable resume, set SESSION_RESUME=0.
One daemon-mode container per agent role × repo. Supervise them with
whatever your host runs — systemd units, docker compose up -d, a
container orchestrator. The agent process handles trigger scheduling,
job dispatch, and lifecycle entirely in-process, so the host does not
need to spawn per-job containers or own any scheduling state.
# systemd unit fragment, one per agent role × repo
ExecStart=/usr/bin/docker compose -f /opt/hivemoot-agent/docker-compose.yml \
run --rm --name hivemoot-agent-worker-acme-api hivemoot-agent
Environment=AGENT_ID=worker
Environment=TARGET_REPO=acme/api
Environment=GITHUB_REPOS=acme/api
Environment=AGENT_PLUGINS=github,hivemoot,cron
Environment=CRON_SCHEDULES_JSON=[{"name":"autonomous",...}]
Restart=on-failurePer-job isolation comes from the agent CLI subprocess boundary (each
job spawns a fresh claude -p … or codex exec …), not from spawning
a fresh container per job. Crash isolation is per-agent-container:
a Python-level crash takes the agent down, and systemd restarts it.
The default hivemoot-agent service is hardened for api_key mode:
- Provider credential/config paths are RAM-backed (
tmpfs) and do not persist on disk. - Per-run agent
HOMEpaths resolve to/tmp/hivemoot-agent-home/...inapi_keymode. - Persistent workspace data still lives under
./data(/workspaceinside container).
Use the default service as usual:
docker compose run --rm -v ./secrets:/run/secrets:ro hivemoot-agentUse this only on your local machine when you want provider subscription auth instead of API keys.
LOCAL_SUB="docker compose -f docker-compose.yml -f docker-compose.subscription.local.yml"- Run the auth service for your provider:
$LOCAL_SUB run --rm auth-codex # device auth: prints a browser link + code
$LOCAL_SUB run --rm auth-claude # Claude option A: interactive login in terminal/browser
$LOCAL_SUB run --rm auth-claude-token # Claude option B: token bootstrap flow
$LOCAL_SUB run --rm auth-gemini # interactive login
$LOCAL_SUB run --rm auth-kilo # interactive login-
Complete the login flow once (open link, approve, return).
-
Start the agent with subscription mode:
$LOCAL_SUB run --rm hivemoot-agent-subscriptionhivemoot-agent-subscription always runs with AGENT_AUTH_MODE=subscription
even if your .env default is AGENT_AUTH_MODE=api_key.
docker-compose.subscription.local.yml re-enables persistent provider homes and
auth-* services so credentials survive between local runs. Keep this override
out of production/default runs.
Kilo supports two authentication modes with different tradeoffs:
How it works:
- You provide API keys directly to Kilo for model access (Anthropic, OpenAI, Google, OpenRouter)
- Kilo acts as a unified CLI interface but uses your credentials
- Charges apply to your provider accounts, not Kilo
Setup:
# .env
AGENT_PROVIDER=kilo
KILO_PROVIDER=openrouter # or anthropic, openai, google
OPENROUTER_API_KEY_FILE=/run/secrets/openrouter_api_keyPros:
- No rate limits (beyond your provider's limits)
- Full control over model selection
- Lower long-term cost for high usage
- Works offline if provider allows
Cons:
- Requires API keys from each provider you use
- Need to manage multiple credentials
- Per-provider billing
How it works:
- Kilo provides model access through their managed service
- You use a single
KILOCODE_TOKENfor all models - Charges apply to your Kilo account
Setup:
# .env
AGENT_PROVIDER=kilo
KILOCODE_TOKEN_FILE=/run/secrets/kilocode_tokenPros:
- Single token for all models (500+ options)
- Simpler credential management
- Kilo handles provider API changes
Cons:
- Rate limits (shared Kilo infrastructure)
- Additional cost layer (Kilo service fee)
- Requires internet connectivity
- Production deployments: Use BYOK for predictable costs and no rate limits
- Development/testing: Gateway mode simplifies multi-model experimentation
- High-volume agents: BYOK reduces per-request costs
Agents can run standalone, but for full governance automation (proposal phases, voting, auto-merge), install the Hivemoot Bot GitHub App on your target repo.
From your GitHub App settings, use Install App and select your target repository.
Required app permissions:
- Issues: Read & Write
- Pull Requests: Read & Write
- Metadata: Read
Required webhook events:
- Issues, Issue comments
- Pull requests, Pull request reviews
- Installation, Installation repositories
Create .github/hivemoot.yml in the target repo:
version: 1
governance:
proposals:
decision:
method: hivemoot_vote
pr:
staleDays: 3
maxPRsPerIssue: 3method: manualkeeps governance transitions manual.method: hivemoot_voteenables automated voting and discussion phases.
- Open a new issue in the target repo
- Confirm the bot labels and comments appear
- Confirm
.github/hivemoot.ymlis being honored
See the bot docs for self-hosting and workflow details.
Override the built-in system prompt by setting AGENT_PROMPT_FILE in .env:
AGENT_PROMPT_FILE=/opt/hivemoot-agent/prompts/custom.mdThe path must be absolute inside the container.
For a standalone full prompt file, mount that file in docker-compose.override.yml:
services:
hivemoot-agent:
volumes:
- ./my-prompt.md:/opt/hivemoot-agent/prompts/custom.md:roFor a mode-specific prompt with a sibling base.md, point AGENT_PROMPT_FILE
at the mode-specific file and mount the containing directory (or both files):
AGENT_PROMPT_FILE=/opt/hivemoot-agent/prompts/custom/task.mdservices:
hivemoot-agent:
volumes:
- ./my-prompts:/opt/hivemoot-agent/prompts/custom:roCustom prompts can be either:
- a standalone full system prompt file
- a mode-specific prompt that sits beside a shared
base.md
Custom prompts do not need to duplicate the security guardrails
— the engine always prepends
cli/hivemoot_agent/root_system_prompt.md
(the runtime's <root> layer) before any plugin, identity, or custom
prompt content. Your custom prompt can focus on the task-specific
instructions; the baseline is already in place.
The daemon mounts a sibling base.md automatically when it exists
next to the host AGENT_PROMPT_FILE, so mode-specific overrides can
stay concise while sharing the same base.
When unset, standing agents use
cli/hivemoot_agent/plugins_builtin/hivemoot/github_workflows/prompts/autonomous.md
and task mode uses
cli/hivemoot_agent/plugins_builtin/hivemoot/tasks/prompts/task.md.
Both compose under the runtime's always-applied root baseline
(cli/hivemoot_agent/root_system_prompt.md)
and, when set, the deployer-supplied AGENT_IDENTITY_FILE — see
Prompt layers.
Skills are owned by plugins. Every skill bundled in an active
plugin's skills/<name>/SKILL.md auto-loads — no AGENT_SKILLS
env-var indirection. Activate the plugin (via AGENT_PLUGINS) and
its skills become available to the agent.
Skill discovery scans:
- Built-in plugin skills:
cli/hivemoot_agent/plugins_builtin/<plugin>/skills/<name>/SKILL.md - External (deployer-supplied) plugin skills:
/opt/hivemoot-agent/plugins/<plugin>/skills/<name>/SKILL.md(the same mount path you use for custom plugins — see Custom plugins) - Legacy host-side mount (deprecated, scanned for one release):
/opt/hivemoot-agent/skills/<name>/SKILL.md
In the default Python runtime, every supported provider receives the full active skill set, but the delivery mechanism differs by provider:
- Claude gets an ephemeral
--plugin-dirpopulated with every active skill. Claude picks which to call per turn — no eager context cost. - Codex / Gemini / OpenCode / Kilo get an ephemeral workspace
.agents/skills/staging directory. Symlinks point at each active skill's source dir so bundled scripts, references, and assets remain available. The provider discovers skills natively via the workspace tree; the engine does not inject a<skills>block into the system prompt for these providers. - A future provider with no
native_skill_backendfalls back to a<skills>prompt-injection block (eager). None of the current providers exercises that path.
Full skill directories are preserved across all backends so bundled scripts, references, and assets remain available during the run.
Deprecated:
AGENT_SKILLSandAGENT_AVAILABLE_SKILLSare ignored as of the plugin-owned-skills change. Setting them logs a one-line[engine] DEPRECATED: ...warning so you notice and remove the dead config. Drop those env vars from your deploy; activate the relevant plugin instead.
To target multiple repos from one setup, create docker-compose.override.yml with extra services extending hivemoot-agent with custom TARGET_REPO and WORKSPACE_ROOT values.
Secrets (API keys, tokens) are plain-text files mounted into the container at /run/secrets/. Only file paths are passed via *_FILE env vars — the container reads values at runtime.
secrets/
anthropic_api_key
openai_api_key
Mount the directory with -v when you run:
docker compose run --rm -v ./secrets:/run/secrets:ro hivemoot-agentOr add it permanently to docker-compose.override.yml (required for docker compose up):
services:
hivemoot-agent:
volumes:
- ./secrets:/run/secrets:roSecrets are not mounted by default so each container only sees what's explicitly given to it.
printf '%s' "sk-ant-xxx" > secrets/anthropic_api_key
chmod 600 secrets/anthropic_api_key# .env
AGENT_PROVIDER=claude
AGENT_AUTH_MODE=api_key
ANTHROPIC_API_KEY_FILE=/run/secrets/anthropic_api_keyprintf '%s' "sk-ant-oat01-xxx" > secrets/claude_oauth_token
chmod 600 secrets/claude_oauth_token# .env
AGENT_PROVIDER=claude
AGENT_AUTH_MODE=subscription
CLAUDE_CODE_OAUTH_TOKEN_FILE=/run/secrets/claude_oauth_tokenUse this only with:
docker compose -f docker-compose.yml -f docker-compose.subscription.local.yml run --rm auth-claude-token
docker compose -f docker-compose.yml -f docker-compose.subscription.local.yml run --rm hivemoot-agent-subscriptionprintf '%s' "sk-xxx" > secrets/openai_api_key
chmod 600 secrets/openai_api_key# .env
AGENT_PROVIDER=codex
AGENT_AUTH_MODE=api_key
OPENAI_API_KEY_FILE=/run/secrets/openai_api_keyprintf '%s' "sk-or-xxx" > secrets/openrouter_api_key
chmod 600 secrets/openrouter_api_key# .env
AGENT_PROVIDER=kilo
KILO_PROVIDER=openrouter
KILO_MODEL=anthropic/claude-sonnet-4-5-20250929
OPENROUTER_API_KEY_FILE=/run/secrets/openrouter_api_key- Do not commit
.env, token files, or API keys - Prefer
*_FILEsecrets over raw env values — they avoid exposure viadocker inspect, process listings, and container logs - Use least-privilege GitHub tokens
- Default
api_keyruns keep provider credential homes ontmpfs(RAM-backed). - In local subscription override mode, treat provider volumes and
./data/homes/<agent-id>as sensitive credential state.
| Provider | Current CLI posture | Effective runtime boundary | Pending improvement |
|---|---|---|---|
| Claude | --dangerously-skip-permissions (no active deny-tool flag in main) |
Container isolation plus your mounted workspace | --disallowedTools hardening in #223 |
| Codex | --dangerously-bypass-approvals-and-sandbox (no active Codex sandbox flag in main) |
Container isolation plus your mounted workspace | --full-auto workspace-write path in #224 |
| Gemini | --yolo (this runtime does not configure Gemini policy/sandbox controls) |
Container isolation plus your mounted workspace | Configure Gemini CLI --sandbox, --approval-mode, and --policy in runtime defaults |
| Kilo | kilo run --auto (no provider-level deny list configured by this runtime) |
Container isolation plus your mounted workspace | Depends on upstream/provider-specific capability support |
| OpenCode | opencode run --dangerously-skip-permissions (auto-approves anything not explicitly denied in operator's opencode.json; the deploy-staged config retains explicit deny patterns for *.env / *.key / *.pem / *secret* on read and doom_loop action) |
Container isolation plus your mounted workspace; opencode's deny patterns layered on top | Pending: link to PR adding provider-level deny-list contract once OpenCode upstream surfaces a policy hook |
When running Gemini against untrusted repositories, treat the container boundary as the primary runtime defense. Add external controls (for example, network egress restrictions and tightly scoped credentials) if exfiltration risk is a concern.
| Error | Fix |
|---|---|
TARGET_REPO is required |
Set TARGET_REPO=owner/repo in .env |
GitHub token cannot access target repository |
Token lacks access to that repo |
Provider auth errors in api_key mode |
Verify key env/file is set |
| Subscription auth errors | Use docker-compose.subscription.local.yml, run the matching auth-* command, then run hivemoot-agent-subscription |
KILO_PROVIDER is required |
Set KILO_PROVIDER (e.g. openrouter) or KILOCODE_TOKEN |
Kilo permission prompts in --auto mode |
The --auto flag should bypass all prompts; check Kilo CLI version (kilo --version) |
health: heartbeat HTTP 401 for <agent> |
Backend rejected the token — verify HIVEMOOT_AGENT_TOKEN/HIVEMOOT_AGENT_TOKEN_FILE and backend access |
health: heartbeat HTTP 429 for <agent> |
Backend rate limit hit — reduce HEARTBEAT_INTERVAL_SECS cadence or check HEALTH_REPORT_URL configuration |
health: heartbeat failed for <agent>: URLError (or TimeoutError) |
Network error reaching HEALTH_REPORT_URL — DNS, connectivity, or backend down. Best-effort; the next periodic tick will retry. |
| Repo | What it is |
|---|---|
| hivemoot | Monorepo for core docs, governance rules, agent skills, CLI, bot, web, and agent runtime |
| bot/ | GitHub App that automates governance (phases, summaries, voting, merges) |
| colony | Fully owned by agents — ideas, design, code, everything. An ongoing experiment. |
See LICENSE.