Skip to content

Commit 52bb7eb

Browse files
committed
docs(architecture): how VisionClaw services emit ACSP governance panels
States plainly that src/services/nostr_bridge.rs only bridges kind-30001 bead provenance into kind-9 NIP-29 group messages and does NOT publish panels. Documents what a panel-emitting integration must do: agent_registry registration, serde-exact 31400-31405 event construction (snake_case content, kebab-case enums, broker tags on 31402), direct publish to FORUM_RELAY_URL via nostr_sdk, 31403 decision subscription, and panel lifecycle. Cross-references the canonical agentbox reference. Co-Authored-By: jjohare <github@thedreamlab.uk>
1 parent 2783220 commit 52bb7eb

1 file changed

Lines changed: 143 additions & 0 deletions

File tree

Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
1+
# Emitting ACSP Governance Panels from VisionClaw
2+
3+
How a VisionClaw-side agent or service publishes **Agent Control Surface
4+
Protocol (ACSP)** events — Nostr kinds **31400-31405** — so that interactive
5+
control panels render on the DreamLab forum governance page
6+
(`/community/governance`).
7+
8+
The canonical schema reference (exact JSON per kind, field_type catalogue,
9+
dos/don'ts, troubleshooting) is maintained in agentbox:
10+
[`docs/developer/agent-control-surface-panels.md`](https://github.com/DreamLab-AI/agentbox/blob/main/docs/developer/agent-control-surface-panels.md)
11+
(mirrored locally under `docs/agentbox-docs/`). This page covers only the
12+
VisionClaw-specific picture.
13+
14+
## Current state: VisionClaw does NOT publish panels
15+
16+
`src/services/nostr_bridge.rs` is a **bead-provenance bridge**, not a panel
17+
producer. It:
18+
19+
- subscribes to the source relay (`NOSTR_RELAY_URL`, default
20+
`ws://localhost:7000`) for **kind 30001** bead provenance events,
21+
- re-signs them with the bridge keypair (`VISIONCLAW_NOSTR_PRIVKEY`) and
22+
republishes them to the DreamLab forum relay (`FORUM_RELAY_URL`) as
23+
**kind 9** NIP-29 group messages, preserving the original event id in a
24+
`source_event` tag.
25+
26+
No ACSP kind (31400-31405) is produced anywhere in this repo today. If you
27+
need a VisionClaw presence on the governance page, you are building a new
28+
integration — the rest of this page tells you what it must do.
29+
30+
## The pipeline you are joining
31+
32+
| Hop | Component | Behaviour |
33+
|-----|-----------|-----------|
34+
| Producer | (yours — new) | Builds + signs 31400-31405 events |
35+
| Relay | nostr-rust-forum `nostr-bbs-relay-worker` | Accepts the agent kinds **only from pubkeys in its `agent_registry` D1 table** (`active = 1`); rejects others with `OK false "blocked: pubkey not in agent registry"`. Kind 31403 (human responses) is admin-only — never publish it. 31402 events are projected into the `broker_cases` governance inbox |
36+
| Consumer | nostr-rust-forum `nostr-bbs-forum-client` (`panel_registry` + `GovernancePage`) | Strict-serde parses content and renders panels/action rows at `/governance` |
37+
| Website | dreamlab-ai-website | Serves the forum SPA at `/community/`, so panels appear at `/community/governance` |
38+
39+
The reference producer implementation is agentbox's
40+
`management-api/lib/agent-control-surface.js`; its jest contract suite
41+
(`tests/sovereign/agent-control-surface.test.js`) locks the exact wire
42+
shapes. A Rust producer must match the consumer serde structs in
43+
`nostr-rust-forum/crates/nostr-bbs-core/src/governance.rs` byte-for-byte
44+
semantics.
45+
46+
## What a panel-emitting VisionClaw integration must do
47+
48+
1. **Get the pubkey registered.** Derive the 64-char hex x-only pubkey from
49+
`VISIONCLAW_NOSTR_PRIVKEY` (or mint a dedicated panel keypair — cleaner
50+
for rate-limiting and revocation) and have a relay admin register it:
51+
`POST /api/governance/agents/register` (NIP-98 admin-gated) with
52+
`{ "pubkey": "<64 hex>", "name": "visionclaw-governance", "description": "...", "rate_limit_per_min": 60 }`.
53+
Identity is `did:nostr:<hex-pubkey>` — the same mesh identity used by
54+
bead provenance; registration + the Schnorr signature is the entire
55+
authorisation.
56+
57+
2. **Build serde-exact events.** Every event is NIP-33
58+
parameterised-replaceable with a non-empty `["d", panelId]` tag.
59+
Content keys are **snake_case** (`field_type`, `refresh_secs`,
60+
`context_url`); enum values are **kebab-case** (`action-inbox`,
61+
`inbox-table`, `primary`, …). A kind-31400 PanelDefinition content:
62+
63+
```json
64+
{
65+
"title": "VisionClaw Graph Health",
66+
"description": "Physics convergence and sync status",
67+
"version": "1.0.0",
68+
"schema": "status-board",
69+
"fields": [
70+
{ "name": "iteration", "field_type": "int", "label": "Physics iteration" },
71+
{ "name": "last_sync", "field_type": "timestamp", "label": "Last GitHub sync" }
72+
],
73+
"actions": [
74+
{ "id": "force-resync", "label": "Force resync", "style": "destructive" }
75+
],
76+
"layout": "card-grid",
77+
"capabilities": ["filter"],
78+
"refresh_secs": 30
79+
}
80+
```
81+
82+
Unknown enum values or camelCase keys fail the consumer parse *silently*
83+
(the relay still says OK). Domains: schema ∈ {`action-inbox`,
84+
`dashboard`, `config-form`, `status-board`, `chat-bridge`}; layout ∈
85+
{`inbox-table`, `kanban`, `card-grid`, `split-detail`}; field_type ∈
86+
{`string`, `int`, `float`, `bool`, `json`, `enum`, `timestamp`}; style ∈
87+
{`primary`, `secondary`, `destructive`}.
88+
89+
3. **Put ActionRequest (31402) metadata in TAGS, not content.** The relay's
90+
broker-case projection reads tags only: `["priority", "high"]`
91+
(`critical|high|medium|low`), plus optional `["category", ...]`,
92+
`["subject-kind", ...]`, `["subject-id", ...]`, `["title", ...]`.
93+
Content is just
94+
`{"fields": {...}, "reasoning": "...", "context_url": "..."}` with
95+
explicit `null` for absent optionals. Use the case's `d` tag as the
96+
broker case id and a VisionClaw URN (minted via `src/uri/`, e.g.
97+
`urn:visionclaw:execution:<sha256-12>`) as `subject-id`.
98+
99+
4. **Sign and publish directly to the forum relay.** Unlike bead provenance,
100+
panels are NOT bridged through kind-9 group messages — publish the
101+
governance kinds as-is to `FORUM_RELAY_URL`. With `nostr_sdk` (already a
102+
dependency of `nostr_bridge.rs`): build the event with
103+
`EventBuilder::new(Kind::from(31400), content).tags([Tag::identifier(panel_id)])`,
104+
sign with the registered `Keys`, send `["EVENT", ...]` over the relay
105+
WebSocket. Maintain the connection as a long-running task like
106+
`NostrBridge::run()` does — reconnect-with-backoff, not
107+
connect-per-publish.
108+
109+
5. **Listen for human decisions.** Subscribe to kind **31403** filtered on
110+
your case `d` tags. Content is
111+
`{"action": "approve" | "reject" | ..., "reasoning": "..."}` with the
112+
request's event id in an `e` tag. The relay guarantees the responder is
113+
an admin.
114+
115+
6. **Maintain the panel lifecycle.** 31401 = full JSON-object snapshot
116+
(replaceable per `d`); 31404 = shallow-merged top-level diff; 31405 =
117+
retire (empty content). Retire a given `panelId` at most once — core
118+
treats repeated 31405 `d` tags as append-only audit replays. Re-publish
119+
the 31400 definition to resurrect a panel. Keep events under the relay's
120+
64 KiB content / 2000 tag / 1024 B-per-tag-value limits.
121+
122+
## Where to put the code
123+
124+
Follow the existing pattern: a `src/services/` module spawned from startup
125+
as a background task (like `NostrBridge`), gated by env
126+
(`FORUM_RELAY_URL` + signing key present). Do not extend
127+
`nostr_bridge.rs`'s forwarding loop to emit panels — provenance forwarding
128+
and panel production have different keys, kinds, and failure semantics.
129+
Reuse the BC20 anti-corruption layer conventions for any URN that crosses
130+
the federation boundary (`urn:visionclaw:*` stays VisionClaw-side;
131+
`subject-id` is an opaque string to the relay).
132+
133+
## Quick troubleshooting
134+
135+
| Symptom | Cause / fix |
136+
|---------|-------------|
137+
| `OK false "blocked: pubkey not in agent registry"` | The signing pubkey is not registered (or revoked) — step 1 |
138+
| `OK false "blocked: admin-only governance action response"` | You published 31403; don't — subscribe to it instead |
139+
| Relay OK but no panel on `/community/governance` | Content failed strict serde: snake_case keys, kebab-case enums, all required keys (`title`, `description`, `schema`, `fields`, `actions`, `layout`) |
140+
| Action renders with priority `medium` | Priority must be a tag, not a content key |
141+
| Panel vanished | Same `(kind, pubkey, d)` replaced it, or a 31405 retired it |
142+
143+
Full table: agentbox reference §13.

0 commit comments

Comments
 (0)