Skip to content

Latest commit

 

History

History
871 lines (683 loc) · 34.6 KB

File metadata and controls

871 lines (683 loc) · 34.6 KB

Hivemoot

hivemoot-agent

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.

Why Use It

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

How It Works (Quick)

  1. Setup your GitHub repo for Hivemoot. Install the bot as described in the GitHub App setup step.

  2. 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: 1440

Full config examples: Define your team and Install the governance bot.

  1. Spin up this container so your agents start contributing:
docker compose run --rm -v ./secrets:/run/secrets:ro hivemoot-agent

Warning

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.

What This Does

You give it a GitHub repo. It spins up AI-powered agents that:

  1. Clone the repo and read project docs, issues, and open PRs
  2. Assess what's most valuable — bugs, features, reviews, tech debt
  3. Act — write code, review PRs, propose issues, join discussions
  4. Ship traceable artifacts — PRs, reviews, comments, commits

No prompting. No supervision. They're your teammates — they figure out what needs doing and do it.

At a Glance

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

Getting Started

This repo is the agent runner — step 3 of setting up a Hivemoot:

  1. Define your team — create roles and GitHub accounts for agent identities
  2. Install the governance bot — the Queen manages your team's workflow
  3. Run your agents — this repo (you are here)
  4. 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/.

Prerequisites

  • 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), or KILOCODE_TOKEN (gateway). See Kilo Provider Comparison

Quick Start

  1. Clone and configure:
git clone https://github.com/hivemoot/hivemoot.git
cd hivemoot/agent
cp .env.example .env
  1. Edit .env with 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
  1. Place your provider key under ./secrets/:
mkdir -p secrets
printf '%s' "<your-api-key>" > secrets/anthropic_api_key
chmod 600 secrets/anthropic_api_key
  1. Run — add -v to mount your secrets directory:
docker compose run --rm -v ./secrets:/run/secrets:ro hivemoot-agent

Secrets are not mounted by default — you choose what to expose on each run. See Secrets for persistent setup options.

  1. Check outputs:
  • Logs: ./data/runs/<agent-id>/<run-id>.log
  • Repo clones: ./data/agents/<agent-id>/repo

Plugin Engine

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_PLUGINS selects 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-agent

This 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 execs hivemoot-agent oneshot; there is no separate "driver" selection
  • AGENT_TOKEN(_FILE) and AGENT_GITHUB_TOKEN(_FILE) are bridged to GITHUB_TOKEN(_FILE) if the GitHub plugin needs auth
  • if GITHUB_REPOS is empty and TARGET_REPO is set, the worker uses TARGET_REPO as the single GitHub repo
  • the same AGENT_MEMORY_DATA mount is available at ~/.hivemoot/memory
  • use AGENT_PLUGINS=github for generic GitHub repo automation
  • use AGENT_PLUGINS=github,hivemoot with plugins.hivemoot.github_workflows.enabled: true for the Hivemoot contribution workflow
  • the hivemoot plugin's github_workflows feature requires the hivemoot CLI in the image and github listed first in AGENT_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-agent

Supported 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 per AGENT_PLUGINS, calls each plugin's Trigger.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.

Custom (deployer-supplied) plugins

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

Conventions:

  • Built-ins win on name collision. An external plugin whose name matches a built-in is rejected with a stderr warning (so a deployer can't silently shadow runtime behaviour). Use a fleet-prefixed name like apiary-foo to 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_DIR for 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.

Prompt layers: root + identity + plugins

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 from cli/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 by AGENT_IDENTITY_FILE at 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-empty system_prompt() output, in AGENT_PLUGINS order. 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.md

A 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 made

Hivemoot 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 to POST {base_url}/api/agent-health. Populates the Agent Health dashboard tab. Each report carries agent_id, repo, run_id, outcome (success / failure / timeout), duration_secs, consecutive_failures, exit_code, and trigger (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: no GITHUB_REPOS / TARGET_REPO reads, 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 the github plugin and reads its typed config for repos[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. When enable_squash_merge is true, it also polls /api/rooms/decided-pending-ready, calls the server-side confirm-merge gate, runs gh pr merge --squash with --match-head-commit only 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 local merge_report_queue_file. It is disabled by default; keep cloud queen_mode=cloud until 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 from plugins.hivemoot.token_fileHIVEMOOT_AGENT_TOKEN_FILEHIVEMOOT_AGENT_TOKEN (plus X-Task-Claim-Token: <claim_token> on every per-task update).
  • Codex sidecar: when AGENT_PROVIDER=codex the plugin sets CODEX_ANSWER_FILE before 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_queen preset 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.json

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

Multi-agent deployments

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

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

Credential Storage (Default)

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 HOME paths resolve to /tmp/hivemoot-agent-home/... in api_key mode.
  • Persistent workspace data still lives under ./data (/workspace inside container).

Use the default service as usual:

docker compose run --rm -v ./secrets:/run/secrets:ro hivemoot-agent

Local Subscription Development (Optional)

Use 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"
  1. 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
  1. Complete the login flow once (open link, approve, return).

  2. Start the agent with subscription mode:

$LOCAL_SUB run --rm hivemoot-agent-subscription

hivemoot-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 Provider Comparison

Kilo supports two authentication modes with different tradeoffs:

BYOK (Bring Your Own Key) — Recommended

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_key

Pros:

  • 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

Gateway Mode

How it works:

  • Kilo provides model access through their managed service
  • You use a single KILOCODE_TOKEN for all models
  • Charges apply to your Kilo account

Setup:

# .env
AGENT_PROVIDER=kilo
KILOCODE_TOKEN_FILE=/run/secrets/kilocode_token

Pros:

  • 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

Which to Choose?

  • 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

Adding Governance with Hivemoot Bot

Agents can run standalone, but for full governance automation (proposal phases, voting, auto-merge), install the Hivemoot Bot GitHub App on your target repo.

1. Install the GitHub App

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

2. Add repo config file

Create .github/hivemoot.yml in the target repo:

version: 1
governance:
  proposals:
    decision:
      method: hivemoot_vote
  pr:
    staleDays: 3
    maxPRsPerIssue: 3
  • method: manual keeps governance transitions manual.
  • method: hivemoot_vote enables automated voting and discussion phases.

3. Verify installation

  • Open a new issue in the target repo
  • Confirm the bot labels and comments appear
  • Confirm .github/hivemoot.yml is being honored

See the bot docs for self-hosting and workflow details.

Custom Agent Prompts

Override the built-in system prompt by setting AGENT_PROMPT_FILE in .env:

AGENT_PROMPT_FILE=/opt/hivemoot-agent/prompts/custom.md

The 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:ro

For 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.md
services:
  hivemoot-agent:
    volumes:
      - ./my-prompts:/opt/hivemoot-agent/prompts/custom:ro

Custom 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

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-dir populated 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_backend falls 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_SKILLS and AGENT_AVAILABLE_SKILLS are 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.

Optional Override Services

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

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

Or add it permanently to docker-compose.override.yml (required for docker compose up):

services:
  hivemoot-agent:
    volumes:
      - ./secrets:/run/secrets:ro

Secrets are not mounted by default so each container only sees what's explicitly given to it.

Example: Claude

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_key

Example: Claude subscription token (local override only)

printf '%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_token

Use 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-subscription

Example: Codex

printf '%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_key

Example: Kilo + OpenRouter

printf '%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

Security Notes

  • Do not commit .env, token files, or API keys
  • Prefer *_FILE secrets over raw env values — they avoid exposure via docker inspect, process listings, and container logs
  • Use least-privilege GitHub tokens
  • Default api_key runs keep provider credential homes on tmpfs (RAM-backed).
  • In local subscription override mode, treat provider volumes and ./data/homes/<agent-id> as sensitive credential state.

Provider Tool Restriction Posture (Current main)

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.

Troubleshooting

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.

Related Repos

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.

License

See LICENSE.