Skip to content

Feature flags

Some Hivemind surfaces are dark by default: the code ships in every build, but the routes stay unavailable until you switch the feature on. That is deliberate — a surface you have not asked for is a surface that cannot be misused on your instance.

This page lists those flags, the values they accept, and how to tell the difference between "I turned it on and it worked" and "I turned it on and nothing happened."


Accepted values

Every HIVEMIND_*_ENABLED flag accepts exactly these, case-insensitively and ignoring surrounding whitespace:

1   true   yes   on

Anything else means OFF. enabled, TRUE., 2, y, Y, yep and an empty value are all not recognized, and the feature stays dark.

This is the single most common way a feature appears not to work: the flag looks set, and is not. Hivemind reports this at boot rather than leaving you to guess — see At boot below.


The flags

Flag Feature What it turns on
HIVEMIND_FEEDBACK_ENABLED feedback POST /api/v1/feedback — in-product bug reports and feedback, identity stamped from the verified principal.
HIVEMIND_CONFIG_STORE_ENABLED config store /api/v1/config/* — the per-user configuration store. Every read and write is scoped to (tenant, owner_id) from the verified principal.
HIVEMIND_AGENT_REAPER_ENABLED agent reaper On by default. A background sweep that marks agents which stopped reporting, so a run that died mid-work stops sitting at running forever, and tells the conversation that started it. It only updates the agent row; it never stops a process. Set it to false to turn it off.

The first two default to off; the agent reaper defaults to on. For the two the shipped docker-compose.yml declares — HIVEMIND_CONFIG_STORE_ENABLED and HIVEMIND_AGENT_REAPER_ENABLED — set the flag in the same .env the rest of your Hivemind configuration lives in, then restart the container; these are read once at boot, not per request.

HIVEMIND_FEEDBACK_ENABLED is not customer-settable on the compose product, so do not follow that instruction for it. The shipped docker-compose.yml declares it on no service, so a value set in .env does not reach the server and the feature stays off.

If a flag has no effect, check that your compose declares it

A variable in .env reaches the container only if docker-compose.yml lists it under the server service's environment: block. There is no env_file: directive, so an undeclared name is not passed through — setting it succeeds at every visible step and changes nothing.

HIVEMIND_AGENT_REAPER_ENABLED was undeclared before 0.1.83. If your docker-compose.yml predates that release, add the line

      - HIVEMIND_AGENT_REAPER_ENABLED

to the server service's environment: list, then recreate the server service. Note that re-running scripts/install.sh does not rewrite docker-compose.yml — it regenerates .env, the override and the secrets, and leaves compose as it was.

The reaper depends on agent heartbeats

Leave the reaper off unless your agents are heartbeating. Its only input is heartbeat freshness: an agent whose heartbeat is missing for longer than the stale window is treated as a dead process and marked failed. On an install where the heartbeat write is failing for an unrelated reason, enabling the reaper converts a silent problem into every agent being marked failed shortly after it starts — while it is still working. Confirm last_heartbeat is being written before you turn this on.


Operator alerts (the Chatalot command bridge)

By default, an alert Hivemind raises — an escalation that needs a human decision, a delivery finding, anything the orchestrator cannot resolve on its own — is recorded and readable at /api/admin/alerts and /api/admin/orchestration/attempts, and nobody is notified. You have to know to go look.

If you connect a Chatalot bot, that connection is the intended alert channel: a dedicated bridge posts each alert, end-to-end encrypted, into a Chatalot #command channel, so it reaches you as a push notification on whichever device has that channel open (the Chatalot mobile app, for example). The same bridge can optionally relay approve/deny replies back in — inbound control — from an allowlisted set of Chatalot senders.

Setup:

  1. In the Hivemind admin UI, create a Chatalot bot profile for this purpose. Its reply mode must be dm_only — the bridge refuses to start against any other mode, because a channel-reply bot racing the bridge's own writes to the command channel's encryption ratchet would corrupt it. Note the profile's id.
  2. In Chatalot, note the id of the #command channel you want alerts posted into.
  3. Set, in .env:
HIVEMIND_COMMAND_BOT_AGENT_ID=<the bot profile id from step 1>
HIVEMIND_COMMAND_CHANNEL_ID=<the Chatalot channel id from step 2>

This enables push only. To also allow specific people to approve or deny an escalation by replying in that channel, additionally set:

HIVEMIND_COMMAND_OPERATORS=<comma-separated Chatalot sender ids>

Left unset or empty, inbound control stays disabled — push-only, by design (default-deny on who may act).

HIVEMIND_PUSH_MIN_SEVERITY optionally raises the floor on what gets pushed (it has a working default; only set it to change what you already get).

  1. Restart the server service. These are read once at boot.

If it has no effect, check that your compose declares these four

Same caveat as the reaper above, and it applied here identically until this was fixed: HIVEMIND_COMMAND_BOT_AGENT_ID, HIVEMIND_COMMAND_CHANNEL_ID, HIVEMIND_COMMAND_OPERATORS and HIVEMIND_PUSH_MIN_SEVERITY are now declared in the shipped docker-compose.yml, but a docker-compose.yml from before this was added will not have them — no value you put in .env reaches the container regardless of how correctly you set it. If your boot log shows

WARN  command-bridge: HIVEMIND_COMMAND_BOT_AGENT_ID/CHANNEL_ID unset — alert
      push and inbound control are DISABLED. ...

even though you have set both, this is why. Add the four lines to the server service's environment: list yourself:

      - HIVEMIND_COMMAND_BOT_AGENT_ID
      - HIVEMIND_COMMAND_CHANNEL_ID
      - HIVEMIND_COMMAND_OPERATORS
      - HIVEMIND_PUSH_MIN_SEVERITY

then recreate the server service. As with the reaper, re-running scripts/install.sh does not rewrite docker-compose.yml; re-running the invite bootstrap does (see Upgrading).


Security gates (default ON)

These are not features. They are protections that are on unless you turn them off, so the table above does not apply to them: there, setting the flag enables something; here, setting it disables a protection.

Flag Default What turning it OFF costs you
HIVEMIND_GATE_WS_LIVE on The browser WebSocket /ws/live stops requiring a credential. Any client that can reach the port receives AgentLogChunk, which is raw stdout from every running agent.
HIVEMIND_GATE_WEB_UI on The orchestration tier gate stops covering the network-isolated web /api/* mounts.

Accepted falsey values are the same as elsewhere: false, 0, no, off. Anything else — including leaving the variable unset — keeps the gate ON.

HIVEMIND_GATE_WS_LIVE

/ws/live is the socket the dashboard opens for live updates. It used to accept any connection without asking for a credential, which meant the server could not tell an operator from a stranger even on an instance where SSO had authenticated the human moments earlier — the page opened the socket and the identity was never read.

Gated (the default) the socket goes behind the same authentication the rest of the API uses, in the same order: forward-auth headers, then the session cookie, then X-API-Key. A browser already sends what those need, because a WebSocket upgrade is an HTTP request: same-origin cookies ride it, and a reverse proxy injects its forward-auth headers onto it. If you followed SSO setup, you do not need to change anything — your browser authenticates the socket the same way it authenticates every other request.

Turn it off only for a bare, network-isolated install with no SSO and no login — the case where the socket would otherwise refuse the operator's own browser and the live-log view would sit empty. That deployment is relying on network reachability as its only control, and the socket then carries agent stdout to anything that can reach the port. Prefer putting a reverse proxy in front (see Install) over turning this off.

HIVEMIND_GATE_WS_LIVE=false   # anonymous socket; agent stdout is readable

Read once at boot. Restart the container after changing it.


At boot

Hivemind reports the posture of every feature flag at startup, so you learn about a mistake without having to make a request first.

Cleanly off — this is the default and is not a problem:

INFO  feature disabled — HIVEMIND_FEEDBACK_ENABLED is not set
      (accepted values: 1, true, yes, on) feature=feedback

On:

INFO  feature ENABLED feature=feedback env_var=HIVEMIND_FEEDBACK_ENABLED

Set, but to a value that is not accepted — this one is a WARN, because it is a mistake rather than a posture:

WARN  feature disabled — HIVEMIND_FEEDBACK_ENABLED is set to 'enabled', which is
      NOT a recognized truthy value (accepted: 1, true, yes, on). The flag looks
      set and is not; requests to this surface will report it as disabled.
      feature=feedback observed=enabled

If you see that line, the fix is on the line it names.


When a feature is off

A request to a disabled surface, from a caller who is authenticated, gets a 503 that names the feature and the flag:

$ curl -s -H "X-API-Key: hm-..." -X POST https://hivemind.example.com/api/v1/feedback
{"error":{"status":503,
          "code":"feature_disabled",
          "message":"feature 'feedback' is disabled on this instance: set HIVEMIND_FEEDBACK_ENABLED to enable it (accepted: 1, true, yes, on)",
          "feature":"feedback",
          "env_var":"HIVEMIND_FEEDBACK_ENABLED"}}

If the flag is set to something unaccepted, the message says so and quotes what it saw, rather than telling you the flag is unset when you can see that it is not:

"message":"feature 'feedback' is disabled on this instance: HIVEMIND_FEEDBACK_ENABLED is set to 'enabled', which is not a recognized truthy value (accepted: 1, true, yes, on)"

Branch on error.code == "feature_disabled" rather than on the prose.

Why an unauthenticated caller sees 401 instead

To a caller with no credential, a disabled surface answers exactly what a live one answers — 401, byte for byte, with no hint about the flag. That is intentional: if a disabled feature answered differently, anyone on the network could map which features your instance runs without holding any credential.

So 401 from these paths means "authenticate first," not "this feature is off." Authenticate, and the 503 above will tell you which it is.

Prior to the release carrying this change, a disabled feature answered a bare, empty-body 404 — indistinguishable from a typo, a wrong API version, or a proxy misroute — and nothing appeared in the logs at any verbosity. If you are on an older build, none of the boot lines or feature_disabled bodies above exist. Check for the code field in the response: if it is absent, you are on an older build.


Build-time: the chatalot-integration Cargo feature

Everything above this line is a runtime flag — the code is in every binary, an env var just decides whether a route answers. This section is different: chatalot-integration is a compile-time Cargo feature on the hivemind-server crate. It is default ON — every published binary and Docker image ships with it, and a normal cargo build (no flags) enables it. It only comes off if you explicitly pass --no-default-features, which today only the public code-showcase export does.

If your install came from a released image or a plain cargo build, this section does not apply to you — skip it.

With the feature off, hivemind-server does not link chatalot-crypto (Seglamater's private E2E crypto crate) or the two key-type crates that version-match it. A build like that:

  • Cannot run a chatalot bot connection at all. bot_ws::run becomes a stub that logs once and returns; no WS connection is ever opened, so no bot ever comes online, regardless of HIVEMIND_COMMAND_BOT_AGENT_ID / HIVEMIND_COMMAND_CHANNEL_ID (see above) or any per-profile chatalot provisioning.
  • Serves an empty /agents/{id}/chatalot surface. Both the /api/v1/agents/{id}/chatalot and /api/admin/agents/{id}/chatalot mounts exist (so nothing else needs to change to build either way) but 404 on every path — there is no chatalot integration to administer.
  • Cannot dispatch the three chatalot agent tools (send message, create channel, invite member). An agent that tries gets a tool-result error explaining the build was compiled without the feature, not a silent no-op.
  • Never runs prekey replenishment. prekey_replenish::run is the same kind of stub — there are no bot identity keys to keep topped up.
  • Reports fleet integration_active: false for every agent on /api/v1/admin/fleet — there is no provisioning data source to read, so every profile evaluates as not-integrated rather than erroring.

Nothing else changes. Every other Hivemind capability — the agent chat SDK surface, orchestration, memories, the admin console, the updater sidecar, SSO — is unaffected; none of them depend on this feature.

Turning it back on is a rebuild, not a data migration: nothing is deleted when the feature is off, so re-enabling it on the next build picks up existing chatalot integrations exactly where they left off.


  • Troubleshooting — including the feature_disabled entry.
  • Install — where .env lives and how to restart after a change.