Positif β Agent Switchboard (sbd)
Positif (positif.ai) is the canonical product name. sbd is the CLI, and
switchboard / POSITIF_* remain the stable technical namespace (package, env
vars, pos1. token prefix, /opt/positif-* paths, the positif.service
unit). Those identifiers stay put β renaming them is a separate, box-coordinated
migration β but the product you're reading about is Positif.
Status: 0.2.1 pre-alpha. This repository is release-preparation work,
not a stable v1 release. Public distribution, deployment outside RADLAB, or
release publication still requires human release authorization under RADLAB
governance.
The security-first agent control plane β the governance + routing layer for a
multi-agent fleet. Not "another orchestrator": it authenticates the caller
(tailnet whois), enforces a metadata-only policy, dedupes, routes (explicit /
by capability), brokers loop-guarded handoff between agents, rate-limits, and
audits every decision β then gets out of the data path. It does not own memory
(that is Bourdon) and does not execute work (that is the agents); it governs
who can reach whom, with what, and proves it happened.
Sibling to Bourdon in one ecosystem β Bourdon = what the fleet knows,
Positif = who's online + what's in flight. They meet at exactly one MCP
boundary. (Positioning + productization path: claude-brain SWITCHBOARD_PRODUCTIZATION.md.)
Lives on the always-on fleet-box box (/opt/positif-{src,venv,data}),
mirroring Bourdon's layout. Port 7600 (Bourdon = 7500).
0.2.0 Pre-Alpha: What's Built
sbd is a FastMCP server exposing one secure tool, dispatch(target, payload),
over streamable-HTTP bound to the tailnet IP. The HTTP transport is deliberate:
it's what makes whois-gated auth real β the caller's peer IP comes from the
live request and tailscaled turns it into an authoritative node identity.
Dispatch state machine (core/dispatch.py):
- whois the caller's peer IP β node + tags + user (unknown β default-deny)
- provenance tag β
trusted | tainted (caller may self-declare tainted)
- target card lookup (A2A Agent Card + reachability), unknown β deny
- policy check β static
(caller, target) table, metadata only, never the
payload; tainted is forbidden from sensitive targets
(write/spend/prod/privileged)
- idempotency β dedup-hit returns the cached result, never re-dispatches
- shadow classifier β predicts + logs the
(intent β target) decision but
does not route (the event log is the training set)
- forward β over the card's transport (
acp β drives hermes acp; http β
POSTs to an HTTP worker, ready for Clyde / a *-automations worker)
- log + cache the decision
A per-caller token-bucket rate limit runs before forwarding (caps cost/DoS
from a runaway or compromised caller). Observability: the health MCP tool and
sbd stats aggregate the event log over a default 7-day window (per-target
counts, deny rate, latency p50/p95). Use --all-time only when an operator
intentionally needs the full retained dataset.
Routing is explicit-target today. The classifier is scaffolded and learning
from day one.
Reaching Hermes (ACP)
Hermes is delegated to over the Zed Agent Client Protocol (newline-delimited
JSON-RPC over stdio) β the Bourdon-aware path. core/acp_client.py spawns
hermes acp --accept-hooks, runs initialize β session/new β session/prompt,
accumulates agent_message_chunk text, auto-answers any session/request_permission
(else the turn deadlocks), and returns on stopReason: end_turn.
Layout
core/ db schema.sql whois policy policy_studio idempotency classifier classifier_pipeline cards acp_client dispatch server
default_data/ packaged policy + card seeds used by installed `sbd init`
cli/ main.py (sbd serve | init | policy-lint | policy-diff | policy-simulate | ...)
data/ editable mirror/override seeds for source-tree operators
deploy/ positif.service
scripts/ smoke_dispatch.py tests/ test_dispatch.py
Operator Feature Notes
Draft feature notes live at:
docs/features/README.md
docs/features/policy-simulator.md
docs/features/policy-change-studio.md
docs/features/dry-run-dispatch.md
docs/features/agent-registry.md
docs/features/audit-reporting.md
docs/features/classifier-promotion.md
docs/features/sandbox-manager.md
docs/features/retention-automation.md
docs/features/presence-heartbeat.md
docs/features/release-readiness.md
docs/features/release-evidence.md
docs/features/ha-durability.md
docs/features/render-surfaces.md
docs/features/operator-tui.md
sbd render emits JSON Render flat specs for those operator surfaces. This is
the renderer-ready contract for operator clients, not a release authorization
path:
sbd render command-center --db /opt/positif-data/positif.db
sbd render agent-detail --db /opt/positif-data/positif.db
sbd render audit --db /opt/positif-data/positif.db --window-days 7
sbd render audit-detail --db /opt/positif-data/positif.db --window-days 7
sbd render retention --db /opt/positif-data/positif.db
sbd render release --root /opt/positif-src
Every database-backed render surface requires explicit --db; release is the
only render surface that may run from --root without a database path.
Heartbeat probes update bounded agent presence metadata without dispatching
work:
sbd heartbeat --db /opt/positif-data/positif.db --target hermes --timeout-ms 1500
sbd heartbeat --db /opt/positif-data/positif.db --all --timeout-ms 1500
sbd agents --db /opt/positif-data/positif.db --stale-after 300 --json
Release evidence packs collect local review artifacts without authorizing,
tagging, publishing, or deploying anything. The manifest always records
release_authorized: false:
sbd release-evidence --root /opt/positif-src --output /opt/positif-data/release-evidence
sbd release-evidence --root /opt/positif-src --output /opt/positif-data/release-evidence --run-checks --force
HA durability checks start with local SQLite backup and read-only restore
validation only. Phase A is local backup/restore; Phase B is a future
second-node dry run; Phase C is Litestream/B2/NATS or other external replication
only after separate third-party review. This feature adds no B2, NATS,
Litestream config, credentials, bucket names, endpoints, or external replication
calls:
sbd backup --db /opt/positif-data/positif.db --output /opt/positif-data/backups
sbd restore-check --backup /opt/positif-data/backups/positif-20260630T000000Z.sqlite --root /opt/positif-src --json
sbd durability-report --db /opt/positif-data/positif.db --backup-dir /opt/positif-data/backups --json
Policy Change Studio reviews candidate policy JSON before seeding. Lint, diff,
and simulation are review-only: they do not mutate the live database, do not log
events, do not claim idempotency rows, and do not call target transports.
sbd policy-lint --policy ./policy.candidate.json
sbd policy-diff --db /opt/positif-data/positif.db --policy ./policy.candidate.json --json
sbd policy-simulate --db /opt/positif-data/positif.db --policy ./policy.candidate.json --caller-node pc.example.ts.net --target hermes --payload "diagnose" --provenance trusted --json
Classifier promotion reporting and assist-mode recommendations stay advisory.
classifier-report reads shadow prediction metadata, while classifier-assist
returns a target only when the caller is already policy-authorized for that
target. Primary classifier routing remains out of scope.
sbd classifier-report --db /opt/positif-data/positif.db --window-days 7 --json
sbd classifier-assist --db /opt/positif-data/positif.db --intent "review this patch" --caller-node pc.example.ts.net --provenance trusted --json
An optional local Ink renderer lives in tui/ for the same surfaces:
npm --prefix tui ci
npm --prefix tui test
npm --prefix tui run build
node tui/dist/index.js --surface command-center --db /opt/positif-data/positif.db
node tui/dist/index.js --surface agents --db /opt/positif-data/positif.db --status error
node tui/dist/index.js --surface release --root /opt/positif-src --once
The TUI supports command-center, agents, agent-detail, audit,
audit-detail, metrics, retention, and release JSON Render specs. It is
read-only: it does not dispatch, prune, mutate policy, generate release
evidence, authorize release, call external services, or emit analytics.
Run
/opt/positif-venv/bin/python -m pip install -e /opt/positif-src
openssl rand -base64 32 > /opt/positif-data/handoff.secret
chown switchboard:switchboard /opt/positif-data/handoff.secret
chmod 600 /opt/positif-data/handoff.secret
sbd init --db /opt/positif-data/positif.db
sbd serve --transport http --host 127.0.0.1 --port 7600 --db /opt/positif-data/positif.db
/opt/positif-venv/bin/python scripts/smoke_dispatch.py
/opt/positif-venv/bin/python scripts/smoke_dispatch.py --yes-live
Wire a coding agent as an MCP client by pointing it at
http://127.0.0.1:7600/mcp/.
Audit retention
Positif bounds and redacts operator-facing metadata before storage or export.
event_log.intent and event_log.error are stored as short redacted strings, and
sbd events / sbd training re-apply the same bounds when reading older rows.
Stats, metrics, classifier accuracy, and training export default to the last 7
days; pass --all-time for an explicit full retained read.
Retention is manual, not automatic during dispatch:
sbd retention-report --db /opt/positif-data/positif.db --events-days 30 --idempotency-hours 24 --json
sbd prune --db /opt/positif-data/positif.db --events-days 30 --idempotency-hours 24 --batch-size 1000
The default policy keeps audit events for 30 days and idempotency cache rows for
24 hours. Prune deletes in bounded batches so operators can run it from cron or
systemd timers without putting cleanup work on the request path.
Reviewed systemd templates are included at deploy/positif-prune.service and
deploy/positif-prune.timer. A human operator must review and install them on
the target host before enabling the timer.
Security Stance (0.2.0 Pre-Alpha)
- Routes on metadata, never payload; no token minting or forwarding for
caller identity (Hermes uses its own key). Positif mints only short-lived
signed handoff bearers for trace/depth/path continuity.
- Caller identity is asserted on the tailnet β the peer IP must be in the
Tailscale CGNAT range (
100.64.0.0/10) before whois is even consulted, so a
loopback/proxy/public peer can't be mistaken for a fleet node. Default-deny on
unknown caller / unknown target / no policy row (a targeted deny overrides a
broader allow).
tainted (untrusted-origin) dispatches are denied from privileged targets at
ingress, and are additionally bounded to non-destructive tools.
- Fully unprivileged: no root, no sudo.
sbd runs as the
switchboard user, and the Hermes it drives runs as that same unprivileged
user with its own private HERMES_HOME (/opt/positif-data/hermes-home β
config + Anthropic key, no Slack tokens). A compromise of sbd or its Hermes
yields only the switchboard user: it cannot read /root, touch system files,
or escalate. The gateway's separate root Hermes instance is untouched.
deploy/preflight.sh refuses to start unless the HERMES_HOME is present,
private, and switchboard-owned (and hermes is root-owned); an in-code argv
allowlist in acp_client is the second barrier on the spawn.
- Per-dispatch tool bound + audit (allowlist, fail-closed). Every tool the
target routes through the ACP permission gate is logged (
event_log.tool_audit,
preserved even on error/timeout turns). compute (the default for Hermes;
forced for tainted; can be narrowed-to via max_capability) is an allowlist
β only read/search/fetch/think pass; edit/delete/move/execute and any
absent/unknown kind are denied. Verified: under compute, Hermes was
denied a file edit and a dangerous rm at the gate, while Bourdon MCP tools +
Q&A still work. Truly benign commands (e.g. whoami) auto-run in Hermes and skip
the gate β but since Hermes now runs unprivileged, even those can't read
/root, touch system files, or escalate, so the residual is contained.
- Security selectors fail closed β
provenance and max_capability normalize
and resolve any typo/casing to the more restrictive branch, never the privileged
one. A cap can only narrow.
- Idempotency keys are namespaced by caller+target β a caller-supplied key
can't read another caller's or another target's cached result.
- Spawn cleanup is process-group aware. ACP and CLI agent launches create a
subprocess group/session where the platform supports it. On timeout,
Positif terminates the group, then kills it if it does not exit; non-POSIX
platforms fall back to best-effort direct child cleanup.
- CLI review runners must not hide write capability. The Codex card runs
codex exec --skip-git-repo-check without
--dangerously-bypass-approvals-and-sandbox, is marked compute, and is a
review/reasoning node rather than an autonomous writer. A future write-capable
Codex runner must be a separate, explicitly named card.
- Per-agent Unix users are a deploy design, not a hidden runtime shortcut.
Future
sbd-hermes, sbd-coder, and sbd-codex execution must go through
exact root-owned wrapper commands before code enables it. Broad sudo is not
acceptable. Until that runner exists, preflight enforces private
switchboard-owned homes/workspaces for the seeded agents.
- Heartbeat probes are metadata-only.
sbd heartbeat never dispatches
payloads, never runs prompts, and never creates agent work sessions. CLI probes
check only configured filesystem paths, ACP probes stay session-free, and HTTP
probes use only explicit card health URLs with enforced timeouts. Probe errors
are bounded and redacted before storage.
- Signed handoff metadata. Brokered handoff trace IDs, depths, and paths are
carried as
Authorization: Bearer pos1.<payload>.<hmac> tokens signed with
POSITIF_HANDOFF_SECRET_FILE (default deploy path:
/opt/positif-data/handoff.secret). Unsigned X-SBD-* headers are ignored
by default; set POSITIF_ALLOW_LEGACY_HANDOFF=1 only for a temporary compatibility
window. Handoff tokens authenticate metadata only; payload bodies are never used
to derive or validate handoff identity.
Roadmap (Not Yet Built)
Second-node dry runs, Litestream/B2 durability, capability-match + semantic
routing as primary, NATS, brokered agent-to-agent handoff, a second sbd to
kill the SPOF. Litestream, B2, NATS, and other external replication services
require separate third-party review before implementation.
See claude-brain/PROJECTS/RADLAB-CLOUD/SWITCHBOARD.md.
RADLAB LLC, a Wyoming limited liability company Β· BUSL-1.1