positif-ai 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (78) hide show
  1. positif_ai-0.1.0/LICENSE +109 -0
  2. positif_ai-0.1.0/LICENSE-MIT +21 -0
  3. positif_ai-0.1.0/NOTICE +8 -0
  4. positif_ai-0.1.0/PKG-INFO +244 -0
  5. positif_ai-0.1.0/README.md +215 -0
  6. positif_ai-0.1.0/cli/__init__.py +1 -0
  7. positif_ai-0.1.0/cli/main.py +583 -0
  8. positif_ai-0.1.0/core/__init__.py +3 -0
  9. positif_ai-0.1.0/core/acp_client.py +369 -0
  10. positif_ai-0.1.0/core/agent_registry.py +92 -0
  11. positif_ai-0.1.0/core/audit_report.py +127 -0
  12. positif_ai-0.1.0/core/boardroom.py +177 -0
  13. positif_ai-0.1.0/core/cards.py +121 -0
  14. positif_ai-0.1.0/core/classifier.py +92 -0
  15. positif_ai-0.1.0/core/db.py +92 -0
  16. positif_ai-0.1.0/core/default_data/cards/coder.card.json +30 -0
  17. positif_ai-0.1.0/core/default_data/cards/codex.card.json +34 -0
  18. positif_ai-0.1.0/core/default_data/cards/gemini.card.json +43 -0
  19. positif_ai-0.1.0/core/default_data/cards/hermes.card.json +35 -0
  20. positif_ai-0.1.0/core/default_data/cards/research.card.json +28 -0
  21. positif_ai-0.1.0/core/default_data/policy.seed.json +155 -0
  22. positif_ai-0.1.0/core/dispatch.py +522 -0
  23. positif_ai-0.1.0/core/doctor.py +229 -0
  24. positif_ai-0.1.0/core/explain.py +338 -0
  25. positif_ai-0.1.0/core/handoff.py +338 -0
  26. positif_ai-0.1.0/core/idempotency.py +110 -0
  27. positif_ai-0.1.0/core/metrics.py +238 -0
  28. positif_ai-0.1.0/core/planner.py +332 -0
  29. positif_ai-0.1.0/core/policy.py +119 -0
  30. positif_ai-0.1.0/core/processes.py +86 -0
  31. positif_ai-0.1.0/core/provenance.py +51 -0
  32. positif_ai-0.1.0/core/ratelimit.py +75 -0
  33. positif_ai-0.1.0/core/redaction.py +54 -0
  34. positif_ai-0.1.0/core/release_check.py +169 -0
  35. positif_ai-0.1.0/core/render_specs.py +343 -0
  36. positif_ai-0.1.0/core/retention.py +119 -0
  37. positif_ai-0.1.0/core/routing.py +56 -0
  38. positif_ai-0.1.0/core/sandbox_manager.py +114 -0
  39. positif_ai-0.1.0/core/schema.sql +125 -0
  40. positif_ai-0.1.0/core/server.py +481 -0
  41. positif_ai-0.1.0/core/sse.py +45 -0
  42. positif_ai-0.1.0/core/stats.py +64 -0
  43. positif_ai-0.1.0/core/transcript.py +151 -0
  44. positif_ai-0.1.0/core/whois.py +93 -0
  45. positif_ai-0.1.0/positif_ai.egg-info/PKG-INFO +244 -0
  46. positif_ai-0.1.0/positif_ai.egg-info/SOURCES.txt +116 -0
  47. positif_ai-0.1.0/positif_ai.egg-info/dependency_links.txt +1 -0
  48. positif_ai-0.1.0/positif_ai.egg-info/entry_points.txt +2 -0
  49. positif_ai-0.1.0/positif_ai.egg-info/requires.txt +7 -0
  50. positif_ai-0.1.0/positif_ai.egg-info/top_level.txt +3 -0
  51. positif_ai-0.1.0/pyproject.toml +71 -0
  52. positif_ai-0.1.0/setup.cfg +4 -0
  53. positif_ai-0.1.0/switchboard_cli.py +45 -0
  54. positif_ai-0.1.0/tests/test_agent_registry.py +164 -0
  55. positif_ai-0.1.0/tests/test_audit_report.py +131 -0
  56. positif_ai-0.1.0/tests/test_boardroom.py +166 -0
  57. positif_ai-0.1.0/tests/test_db_ro.py +27 -0
  58. positif_ai-0.1.0/tests/test_dispatch.py +971 -0
  59. positif_ai-0.1.0/tests/test_doctor.py +154 -0
  60. positif_ai-0.1.0/tests/test_explain.py +276 -0
  61. positif_ai-0.1.0/tests/test_handoff.py +154 -0
  62. positif_ai-0.1.0/tests/test_idempotency.py +54 -0
  63. positif_ai-0.1.0/tests/test_init.py +164 -0
  64. positif_ai-0.1.0/tests/test_metrics.py +128 -0
  65. positif_ai-0.1.0/tests/test_planner.py +228 -0
  66. positif_ai-0.1.0/tests/test_processes.py +76 -0
  67. positif_ai-0.1.0/tests/test_provenance.py +146 -0
  68. positif_ai-0.1.0/tests/test_release_check.py +121 -0
  69. positif_ai-0.1.0/tests/test_release_readiness.py +175 -0
  70. positif_ai-0.1.0/tests/test_render_specs.py +246 -0
  71. positif_ai-0.1.0/tests/test_retention.py +208 -0
  72. positif_ai-0.1.0/tests/test_sandbox_manager.py +129 -0
  73. positif_ai-0.1.0/tests/test_security.py +208 -0
  74. positif_ai-0.1.0/tests/test_server.py +51 -0
  75. positif_ai-0.1.0/tests/test_smoke_dispatch.py +113 -0
  76. positif_ai-0.1.0/tests/test_sse.py +46 -0
  77. positif_ai-0.1.0/tests/test_transcript.py +238 -0
  78. positif_ai-0.1.0/tests/test_worker_template.py +84 -0
@@ -0,0 +1,109 @@
1
+ Positif is licensed under TWO licenses:
2
+
3
+ * The `sbd` CLI client (switchboard_cli.py) is MIT (full text in LICENSE-MIT).
4
+ It covers what a third party needs to talk to a Positif switchboard from
5
+ their own tooling. The file carries an `SPDX-License-Identifier: MIT` header.
6
+
7
+ * Everything else -- the switchboard engine (core/, cli/, deploy/, workers/) --
8
+ is licensed under the Business Source License 1.1 (BUSL-1.1), reproduced
9
+ below. It converts to the Change License (Apache-2.0) on the Change Date,
10
+ per-version.
11
+
12
+ This mirrors the Bourdon (sibling product) licensing structure. Free for solo
13
+ developers, research, and non-competing commercial use per the Additional Use
14
+ Grant below.
15
+
16
+ ===============================================================================
17
+
18
+ License text copyright (c) 2020 MariaDB Corporation Ab, All Rights Reserved.
19
+ "Business Source License" is a trademark of MariaDB Corporation Ab.
20
+
21
+ Parameters
22
+
23
+ Licensor: RADLAB LLC
24
+ Licensed Work: Positif Version 0.1.0 or later. The Licensed Work is (c) 2026
25
+ RADLAB LLC.
26
+ Additional Use Grant: You may make production use of the Licensed Work, provided
27
+ Your use does not include offering the Licensed Work to third
28
+ parties on a hosted or embedded basis in order to compete with
29
+ RADLAB LLC's paid version(s) of the Licensed Work. For purposes
30
+ of this license:
31
+
32
+ A "competitive offering" is a Product that is offered to third
33
+ parties on a paid basis, including through paid support
34
+ arrangements, that significantly overlaps with the capabilities
35
+ of RADLAB LLC's paid version(s) of the Licensed Work. If Your
36
+ Product is not a competitive offering when You first make it
37
+ generally available, it will not become a competitive offering
38
+ later due to RADLAB LLC releasing a new version of the Licensed
39
+ Work with additional capabilities. In addition, Products that
40
+ are not provided on a paid basis are not competitive.
41
+
42
+ "Product" means software that is offered to end users to manage
43
+ in their own environments or offered as a service on a hosted
44
+ basis.
45
+
46
+ "Embedded" means including the source code or executable code
47
+ from the Licensed Work in a competitive offering. "Embedded"
48
+ also means packaging the competitive offering in such a way
49
+ that the Licensed Work must be accessed or downloaded for the
50
+ competitive offering to operate.
51
+
52
+ Hosting or using the Licensed Work(s) for internal purposes
53
+ within an organization is not considered a competitive
54
+ offering. RADLAB LLC considers your organization to include all
55
+ of your affiliates under common control.
56
+
57
+ For binding interpretive guidance on using RADLAB LLC products
58
+ under the Business Source License, please see the LICENSE_FAQ
59
+ at https://github.com/getbourdon/bourdon/blob/main/LICENSE_FAQ.md
60
+ Change Date: Four years from the date the Licensed Work is published.
61
+ Change License: Apache License, Version 2.0
62
+
63
+ For information about alternative licensing arrangements for the Licensed Work,
64
+ please contact positif@radlab.online.
65
+
66
+ Notice
67
+
68
+ Business Source License 1.1
69
+
70
+ Terms
71
+
72
+ The Licensor hereby grants you the right to copy, modify, create derivative
73
+ works, redistribute, and make non-production use of the Licensed Work. The
74
+ Licensor may make an Additional Use Grant, above, permitting limited production use.
75
+
76
+ Effective on the Change Date, or the fourth anniversary of the first publicly
77
+ available distribution of a specific version of the Licensed Work under this
78
+ License, whichever comes first, the Licensor hereby grants you rights under
79
+ the terms of the Change License, and the rights granted in the paragraph
80
+ above terminate.
81
+
82
+ If your use of the Licensed Work does not comply with the requirements
83
+ currently in effect as described in this License, you must purchase a
84
+ commercial license from the Licensor, its affiliated entities, or authorized
85
+ resellers, or you must refrain from using the Licensed Work.
86
+
87
+ All copies of the original and modified Licensed Work, and derivative works
88
+ of the Licensed Work, are subject to this License. This License applies
89
+ separately for each version of the Licensed Work and the Change Date may vary
90
+ for each version of the Licensed Work released by Licensor.
91
+
92
+ You must conspicuously display this License on each original or modified copy
93
+ of the Licensed Work. If you receive the Licensed Work in original or
94
+ modified form from a third party, the terms and conditions set forth in this
95
+ License apply to your use of that work.
96
+
97
+ Any use of the Licensed Work in violation of this License will automatically
98
+ terminate your rights under this License for the current and all other
99
+ versions of the Licensed Work.
100
+
101
+ This License does not grant you any right in any trademark or logo of
102
+ Licensor or its affiliates (provided that you may use a trademark or logo of
103
+ Licensor as expressly required by this License).
104
+
105
+ TO THE EXTENT PERMITTED BY APPLICABLE LAW, THE LICENSED WORK IS PROVIDED ON
106
+ AN "AS IS" BASIS. LICENSOR HEREBY DISCLAIMS ALL WARRANTIES AND CONDITIONS,
107
+ EXPRESS OR IMPLIED, INCLUDING (WITHOUT LIMITATION) WARRANTIES OF
108
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, NON-INFRINGEMENT, AND
109
+ TITLE.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 RADLAB LLC
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,8 @@
1
+ Positif (Agent Switchboard)
2
+ Copyright (c) 2026 RADLAB LLC, a Wyoming limited liability company.
3
+
4
+ Licensing: MIT for the `sbd` CLI client (switchboard_cli.py, see LICENSE-MIT);
5
+ Business Source License 1.1 for the switchboard engine (see LICENSE), which
6
+ converts to Apache-2.0 four years after each version's publication.
7
+
8
+ https://positif.ai - Contact: positif@radlab.online
@@ -0,0 +1,244 @@
1
+ Metadata-Version: 2.4
2
+ Name: positif-ai
3
+ Version: 0.1.0
4
+ Summary: Positif — Agent Switchboard: security-first inter-agent control plane (sbd) for the RADLAB fleet
5
+ Author-email: Ryan Davis <ryandavispro1@gmail.com>
6
+ Maintainer: RADLAB LLC
7
+ License-Expression: BUSL-1.1 AND MIT
8
+ Project-URL: Homepage, https://positif.ai
9
+ Project-URL: Repository, https://gitlab.com/radlab1/positif/positif
10
+ Project-URL: Documentation, https://positif.ai/docs/
11
+ Keywords: agent,router,switchboard,mcp,a2a,acp,tailnet
12
+ Classifier: Development Status :: 2 - Pre-Alpha
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: Operating System :: POSIX :: Linux
15
+ Classifier: Programming Language :: Python :: 3
16
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
17
+ Requires-Python: >=3.10
18
+ Description-Content-Type: text/markdown
19
+ License-File: LICENSE
20
+ License-File: LICENSE-MIT
21
+ License-File: NOTICE
22
+ Requires-Dist: fastmcp>=3.0
23
+ Requires-Dist: httpx>=0.27
24
+ Provides-Extra: dev
25
+ Requires-Dist: pytest>=8.0; extra == "dev"
26
+ Requires-Dist: pytest-asyncio>=0.23; extra == "dev"
27
+ Requires-Dist: ruff>=0.4; extra == "dev"
28
+ Dynamic: license-file
29
+
30
+ # Positif — Agent Switchboard (`sbd`)
31
+
32
+ <!-- mcp-name: ai.positif/positif -->
33
+
34
+ > **Positif** (`positif.ai`) is the canonical product name. `sbd` is the CLI, and
35
+ > `switchboard` / `POSITIF_*` remain the stable **technical namespace** (package, env
36
+ > vars, `pos1.` token prefix, `/opt/positif-*` paths, the `positif.service`
37
+ > unit). Those identifiers stay put — renaming them is a separate, box-coordinated
38
+ > migration — but the product you're reading about is **Positif**.
39
+ >
40
+ > **Status:** `0.1.0` pre-alpha. This repository is release-preparation work,
41
+ > not a stable v1 release. Public distribution, deployment outside RADLAB, or
42
+ > release publication still requires human release authorization under RADLAB
43
+ > governance.
44
+
45
+ **The security-first agent control plane** — the governance + routing layer for a
46
+ multi-agent fleet. Not "another orchestrator": it authenticates the caller
47
+ (tailnet whois), enforces a metadata-only policy, dedupes, **routes** (explicit /
48
+ by capability), **brokers loop-guarded handoff** between agents, rate-limits, and
49
+ **audits every decision** — then gets out of the data path. It does not own memory
50
+ (that is **Bourdon**) and does not execute work (that is the agents); it governs
51
+ *who can reach whom, with what, and proves it happened.*
52
+
53
+ Sibling to **Bourdon** in one ecosystem — Bourdon = what the fleet *knows*,
54
+ Positif = who's *online* + what's *in flight*. They meet at exactly one MCP
55
+ boundary. (Positioning + productization path: `claude-brain SWITCHBOARD_PRODUCTIZATION.md`.)
56
+
57
+ Lives on the always-on `radlab-cloud` box (`/opt/positif-{src,venv,data}`),
58
+ mirroring Bourdon's layout. Port **7600** (Bourdon = 7500).
59
+
60
+ ## 0.1.0 Pre-Alpha: What's Built
61
+
62
+ `sbd` is a FastMCP server exposing one secure tool, `dispatch(target, payload)`,
63
+ over streamable-HTTP bound to the tailnet IP. The HTTP transport is deliberate:
64
+ it's what makes `whois`-gated auth real — the caller's peer IP comes from the
65
+ live request and tailscaled turns it into an authoritative node identity.
66
+
67
+ **Dispatch state machine** (`core/dispatch.py`):
68
+
69
+ 1. **whois** the caller's peer IP → node + tags + user (unknown ⇒ default-deny)
70
+ 2. **provenance** tag — `trusted | tainted` (caller may self-declare tainted)
71
+ 3. **target card** lookup (A2A Agent Card + reachability), unknown ⇒ deny
72
+ 4. **policy** check — static `(caller, target)` table, **metadata only, never the
73
+ payload**; `tainted` is forbidden from sensitive targets
74
+ (`write/spend/prod/privileged`)
75
+ 5. **idempotency** — dedup-hit returns the cached result, never re-dispatches
76
+ 6. **shadow classifier** — predicts + logs the `(intent → target)` decision but
77
+ **does not route** (the event log is the training set)
78
+ 7. **forward** — over the card's transport (`acp` → drives `hermes acp`; `http` →
79
+ POSTs to an HTTP worker, ready for Clyde / a `*-automations` worker)
80
+ 8. **log + cache** the decision
81
+
82
+ A per-caller **token-bucket rate limit** runs before forwarding (caps cost/DoS
83
+ from a runaway or compromised caller). Observability: the `health` MCP tool and
84
+ `sbd stats` aggregate the event log over a default 7-day window (per-target
85
+ counts, deny rate, latency p50/p95). Use `--all-time` only when an operator
86
+ intentionally needs the full retained dataset.
87
+
88
+ Routing is **explicit-target** today. The classifier is scaffolded and learning
89
+ from day one.
90
+
91
+ ### Reaching Hermes (ACP)
92
+
93
+ Hermes is delegated to over the Zed Agent Client Protocol (newline-delimited
94
+ JSON-RPC over stdio) — the Bourdon-aware path. `core/acp_client.py` spawns
95
+ `hermes acp --accept-hooks`, runs `initialize → session/new → session/prompt`,
96
+ accumulates `agent_message_chunk` text, auto-answers any `session/request_permission`
97
+ (else the turn deadlocks), and returns on `stopReason: end_turn`.
98
+
99
+ ## Layout
100
+
101
+ ```
102
+ core/ db schema.sql whois policy idempotency classifier cards acp_client dispatch server
103
+ default_data/ packaged policy + card seeds used by installed `sbd init`
104
+ cli/ main.py (sbd serve | init | whois | events | cards | stats | metrics | training | prune)
105
+ data/ editable mirror/override seeds for source-tree operators
106
+ deploy/ positif.service
107
+ scripts/ smoke_dispatch.py tests/ test_dispatch.py
108
+ ```
109
+
110
+ ## Operator Feature Notes
111
+
112
+ Draft feature notes live at:
113
+
114
+ - `docs/features/README.md`
115
+ - `docs/features/policy-simulator.md`
116
+ - `docs/features/dry-run-dispatch.md`
117
+ - `docs/features/agent-registry.md`
118
+ - `docs/features/audit-reporting.md`
119
+ - `docs/features/sandbox-manager.md`
120
+ - `docs/features/release-readiness.md`
121
+ - `docs/features/render-surfaces.md`
122
+
123
+ `sbd render` emits JSON Render flat specs for those operator surfaces. This is
124
+ the renderer-ready contract for operator clients, not a release authorization
125
+ path:
126
+
127
+ ```bash
128
+ sbd render command-center --db /opt/positif-data/positif.db
129
+ sbd render audit --db /opt/positif-data/positif.db --window-days 7
130
+ sbd render release --root /opt/positif-src
131
+ ```
132
+
133
+ An optional local Ink renderer lives in `tui/` for the same surfaces:
134
+
135
+ ```bash
136
+ npm --prefix tui ci
137
+ npm --prefix tui run build
138
+ node tui/dist/index.js --surface command-center --db /opt/positif-data/positif.db
139
+ node tui/dist/index.js --surface release --root /opt/positif-src --once
140
+ ```
141
+
142
+ ## Run
143
+
144
+ ```bash
145
+ # on the box
146
+ /opt/positif-venv/bin/python -m pip install -e /opt/positif-src
147
+ openssl rand -base64 32 > /opt/positif-data/handoff.secret
148
+ chown switchboard:switchboard /opt/positif-data/handoff.secret
149
+ chmod 600 /opt/positif-data/handoff.secret
150
+ sbd init --db /opt/positif-data/positif.db # schema + seed policy + cards
151
+ sbd serve --transport http --host 100.113.192.59 --port 7600 --db /opt/positif-data/positif.db
152
+ # in another shell (on the box, so whois resolves a real tailnet node):
153
+ /opt/positif-venv/bin/python scripts/smoke_dispatch.py
154
+ # add --yes-live only when you intentionally want to spawn the target agent:
155
+ /opt/positif-venv/bin/python scripts/smoke_dispatch.py --yes-live
156
+ ```
157
+
158
+ Wire a coding agent as an MCP client by pointing it at
159
+ `http://100.113.192.59:7600/mcp/`.
160
+
161
+ ## Audit retention
162
+
163
+ Positif bounds and redacts operator-facing metadata before storage or export.
164
+ `event_log.intent` and `event_log.error` are stored as short redacted strings, and
165
+ `sbd events` / `sbd training` re-apply the same bounds when reading older rows.
166
+ Stats, metrics, classifier accuracy, and training export default to the last 7
167
+ days; pass `--all-time` for an explicit full retained read.
168
+
169
+ Retention is manual, not automatic during dispatch:
170
+
171
+ ```bash
172
+ sbd prune --db /opt/positif-data/positif.db --events-days 30 --idempotency-hours 24 --batch-size 1000
173
+ ```
174
+
175
+ The default policy keeps audit events for 30 days and idempotency cache rows for
176
+ 24 hours. Prune deletes in bounded batches so operators can run it from cron or
177
+ systemd timers without putting cleanup work on the request path.
178
+
179
+ ## Security Stance (0.1.0 Pre-Alpha)
180
+
181
+ * Routes on **metadata, never payload**; **no token minting or forwarding** for
182
+ caller identity (Hermes uses its own key). Positif mints only short-lived
183
+ signed handoff bearers for trace/depth/path continuity.
184
+ * **Caller identity is asserted on the tailnet** — the peer IP must be in the
185
+ Tailscale CGNAT range (`100.64.0.0/10`) before whois is even consulted, so a
186
+ loopback/proxy/public peer can't be mistaken for a fleet node. Default-deny on
187
+ unknown caller / unknown target / no policy row (a targeted `deny` overrides a
188
+ broader `allow`).
189
+ * `tainted` (untrusted-origin) dispatches are denied from privileged targets at
190
+ ingress, and are additionally bounded to non-destructive tools.
191
+ * **Fully unprivileged: no root, no sudo.** `sbd` runs as the
192
+ `switchboard` user, and the Hermes it drives runs as that *same* unprivileged
193
+ user with its own private `HERMES_HOME` (`/opt/positif-data/hermes-home` —
194
+ config + Anthropic key, **no Slack tokens**). A compromise of `sbd` or its Hermes
195
+ yields only the `switchboard` user: it **cannot read `/root`, touch system files,
196
+ or escalate**. The gateway's separate root Hermes instance is untouched.
197
+ `deploy/preflight.sh` refuses to start unless the `HERMES_HOME` is present,
198
+ private, and switchboard-owned (and `hermes` is root-owned); an in-code argv
199
+ allowlist in `acp_client` is the second barrier on the spawn.
200
+ * **Per-dispatch tool bound + audit (allowlist, fail-closed).** Every tool the
201
+ target routes through the ACP permission gate is logged (`event_log.tool_audit`,
202
+ preserved even on error/timeout turns). `compute` (the **default** for Hermes;
203
+ forced for `tainted`; can be narrowed-to via `max_capability`) is an *allowlist*
204
+ — only `read/search/fetch/think` pass; `edit/delete/move/execute` and any
205
+ absent/unknown kind are **denied**. **Verified:** under `compute`, Hermes was
206
+ denied a file edit *and* a dangerous `rm` at the gate, while Bourdon MCP tools +
207
+ Q&A still work. Truly benign commands (e.g. `whoami`) auto-run in Hermes and skip
208
+ the gate — but since Hermes now runs **unprivileged**, even those can't read
209
+ `/root`, touch system files, or escalate, so the residual is contained.
210
+ * **Security selectors fail closed** — `provenance` and `max_capability` normalize
211
+ and resolve any typo/casing to the *more restrictive* branch, never the privileged
212
+ one. A cap can only narrow.
213
+ * **Idempotency keys are namespaced** by caller+target — a caller-supplied key
214
+ can't read another caller's or another target's cached result.
215
+ * **Spawn cleanup is process-group aware.** ACP and CLI agent launches create a
216
+ subprocess group/session where the platform supports it. On timeout,
217
+ Positif terminates the group, then kills it if it does not exit; non-POSIX
218
+ platforms fall back to best-effort direct child cleanup.
219
+ * **CLI review runners must not hide write capability.** The Codex card runs
220
+ `codex exec --skip-git-repo-check` without
221
+ `--dangerously-bypass-approvals-and-sandbox`, is marked `compute`, and is a
222
+ review/reasoning node rather than an autonomous writer. A future write-capable
223
+ Codex runner must be a separate, explicitly named card.
224
+ * **Per-agent Unix users are a deploy design, not a hidden runtime shortcut.**
225
+ Future `sbd-hermes`, `sbd-coder`, and `sbd-codex` execution must go through
226
+ exact root-owned wrapper commands before code enables it. Broad sudo is not
227
+ acceptable. Until that runner exists, preflight enforces private
228
+ switchboard-owned homes/workspaces for the seeded agents.
229
+ * **Signed handoff metadata.** Brokered handoff trace IDs, depths, and paths are
230
+ carried as `Authorization: Bearer pos1.<payload>.<hmac>` tokens signed with
231
+ `POSITIF_HANDOFF_SECRET_FILE` (default deploy path:
232
+ `/opt/positif-data/handoff.secret`). Unsigned `X-SBD-*` headers are ignored
233
+ by default; set `POSITIF_ALLOW_LEGACY_HANDOFF=1` only for a temporary compatibility
234
+ window. Handoff tokens authenticate metadata only; payload bodies are never used
235
+ to derive or validate handoff identity.
236
+
237
+ ## Roadmap (Not Yet Built)
238
+
239
+ Litestream/B2 durability, presence/heartbeat, capability-match + semantic routing
240
+ as primary, NATS, brokered agent↔agent handoff, a second `sbd` to kill the SPOF.
241
+ See `claude-brain/PROJECTS/RADLAB-CLOUD/SWITCHBOARD.md`.
242
+
243
+ ---
244
+ RADLAB LLC, a Wyoming limited liability company · BUSL-1.1
@@ -0,0 +1,215 @@
1
+ # Positif — Agent Switchboard (`sbd`)
2
+
3
+ <!-- mcp-name: ai.positif/positif -->
4
+
5
+ > **Positif** (`positif.ai`) is the canonical product name. `sbd` is the CLI, and
6
+ > `switchboard` / `POSITIF_*` remain the stable **technical namespace** (package, env
7
+ > vars, `pos1.` token prefix, `/opt/positif-*` paths, the `positif.service`
8
+ > unit). Those identifiers stay put — renaming them is a separate, box-coordinated
9
+ > migration — but the product you're reading about is **Positif**.
10
+ >
11
+ > **Status:** `0.1.0` pre-alpha. This repository is release-preparation work,
12
+ > not a stable v1 release. Public distribution, deployment outside RADLAB, or
13
+ > release publication still requires human release authorization under RADLAB
14
+ > governance.
15
+
16
+ **The security-first agent control plane** — the governance + routing layer for a
17
+ multi-agent fleet. Not "another orchestrator": it authenticates the caller
18
+ (tailnet whois), enforces a metadata-only policy, dedupes, **routes** (explicit /
19
+ by capability), **brokers loop-guarded handoff** between agents, rate-limits, and
20
+ **audits every decision** — then gets out of the data path. It does not own memory
21
+ (that is **Bourdon**) and does not execute work (that is the agents); it governs
22
+ *who can reach whom, with what, and proves it happened.*
23
+
24
+ Sibling to **Bourdon** in one ecosystem — Bourdon = what the fleet *knows*,
25
+ Positif = who's *online* + what's *in flight*. They meet at exactly one MCP
26
+ boundary. (Positioning + productization path: `claude-brain SWITCHBOARD_PRODUCTIZATION.md`.)
27
+
28
+ Lives on the always-on `radlab-cloud` box (`/opt/positif-{src,venv,data}`),
29
+ mirroring Bourdon's layout. Port **7600** (Bourdon = 7500).
30
+
31
+ ## 0.1.0 Pre-Alpha: What's Built
32
+
33
+ `sbd` is a FastMCP server exposing one secure tool, `dispatch(target, payload)`,
34
+ over streamable-HTTP bound to the tailnet IP. The HTTP transport is deliberate:
35
+ it's what makes `whois`-gated auth real — the caller's peer IP comes from the
36
+ live request and tailscaled turns it into an authoritative node identity.
37
+
38
+ **Dispatch state machine** (`core/dispatch.py`):
39
+
40
+ 1. **whois** the caller's peer IP → node + tags + user (unknown ⇒ default-deny)
41
+ 2. **provenance** tag — `trusted | tainted` (caller may self-declare tainted)
42
+ 3. **target card** lookup (A2A Agent Card + reachability), unknown ⇒ deny
43
+ 4. **policy** check — static `(caller, target)` table, **metadata only, never the
44
+ payload**; `tainted` is forbidden from sensitive targets
45
+ (`write/spend/prod/privileged`)
46
+ 5. **idempotency** — dedup-hit returns the cached result, never re-dispatches
47
+ 6. **shadow classifier** — predicts + logs the `(intent → target)` decision but
48
+ **does not route** (the event log is the training set)
49
+ 7. **forward** — over the card's transport (`acp` → drives `hermes acp`; `http` →
50
+ POSTs to an HTTP worker, ready for Clyde / a `*-automations` worker)
51
+ 8. **log + cache** the decision
52
+
53
+ A per-caller **token-bucket rate limit** runs before forwarding (caps cost/DoS
54
+ from a runaway or compromised caller). Observability: the `health` MCP tool and
55
+ `sbd stats` aggregate the event log over a default 7-day window (per-target
56
+ counts, deny rate, latency p50/p95). Use `--all-time` only when an operator
57
+ intentionally needs the full retained dataset.
58
+
59
+ Routing is **explicit-target** today. The classifier is scaffolded and learning
60
+ from day one.
61
+
62
+ ### Reaching Hermes (ACP)
63
+
64
+ Hermes is delegated to over the Zed Agent Client Protocol (newline-delimited
65
+ JSON-RPC over stdio) — the Bourdon-aware path. `core/acp_client.py` spawns
66
+ `hermes acp --accept-hooks`, runs `initialize → session/new → session/prompt`,
67
+ accumulates `agent_message_chunk` text, auto-answers any `session/request_permission`
68
+ (else the turn deadlocks), and returns on `stopReason: end_turn`.
69
+
70
+ ## Layout
71
+
72
+ ```
73
+ core/ db schema.sql whois policy idempotency classifier cards acp_client dispatch server
74
+ default_data/ packaged policy + card seeds used by installed `sbd init`
75
+ cli/ main.py (sbd serve | init | whois | events | cards | stats | metrics | training | prune)
76
+ data/ editable mirror/override seeds for source-tree operators
77
+ deploy/ positif.service
78
+ scripts/ smoke_dispatch.py tests/ test_dispatch.py
79
+ ```
80
+
81
+ ## Operator Feature Notes
82
+
83
+ Draft feature notes live at:
84
+
85
+ - `docs/features/README.md`
86
+ - `docs/features/policy-simulator.md`
87
+ - `docs/features/dry-run-dispatch.md`
88
+ - `docs/features/agent-registry.md`
89
+ - `docs/features/audit-reporting.md`
90
+ - `docs/features/sandbox-manager.md`
91
+ - `docs/features/release-readiness.md`
92
+ - `docs/features/render-surfaces.md`
93
+
94
+ `sbd render` emits JSON Render flat specs for those operator surfaces. This is
95
+ the renderer-ready contract for operator clients, not a release authorization
96
+ path:
97
+
98
+ ```bash
99
+ sbd render command-center --db /opt/positif-data/positif.db
100
+ sbd render audit --db /opt/positif-data/positif.db --window-days 7
101
+ sbd render release --root /opt/positif-src
102
+ ```
103
+
104
+ An optional local Ink renderer lives in `tui/` for the same surfaces:
105
+
106
+ ```bash
107
+ npm --prefix tui ci
108
+ npm --prefix tui run build
109
+ node tui/dist/index.js --surface command-center --db /opt/positif-data/positif.db
110
+ node tui/dist/index.js --surface release --root /opt/positif-src --once
111
+ ```
112
+
113
+ ## Run
114
+
115
+ ```bash
116
+ # on the box
117
+ /opt/positif-venv/bin/python -m pip install -e /opt/positif-src
118
+ openssl rand -base64 32 > /opt/positif-data/handoff.secret
119
+ chown switchboard:switchboard /opt/positif-data/handoff.secret
120
+ chmod 600 /opt/positif-data/handoff.secret
121
+ sbd init --db /opt/positif-data/positif.db # schema + seed policy + cards
122
+ sbd serve --transport http --host 100.113.192.59 --port 7600 --db /opt/positif-data/positif.db
123
+ # in another shell (on the box, so whois resolves a real tailnet node):
124
+ /opt/positif-venv/bin/python scripts/smoke_dispatch.py
125
+ # add --yes-live only when you intentionally want to spawn the target agent:
126
+ /opt/positif-venv/bin/python scripts/smoke_dispatch.py --yes-live
127
+ ```
128
+
129
+ Wire a coding agent as an MCP client by pointing it at
130
+ `http://100.113.192.59:7600/mcp/`.
131
+
132
+ ## Audit retention
133
+
134
+ Positif bounds and redacts operator-facing metadata before storage or export.
135
+ `event_log.intent` and `event_log.error` are stored as short redacted strings, and
136
+ `sbd events` / `sbd training` re-apply the same bounds when reading older rows.
137
+ Stats, metrics, classifier accuracy, and training export default to the last 7
138
+ days; pass `--all-time` for an explicit full retained read.
139
+
140
+ Retention is manual, not automatic during dispatch:
141
+
142
+ ```bash
143
+ sbd prune --db /opt/positif-data/positif.db --events-days 30 --idempotency-hours 24 --batch-size 1000
144
+ ```
145
+
146
+ The default policy keeps audit events for 30 days and idempotency cache rows for
147
+ 24 hours. Prune deletes in bounded batches so operators can run it from cron or
148
+ systemd timers without putting cleanup work on the request path.
149
+
150
+ ## Security Stance (0.1.0 Pre-Alpha)
151
+
152
+ * Routes on **metadata, never payload**; **no token minting or forwarding** for
153
+ caller identity (Hermes uses its own key). Positif mints only short-lived
154
+ signed handoff bearers for trace/depth/path continuity.
155
+ * **Caller identity is asserted on the tailnet** — the peer IP must be in the
156
+ Tailscale CGNAT range (`100.64.0.0/10`) before whois is even consulted, so a
157
+ loopback/proxy/public peer can't be mistaken for a fleet node. Default-deny on
158
+ unknown caller / unknown target / no policy row (a targeted `deny` overrides a
159
+ broader `allow`).
160
+ * `tainted` (untrusted-origin) dispatches are denied from privileged targets at
161
+ ingress, and are additionally bounded to non-destructive tools.
162
+ * **Fully unprivileged: no root, no sudo.** `sbd` runs as the
163
+ `switchboard` user, and the Hermes it drives runs as that *same* unprivileged
164
+ user with its own private `HERMES_HOME` (`/opt/positif-data/hermes-home` —
165
+ config + Anthropic key, **no Slack tokens**). A compromise of `sbd` or its Hermes
166
+ yields only the `switchboard` user: it **cannot read `/root`, touch system files,
167
+ or escalate**. The gateway's separate root Hermes instance is untouched.
168
+ `deploy/preflight.sh` refuses to start unless the `HERMES_HOME` is present,
169
+ private, and switchboard-owned (and `hermes` is root-owned); an in-code argv
170
+ allowlist in `acp_client` is the second barrier on the spawn.
171
+ * **Per-dispatch tool bound + audit (allowlist, fail-closed).** Every tool the
172
+ target routes through the ACP permission gate is logged (`event_log.tool_audit`,
173
+ preserved even on error/timeout turns). `compute` (the **default** for Hermes;
174
+ forced for `tainted`; can be narrowed-to via `max_capability`) is an *allowlist*
175
+ — only `read/search/fetch/think` pass; `edit/delete/move/execute` and any
176
+ absent/unknown kind are **denied**. **Verified:** under `compute`, Hermes was
177
+ denied a file edit *and* a dangerous `rm` at the gate, while Bourdon MCP tools +
178
+ Q&A still work. Truly benign commands (e.g. `whoami`) auto-run in Hermes and skip
179
+ the gate — but since Hermes now runs **unprivileged**, even those can't read
180
+ `/root`, touch system files, or escalate, so the residual is contained.
181
+ * **Security selectors fail closed** — `provenance` and `max_capability` normalize
182
+ and resolve any typo/casing to the *more restrictive* branch, never the privileged
183
+ one. A cap can only narrow.
184
+ * **Idempotency keys are namespaced** by caller+target — a caller-supplied key
185
+ can't read another caller's or another target's cached result.
186
+ * **Spawn cleanup is process-group aware.** ACP and CLI agent launches create a
187
+ subprocess group/session where the platform supports it. On timeout,
188
+ Positif terminates the group, then kills it if it does not exit; non-POSIX
189
+ platforms fall back to best-effort direct child cleanup.
190
+ * **CLI review runners must not hide write capability.** The Codex card runs
191
+ `codex exec --skip-git-repo-check` without
192
+ `--dangerously-bypass-approvals-and-sandbox`, is marked `compute`, and is a
193
+ review/reasoning node rather than an autonomous writer. A future write-capable
194
+ Codex runner must be a separate, explicitly named card.
195
+ * **Per-agent Unix users are a deploy design, not a hidden runtime shortcut.**
196
+ Future `sbd-hermes`, `sbd-coder`, and `sbd-codex` execution must go through
197
+ exact root-owned wrapper commands before code enables it. Broad sudo is not
198
+ acceptable. Until that runner exists, preflight enforces private
199
+ switchboard-owned homes/workspaces for the seeded agents.
200
+ * **Signed handoff metadata.** Brokered handoff trace IDs, depths, and paths are
201
+ carried as `Authorization: Bearer pos1.<payload>.<hmac>` tokens signed with
202
+ `POSITIF_HANDOFF_SECRET_FILE` (default deploy path:
203
+ `/opt/positif-data/handoff.secret`). Unsigned `X-SBD-*` headers are ignored
204
+ by default; set `POSITIF_ALLOW_LEGACY_HANDOFF=1` only for a temporary compatibility
205
+ window. Handoff tokens authenticate metadata only; payload bodies are never used
206
+ to derive or validate handoff identity.
207
+
208
+ ## Roadmap (Not Yet Built)
209
+
210
+ Litestream/B2 durability, presence/heartbeat, capability-match + semantic routing
211
+ as primary, NATS, brokered agent↔agent handoff, a second `sbd` to kill the SPOF.
212
+ See `claude-brain/PROJECTS/RADLAB-CLOUD/SWITCHBOARD.md`.
213
+
214
+ ---
215
+ RADLAB LLC, a Wyoming limited liability company · BUSL-1.1
@@ -0,0 +1 @@
1
+ """sbd command-line interface."""