@synap-core/cli 1.7.0 → 1.8.0

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 (115) hide show
  1. package/dist/commands/agent-run.d.ts +47 -0
  2. package/dist/commands/agent-run.js +302 -0
  3. package/dist/commands/agent-run.js.map +1 -0
  4. package/dist/commands/artifact.d.ts +26 -0
  5. package/dist/commands/artifact.js +76 -0
  6. package/dist/commands/artifact.js.map +1 -0
  7. package/dist/commands/automation.d.ts +47 -0
  8. package/dist/commands/automation.js +600 -0
  9. package/dist/commands/automation.js.map +1 -0
  10. package/dist/commands/cell.d.ts +55 -0
  11. package/dist/commands/cell.js +277 -0
  12. package/dist/commands/cell.js.map +1 -0
  13. package/dist/commands/connect.js +41 -7
  14. package/dist/commands/connect.js.map +1 -1
  15. package/dist/commands/connections.js +0 -16
  16. package/dist/commands/connections.js.map +1 -1
  17. package/dist/commands/connectors.d.ts +31 -0
  18. package/dist/commands/connectors.js +276 -0
  19. package/dist/commands/connectors.js.map +1 -0
  20. package/dist/commands/context.d.ts +22 -0
  21. package/dist/commands/context.js +151 -0
  22. package/dist/commands/context.js.map +1 -0
  23. package/dist/commands/data-extra.d.ts +13 -0
  24. package/dist/commands/data-extra.js +161 -0
  25. package/dist/commands/data-extra.js.map +1 -0
  26. package/dist/commands/data.d.ts +13 -9
  27. package/dist/commands/data.js +213 -109
  28. package/dist/commands/data.js.map +1 -1
  29. package/dist/commands/doc.d.ts +39 -0
  30. package/dist/commands/doc.js +130 -0
  31. package/dist/commands/doc.js.map +1 -0
  32. package/dist/commands/events.d.ts +26 -0
  33. package/dist/commands/events.js +68 -0
  34. package/dist/commands/events.js.map +1 -0
  35. package/dist/commands/explain.d.ts +21 -0
  36. package/dist/commands/explain.js +248 -0
  37. package/dist/commands/explain.js.map +1 -0
  38. package/dist/commands/graph.d.ts +18 -0
  39. package/dist/commands/graph.js +73 -0
  40. package/dist/commands/graph.js.map +1 -0
  41. package/dist/commands/home.d.ts +12 -0
  42. package/dist/commands/home.js +54 -0
  43. package/dist/commands/home.js.map +1 -0
  44. package/dist/commands/init.d.ts +0 -2
  45. package/dist/commands/init.js +62 -102
  46. package/dist/commands/init.js.map +1 -1
  47. package/dist/commands/keys.d.ts +9 -0
  48. package/dist/commands/keys.js +79 -0
  49. package/dist/commands/keys.js.map +1 -0
  50. package/dist/commands/knowledge.d.ts +32 -18
  51. package/dist/commands/knowledge.js +301 -55
  52. package/dist/commands/knowledge.js.map +1 -1
  53. package/dist/commands/observe.d.ts +9 -0
  54. package/dist/commands/observe.js +90 -0
  55. package/dist/commands/observe.js.map +1 -0
  56. package/dist/commands/open.d.ts +15 -0
  57. package/dist/commands/open.js +44 -0
  58. package/dist/commands/open.js.map +1 -0
  59. package/dist/commands/pods.d.ts +5 -0
  60. package/dist/commands/pods.js +85 -37
  61. package/dist/commands/pods.js.map +1 -1
  62. package/dist/commands/proposals-actions.d.ts +7 -0
  63. package/dist/commands/proposals-actions.js +40 -0
  64. package/dist/commands/proposals-actions.js.map +1 -0
  65. package/dist/commands/providers.d.ts +38 -0
  66. package/dist/commands/providers.js +170 -0
  67. package/dist/commands/providers.js.map +1 -0
  68. package/dist/commands/sessions.d.ts +31 -0
  69. package/dist/commands/sessions.js +224 -0
  70. package/dist/commands/sessions.js.map +1 -0
  71. package/dist/commands/skills-commands.d.ts +26 -0
  72. package/dist/commands/skills-commands.js +186 -0
  73. package/dist/commands/skills-commands.js.map +1 -0
  74. package/dist/commands/skills.d.ts +26 -0
  75. package/dist/commands/skills.js +195 -0
  76. package/dist/commands/skills.js.map +1 -0
  77. package/dist/commands/status.js +68 -30
  78. package/dist/commands/status.js.map +1 -1
  79. package/dist/commands/subscribe.d.ts +19 -0
  80. package/dist/commands/subscribe.js +91 -0
  81. package/dist/commands/subscribe.js.map +1 -0
  82. package/dist/commands/view.d.ts +57 -0
  83. package/dist/commands/view.js +194 -0
  84. package/dist/commands/view.js.map +1 -0
  85. package/dist/index.d.ts +3 -4
  86. package/dist/index.js +812 -206
  87. package/dist/index.js.map +1 -1
  88. package/dist/lib/agent-templates.d.ts +25 -0
  89. package/dist/lib/agent-templates.js +148 -0
  90. package/dist/lib/agent-templates.js.map +1 -0
  91. package/dist/lib/agents-config.d.ts +4 -1
  92. package/dist/lib/agents-config.js +19 -2
  93. package/dist/lib/agents-config.js.map +1 -1
  94. package/dist/lib/capture-lane.d.ts +49 -0
  95. package/dist/lib/capture-lane.js +79 -0
  96. package/dist/lib/capture-lane.js.map +1 -0
  97. package/dist/lib/hub-client.d.ts +6 -0
  98. package/dist/lib/hub-client.js +31 -3
  99. package/dist/lib/hub-client.js.map +1 -1
  100. package/dist/lib/pod.d.ts +14 -1
  101. package/dist/lib/pod.js +24 -2
  102. package/dist/lib/pod.js.map +1 -1
  103. package/dist/lib/skills-installer.d.ts +1 -1
  104. package/dist/lib/skills-installer.js +1 -1
  105. package/dist/lib/skills-installer.js.map +1 -1
  106. package/dist/lib/targets.d.ts +17 -1
  107. package/dist/lib/targets.js +713 -63
  108. package/dist/lib/targets.js.map +1 -1
  109. package/package.json +2 -1
  110. package/skills/synap/SKILL.md +357 -82
  111. package/skills/synap-ui/SKILL.md +12 -9
  112. package/dist/lib/templates.d.ts +0 -11
  113. package/dist/lib/templates.js +0 -13
  114. package/dist/lib/templates.js.map +0 -1
  115. package/dist/templates/agent-os.json +0 -3090
@@ -37,17 +37,43 @@ Your job is to turn unstructured input into a **connected** knowledge graph. Iso
37
37
 
38
38
  ## Mental model
39
39
 
40
- Synap is a typed knowledge graph. Six layers you need:
40
+ Synap is a typed knowledge graph. **Reading is one verb (`synap ask`) — it routes for you.** Writing is where you must pick the right lane: the destination is decided by the **KIND** of knowledge, not by whichever workspace happens to be active.
41
+
42
+ ### Where to write what — the three lanes (decide by KIND)
43
+
44
+ Ask yourself: _who does this knowledge serve?_ **There is no private AI scratchpad** — structuring knowledge into a real lane IS your job. Never write a `note` (that's the human's raw inbox); always `capture` into a lane.
45
+
46
+ | If it… | Lane | Where it goes | Governance |
47
+ | ------------------------------------------------------------------------------------------------------------------------------- | -------------------- | ---------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------- |
48
+ | **is about the CURRENT WORK** — domain know-how for the project/task you're on (incl. a domain-specific gotcha/lesson/decision) | **Work** _(default)_ | a `knowledge` entity in the **active workspace** (`synap capture --type …`) | proposal-gated (it's the user's real data; the workspace IS the domain — Builder ≠ marketing) |
49
+ | **is GLOBAL truth** — a best-practice / runbook / how-to that holds across ALL projects | **Global** | pod-wide procedural `knowledge_keys` (`synap capture --global --type … [--key ns:slug]`) | reviewed for shared truth |
50
+ | **is about the USER** — how they work/talk/decide, their preferences, their life | **User** | pod-wide `user_observation` (`synap observe write` / `record_observation` tool) | inferences are **proposed** (you review); explicit "I always X" auto-saves — never model the user silently |
51
+
52
+ > **Why this matters:** writing to the wrong lane degrades the graph. A gotcha you learned about the **current project** is **Work** (the active workspace — its domain). A best-practice that holds **everywhere** is **Global** (`--global`, pod-wide). A fact about **how the user works** is **User** (pod-wide, inferences proposed). `synap capture` echoes which lane + governance it used; check it.
53
+
54
+ > **Substrate names (tables under the hood):** _semantic_ = `entities` (the `knowledge` profile, workspace-scoped = domain separation), _episodic_ = `knowledge_facts`, _procedural_ = `knowledge_keys` (pod-wide runbooks). `ask` queries across them so you never pick on read.
55
+
56
+ ### Data layers — the graph itself
41
57
 
42
58
  | Layer | What it is | When to use |
43
59
  | ------------- | ---------------------------------------------- | -------------------------------------------------------- |
44
60
  | **Entities** | Typed structured nodes (task, person, …) | Anything worth filtering, sorting, or linking |
45
61
  | **Relations** | Typed edges between entities | Making the graph traversable |
46
62
  | **Documents** | Long-form markdown attached to an entity | Meeting notes, research writeups, articles |
47
- | **Memory** | Atomic facts, no structure | Preferences, context, ephemeral notes |
48
63
  | **Threads** | Channel conversations, optional entity context | Posting to the user's personal AI channel |
49
64
  | **Proposals** | Writes queued for human approval | Governance for some mutations (not an error — see below) |
50
65
 
66
+ ### Key profiles for AI use
67
+
68
+ | Profile slug | Scope | Who writes | Purpose |
69
+ | ------------------ | --------- | -------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
70
+ | `note` | pod | **human only** | The human's raw "dump now, structure later" inbox. **The AI never writes a note** — structuring into a lane is its job; use `capture` instead. |
71
+ | `knowledge` | workspace | AI | Validated gotchas/lessons/decisions — the **Work lane** (default `synap capture --type`; ek_type/ek_claim/ek_why). DOMAIN = the workspace (a Builder gotcha ≠ a marketing one). Cross-project runbooks go to `knowledge_keys` via `--global`. |
72
+ | `user_observation` | pod | AI only | Durable user model — habits, communication style, preferences |
73
+ | `decision` | pod | human + AI | Architectural decisions with rationale |
74
+ | `research` | pod | AI | Investigation with sources + conclusion |
75
+ | `question` | pod | human + AI | Open inquiry, closed when a decision answers it |
76
+
51
77
  ## Quick reference — 90% of tasks in 30 lines
52
78
 
53
79
  ```bash
@@ -56,11 +82,23 @@ synap orient --json # discover userId + works
56
82
  synap use <workspace-name-or-id> # set active workspace
57
83
  synap create entity --profile=task --name="…" --props='{"status":"todo","priority":"high"}' --json
58
84
  synap set entity <id> --props='{"status":"done"}' --json # merge-patch (only changed keys)
59
- synap search "query" --json
60
- synap remember "fact about the user" --json
61
- synap recall "query" --json
85
+ synap ask "your question" --json # THE read verb — routes to the right store(s) + shows which answered
86
+ synap capture --type=lesson --claim="…" --json # Work lane (default) — domain knowledge → active workspace
87
+ synap capture --global --type=reference --claim="…" --json # Global lane — pod-wide cross-cutting runbook (knowledge_keys)
88
+ synap observe write "…" --json # User lane — durable user model (inferences proposed)
62
89
  ```
63
90
 
91
+ **The canonical verbs:** `ask` (read) · `capture` (structured write — pick a lane:
92
+ Work default / `--global` / `observe` for User) · `orient` (bootstrap). `note` exists
93
+ for the HUMAN's raw "dump now, structure later" inbox — **the AI always `capture`s
94
+ instead.** **Reading is one verb: `ask`** — it classifies your
95
+ question and routes across the three memory substrates (semantic = the typed entity
96
+ graph, procedural = how-to docs, episodic = raw captures), returning one answer
97
+ tagged with which substrate(s) answered (and which, if any, were unavailable). Don't
98
+ pick a store; `ask` picks for you and tells you what it did. (`graph` for an explicit
99
+ traversal and `get`/`show`/`browse` for direct lookups remain; there is no `search`
100
+ or `recall` — `ask` is the door.)
101
+
64
102
  ```bash
65
103
  # REST (when no Bash access)
66
104
  POST /api/hub/entities body: { userId, workspaceId?, profileSlug, title, properties }
@@ -70,6 +108,7 @@ PATCH /api/hub/documents/{id} body: { userId, title?, content? } ← full
70
108
  POST /api/hub/relations body: { userId, sourceEntityId, targetEntityId, type }
71
109
  GET /api/hub/entities?q=…&profileSlug=task&workspaceId=…
72
110
  GET /api/hub/entities/{id}/connections?userId=…
111
+ POST /api/hub/knowledge/ask body: { query, workspaceId?, limit? } ← ONE read door, routes across substrates
73
112
  POST /api/hub/memory body: { userId, fact }
74
113
  GET /api/hub/memory?userId=…&query=…
75
114
  ```
@@ -128,8 +167,8 @@ GET /api/hub/discover?userId={userId}&workspaceId={workspaceId}
128
167
  `scope: "workspace"` = scoped to one workspace (deal, file, capture, custom profiles).
129
168
  Each profile includes its full property schema. Use `createCommand` as a template.
130
169
 
131
- **2. Search before answering**
132
- Before answering any question about the user's projects, tasks, contacts, decisions, or anything they might have captured — search Synap first. Do not answer from your training or context window when Synap may have the authoritative answer.
170
+ **2. Ask before answering**
171
+ Before answering any question about the user's projects, tasks, contacts, decisions, or anything they might have captured — `ask` Synap first (`synap ask "…"` / `POST /api/hub/knowledge/ask`). It routes across all three memory substrates in one call. Do not answer from your training or context window when Synap may have the authoritative answer.
133
172
 
134
173
  **3. Save proactively — without waiting to be asked**
135
174
  When the user shares a decision, task, meeting outcome, contact, or any durable information: save it. Don't ask "should I save this?" for obviously important information. Use:
@@ -156,8 +195,7 @@ The CLI reads the active pod and workspace from `~/.synap/config.json`. Set cont
156
195
 
157
196
  ```bash
158
197
  synap pods use <profile-name> # switch active pod
159
- synap use <workspace-id> # switch active workspace
160
- synap workspace provision-agent --json # provision your agent workspace + auto-sets it active
198
+ synap use <workspace-id> # switch active workspace (captures land here — it IS the domain)
161
199
  ```
162
200
 
163
201
  **Always orient first:**
@@ -168,14 +206,14 @@ synap orient --json
168
206
  # Never hardcode workspace IDs — discover them here.
169
207
  ```
170
208
 
171
- **Search (Typesense-powered, cross-collection):**
209
+ **Ask (the one read verb — routes across all substrates):**
172
210
 
173
211
  ```bash
174
- synap search "project ideas" --json
175
- synap search "Antoine" --type=entity --workspace=<id> --json
176
- synap search "meeting notes" --type=doc --limit=5 --json
177
- # Omit --workspace to search pod-wide. Include it to scope to one workspace.
178
- # Use search for name/keyword queries. For semantic/conceptual search, use Hub Protocol memory endpoints.
212
+ synap ask "project ideas" --json
213
+ synap ask "what did Antoine decide about auth" --workspace=<id> --json
214
+ synap ask "how do I deploy the backend" --json # routes to procedural how-to docs
215
+ # Omit --workspace for pod-wide; include it to scope to one workspace.
216
+ # `ask` classifies intent and unions the right substrate(s) — it replaces search/recall.
179
217
  ```
180
218
 
181
219
  **Read entities:**
@@ -187,17 +225,19 @@ synap list entities --profile=task --workspace=<id> --json
187
225
  synap get entity <id> --json
188
226
  ```
189
227
 
190
- **Episodic memory (session facts, loose context):**
228
+ **Capturing a decision (the AI structures — it never `note`s):**
191
229
 
192
230
  ```bash
193
- synap remember "Key decision: use Typesense for search" --json
194
- synap recall "Typesense" --limit=5 --json
231
+ synap capture --type decision --claim "Use Typesense for entity search" --json
232
+ # Retrieve later with the one read verb: synap ask "Typesense decision"
195
233
  ```
196
234
 
235
+ > `synap note` is the HUMAN's raw "dump now, structure later" inbox. As the AI, always `capture` into a lane — structuring is your job.
236
+
197
237
  **Structured knowledge (durable, typed, searchable — preferred for engineering learnings):**
198
238
 
199
239
  ```bash
200
- # Capture a gotcha, lesson, decision, or reference into your agent workspace
240
+ # Work lane (default): a domain gotcha/lesson/decision → knowledge entity in the ACTIVE workspace
201
241
  synap capture --type gotcha --claim "Hono static routes must come before /:id" \
202
242
  --why "First-match routing; dynamic routes eat static ones" \
203
243
  --tags "repo:synap-backend,layer:routing" --json
@@ -205,17 +245,20 @@ synap capture --type gotcha --claim "Hono static routes must come before /:id" \
205
245
  synap capture --type lesson --claim "code-read ≠ runtime-true for library APIs" \
206
246
  --evidence "tldraw 2.4.6 binding API changed silently from props.start.boundShapeId"
207
247
 
208
- # Recall across your knowledge base with full-text search
209
- synap recall "hono routing" --structured --json
210
- synap recall "tldraw" --structured --type gotcha --json
248
+ # A quick decision-note is a typed knowledge entry (ek_type=decision):
249
+ synap capture --type decision --claim "Use Typesense for entity search" \
250
+ --why "pgvector deferred to V1; Typesense ships now" --json
211
251
 
212
- # Prerequisite (run once): provision your agent workspace and set it active
213
- synap workspace provision-agent --json
252
+ # Global lane: a runbook/best-practice that holds across ALL projects → pod-wide knowledge_keys
253
+ synap capture --global --type reference --claim "Always fix the canonical path, never a workaround" \
254
+ --key "principle:root-cause" --json
255
+
256
+ # Retrieve any of it later with the one read verb (it spans every lane):
257
+ synap ask "hono routing gotcha" --json
214
258
  ```
215
259
 
216
- `synap capture` / `synap recall --structured` uses the `engineering_knowledge` entity profile.
217
- `synap remember` / `synap recall` (without `--structured`) uses the ephemeral `/memory` store.
218
- Use structured knowledge for anything worth remembering across sessions and projects.
260
+ `synap capture --type` writes a typed **`knowledge`** entity in the **active workspace** (the Work lane); `ek_type` (gotcha|lesson|decision|reference) discriminates the kind — **one store, type tags, not a residual dump**. It's workspace-scoped, so the active workspace supplies the domain — a Builder gotcha ≠ a marketing one (there is no `engineering_knowledge`). Add **`--global`** to write a pod-wide cross-cutting runbook to `knowledge_keys` instead. A formal **decision RECORD** (rationale, alternatives, superseded-by lifecycle) is a different artifact — use smart `synap capture "<free text>"` or `synap create entity --profile=decision`. Retrieve everything with `synap ask`.
261
+ Use `capture` for anything worth remembering across sessions and projects.
219
262
 
220
263
  **Write:**
221
264
 
@@ -231,7 +274,7 @@ synap set entity <id> --props='{"status":"done"}' --json
231
274
  - Always use `--json` when calling from code — clean stdout, no spinners, machine-parseable
232
275
  - Run `synap orient` first to discover workspace IDs — never hardcode them
233
276
  - Omit `--workspace` to operate pod-wide; include it to scope to a specific workspace
234
- - `synap search` is Typesense (fast keyword/name search). Semantic search → Hub Protocol `GET /api/hub/memory?query=…`
277
+ - `synap ask` is the one read verb — it routes keyword + semantic + procedural automatically; you never choose a search backend.
235
278
 
236
279
  ---
237
280
 
@@ -417,6 +460,47 @@ Every write returns a `status` field:
417
460
  2. Give them the link to review — `reviewUrl` opens the proposal in Synap Studio. Show the link as-is.
418
461
  3. Move on with the conversation. Don't wait or poll.
419
462
 
463
+ ### The `reasoning` field — required, structured, contextual
464
+
465
+ Every write call (create, update, delete) must include a `reasoning` field. This is what the governance reviewer reads to understand your decision. It is **not optional**.
466
+
467
+ Use this exact structure:
468
+
469
+ ```
470
+ Context: [what the user said or what event triggered this write — one sentence]
471
+ Intent: [what this entity or change accomplishes — one sentence]
472
+ Links: [actual entity IDs or slugs this relates to, e.g. "ent_abc, ent_xyz"]
473
+ ```
474
+
475
+ For updates, add:
476
+
477
+ ```
478
+ Changed: [field] [old value] → [new value]
479
+ ```
480
+
481
+ **Example (create):**
482
+
483
+ ```
484
+ Context: User asked to track the Acme deal they mentioned in today's call.
485
+ Intent: Creates a deal entity for Acme at lead stage linked to Alice Johnson.
486
+ Links: ent_person_alice_johnson, ent_company_acme
487
+ ```
488
+
489
+ **Example (update):**
490
+
491
+ ```
492
+ Context: User confirmed the Acme deal moved to proposal stage.
493
+ Intent: Advances the deal through the pipeline so it appears in the proposal view.
494
+ Links: ent_deal_acme, ent_person_alice_johnson
495
+ Changed: dealStage lead → proposal
496
+ ```
497
+
498
+ Rules:
499
+
500
+ - One sentence per field. No padding.
501
+ - `Links` must reference real entity IDs or slugs visible in the current context — not descriptions like "the related project".
502
+ - **"Agent requires proposal for all write operations."** is never acceptable as a `reasoning` value. That is an internal governance message, not agent reasoning. Write it and the proposal is meaningless to the reviewer.
503
+
420
504
  Example response to the user:
421
505
 
422
506
  > I queued **Delete task "Q2 plan review"** for your review. Destructive actions need your approval. Open it: https://studio.synap.live/proposals/prp_abc
@@ -565,7 +649,20 @@ POST /api/hub/threads/{threadId}/messages
565
649
 
566
650
  ## Reading
567
651
 
568
- Graph-based, not semantic. Type filter → relations → neighborhood.
652
+ **Start with `ask` — the one routed read door.** It classifies the question and
653
+ queries the right substrate(s) for you, returning a glass-box answer (which
654
+ substrates answered, which were unavailable, plus the engine's verdict). Reach for
655
+ the low-level doors below only when you deliberately want a single substrate or a
656
+ specific shape (a graph traversal, a typed-entity filter, an entity's neighborhood).
657
+
658
+ ```
659
+ # THE read door — routes across semantic / procedural / episodic, tells you which answered
660
+ POST /api/hub/knowledge/ask body: { query, workspaceId?, limit? }
661
+ → { query, routedTo: [...], primary, answers: [{ substrate, items, status }], degraded, understanding, verdict }
662
+ ```
663
+
664
+ Low-level doors (`ask` routes to these — graph-based, not semantic; type filter →
665
+ relations → neighborhood):
569
666
 
570
667
  ```
571
668
  # Keyword search across everything (entities, documents, views, threads)
@@ -798,6 +895,52 @@ When the user is interacting with Synap's AI Companion (the in-browser chat pane
798
895
  - **Only in Companion replies.** These patterns are silently ignored in non-companion channels, documents, and memory. Do not use them there.
799
896
  - **Combine with prose.** Don't lead with a chip — embed it naturally: `"Here are your open deals → [[view:xyz|Deals Pipeline]] · [[open:side|view:xyz]]"`
800
897
 
898
+ ## Focus Sessions — Goal-Bound Work Rooms
899
+
900
+ A **focus session** is a named, multi-step work room where you and AI agents collaborate on a specific goal. Use one whenever the work has a clear end state, will take more than one exchange, or involves multiple agents.
901
+
902
+ **When to propose a session** (via the proposal system — always ask first):
903
+
904
+ - Research with 5+ sources → decision memo
905
+ - Lead generation sprint → qualified list + outreach drafts
906
+ - Incident investigation → postmortem doc
907
+ - Data import → structured knowledge base
908
+ - Any task you'd naturally call "a project" rather than "a question"
909
+
910
+ **How the AI proposes a session:**
911
+
912
+ ```
913
+ create_proposal with targetType: "focus_session"
914
+ → user reviews goal + rationale + expected outputs in ProposalReviewBoard
915
+ → on approval, session is created in focus_sessions table
916
+ → AI updates progress (0→100) via PATCH /api/hub/focus-sessions/:id { workspaceId, progress: N }
917
+ → session auto-surfaces in the Active Sessions bento widget on the user's home
918
+ ```
919
+
920
+ **Session templates** (pass as `templateId`):
921
+ `research-room` · `lead-sprint` · `decision-memo` · `import-cleanup` · `incident-room` · `campaign-intel`
922
+
923
+ **Hub Protocol REST** (for IS → backend; always include `workspaceId`):
924
+
925
+ - `POST /api/hub/focus-sessions` — create (include `correlationId` for idempotency)
926
+ - `GET /api/hub/focus-sessions/:id?workspaceId=<id>` — read
927
+ - `PATCH /api/hub/focus-sessions/:id` — update `{ workspaceId, progress, status, goal, agentIds }`
928
+
929
+ **CLI** (use when running as Claude Code / OpenClaw agent):
930
+
931
+ ```bash
932
+ synap session start --goal "<goal>" [--workspace <id>] # create + start a session
933
+ synap session list [--workspace <id>] [--status active|paused|closed] # list sessions
934
+ synap session get <id> [--workspace <id>] # read a session
935
+ synap session update <id> --workspace <id> --progress 50 # report progress
936
+ synap session update <id> --workspace <id> --status paused # pause
937
+ synap session close <id> --workspace <id> [--recap "what was done"] # close + recap
938
+ ```
939
+
940
+ Note: `synap session start` creates a session directly (the agent-facing path). All hub-protocol writes are governance-gated server-side; the in-browser AI companion surfaces session creation through the proposal flow.
941
+
942
+ **Discoverability**: the `active-sessions` bento widget is on the default home dashboard. Sessions group their related proposals under a shared `correlationId` in the Proposal Review Board.
943
+
801
944
  ## When you need more
802
945
 
803
946
  - Linking conventions, auto-sync table, relation types → **`linking.md`**
@@ -820,87 +963,225 @@ ViewFrame is the standard way to create custom data visualizations in Synap. Use
820
963
 
821
964
  ### What ViewFrame Is
822
965
 
823
- - A sandboxed iframe that renders any ES module (React component or plain JS module)
824
- - Dependencies resolved at runtime via **esm.sh import maps** — no build step
825
- - The host injects a `SynapWidget` bridge for data access (same postMessage protocol as iframe widgets)
966
+ - A sandboxed iframe that renders **one ES module** that default-exports a React component (or plain JS)
967
+ - Dependencies resolved at runtime via **esm.sh import maps** built from the `deps` map — no build step required
968
+ - The host injects a `SynapWidget` bridge for data access and shell actions
826
969
  - Security: `sandbox="allow-scripts allow-modals allow-popups"`, no `allow-same-origin`, no cookies, no pod token
827
970
 
828
- ### Register a Widget via the Hub Protocol
971
+ ### Authoring contract
829
972
 
830
- List installed widgets:
973
+ A ViewFrame cell is **one self-contained ES module** (inline in `rendererSource`):
831
974
 
832
- ```
833
- GET /api/hub/widget-definitions?workspaceId={workspaceId}
834
- Authorization: Bearer {SYNAP_HUB_API_KEY}
835
- ```
975
+ - The module **default-exports a React component** (or calls `SynapWidget.onInit()` for plain JS).
976
+ - Bare imports (`"react"`, `"recharts"`, etc.) are resolved via the esm.sh import map generated from `deps`.
977
+ - **No bundler, no `import` of local files** — everything is either inlined or declared in `deps`.
978
+ - External CSS is not supported; inline `<style>` tags or CSS-in-JS only.
836
979
 
837
- Install a new ViewFrame widget (creates a proposal for user review):
980
+ ### Register a Cell via the Hub Protocol (canonical path)
981
+
982
+ **Use `POST /api/hub/cells/define` — this is the canonical Hub Protocol path for AI-generated cells.**
983
+
984
+ It is idempotent (upserts on typeKey), pod-global by default (no workspaceId needed), and immediately available across all of the user's workspaces without any proposal step.
838
985
 
839
986
  ```
840
- POST /api/hub/widget-definitions
987
+ POST /api/hub/cells/define
841
988
  Authorization: Bearer {SYNAP_HUB_API_KEY}
842
989
  Content-Type: application/json
843
990
 
844
991
  {
845
- "userId": "{SYNAP_USER_ID}",
846
- "workspaceId": "{workspaceId}",
847
- "typeKey": "deal-stage-funnel",
848
992
  "name": "Deal Stage Funnel",
849
- "description": "Funnel chart of deal pipeline stages",
850
- "rendererType": "iframe",
851
- "rendererSource": "<full HTML document — see template below>",
852
- "defaultSize": { "w": 8, "h": 6 },
853
- "category": "visualization"
993
+ "rendererSource": "<!DOCTYPE html>…</html>",
994
+ "typeKey": "deal-stage-funnel", // optional — derived from name if omitted
995
+ "description": "Funnel chart of deal pipeline stages", // optional
996
+ "defaultSize": { "w": 8, "h": 6 }, // optional
997
+ "deps": { // optional — npm packages pinned for the import map
998
+ "recharts": "2.12.0",
999
+ "@tanstack/react-table": "8"
1000
+ }
854
1001
  }
855
1002
  ```
856
1003
 
857
- `typeKey` must be kebab-case matching `/^[a-z][a-z0-9-]+$/`. Use a descriptive name specific to the widget's content.
1004
+ **`deps` rules:**
1005
+
1006
+ - Keys are npm package names (`pkg` or `@scope/pkg`). URLs and protocols are rejected.
1007
+ - Values are version strings or `"latest"` — used verbatim in the esm.sh import map URL.
1008
+ - Maximum 30 entries. Omit `deps` (or pass `{}`) for React-only cells (React is always available).
1009
+
1010
+ **`workspaceId` is intentionally omitted** — cells defined without it are pod-global (`workspaceId IS NULL`), visible in every workspace the user owns. Pass `workspaceId` only when you explicitly want a cell scoped to a single workspace.
1011
+
1012
+ Response: `{ "success": true, "typeKey": "generated:deal-stage-funnel" }`
1013
+
1014
+ The typeKey is auto-prefixed `generated:` when not explicitly provided.
1015
+
1016
+ **List cells (all pod-global + optionally workspace-specific):**
1017
+
1018
+ ```
1019
+ GET /api/hub/cells — pod-global only
1020
+ GET /api/hub/cells?workspaceId={id} — pod-global + workspace-scoped
1021
+ Authorization: Bearer {SYNAP_HUB_API_KEY}
1022
+ ```
1023
+
1024
+ **Delete a cell:**
1025
+
1026
+ ```
1027
+ DELETE /api/hub/cells/{typeKey} — pod-global row
1028
+ DELETE /api/hub/cells/{typeKey}?workspaceId={id} — workspace-scoped row
1029
+ Authorization: Bearer {SYNAP_HUB_API_KEY}
1030
+ ```
858
1031
 
859
- The response carries `status: "ok"` (installed immediately) or `status: "proposed"` (queued for user review — surface `reviewUrl` to the user).
1032
+ **Open the cell in the browser (deep link):**
1033
+
1034
+ ```
1035
+ synap://open/cell/{typeKey}
1036
+ ```
1037
+
1038
+ The browser receives this deep link, looks the typeKey up in the cell registry (which polls `widget_definitions` every 10s), and opens it as a side panel tab with the cell's registered `meta.name` as the tab title.
1039
+
1040
+ **Full AI artifact workflow:**
1041
+
1042
+ ```
1043
+ // 1. Generate the HTML/React cell
1044
+ POST /api/hub/cells/define
1045
+ { "name": "Q2 Revenue Report", "rendererSource": "<!DOCTYPE html>…</html>",
1046
+ "deps": { "recharts": "2.12.0" } }
1047
+ // → { "success": true, "typeKey": "generated:q2-revenue-report" }
1048
+
1049
+ // 2. Open it in the user's browser
1050
+ synap://open/cell/generated:q2-revenue-report
1051
+ ```
1052
+
1053
+ The cell appears immediately in the side panel with "Q2 Revenue Report" as the tab title. It persists across sessions and is available from any workspace.
1054
+
1055
+ > **Note:** `POST /api/hub/widget-definitions` (tRPC path) still works but is the internal/admin path. Use `POST /api/hub/cells/define` for all agent-generated cells.
1056
+
1057
+ ### CLI commands (when running as Claude Code / OpenClaw agent)
1058
+
1059
+ ```bash
1060
+ # Build a multi-file cell source into a single ES module bundle + emit deps map
1061
+ synap cell build <entry> # e.g. synap cell build ./src/my-chart.tsx
1062
+ # → prints bundled source to stdout and writes deps.json alongside the entry
1063
+
1064
+ # Push a built cell (source + deps) to the pod
1065
+ synap cell define \
1066
+ --name "My Chart" \
1067
+ --source ./dist/my-chart.js \
1068
+ --deps ./deps.json \
1069
+ [--typeKey my-chart] \
1070
+ [--workspace <id>]
1071
+
1072
+ # Document operations (attach prose or reports to entities)
1073
+ synap doc create --title "Q2 Report" --content ./report.md --entity <entityId>
1074
+ synap doc update <docId> --content ./updated-report.md
1075
+
1076
+ # Arrange widgets on an existing bento view
1077
+ synap view arrange <viewId> --blocks '[{"id":"b1","kind":"widget","widgetKind":"generated:my-chart","layout":{"x":0,"y":0,"w":8,"h":6}}]'
1078
+ ```
860
1079
 
861
1080
  ### The SynapWidget Bridge (inside the iframe)
862
1081
 
863
1082
  `window.SynapWidget` is injected automatically — do NOT import or `<script>` it.
864
1083
 
1084
+ #### Queries (read-only, always approved)
1085
+
865
1086
  ```js
866
1087
  SynapWidget.onInit(async ({ config, context }) => {
867
1088
  // context: { workspaceId, viewId?, entityId?, sdkVersion }
868
- const items = await SynapWidget.query("list_entities", {
869
- profileSlug: "deal", // or 'task', 'person', 'company', any custom slug
1089
+
1090
+ // List entities
1091
+ const deals = await SynapWidget.query("entities.list", {
1092
+ profileSlug: "deal",
870
1093
  limit: 200,
871
1094
  });
872
- render(items ?? []);
1095
+
1096
+ // Get a single entity
1097
+ const entity = await SynapWidget.query("entities.get", { id: "uuid" });
1098
+
1099
+ // List views
1100
+ const views = await SynapWidget.query("views.list", {
1101
+ workspaceId: context.workspaceId,
1102
+ });
1103
+
1104
+ // List profiles
1105
+ const profiles = await SynapWidget.query("profiles.list", {});
1106
+
1107
+ render(deals ?? []);
873
1108
  SynapWidget.resize(document.body.scrollHeight);
874
1109
  });
875
1110
  ```
876
1111
 
877
1112
  All `query()` calls return a Promise. Entity shape: `{ id, title, profileSlug, properties, createdAt, … }`.
878
1113
 
879
- Navigation and notifications:
1114
+ #### Mutations (governance-gated — return `{ status: "approved" | "proposed" | "denied" }`)
1115
+
1116
+ ```js
1117
+ // Create an entity
1118
+ const result = await SynapWidget.mutate("create_entity", {
1119
+ profileSlug: "task",
1120
+ title: "Follow up",
1121
+ properties: { status: "todo" },
1122
+ });
1123
+ // result.status === "approved" → result.id is the new entity id
1124
+ // result.status === "proposed" → result.proposalId, result.reviewUrl
1125
+
1126
+ // Update an entity
1127
+ await SynapWidget.mutate("update_entity", {
1128
+ id: "uuid",
1129
+ properties: { status: "done" },
1130
+ });
1131
+
1132
+ // Delete an entity (always proposed for agent-generated cells)
1133
+ await SynapWidget.mutate("delete_entity", { id: "uuid" });
1134
+
1135
+ // Create a relation
1136
+ await SynapWidget.mutate("create_relation", {
1137
+ sourceEntityId: "uuid-a",
1138
+ targetEntityId: "uuid-b",
1139
+ type: "related_to",
1140
+ });
1141
+ ```
1142
+
1143
+ **Always check `result.status`.** `"proposed"` is not an error — surface `result.reviewUrl` to the user.
1144
+
1145
+ #### Shell actions
880
1146
 
881
1147
  ```js
882
- SynapWidget.navigate({ entityId: "entity-uuid" }); // opens entity detail
883
- SynapWidget.toast("Done!", "success"); // 'success' | 'error' | 'info'
1148
+ SynapWidget.navigate({ entityId: "entity-uuid" }); // open entity detail in side panel
1149
+ SynapWidget.openPanel("entity-detail", { entityId: "uuid" }); // explicit panel open
1150
+ SynapWidget.toast("Saved!", "success"); // 'success' | 'error' | 'info'
1151
+ SynapWidget.resize(document.body.scrollHeight); // resize the iframe to content height
1152
+ SynapWidget.updateContext({ viewId: "uuid" }); // update ambient context
1153
+
1154
+ // Subscribe to live entity changes
1155
+ SynapWidget.subscribe("entity:changed", ({ entityId }) => {
1156
+ // re-fetch and re-render when any entity in the pod changes
1157
+ });
884
1158
  ```
885
1159
 
886
1160
  ### Common Dependency Patterns (esm.sh import map)
887
1161
 
888
- ```html
889
- <script type="importmap">
890
- {
891
- "imports": {
892
- "react": "https://esm.sh/react@19",
893
- "react-dom/client": "https://esm.sh/react-dom@19/client",
894
- "react/jsx-runtime": "https://esm.sh/react@19/jsx-runtime",
895
- "recharts": "https://esm.sh/recharts@2.12.0"
896
- }
1162
+ The `deps` map in `/cells/define` drives the import map. Each key becomes a bare specifier in the `<script type="importmap">`, resolved to `https://esm.sh/<pkg>@<version>`.
1163
+
1164
+ ```json
1165
+ // deps in the define call:
1166
+ { "recharts": "2.12.0", "d3": "7" }
1167
+
1168
+ // → generates this importmap inside the frame:
1169
+ {
1170
+ "imports": {
1171
+ "react": "https://esm.sh/react@19",
1172
+ "react-dom/client": "https://esm.sh/react-dom@19/client",
1173
+ "react/jsx-runtime": "https://esm.sh/react@19/jsx-runtime",
1174
+ "recharts": "https://esm.sh/recharts@2.12.0",
1175
+ "d3": "https://esm.sh/d3@7"
897
1176
  }
898
- </script>
1177
+ }
899
1178
  ```
900
1179
 
1180
+ React 19 core entries are always injected by the host — never put them in `deps`.
1181
+
901
1182
  Common library choices:
902
1183
 
903
- | Category | Packages |
1184
+ | Category | Packages (put in `deps`) |
904
1185
  | -------- | -------------------------------------------------------------- |
905
1186
  | Data viz | `recharts@2.12.0`, `d3@7`, `chart.js@4`, `observable-plot@0.6` |
906
1187
  | Tables | `@tanstack/react-table@8` |
@@ -925,30 +1206,23 @@ Common library choices:
925
1206
  background: transparent;
926
1207
  }
927
1208
  </style>
928
- <script type="importmap">
929
- {
930
- "imports": {
931
- "react": "https://esm.sh/react@19",
932
- "react-dom/client": "https://esm.sh/react-dom@19/client",
933
- "react/jsx-runtime": "https://esm.sh/react@19/jsx-runtime"
934
- }
935
- }
936
- </script>
1209
+ <!-- importmap is injected by the host from deps — do not write one manually -->
937
1210
  </head>
938
1211
  <body>
939
1212
  <div id="root"></div>
940
1213
  <script type="module">
941
1214
  import { createRoot } from "react-dom/client";
1215
+ import { createElement as h, useState } from "react";
942
1216
 
943
1217
  SynapWidget.onInit(async ({ context }) => {
944
- const items = await SynapWidget.query("list_entities", {
1218
+ const items = await SynapWidget.query("entities.list", {
945
1219
  profileSlug: "deal",
946
1220
  limit: 200,
947
1221
  }).catch(() => []);
948
1222
 
949
- // Build your UI here. Plain DOM or React both work.
950
- document.getElementById("root").textContent =
951
- `Loaded ${(items ?? []).length} deals`;
1223
+ createRoot(document.getElementById("root")).render(
1224
+ h("p", null, `Loaded ${(items ?? []).length} deals`)
1225
+ );
952
1226
 
953
1227
  SynapWidget.resize(document.body.scrollHeight);
954
1228
  });
@@ -961,10 +1235,11 @@ Common library choices:
961
1235
 
962
1236
  - **Always call `SynapWidget.onInit()`** — the host will not send data until you register this handler.
963
1237
  - **Call `SynapWidget.resize()`** after rendering to prevent clipping.
964
- - **Handle errors** — `query()` can fail; always `.catch()`.
1238
+ - **Handle errors** — `query()` and `mutate()` can fail; always `.catch()`.
1239
+ - **Check `result.status` on mutations** — `"proposed"` is governance, not an error; surface `reviewUrl`.
965
1240
  - **Transparent background** — `background: transparent` on `body` inherits the host surface color.
966
1241
  - **No external fetch** — the sandbox has no cross-origin access; all data must go through `SynapWidget`.
967
- - **Inline all styles** — no external CSS imports; CDN JS via import map is fine.
1242
+ - **Declare all non-React imports in `deps`** — the host generates the import map from that field.
968
1243
 
969
1244
  ---
970
1245