Skip to main content

Positif — Agent Switchboard: security-first inter-agent control plane (sbd) for the RADLAB fleet

Project description

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.0 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):

  1. whois the caller's peer IP → node + tags + user (unknown ⇒ default-deny)
  2. provenance tag — trusted | tainted (caller may self-declare tainted)
  3. target card lookup (A2A Agent Card + reachability), unknown ⇒ deny
  4. policy check — static (caller, target) table, metadata only, never the payload; tainted is forbidden from sensitive targets (write/spend/prod/privileged)
  5. idempotency — dedup-hit returns the cached result, never re-dispatches
  6. shadow classifier — predicts + logs the (intent → target) decision but does not route (the event log is the training set)
  7. forward — over the card's transport (acp → drives hermes acp; http → POSTs to an HTTP worker, ready for Clyde / a *-automations worker)
  8. 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

# on the box
/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          # schema + seed policy + cards
sbd serve --transport http --host 127.0.0.1 --port 7600 --db /opt/positif-data/positif.db
# in another shell (on the box, so whois resolves a real tailnet node):
/opt/positif-venv/bin/python scripts/smoke_dispatch.py
# add --yes-live only when you intentionally want to spawn the target agent:
/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 closedprovenance 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

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

positif_ai-0.2.1.tar.gz (307.6 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

positif_ai-0.2.1-py3-none-any.whl (221.5 kB view details)

Uploaded Python 3

File details

Details for the file positif_ai-0.2.1.tar.gz.

File metadata

  • Download URL: positif_ai-0.2.1.tar.gz
  • Upload date:
  • Size: 307.6 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.12.9

File hashes

Hashes for positif_ai-0.2.1.tar.gz
Algorithm Hash digest
SHA256 f7525aca52f4488201f0f50d48af7a22ea47eb8710349c5b996b47091faadcc9
MD5 1863539d61ba409ac4d5f5a77bf66a19
BLAKE2b-256 c51dd2358a91fb91c61da2c4a495ba25e1d51c4ee86af542e53a71308713dd80

See more details on using hashes here.

File details

Details for the file positif_ai-0.2.1-py3-none-any.whl.

File metadata

  • Download URL: positif_ai-0.2.1-py3-none-any.whl
  • Upload date:
  • Size: 221.5 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.12.9

File hashes

Hashes for positif_ai-0.2.1-py3-none-any.whl
Algorithm Hash digest
SHA256 b5b2af63c87832a0b0f5275b0b2335beac7e28e5e6543757a47cde43f966b758
MD5 223ff0a24ede53ea23141aa20c577f47
BLAKE2b-256 9b89331b754906d9238c782228a420368e9112a81bef26e4e2c85f80dc66e520

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page