mirror of
https://github.com/block/buzz.git
synced 2026-10-27 12:40:30 +00:00
Adds authenticated, role-based moderation to the relay admin API. On
`main` the admin API is read-only and gated only by `Host`/`Origin`
matching; this branch adds NIP-98 authentication, a two-tier
Operator/Moderator principal model, mutation and staffing endpoints, and
NIP-11 auto-discovery so clients never type the admin URL by hand.
## Authentication (`BUZZ_ADMIN_AUTH`)
`BUZZ_ADMIN_AUTH` accepts `nip98` or `disabled`. Leaving it unset
defaults to `nip98` (fail-secure). Configuration fails closed: any other
value aborts startup, while a lingering `BUZZ_ADMIN_TOKEN` is ignored
with a startup warning — token (bearer) authentication is not supported.
`Host`/`Origin` matching is retained in every mode as defense-in-depth.
- **`nip98`** (default) — per-request signed NIP-98 (kind 27235) events,
resolved to an Operator or Moderator principal with per-person
attribution and individual revocability. Read-write per resolved
principal.
- **`disabled`** — no credential; relies entirely on network-layer
controls (reverse proxy, VPN, firewall) and logs a `WARN` on every boot.
Always read-only: `authorize()` resolves no principal, so mutation and
staffing routes always `403`.
## Roles
Buzz has two independent authority axes after this change.
**Relay-level** roles (new here) are deployment-global: they act across
every community on the relay, through the admin API. **Community-level**
roles (pre-existing, unchanged by this PR) are tenant-scoped: they act
inside one community, through signed Nostr moderation commands.
### Relay level (new)
| Role | Description |
|---|---|
| **Operator** | Full control of the deployment's moderation surface:
read all reports, feedback, and attachments across every community;
resolve reports with enforcement (`delete`/`kick`/`ban`/`timeout`) or
decisions (`dismiss`/`escalate`); reopen and cancel; update feedback
status; and manage the Operator/Moderator roster via the staffing
endpoints. |
| **Moderator** | Day-to-day triage: everything an Operator can do
except staffing — cannot view or change the roster. |
How a pubkey acquires a relay role (resolution order; config always
outranks DB):
1. Listed in `RELAY_OPERATOR_PUBKEYS` → **Operator** (source `config`)
2. Equals `RELAY_OWNER_PUBKEY` while `RELAY_OPERATOR_PUBKEYS` is empty →
**Operator** (source `owner_fallback`, a break-glass grant for
self-hosters that deactivates once any operator is configured)
3. Row in the `relay_operators` table → **Operator** or **Moderator**
(source `db`, managed via the staffing endpoints)
4. No match → `403`
### Community level (pre-existing, unchanged)
| Role | Description |
|---|---|
| **Owner** (community) | Full authority within their community: every
moderation action (delete, kick, ban/unban, timeout/untimeout, resolve
reports, view queue) plus member, role, and invite management. No guard
rails. |
| **Admin** (community) | Same community-wide moderation capabilities as
owner, except an admin cannot ban or time out the owner or a fellow
admin — only the owner may action an admin. Manages members and invites;
only the owner grants the admin role. |
| **Member** (community) | Standard participant; no moderation
capability. |
| **Owner / Admin** (channel) | Channel-local authority only: delete
messages and kick users within their own channel. |
| **Member / Guest / Bot** (channel) | No moderation authority. |
There is no community-level Moderator tier in v1; relay-level Moderator
is the only role by that name.
## Escalation scoping
The operator report queue is an escalation backstop, not the community's
day-to-day triage surface (per `VISION_MODERATION`, the severe class is
the platform's to review rather than the community's). Two rules enforce
that:
- **Escalated-by-default listing.** `GET /reports` with no `status`
parameter returns only `escalated` reports. An explicit
`status=<open|resolved|dismissed|escalated>` filter is always honored as
given, and full visibility across every status stays available for
platform-safety and legal review via `scope=all` (which lists reports
regardless of status). `scope` accepts only `all` and is ignored when an
explicit `status` is present.
- **Auto-escalated `illegal` reports.** Member reports whose category is
`illegal` are ingested with `status=escalated` rather than `open`, so
the severe class reaches the operator backstop without waiting for a
community admin to forward it. Every other category still lands `open`.
Auto-escalation only sets the queue status — it records no moderator
decision and stamps no resolver, so an auto-escalated report is
indistinguishable downstream from an admin-escalated one: the reopen
route returns it to `open` on the same terms, keyed only on status,
never on how the report became escalated.
## Principal resolution and NIP-98 admission
`resolve_admin_principal()` returns `AdminPrincipal { pubkey, role,
source }` per the resolution order above; `None` never falls through as
a role. Admission is ordered so the replay guard is a privilege, not a
public surface: signature/URL/method/payload-hash verification first,
roster check second, and only then is the deployment-scoped replay id
atomically consumed — a validly-signing but unrostered key never
allocates a replay slot. Redis failure fails closed.
## Report resolution, recovery, and enforcement provenance
`POST /reports/{id}/resolve` is a crash-safe enforcement state machine:
decision-only outcomes (`dismiss`/`escalate`) are a single
CAS-plus-audit transaction; enforcement
(`delete`/`kick`/`ban`/`timeout`) claims the report
(`open`→`processing`), runs the durable mutation, then finalizes — a
re-drive resumes at the step marker and converges to exactly-one
enforcement, fenced by a lease and an outbox claim token.
Person-directed enforcement on an `event`-kind report derives its target
from the stored event's author (server-owned truth, never the reporter's
`p` tag) via a single `derive_enforcement_target` shared by the HTTP
driver and the recovery worker. If the reported event was purged before
its author could be read, person-directed actions are rejected pre-claim
and the report stays `open`; `delete` needs only the event id and is
exempt.
`GET /reports/{id}` and the resolve response carry an `activeAction`
field surfacing the enforcement that actually executed — a report
dismissed after a reopen still reports the ban that ran. `POST
/reports/{id}/reopen` returns a terminal report to `open` (idempotent on
`requestId`). `POST /reports/{id}/cancel` is the sole recovery path for
a pre-mutation `failed` action, attributed via
`relay_admin_actions.cancelled_by`.
## Feedback
`GET /feedback` and `/feedback/{id}` survive a tenant purge: provenance
columns are severed to `NULL` rather than cascade-deleted, and the
attachment path fails closed to `404` on a severed row. `PATCH
/feedback/{id}` updates lifecycle `status`
(`new`/`reviewed`/`archived`).
## Staffing and probe
`GET/PUT/DELETE /operators/{pubkey}` are Operator-only; mutating a
config-backed pubkey returns `409 Conflict`. `GET /operators` returns
the union of config and DB principals with per-entry `source`. `GET
/probe` reports auth mode, role, source, `canAct`, and `canStaff` for
the desktop console.
## NIP-11 auto-discovery
The NIP-11 relay-information document gains an optional `admin_api`
field carrying the canonical admin origin (`scheme://host[:port]`, no
path), present iff `BUZZ_ADMIN_HOST` is set and omitted otherwise. The
scheme follows the same loopback rule as NIP-98 `u`-tag verification via
a shared `scheme_for_host` helper, so the advertised origin and the
origin the relay verifies against can never diverge.
## Operator API origin decoupling
`RELAY_OPERATOR_API_ORIGIN` is no longer required at boot when
`RELAY_OPERATOR_PUBKEYS` is set — it is used only by the
community-provisioning endpoints, which fail closed at request time
(with a boot-time `WARN`) until it is set. The admin console needs no
origin.
## Admin-web adaptation
The standalone `admin-web` dashboard signs each request as a NIP-98
event via a NIP-07 browser extension, discovers the auth mode with a
single unauthenticated probe (`200` → `disabled`, anything else →
`nip98`, fail-secure), and carries no token entry surface. Playwright
coverage exercises the NIP-98 and CSP paths.
## Security hardening
Three findings from security review are folded in:
- **Append-only roster audit.** `PUT`/`DELETE /operators/{pubkey}`
mutate the deployment-wide root of trust, but the upsert overwrites
`role`/`added_by` in place and the delete removes the only row — so a
grant→revoke sequence left no trace of who was ever granted or by whom.
Each mutation now writes an `relay_operator_audit` row (actor, target,
`grant`/`revoke`, pre-image `prev_role`, `new_role`, timestamp) inside
the same transaction as the mutation. A per-target transaction-scoped
advisory lock serializes concurrent mutations of the same pubkey before
the pre-image read, so the recorded `prev_role` is always the true
predecessor even under a concurrent-grant race. Chronology is keyed on a
`BIGINT GENERATED ALWAYS AS IDENTITY` `seq` column, not the wall clock:
the serializing lock guarantees insertion order and `seq` captures it,
so ordered reads (`ORDER BY seq`) follow the true privilege chain even
across a backward NTP step that a `clock_timestamp()` ordering would
invert. `created_at` (`clock_timestamp()`) is retained as informational
occurrence time only. Append-only by construction — no `UPDATE`/`DELETE`
path and no API surface. A no-op delete writes nothing.
- **`expirationSecs` overflow.** The timeout path built `Utc::now() +
Duration::seconds(secs as i64)` from an attacker-controlled `u64`:
`i64::MAX` panicked the handler, and a wrapped-negative magnitude minted
a *past* expiry that still passed validation. `compute_timeout_until`
now rejects zero, rejects magnitudes above a documented
`MAX_TIMEOUT_SECS` (365 days), and uses checked
`try_seconds`/`checked_add_signed` so no input can panic or produce a
past expiry — over-cap, zero, `i64::MAX`, and wrapping-negative inputs
all return a clean `4xx`.
- **Uppercase-hex config-backed bypass.** Config pubkeys are lowercased
at parse, but the `409` immutability check raw-string-compared the path
param while `decode_hex_pubkey` accepted uppercase — so `PUT
/operators/{UPPERCASE}` skipped the guard and wrote a shadow row for the
same 32 bytes. The validated param is now canonicalized (lowercased)
before the `409` check, DB write, `DELETE`, and response body.
## Migrations
- `0035_relay_operators.sql` — `relay_operators` roster table
(deployment-global), `actor_authority` on `moderation_actions`,
`processing` status plus `active_action_id` on `moderation_reports`,
`status` on `product_feedback`.
- `0036_relay_admin_actions.sql` — enforcement-action table with a
`request_id` idempotency key, a `step_marker` for crash recovery, and a
`cancelled_by` attribution column.
- `0037_relay_admin_action_lease.sql` — lease fencing for the action
worker.
- `0038_relay_admin_outbox_claim_token.sql` — fenced claim token on the
outbox worker.
- `0039_relay_operator_audit.sql` — append-only `relay_operator_audit`
trail for roster mutations (see Security hardening).
`docs/admin/README.md` documents the full principal model, NIP-98 event
requirements, capabilities by role, the startup error matrix, and the
discovery field.
## Production blast radius
A relay without `BUZZ_ADMIN_HOST` is completely unaffected — the admin
surface stays disabled and `BUZZ_ADMIN_AUTH` is ignored; a lingering
`BUZZ_ADMIN_TOKEN` logs a startup warning and must be removed. Where
`BUZZ_ADMIN_HOST` **is** set, unset `BUZZ_ADMIN_AUTH` defaults to
`nip98` (per-person signed auth); `BUZZ_ADMIN_AUTH=disabled` reproduces
`main`'s prior `Host`/`Origin`-only gating but is read-only (mutation
routes `403`). The five migrations add tables and columns without
touching existing data.
---
Related: [block/buzz#4768](https://github.com/block/buzz/pull/4768)
(desktop admin console consuming the `admin_api` field),
[squareup/bb-public#339](https://github.com/squareup/bb-public/pull/339)
(Phase 4 rollout config)
---------
Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
Signed-off-by: Duncan <dcfd242e557282d7a1e2cf2e6877522682f1e5c6156dc92ca7d90eaedd3b0f95@buzz.block.builderlab.xyz>
Co-authored-by: Duncan <dcfd242e557282d7a1e2cf2e6877522682f1e5c6156dc92ca7d90eaedd3b0f95@buzz.block.builderlab.xyz>
306 lines
16 KiB
Plaintext
306 lines
16 KiB
Plaintext
# =============================================================================
|
||
# Buzz Backend — Local Development Environment
|
||
# =============================================================================
|
||
# Copy this file to .env and adjust as needed:
|
||
# cp .env.example .env
|
||
#
|
||
# All defaults here work with `docker compose up` out of the box.
|
||
#
|
||
# Service ports (defaults):
|
||
# Postgres → localhost:5432
|
||
# Redis → localhost:6379
|
||
# Typesense → localhost:8108
|
||
# Adminer → localhost:8082 (DB browser UI)
|
||
#
|
||
# Note: If port 8082 conflicts, change the adminer port in docker-compose.yml
|
||
# =============================================================================
|
||
|
||
# -----------------------------------------------------------------------------
|
||
# Database (Postgres 17)
|
||
# -----------------------------------------------------------------------------
|
||
DATABASE_URL=postgres://buzz:buzz_dev@localhost:5432/buzz
|
||
# Optional read-replica URL; unset/blank keeps all reads on the writer.
|
||
# READ_DATABASE_URL=postgres://buzz:buzz_dev@localhost:5433/buzz
|
||
PGHOST=localhost
|
||
PGPORT=5432
|
||
PGUSER=buzz
|
||
PGPASSWORD=buzz_dev
|
||
PGDATABASE=buzz
|
||
|
||
# -----------------------------------------------------------------------------
|
||
# Redis 7
|
||
# -----------------------------------------------------------------------------
|
||
REDIS_URL=redis://localhost:6379
|
||
# Max connections in the relay's shared Redis pool (default 16).
|
||
# BUZZ_REDIS_POOL_SIZE=16
|
||
|
||
# Max connections in each of the relay's Postgres pools — writer and, when
|
||
# READ_DATABASE_URL is set, reader (default 50).
|
||
# BUZZ_DB_POOL_SIZE=50
|
||
|
||
# -----------------------------------------------------------------------------
|
||
# Typesense (search)
|
||
# -----------------------------------------------------------------------------
|
||
TYPESENSE_API_KEY=buzz_dev_key
|
||
TYPESENSE_URL=http://localhost:8108
|
||
|
||
# -----------------------------------------------------------------------------
|
||
# Relay (WebSocket server)
|
||
# -----------------------------------------------------------------------------
|
||
# Bind address for the relay (host:port)
|
||
BUZZ_BIND_ADDR=0.0.0.0:3000
|
||
# Public WebSocket URL — used in NIP-42 auth challenges
|
||
RELAY_URL=ws://localhost:3000
|
||
# Stable relay signing key (required). `just bootstrap` generates a random key in
|
||
# the gitignored .env file. Preserve that value across restarts and backups.
|
||
# BUZZ_RELAY_PRIVATE_KEY=<32-byte hex private key>
|
||
# Optional: path to the web UI dist directory. When set, the relay serves
|
||
# the web frontend at / for browser requests. Leave unset for local dev
|
||
# (use `just web` for Vite HMR instead).
|
||
# BUZZ_WEB_DIR=./web/dist
|
||
|
||
# -----------------------------------------------------------------------------
|
||
# Admin Dashboard (private moderation surface)
|
||
# -----------------------------------------------------------------------------
|
||
# Host name that serves the moderation dashboard and its /api/admin/v1
|
||
# endpoints. Leave unset to keep the admin surface absent.
|
||
# BUZZ_ADMIN_HOST=admin.localhost:3000
|
||
#
|
||
# Authentication mode. Accepted values: nip98 (default), disabled.
|
||
# Any other value is a startup error. Token authentication was removed:
|
||
# BUZZ_ADMIN_TOKEN is ignored with a startup warning — remove it from the environment.
|
||
# BUZZ_ADMIN_AUTH=nip98
|
||
#
|
||
# Option A — BUZZ_ADMIN_AUTH=nip98 (Nostr pubkey-based auth, default):
|
||
# NIP-98 HTTP Auth. Each request must carry an Authorization: Nostr header
|
||
# with a signed kind-27235 event. Authorized principals are resolved from:
|
||
# 1. RELAY_OPERATOR_PUBKEYS — comma-separated 64-char hex pubkeys (config Operators).
|
||
# 2. RELAY_OWNER_PUBKEY — implicit Operator fallback when RELAY_OPERATOR_PUBKEYS is unset.
|
||
# 3. relay_operators table — DB-managed Operator/Moderator roster.
|
||
# The dashboard requires a NIP-07 browser extension.
|
||
# Setting RELAY_OPERATOR_PUBKEYS for the admin console does NOT require
|
||
# RELAY_OPERATOR_API_ORIGIN; that origin is only for community provisioning
|
||
# (see below). When BUZZ_ADMIN_HOST is set, the relay advertises the admin
|
||
# origin in its NIP-11 document (`admin_api` field) so clients can auto-discover
|
||
# the console without manual URL entry.
|
||
# RELAY_OPERATOR_PUBKEYS=<64-char hex pubkey>[,<64-char hex pubkey>...]
|
||
#
|
||
# Option B — BUZZ_ADMIN_AUTH=disabled (network-layer auth only):
|
||
# Set only when the admin API is already protected at the network layer
|
||
# (VPN, private ingress). The relay logs a WARN on every startup.
|
||
# `just admin` defaults to this mode for local review.
|
||
#
|
||
# Directory holding the built dashboard assets (`pnpm -C admin-web build`).
|
||
# BUZZ_ADMIN_WEB_DIR=./admin-web/dist
|
||
#
|
||
# Canonical origin (http(s)://host[:port], no path) that community-provisioning
|
||
# NIP-98 requests are verified against. Required only to USE the provisioning
|
||
# endpoints (POST /operator/communities) — not for the admin console. When
|
||
# RELAY_OPERATOR_PUBKEYS is set but this is unset, the relay boots with a WARN
|
||
# and provisioning requests fail closed until it is set.
|
||
# RELAY_OPERATOR_API_ORIGIN=http://127.0.0.1:3000
|
||
|
||
# Optional relay-owned KLIPY key. When set, NIP-11 advertises GIF search and
|
||
# authenticated desktop clients use this relay as the metadata/search proxy.
|
||
# Keep the real value in your deployment's secret manager; never commit it.
|
||
# BUZZ_KLIPY_API_KEY=
|
||
|
||
# Shared Redis-backed admission limits. Defaults shown below; each value must
|
||
# be a positive integer.
|
||
# BUZZ_RATE_LIMIT_HUMAN_MESSAGES_PER_MIN=60
|
||
# BUZZ_RATE_LIMIT_GIF_SEARCHES_PER_MIN=30
|
||
# BUZZ_RATE_LIMIT_HUMAN_API_CALLS_PER_MIN=300
|
||
# BUZZ_RATE_LIMIT_HUMAN_WS_EVENTS_PER_SEC=10
|
||
# BUZZ_RATE_LIMIT_AGENT_STANDARD_MESSAGES_PER_MIN=120
|
||
# BUZZ_RATE_LIMIT_AGENT_STANDARD_API_CALLS_PER_MIN=600
|
||
# BUZZ_RATE_LIMIT_AGENT_ELEVATED_MESSAGES_PER_MIN=300
|
||
# BUZZ_RATE_LIMIT_AGENT_PLATFORM_MESSAGES_PER_MIN=600
|
||
|
||
# -----------------------------------------------------------------------------
|
||
# Git (NIP-34 bare repositories)
|
||
# -----------------------------------------------------------------------------
|
||
# Root directory for ephemeral Git workspaces and the disposable pack cache.
|
||
# Default: ./repos (relative to CWD).
|
||
# BUZZ_GIT_REPO_PATH=./repos
|
||
# BUZZ_GIT_MAX_PACK_BYTES=524288000
|
||
# BUZZ_GIT_MAX_REPO_BYTES=1048576000
|
||
# Process-local immutable pack/index cache. Zero disables retention.
|
||
# BUZZ_GIT_PACK_CACHE_PATH=./repos/.pack-cache
|
||
# BUZZ_GIT_PACK_CACHE_MAX_BYTES=5368709120
|
||
# BUZZ_GIT_PACK_CACHE_MAX_CONCURRENT_POPULATIONS=2
|
||
|
||
# -----------------------------------------------------------------------------
|
||
# S3-Compatible Object Storage (media + Git/CAS)
|
||
# -----------------------------------------------------------------------------
|
||
# The local MinIO container is reachable from host processes at localhost:9000.
|
||
# Path style keeps the bucket in the URL path and is required by this local DNS
|
||
# setup. Use `virtual` only when the provider requires bucket-as-subdomain URLs.
|
||
BUZZ_S3_ENDPOINT=http://localhost:9000
|
||
BUZZ_S3_ACCESS_KEY=buzz_dev
|
||
BUZZ_S3_SECRET_KEY=buzz_dev_secret
|
||
BUZZ_S3_BUCKET=buzz-media
|
||
BUZZ_S3_REGION=us-east-1
|
||
BUZZ_S3_ADDRESSING_STYLE=path
|
||
|
||
# -----------------------------------------------------------------------------
|
||
# Media Upload Admission
|
||
# -----------------------------------------------------------------------------
|
||
# Bound image/video parser and storage work admitted by one relay process.
|
||
# BUZZ_MEDIA_MAX_CONCURRENT_UPLOADS=8
|
||
# BUZZ_MEDIA_MAX_CONCURRENT_UPLOADS_PER_PUBKEY=2
|
||
# BUZZ_MEDIA_UPLOADS_PER_MINUTE=30
|
||
# GET/HEAD /media/* always require Blossom t=get auth and relay membership.
|
||
# BUZZ_REQUIRE_MEDIA_GET_AUTH and BUZZ_REQUIRE_MEDIA_READ_AUTH are no longer
|
||
# read; setting either (including to false) changes nothing and the relay warns
|
||
# about it at startup.
|
||
|
||
# -----------------------------------------------------------------------------
|
||
# Ephemeral Channels (TTL testing)
|
||
# -----------------------------------------------------------------------------
|
||
# Override the TTL for all ephemeral channels (in seconds). When set, any
|
||
# channel created with a TTL tag will use this value instead of the
|
||
# client-provided one. Unset to use the client-provided TTL.
|
||
# BUZZ_EPHEMERAL_TTL_OVERRIDE=60
|
||
|
||
# How often the reaper checks for expired ephemeral channels (default: 60s).
|
||
# BUZZ_REAPER_INTERVAL_SECS=5
|
||
|
||
# -----------------------------------------------------------------------------
|
||
# Logging / Tracing
|
||
# -----------------------------------------------------------------------------
|
||
RUST_LOG=buzz_relay=debug,buzz_datastore=info,buzz_db=debug,buzz_auth=debug,buzz_pubsub=debug,tower_http=debug
|
||
# Optional OpenTelemetry-only target filter. This is deliberately independent
|
||
# from RUST_LOG so log verbosity changes cannot break trace parentage.
|
||
# BUZZ_OTEL_FILTER=buzz_relay=info,buzz_datastore=info
|
||
|
||
# OTLP tracing endpoint (optional — leave unset to disable)
|
||
# OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4317
|
||
|
||
# -----------------------------------------------------------------------------
|
||
# ACP (Agent Communication Protocol — buzz-acp harness)
|
||
# -----------------------------------------------------------------------------
|
||
# The ACP harness bridges Buzz events to AI agents. Each env var below maps
|
||
# to a CLI flag of the same name (lowercase, hyphens → underscores). All values
|
||
# are optional unless noted; defaults are shown in comments.
|
||
#
|
||
# Quick start:
|
||
# BUZZ_PRIVATE_KEY=<hex> BUZZ_RELAY_URL=ws://localhost:3000 buzz-acp
|
||
|
||
# ── Identity & auth ──────────────────────────────────────────────────────────
|
||
# Nostr private key (hex or bech32). REQUIRED — identifies the agent on the relay.
|
||
# BUZZ_PRIVATE_KEY=<32-byte hex or nsec1… private key>
|
||
|
||
# Relay WebSocket URL the harness connects to.
|
||
# Note: the relay itself uses RELAY_URL (above); this is the ACP harness's
|
||
# connection target — they happen to point at the same place in local dev.
|
||
# BUZZ_RELAY_URL=ws://localhost:3000
|
||
|
||
# ── Agent subprocess ─────────────────────────────────────────────────────────
|
||
# Binary to spawn as the AI agent (e.g. "goose", "codex-acp", "claude-code").
|
||
# BUZZ_ACP_AGENT_COMMAND=goose
|
||
|
||
# Comma-separated arguments passed to the agent binary.
|
||
# Goose default: "acp". Codex/Claude default: "" (empty).
|
||
# BUZZ_ACP_AGENT_ARGS=acp
|
||
|
||
# Binary for an optional MCP server sidecar (e.g. buzz-dev-mcp for buzz-agent).
|
||
# BUZZ_ACP_MCP_COMMAND=
|
||
|
||
# Number of parallel agent subprocesses (1–32).
|
||
# BUZZ_ACP_AGENTS=1
|
||
|
||
# Desired LLM model ID. Applied to every new ACP session.
|
||
# Use `buzz-acp models` to discover available model IDs.
|
||
# BUZZ_ACP_MODEL=
|
||
|
||
# Optional Databricks model-picker visibility filter. Discovery-only; this does
|
||
# not grant inference access. Comma-separated full-string * / ? patterns are
|
||
# OR-matched against raw workspace endpoint and Unity Catalog model-service IDs.
|
||
# Unset or blank shows every catalog entry. A nonblank value with no usable patterns is invalid.
|
||
# DATABRICKS_MODEL_FILTER=databricks-*,data_tools.goose.*
|
||
|
||
# ── Timeouts & sessions ──────────────────────────────────────────────────────
|
||
# Max seconds per agent turn before timeout (default 320 = ~5 min).
|
||
# BUZZ_ACP_TURN_TIMEOUT=320
|
||
|
||
# Max turns per session before proactive rotation. 0 = disabled (rotate only
|
||
# on MaxTokens / MaxTurnRequests). Recommended: 50 for long-running agents.
|
||
# BUZZ_ACP_MAX_TURNS_PER_SESSION=0
|
||
|
||
# ── Prompts ──────────────────────────────────────────────────────────────────
|
||
# System prompt injected into every agent session (inline text).
|
||
# BUZZ_ACP_SYSTEM_PROMPT=
|
||
|
||
# Path to a file containing the system prompt (mutually exclusive with above).
|
||
# BUZZ_ACP_SYSTEM_PROMPT_FILE=
|
||
|
||
# Message sent to the agent immediately after session creation.
|
||
# BUZZ_ACP_INITIAL_MESSAGE=
|
||
|
||
# ── Heartbeat ────────────────────────────────────────────────────────────────
|
||
# Seconds between heartbeat prompts. 0 = disabled. Must be 0 or ≥10.
|
||
# Recommended: 60 for long-running agents to prevent idle session timeouts.
|
||
# BUZZ_ACP_HEARTBEAT_INTERVAL=0
|
||
|
||
# Heartbeat prompt text (inline). Mutually exclusive with file variant.
|
||
# BUZZ_ACP_HEARTBEAT_PROMPT=
|
||
|
||
# Path to a file containing the heartbeat prompt.
|
||
# BUZZ_ACP_HEARTBEAT_PROMPT_FILE=
|
||
|
||
# ── Desktop development ──────────────────────────────────────────────────────
|
||
# DEV-only: replay first-run onboarding and the Welcome Team kickoff on each
|
||
# app launch while keeping the current identity and relay data.
|
||
# VITE_BUZZ_FORCE_FRESH_ONBOARDING=true
|
||
|
||
# ── Subscription & filtering ─────────────────────────────────────────────────
|
||
# Subscribe mode: "mentions" (default), "all", or "config" (rule-based).
|
||
# BUZZ_ACP_SUBSCRIBE=mentions
|
||
|
||
# Comma-separated event kind numbers to subscribe to (overrides mode defaults).
|
||
# BUZZ_ACP_KINDS=
|
||
|
||
# Comma-separated channel UUIDs to limit subscription scope.
|
||
# BUZZ_ACP_CHANNELS=
|
||
|
||
# Set to true to disable the @-mention filter in mentions mode.
|
||
# BUZZ_ACP_NO_MENTION_FILTER=false
|
||
|
||
# Path to TOML config file for rule-based subscriptions (config mode).
|
||
# BUZZ_ACP_CONFIG=./buzz-acp.toml
|
||
|
||
# ── Dedup & self-ignore ──────────────────────────────────────────────────────
|
||
# How to handle duplicate events: "queue" (default) or "drop".
|
||
# BUZZ_ACP_DEDUP=queue
|
||
|
||
# Set to true to process the agent's own messages (default: ignore self).
|
||
# BUZZ_ACP_NO_IGNORE_SELF=false
|
||
|
||
# ── Context ──────────────────────────────────────────────────────────────────
|
||
# Max context messages fetched for thread replies and DMs (0–100). 0 = disabled.
|
||
# BUZZ_ACP_CONTEXT_MESSAGE_LIMIT=12
|
||
|
||
# ── Presence & typing ────────────────────────────────────────────────────────
|
||
# Set to true to disable automatic online/offline presence status.
|
||
# BUZZ_ACP_NO_PRESENCE=false
|
||
|
||
# Set to true to disable typing indicators while the agent is processing.
|
||
# BUZZ_ACP_NO_TYPING=false
|
||
|
||
# ── Advanced tuning ──────────────────────────────────────────────────────────
|
||
# Event channel buffer capacity (WebSocket → harness). Increase for
|
||
# high-throughput agents. Minimum 1.
|
||
# BUZZ_ACP_EVENT_BUFFER=256
|
||
|
||
# ── Legacy aliases ───────────────────────────────────────────────────────────
|
||
# These are accepted for backward compatibility but the canonical names above
|
||
# are preferred:
|
||
# BUZZ_ACP_PRIVATE_KEY → BUZZ_PRIVATE_KEY
|
||
|
||
# Optional relay join policy. Markdown is served by the relay so every join
|
||
# surface can present the same documents. Each document and the independent age
|
||
# attestation are optional; configuring any one enables policy acceptance.
|
||
# BUZZ_TERMS_OF_SERVICE_MARKDOWN="# Terms of Service\n\nFull terms here."
|
||
# BUZZ_PRIVACY_POLICY_MARKDOWN="# Privacy Policy\n\nFull policy here."
|
||
# BUZZ_AGE_ATTESTATION_REQUIRED=true
|