- Rust 76.8%
- Nix 23.2%
Keep bot as the desired default, but inspect authoritative channel membership before adding or upgrading roles. Fall back to member or preserve an existing membership when the fleet owner lacks owner or admin authority, and warn instead of retrying rejected changes. Expose membershipRole for an explicit member opt-out and document the authority-gated behavior. |
||
|---|---|---|
| examples | ||
| nix | ||
| src | ||
| tests | ||
| .gitignore | ||
| AGENT_CONFIGURATION.md | ||
| Cargo.lock | ||
| Cargo.toml | ||
| CONFIGURATION.md | ||
| flake.lock | ||
| flake.nix | ||
| PLAN.md | ||
| README.md | ||
| STAGING.md | ||
| VERIFICATION.md | ||
Beekeeper
Beekeeper is a standalone, declarative Buzz agent host for NixOS. The
services.beekeeper.agents attribute set is the lifecycle authority: Buzz
users can invoke an allowlisted agent, but cannot create, edit, start, stop, or
retire one.
Each declared agent receives a durable Nostr identity, Unix user/group,
systemd unit, private state directory, workspace, journal, credential, and a
required beekeeper-agent-runner sandbox boundary. Beekeeper retains its fleet-owner key,
registry, reconciliation state, retirement queue, and generated agent keys in
the protected /var/lib/beekeeper control-plane state.
NixOS configuration
For a complete operator walkthrough—including flake import, secrets, adding and removing agents, Goose/Codex/Claude runtime settings, and OpenRouter with Goose—see Configuring hosted agents.
{ config, pkgs, ... }: {
services.beekeeper = {
enable = true;
relayUrl = "wss://buzz.example.com";
fleetOwnerKeyFile = "/run/secrets/beekeeper-owner-key";
ownerProfile = {
displayName = "Beekeeper";
about = "Buzz agent fleet controller.";
};
operationsChannel = "00000000-0000-0000-0000-000000000001";
agents.reviewer = {
profile = {
displayName = "Reviewer";
about = "Reviews proposed changes.";
};
channels = [ "00000000-0000-0000-0000-000000000002" ];
callerPubkeys = [
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
];
respondTo = "allowlist";
parallelism = 2;
idleTimeoutSecs = 300;
maxTurnDurationSecs = 1800;
contextMessageLimit = 20;
multipleEventHandling = "queue";
permissionMode = "plan";
capabilities = [ "messages" "reviews" ];
personaPrompt = "Review changes for correctness and regressions.";
runtime = "codex-acp";
runtimePackages = [ pkgs.codex-acp ];
mcpCommand =
"${config.services.beekeeper.buzzPackage}/bin/buzz-dev-mcp";
environmentFile = "/run/secrets/reviewer.env";
resources = {
memoryMax = "8G";
cpuQuota = "300%";
tasksMax = 512;
};
sandbox.cwd = "/source";
sandbox.binds = [{
source = "/srv/source";
target = "/source";
readOnly = true;
}];
};
};
}
Hosted agents receive a baseline PATH containing Bash, Git, OpenSSL, curl, and
ripgrep. Developer MCP tools are disabled by default; set mcpCommand
explicitly for agents that need them. Use services.beekeeper.path for
additional fleet-wide tools and runtimePackages for an agent adapter. A
read-only source bind must be updated outside the sandbox; use a controlled
writable checkout if the agent must fetch or modify repository state itself.
Buzz built-in personas
Beekeeper can provision Buzz's default Fizz, Honey, and Bumble personas without duplicating their profile or prompt configuration:
services.beekeeper.agents = {
fizz = {
preset = "fizz";
channels = [ "00000000-0000-0000-0000-000000000002" ];
callerPubkeys = [ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" ];
};
honey = {
preset = "honey";
channels = [ "00000000-0000-0000-0000-000000000002" ];
callerPubkeys = [ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" ];
};
bumble = {
preset = "bumble";
channels = [ "00000000-0000-0000-0000-000000000002" ];
callerPubkeys = [ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" ];
};
};
The presets advertise the canonical builtin:* persona IDs and
builtin-team:welcome team ID. Explicit fields override preset values. Because
the agents share a fleet owner, the secured Buzz runtime recognizes them as
verified siblings; the caller allowlist is still required for human operators.
The flake pins buzz-client, buzz-sdk, and the default deployed
buzz-runtime to the same Buzz commit, including Buzz's fail-closed DM author
gate. Private keys are generated at runtime, never interpolated into Nix
derivations or command arguments. The stable fleet owner key is loaded with a
systemd credential. Beekeeper derives a NIP-OA
owner attestation for each agent, uses it while registering the agent with the
relay, and passes it to the agent runtime as a separate systemd credential.
The fleet owner attaches agents to every declared channel with owner-signed
NIP-29 add-member events. Agents desire the bot role by default, but Beekeeper
requests it only when relay state identifies the fleet owner as a channel owner
or admin. Otherwise it safely uses or preserves member and upgrades
automatically if the controller later gains authority.
The owner must already be a member of the operations channel and every private
channel listed for an agent so it can add agents and observe and reconcile
private membership.
Because the relay's agent-owner binding is immutable, Beekeeper rejects a
fleet-owner key change while any agent is active or retiring; retire the fleet
with the old key before rotating it.
On boot, beekeeper-prepare.service reconciles the optional fleet-owner
profile, agent identities and kind-0 profiles, kind-10100 agent directory
entries, owner-signed kind-30177 managed-agent records, and channel
memberships before any agent unit may start.
beekeeper.service then continues reconciliation and reports systemd health
changes to the operations channel as controller-signed, machine-readable
beekeeper-health events. Buzz TUI displays these reports on its existing
Agents surface while keeping lifecycle controls disabled for externally
managed agents. Configure ownerProfile only when
fleetOwnerKeyFile contains a dedicated controller identity; Beekeeper will
publish and manage that key's public profile. Reconciliation reads current
relay state as well as the local registry, so deleted or changed profiles,
directory entries, managed-agent records, membership roles, and memberships are
repaired.
capabilities values are informational directory labels for discovery and UI
display. They do not grant tools or enforce runtime permissions; sandbox
binds, runtime packages, MCP configuration, and provider credentials define
what an agent can actually do.
Removing a declaration queues owner-signed membership removals and an agent-signed retirement tombstone. The private agent key is deleted only after all retirement events have been accepted by the relay and the terminal registry state is durable. Interrupted credential cleanup is retried during later reconciliation.
Operators can inspect the local plan and fleet state without changing it:
beekeeper --config /path/to/beekeeper-fleet.toml \
--state-dir /var/lib/beekeeper plan
beekeeper --config /path/to/beekeeper-fleet.toml \
--state-dir /var/lib/beekeeper status
beekeeper --config /path/to/beekeeper-fleet.toml \
--state-dir /var/lib/beekeeper doctor
The JSON status combines declaration, registry, credential, owner-attestation, and systemd observations. See Configuring hosted agents for the backup and restore validation workflow.
Verification
nix flake check path:. --no-build
nix build path:.#checks.x86_64-linux.package \
path:.#checks.x86_64-linux.nixos-module -L --no-link
The full check also verifies that Beekeeper ships its sandbox runner and that
the pinned Buzz runtime supplies buzz-acp, buzz-agent, and buzz-dev-mcp:
nix flake check path:. -L