Variable Substitution

Hive substitutes ${VAR} references in two different places: in hive.yaml at load time, and in an agent’s kick prompt every time it is kicked. This page covers both, and how to add your own variables via config.

The two systems share the ${VAR} syntax but are otherwise independent — people conflate them, so it helps to see them side by side first.

SystemWhereSyntaxResolved whenExample
Config interpolationhive.yaml${ENV}at config loadtoken: ${HIVE_GITHUB_TOKEN}
Kick-template variablespolicy .md templates${VAR}on every kick${ISSUE_LIST}, ${AGENT_NAME}

Config ${VAR} — environment interpolation

When hive loads hive.yaml, it replaces ${VAR} references with environment variables before parsing. This is how secrets and per-deployment endpoints get into the config without being written into it:

github:
  token: ${HIVE_GITHUB_TOKEN}
notifications:
  slack_webhook: ${SLACK_WEBHOOK_URL}

An unset variable is left as the literal text ${VAR} — substitution never blanks a value it can’t resolve. A variable that is set but empty resolves to the empty string.

The env-name-not-value rule

This is the load-bearing security invariant of hive’s config:

Note: hive.yaml stores environment-variable names (api_key_env) and key-file paths (api_key_file) — never secret values. Because Config.Save() rewrites the whole file to disk, a literal secret in YAML would be persisted in plaintext, so the code refuses the pattern.

Where the values actually live:

  • /data/secrets/ — secrets entered through the dashboard (writable PVC).
  • /secrets/ — secrets from Kubernetes Secret mounts (read-only).

Inference backends follow the same rule: you configure a key reference (env var name or file path), and hive resolves it at runtime. See the Security Model for the full treatment.

Note: never inline a secret value in hive.yaml. Use ${ENV} or a *_env / *_file reference.


Kick-template variables

An agent’s kick template is its periodic work prompt. Every time the governor kicks the agent, hive loads the template, substitutes its variables from live runtime state, and dispatches the result to the agent’s session. These variables are computed fresh on each kick — they are not environment variables.

The most-used variables:

VariableInjected valueAvailability
${AGENT_NAME}the agent’s namealways
${AGENT_DISPLAY_NAME}the agent’s dashboard labelalways
${ISSUE_LIST}the open issues in this agent’s lanealways
${PR_LIST}the open pull requestsalways
${QUEUE_ISSUES} / ${QUEUE_PRS} / ${QUEUE_HOLD}current queue countsalways
${SLA_VIOLATIONS}count of aged issuesalways
${KNOWLEDGE}relevant facts primed from git sources and the wikiall levels
${GH_AUTH}GitHub auth instructions for the agentmeasured, hold-gated, and full templates
${PROJECT_ORG} / ${PROJECT_NAME} / ${PROJECT_PRIMARY_REPO} / ${PROJECT_REPOS_LIST}project identityalways
${AUTHORIZED_REPOS}the repo list the agent may act onalways
${MERGE_ELIGIBLE} / ${CI_FAILING}PRs ready to merge / with red CIalways
${TIMESTAMP}the current timealways

Additional variables cover the agent roster (${AGENT_LIST}, ${AGENT_ROLES}, ${ENABLED_AGENTS}) and the inception workflow (${INCEPTION_IDEA}, ${INCEPTION_PHASE}, and related). See the ACMM Policy Matrix for how variable availability tracks an agent’s level.

Template resolution order

Which template file a kick uses is resolved in 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); otherwise the embedded default. This is covered in full under Kick templates.


Adding your own variables

The env, static, script, and http variable types described below are all available. New resolver types can also be added in code through a small registration seam, so the set is open-ended.

You can define your own ${VAR}s in a top-level variables: block in hive.yaml, without editing hive’s code. A defined variable can be used in kick templates and — with the right scope — in the config itself.

variables:
  defs:
    DEPLOY_ENV:
      type: static
      value: production
      scope: both
    REGION:
      type: env
      env: HIVE_REGION
      default: us-east-1     # used when HIVE_REGION is unset

Each definition has:

  • type — the resolver: env, static, script, or http.
  • scope — where the variable applies: template (default, per-kick prompts), config (hive.yaml load), or both.
  • default — a fallback used when the resolver would otherwise leave the variable unresolved (for env, when the variable is unset).

Built-in kick-template variables (the table above) always take precedence over a custom variable of the same name, so you cannot accidentally shadow ${ISSUE_LIST}.

Managing variables from the dashboard

The Governor Configuration → Variables tab lists every defined variable (name, type, scope, and a non-secret source tag), along with whether script execution and http fetch are enabled. From that tab you can add, edit, and delete static and env variables directly.

script and http variables are shown read-only there and are marked seed-managed: because they run commands or reach the network, they can be defined in the seed config (GitOps), never from the dashboard. The same variables are also available programmatically, read-only, at GET /api/config/variables — no secret value is ever returned.

static — a fixed value

variables:
  defs:
    DEPLOY_ENV: { type: static, value: production, scope: both }

env — an environment variable, with an optional default

variables:
  defs:
    REGION: { type: env, env: HIVE_REGION, default: us-east-1 }

This also gives config interpolation the default-value behavior plain ${VAR} never had.

script — the output of a command

variables:
  security:
    allow_exec: true          # required; disabled by default
    exec_timeout_s: 3
  defs:
    BUILD_SHA:
      type: script
      command: [git, rev-parse, --short, HEAD]   # argv, not a shell string
      scope: template

http — a value fetched from a web service

variables:
  security:
    allow_http: true                          # required; disabled by default
    http_allowlist: [vault.internal.example.com]
  defs:
    DB_HOST:
      type: http
      url: "https://vault.internal.example.com/v1/config/db-host"
      headers: { Authorization: "Bearer ${HIVE_VAULT_TOKEN}" }
      scope: config

Security model for script and http

Because a resolver can execute a command or reach the network, the script and http types are disabled by default and gated behind variables.security.allow_exec / allow_http.

Note: the variables.security block is honored from the trusted config seed, never from the dashboard overlay. Because the overlay is user-writable, letting it enable execution would be a code-execution path — so an overlay can add static/env variables but can never turn on script/http.

Additional guardrails: script runs an argv slice (never a shell string, so there is no shell injection) under a timeout, with a non-zero exit leaving the variable unresolved rather than failing the kick. http is GET-only, restricted to a host allowlist, blocks private/loopback addresses, caps the response size, and times out. An unresolvable custom variable, like any other, is left as its literal ${VAR}.


See also

  • Agent Configuration — kick templates and the agent config surface.
  • Governor — how kick prompts are built each cycle; budget-driven model selection is a related automatic substitution.
  • Security Model — the secret-handling and trust model in full.
  • ACMM Policy Matrix — per-level template-variable availability.