# Agent Configuration

A hive **agent** is a long-running AI worker — a CLI session the hive keeps alive in tmux, kicks on a cadence with a work prompt, and watches for stalls, rate limits, and login expiry. An agent's configuration is one YAML entry under `agents:` in `hive.yaml`: it names the agent, picks the engine that powers it (a subscription CLI or a self-hosted inference endpoint), and declares how it behaves. Everything else — cadences, models, pins, ACMM level — layers on top.

Start with only a name, a method, and a model. Add the rest when the agent needs it.

## The smallest agent that works

```yaml
agents:
  scanner:
    backend: copilot
    model: claude-sonnet-4-6
```

That is a complete, valid agent. Defaults fill in the rest at load time:

- `enabled: true` (unless you explicitly write `enabled: false`)
- `clear_on_kick: true` — the session context is cleared before each kick
- `id` and `role` default to the agent's name (`scanner`)
- `bead_role: worker`, `beads_dir: /data/beads/scanner`
- Well-known names (`scanner`, `ci-maintainer`, `architect`, `supervisor`, `sec-check`, `quality`, `guide`, `strategist`, `outreach`) also get a default emoji, color, aliases, and lane keywords, so a bare `scanner:` entry already shows up in the dashboard as 🔍 with sensible triage keywords.

You almost never write a full roster by hand: applying an ACMM level (below) generates one for you, and the dashboard edits it live.

## Where configuration lives

Hive's config is layered, and the layering is the point: **a file's location says who owns the setting.**

```
/etc/hive/hive.yaml            ← ConfigMap seed (Kubernetes) or bind mount (Docker/LXC).
│                                 The operator/platform layer. Re-seeded on every pod
│                                 boot; authoritative for acmm_level and hub.is_public.
├── /data/hive.yaml.dashboard  ← Dashboard overlay on the PVC. Every save from the
│                                 dashboard UI lands here, secret-free, and is merged
│                                 over the ConfigMap seed at the next boot. Wins for
│                                 everything except the two ConfigMap-owned keys.
├── /data/agent-configs/       ← One <name>.yaml per dashboard-managed agent
│                                 (created, imported, or generated by an ACMM pack).
│                                 Merged over the agents: map at load time.
├── /data/hive.yaml.runtime    ← Persisted runtime config (was hive.yaml.bak). Never
│                                 edit. On K8s a post-merge snapshot the entrypoint
│                                 restores from if the seed is lost; on Docker/LXC the
│                                 boot-time source of truth. The legacy name is still
│                                 read as a fallback during the migration.
├── /data/secrets/             ← Secret VALUES written by the dashboard (writable PVC).
│   /secrets/                  ← Secret VALUES from Kubernetes Secret mounts (read-only).
└── /data/policies/agents/     ← Kick templates and per-agent policy Markdown.
```

Read the tree top to bottom and you can answer "who set this?" for any value:

- **The platform owns the seed.** In Kubernetes, an init container re-copies the ConfigMap to `/etc/hive/hive.yaml` on every boot. The entrypoint then merges the dashboard overlay over it — but the ConfigMap stays authoritative for the hub/admin-managed keys (`acmm_level`, `hub.is_public`).
- **You (via the dashboard) own the overlay.** Every dashboard save writes `/data/hive.yaml.dashboard`, so a LiteLLM endpoint or agent tweak survives pod restarts and upgrades.
- **Secrets never enter YAML.** `hive.yaml` stores only env var *names* (`api_key_env`) and key file *paths* (`api_key_file`) — never key values. Values live in `/data/secrets/` (dashboard-entered) or `/secrets/` (Kubernetes Secret mounts). Because `Config.Save()` writes the whole config back to disk, a key value in YAML would be persisted in plaintext — so the code refuses the pattern entirely.

## Anatomy of an agent

Every field below exists in the config schema today. Grouped by what it does:

### Identity — how the agent appears

```yaml
agents:
  scanner:
    display_name: scanner        # dashboard label (defaults to the YAML key)
    description: "Triages issues and opens hold-gated fix PRs."
    emoji: "🔍"                  # dashboard badge
    color: "#3498db"             # dashboard accent color
    role: scanner                # behavioral role; defaults to the agent name
    sort_order: 20               # dashboard ordering (supervisors default to 0, others 100)
    aliases: [sc]                # short names accepted in dispatch/commands
```

### Engine — what powers it

```yaml
    backend: copilot             # the method: claude | copilot | goose | codex | pi |
                                 #   bob | aider, or an inference backend:
                                 #   vllm | llm-d | litellm
    model: claude-sonnet-4-6     # model id for that method
    cli_pinned: true             # pin the CLI so nothing auto-switches it
    launch_cmd: "/usr/bin/copilot --allow-all --model claude-sonnet-4-6"
                                 # explicit launch command (optional — hive builds
                                 #   one from backend + model + mode when omitted)
```

> The dashboard also offers **gemini** as a live method (with live model discovery); as a persisted `backend:` value in `hive.yaml`, stick to the validated list above.

### Behavior — what it may do, and when

```yaml
    enabled: true                # default true; set false to keep it configured but off
    mode: ISSUES_AND_PRS         # GitHub interaction tier: ADVISORY | ISSUES_ONLY |
                                 #   ISSUES_AND_PRS | ISSUES_PRS_MERGE
    bead_role: worker            # worker | supervisor (supervisors sort first,
                                 #   monitor the others); default worker
    kick_template: scanner-holdgated.md
                                 # named work-prompt template in the policies dir
    include_repos: true          # append the project repo list to each kick (default true)
    on_demand: false             # true = never kicked by the governor timer;
                                 #   only triggered explicitly (e.g. inception)
    clear_on_kick: true          # default true; false keeps session context across kicks
    stale_timeout: 28800         # seconds of silence before the agent counts as stale —
                                 #   must exceed its longest cadence
    restart_strategy: immediate  # how to bring a dead session back
    beads_dir: /data/beads/scanner   # work-record (bead) storage; default per-agent
    lane_keywords: [bug, triage, fix]   # routes matching issues into this agent's lane
    detect_keywords: [scanner, triage]  # attributes GitHub activity back to this agent
```

### Declarative extensions

Three optional blocks replace hardcoded behavior with declarations:

```yaml
    channels:                    # how the agent gets triggered. Omit = governor timer.
      - type: kick               # kick | webhook | discord | schedule | bead
      - type: schedule
        schedule: "0 */4 * * *"  # cron; required for type: schedule
    tools:                       # tool permissions. Omit = the mode field governs.
      preset: issues-only        # advisory | issues-only | issues-prs | full
      rules:                     # per-tool allow/deny overrides on top of the preset
        - pattern: "mcp__github__create_issue"
          action: allow
          reason: "advisory issues are fine"
    connections:                 # external integrations
      - name: github-mcp
        type: mcp                # mcp | api | knowledge
        uri: "stdio:///usr/local/bin/mcp-github"
```

Presets map onto modes (`advisory` denies issue and PR creation, `issues-only` denies PRs, `issues-prs` and `full` deny nothing), and an explicit `allow` rule overrides a preset `deny`.

Rounding out the schema — fields you will rarely touch:

| Field | What it does | Default |
|---|---|---|
| `id` | Stable identifier | agent name |
| `acmm_levels` | ACMM levels this agent participates in | all |
| `caveman_mode` | Prompt-compression experiment: `lite`, `full`, `ultra`, `wenyan` | off |
| `metrics_collector` | Named metrics source for the stats panel | none |
| `stats_display` | Custom sidebar metrics (key, label, source, field, style) | none |
| `hidden` (packs only) | Keep a pack agent out of the default roster view | false |

## Methods: subscription CLIs vs self-hosted inference

`backend:` picks one of two families. They differ in how you authenticate and where model lists come from:

| Method | Family | Auth | Model discovery |
|---|---|---|---|
| `claude` | CLI | log in once per method | maintained list (Anthropic exposes no "list my models" API) |
| `copilot` | CLI | log in once per method (GitHub device flow) | **live** — entitlement-filtered `/models` on your plan's API host |
| `gemini` | CLI | API key (`GEMINI_API_KEY`) | **live** — Google models API, filtered to `generateContent` |
| `goose` | CLI | provider-configured (`GOOSE_PROVIDER`) | curated per-provider list |
| `vllm` | inference | endpoint + optional key — no login | **live** — `/v1/models` |
| `llm-d` | inference | endpoint + optional key — no login | **live** — `/v1/models` |
| `litellm` | inference | endpoint + API key — no login | **live** — `/v1/models`, entitlement-filtered per key |

Two rules of thumb:

- **CLI methods are subscriptions.** You log in once per method from the dashboard, and every agent using that method shares the login.
- **Inference methods are endpoints.** You configure a base URL and a key *reference* (env var name or key-file path — the value goes in `/data/secrets/`, never in YAML). Agents on `vllm`/`llm-d`/`litellm` launch the Claude CLI routed through hive's inference translator, so there is no separate login.

Every discovery probe is best-effort: a failed or absent probe falls back to a current static list, so a model dropdown is never empty. Fallback entries are marked "unverified" in the UI.

### Model pinning and auto-switching

Each agent shows a 📌 pin on its CLI and its model in the dashboard. The semantics are deliberately narrow — a model changes out from under you in exactly two cases:

1. **Unpinned + governor.** The governor may auto-select a different model (budget, mode). A pin blocks this — and *only* this. Your own explicit switch always goes through; it simply retargets the pin to the new model, so the agent stays pinned.
2. **Discovery says the model is gone.** When a *genuine* (non-fallback) discovery returns a model set that no longer contains an agent's selected model — a key swap or endpoint change stripped the entitlement — the agent is switched to the first available model, with a toast. A static-fallback list never triggers this, and a model that is still present is never re-selected.

Pin a model when reproducibility matters more than the governor's budget optimizations. Leave it unpinned when you want the hive to manage cost for you.

## Cadences and the governor

Agents don't schedule themselves. The **governor** evaluates the work queue every `eval_interval_s` (default 300s), computes a mode from queue depth — **idle → quiet → busy → surge** (default thresholds: quiet > 2, busy > 10, surge > 20; override with `threshold:`) — and kicks each agent on the cadence that mode assigns it:

```yaml
governor:
  eval_interval_s: 300
  modes:
    busy:
      threshold: 16        # queue depth that activates this mode
      supervisor: 5m       # per-agent kick interval in this mode
      scanner: 15m
      reviewer:
        times: ["09:00", "17:00"]
        days: ["mon", "tue", "wed", "thu", "fri"]
        tz: America/New_York
      release:
        cron: "30 9 * * 1-5"
        tz: America/New_York
      ci-maintainer: 1h
      architect: pause     # "pause" stops kicks for this agent in this mode
```

- **Mutually exclusive modes.** Each per-agent, per-mode cadence is either an interval (`5m`, `2h`, `pause`) or a time-of-day schedule — never both. Config load and API writes reject mixed forms with a 400/error.
- **Time-of-day schedules.** Use `times: ["HH:MM"]` with optional `days` (`mon` … `sun`) and a required IANA `tz`. The timezone is stored explicitly and displayed with the schedule; it does not float with the viewer.
- **Advanced cron.** Power users can provide a constrained five-field cron expression plus `tz`. Hive evaluates these with robfig/cron and schedule-local timezone handling.
- **Governor-mode semantics.** Time-of-day schedules fire at their exact wall-clock times. Governor mode only selects whether that mode's schedule is active; quiet/busy/surge multipliers do not scale exact times. `pause`/`off`, paused agents, on-demand agents, non-kick channels, and budget gates still suppress kicks.
- **Downtime catch-up.** If Hive was down at a scheduled time, restart/eval grants at most one catch-up kick when the missed occurrence is within the last 10 minutes. Older missed occurrences are skipped and the next future occurrence is used.
- **Per-agent, per-mode intervals.** Anything Go's duration parser accepts works (`5m`, `2h`) for interval mode.
- **Pausing.** The value `pause` (or `paused`) suspends an agent for that mode without disabling it.
- **On-demand agents** (`on_demand: true`) are skipped by the governor timer entirely — they run only when explicitly triggered (the inception workflow drives `brainstorm` this way).
- **Budget.** When the weekly token budget is exhausted, kicks are suppressed hive-wide (exempt agents excepted) until the period rolls over.

Set `stale_timeout` with your cadences in mind: an agent kicked every 4h with a 30-minute stale timeout will look dead between kicks. The shipped packs use "longest cadence × 2".

## ACMM levels: agent rosters as packs

You don't have to design a roster. Hive ships six **ACMM packs** (`level-1.yaml` … `level-6.yaml`, embedded in the binary and forkable) that pair a curated agent roster with governor cadences and a merge policy:

| Level | Name | Posture |
|---|---|---|
| L1 | Inception (Assisted) | inception: brainstorm + guide, everything conversational |
| L2 | Advisory (Instructed) | advisory beads only; agents observe, humans act |
| L3 | Quality-Gated (Measured) | quality opens issues and hold-gated test PRs; the rest stay advisory |
| L4 | Security-Aware (Adaptive) | all agents open issues — no PRs yet |
| L5 | Semi-Autonomous (Semi-Automated) | issues **and** hold-gated PRs; humans batch-approve |
| L6 | Fully Autonomous | auto-merge on green CI, no hold label |

Applying a level **reconciles the whole roster**, not just the diff: missing agents are created (as overlay files in `/data/agent-configs/`), existing agents are merged — pack values fill blanks, but your explicit `backend:`, `model:`, and `enabled: false` always win — and the level's `kick_template` and `mode` are updated so the agent's *policy* matches the level. A failed agent doesn't abort the rest; the level is only recorded as cleanly applied when every agent reconciled.

The L5 roster is the canonical worked example — nine agents, eight on the governor timer plus one on demand:

| Agent | | Mode | Cadence (all governor modes) |
|---|---|---|---|
| supervisor 👑 | health monitor, sweeps | ADVISORY | 5m |
| scanner 🔍 | triage + fix PRs | ISSUES_AND_PRS | 4h |
| ci-maintainer 🔧 | CI + dependencies | ISSUES_AND_PRS | 4h |
| quality 🧪 | test coverage | ISSUES_AND_PRS | 2h |
| guide 🧭 | documentation | ISSUES_AND_PRS | 4h |
| sec-check 🛡 | CVEs, vulnerabilities | ISSUES_AND_PRS | 4h |
| architect 🏗 | RFCs, refactors | ISSUES_AND_PRS | 4h |
| strategist 🧠 | cross-agent coordination | ISSUES_AND_PRS | 4h |
| brainstorm 💡 | ideation | ADVISORY | on demand |

At L5, every agent PR gets a `hold` label automatically. The system proposes; it does not merge autonomously.

## Kick templates: what an agent is told to do

`kick_template` names a Markdown file resolved from the hive's policies checkout (`/data/policies/examples/kubestellar/agents/`, or the directory your `policies:` config points at), falling back to the defaults embedded in the binary (`v2/pkg/policies/defaults/`). It is the agent's **periodic work prompt**: on every kick, the template is loaded, variables like `${ISSUE_LIST}`, `${PR_LIST}`, `${AGENT_NAME}`, `${PROJECT_ORG}`, and `${KNOWLEDGE}` are substituted, and the result is dispatched to the agent's session.

Resolution order: the agent's explicit `kick_template` wins; otherwise the ACMM pack's template for that agent at the current level; otherwise convention — `/data/agents/<name>/CLAUDE.md`, then `<name>.md` in the policies checkout, then the embedded default. Pack templates carry the level's policy in their names — `scanner-holdgated.md` is scanner-at-L5; the same scanner at L6 gets `scanner-automerge.md`.

Portable agents bundle everything — config plus a `promptTemplate` — in a single `AgentDefinition` YAML you can import from a URL in the dashboard (see [`v2/examples/agents/customized-agent.yaml`](../examples/agents/customized-agent.yaml)).

## When to add what

| Add… | …when |
|---|---|
| nothing (name + method + model) | you're starting. Defaults are production defaults. |
| `display_name`, `emoji`, `color` | the dashboard roster grows past a handful and you want it scannable |
| a model pin 📌 | you need reproducible behavior and the governor keeps optimizing your model away |
| `cli_pinned: true` | a CLI update broke an agent once and you never want that surprise again |
| `on_demand: true` | the agent should run only when something (you, inception) explicitly fires it |
| cadence overrides | the pack's rhythm doesn't match your project — a hot repo may want `scanner: 15m`, a quiet one `4h` |
| `pause` in a mode | an agent is noisy exactly when the queue is deep (e.g. pause `architect` during surge) |
| `stale_timeout` | you changed cadences — keep it above the longest interval |
| `clear_on_kick: false` | the agent genuinely benefits from remembering previous kicks (rare; context bloat is real) |
| `channels` | governor timer kicks aren't enough — you want webhook-, schedule-, or bead-triggered work |
| `tools` rules | one agent needs a sharper permission edge than its `mode` tier provides |
| a higher ACMM level | your review capacity, CI trust, and appetite for autonomy have all grown — raise the level and let the pack reconcile the roster |
| an inference method | you have GPUs (or a LiteLLM gateway) and want agents off subscription seats |

## What to read next

- **[Introduction](https://kubestellar.io/docs/hive/overview/introduction)** — what hive is, setup, and the command surface.
- **[Architecture](https://kubestellar.io/docs/hive/overview/architecture)** — the two scheduling models and how the supervisor drives agents.
- **[Troubleshooting](https://kubestellar.io/docs/hive/getting-started/troubleshooting)** — stuck sessions, login expiry, restart loops.
- **[ACMM policy matrix](acmm-policy-matrix.md)** — the full per-level, per-agent policy table.
- **[Cross-cluster migration](cross-cluster-migration.md)** — moving a hive (and its PVC state) between clusters.
