@synap-core/cli 1.6.1 → 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 (128) 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 +220 -109
  28. package/dist/commands/data.js.map +1 -1
  29. package/dist/commands/discover.d.ts +18 -0
  30. package/dist/commands/discover.js +71 -0
  31. package/dist/commands/discover.js.map +1 -0
  32. package/dist/commands/doc.d.ts +39 -0
  33. package/dist/commands/doc.js +130 -0
  34. package/dist/commands/doc.js.map +1 -0
  35. package/dist/commands/events.d.ts +26 -0
  36. package/dist/commands/events.js +68 -0
  37. package/dist/commands/events.js.map +1 -0
  38. package/dist/commands/explain.d.ts +21 -0
  39. package/dist/commands/explain.js +248 -0
  40. package/dist/commands/explain.js.map +1 -0
  41. package/dist/commands/graph.d.ts +18 -0
  42. package/dist/commands/graph.js +73 -0
  43. package/dist/commands/graph.js.map +1 -0
  44. package/dist/commands/home.d.ts +12 -0
  45. package/dist/commands/home.js +54 -0
  46. package/dist/commands/home.js.map +1 -0
  47. package/dist/commands/init.d.ts +0 -2
  48. package/dist/commands/init.js +62 -102
  49. package/dist/commands/init.js.map +1 -1
  50. package/dist/commands/keys.d.ts +9 -0
  51. package/dist/commands/keys.js +79 -0
  52. package/dist/commands/keys.js.map +1 -0
  53. package/dist/commands/knowledge.d.ts +32 -18
  54. package/dist/commands/knowledge.js +303 -53
  55. package/dist/commands/knowledge.js.map +1 -1
  56. package/dist/commands/observe.d.ts +9 -0
  57. package/dist/commands/observe.js +90 -0
  58. package/dist/commands/observe.js.map +1 -0
  59. package/dist/commands/open.d.ts +15 -0
  60. package/dist/commands/open.js +44 -0
  61. package/dist/commands/open.js.map +1 -0
  62. package/dist/commands/pods.d.ts +5 -0
  63. package/dist/commands/pods.js +85 -37
  64. package/dist/commands/pods.js.map +1 -1
  65. package/dist/commands/proposals-actions.d.ts +7 -0
  66. package/dist/commands/proposals-actions.js +40 -0
  67. package/dist/commands/proposals-actions.js.map +1 -0
  68. package/dist/commands/providers.d.ts +38 -0
  69. package/dist/commands/providers.js +170 -0
  70. package/dist/commands/providers.js.map +1 -0
  71. package/dist/commands/sessions.d.ts +31 -0
  72. package/dist/commands/sessions.js +224 -0
  73. package/dist/commands/sessions.js.map +1 -0
  74. package/dist/commands/skills-commands.d.ts +26 -0
  75. package/dist/commands/skills-commands.js +186 -0
  76. package/dist/commands/skills-commands.js.map +1 -0
  77. package/dist/commands/skills.d.ts +26 -0
  78. package/dist/commands/skills.js +195 -0
  79. package/dist/commands/skills.js.map +1 -0
  80. package/dist/commands/status.js +68 -30
  81. package/dist/commands/status.js.map +1 -1
  82. package/dist/commands/subscribe.d.ts +19 -0
  83. package/dist/commands/subscribe.js +91 -0
  84. package/dist/commands/subscribe.js.map +1 -0
  85. package/dist/commands/view.d.ts +57 -0
  86. package/dist/commands/view.js +194 -0
  87. package/dist/commands/view.js.map +1 -0
  88. package/dist/index.d.ts +3 -4
  89. package/dist/index.js +823 -206
  90. package/dist/index.js.map +1 -1
  91. package/dist/lib/agent-templates.d.ts +25 -0
  92. package/dist/lib/agent-templates.js +148 -0
  93. package/dist/lib/agent-templates.js.map +1 -0
  94. package/dist/lib/agents-config.d.ts +4 -1
  95. package/dist/lib/agents-config.js +19 -2
  96. package/dist/lib/agents-config.js.map +1 -1
  97. package/dist/lib/capture-lane.d.ts +49 -0
  98. package/dist/lib/capture-lane.js +79 -0
  99. package/dist/lib/capture-lane.js.map +1 -0
  100. package/dist/lib/hub-client.d.ts +14 -0
  101. package/dist/lib/hub-client.js +45 -3
  102. package/dist/lib/hub-client.js.map +1 -1
  103. package/dist/lib/pod.d.ts +22 -1
  104. package/dist/lib/pod.js +32 -2
  105. package/dist/lib/pod.js.map +1 -1
  106. package/dist/lib/skills-installer.d.ts +1 -1
  107. package/dist/lib/skills-installer.js +1 -1
  108. package/dist/lib/skills-installer.js.map +1 -1
  109. package/dist/lib/targets.d.ts +17 -1
  110. package/dist/lib/targets.js +744 -70
  111. package/dist/lib/targets.js.map +1 -1
  112. package/node_modules/@synap/hub-rest-client/dist/index.cjs +881 -0
  113. package/node_modules/@synap/hub-rest-client/dist/index.d.cts +907 -0
  114. package/node_modules/@synap/hub-rest-client/dist/index.d.ts +907 -0
  115. package/node_modules/@synap/hub-rest-client/dist/index.js +851 -0
  116. package/node_modules/@synap/hub-rest-client/package.json +45 -0
  117. package/node_modules/@synap/hub-rest-client/src/client.ts +1143 -0
  118. package/node_modules/@synap/hub-rest-client/src/errors.ts +30 -0
  119. package/node_modules/@synap/hub-rest-client/src/index.ts +111 -0
  120. package/node_modules/@synap/hub-rest-client/src/setup.ts +77 -0
  121. package/node_modules/@synap/hub-rest-client/src/types.ts +639 -0
  122. package/package.json +9 -2
  123. package/skills/synap/SKILL.md +374 -100
  124. package/skills/synap-ui/SKILL.md +12 -9
  125. package/dist/lib/templates.d.ts +0 -11
  126. package/dist/lib/templates.js +0 -13
  127. package/dist/lib/templates.js.map +0 -1
  128. 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,26 +108,24 @@ 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
  ```
76
115
 
77
- **Common `properties` keys by profile** (stable — no round-trip needed for these):
116
+ **Profile schemas are runtime-discovered — never hardcoded:**
78
117
 
79
- | Profile | Key properties (slug: type) |
80
- | ---------- | ----------------------------------------------------------------------------------------------------------------------------------------- |
81
- | `task` | `status` (todo/in-progress/done/blocked), `priority` (low/medium/high), `dueDate` (date), `projectId` (entity_id), `assignee` (entity_id) |
82
- | `note` | `tags` (string[]), `projectId` (entity_id) |
83
- | `project` | `status` (active/paused/completed/cancelled), `startDate`, `endDate`, `description` |
84
- | `person` | `email`, `role`, `companyId` (entity_id), `linkedinUrl`, `phone` |
85
- | `event` | `startDate`, `endDate`, `location`, `attendees` (entity_id[]) |
86
- | `decision` | `decisionStatus` (draft/accepted/superseded), `decidedAt`, `rationale`, `alternatives`, `projectId` |
87
- | `research` | `researchStatus` (ongoing/concluded), `questionId` (entity_id), `conclusion`, `researchConfidence` |
88
- | `question` | `questionStatus` (exploring/answered), `answeredByDecisionId` (entity_id), `projectId` |
89
- | `deal` | `dealStage` (lead/contacted/qualifying/proposal/won/lost), `estimatedValue` (number) |
90
- | `document` | `contentType` (markdown/html), `summary`, `projectId` (entity_id) |
118
+ ```bash
119
+ synap discover --json # CLI: full profile tree with property schemas + command map
120
+ synap discover --profiles --json # CLI: profiles only
121
+ ```
91
122
 
92
- For custom profiles or constraint details: `GET /api/hub/profiles?userId={userId}&workspaceId={workspaceId}`.
123
+ ```
124
+ GET /api/hub/discover?userId={userId}&workspaceId={workspaceId}
125
+ → { profiles: [{ slug, displayName, scope, properties: [{ slug, type, options? }], createCommand }], commands: {...} }
126
+ ```
127
+
128
+ Call this once at session start. The response includes every system profile and any custom workspace profiles the user has created, each with its full property schema. Use `createCommand` per profile as a copy-paste template. Do not rely on a static property list — it will drift.
93
129
 
94
130
  **Load more detail on demand** (`GET /api/hub/skills/system?sections=<id>`):
95
131
 
@@ -122,15 +158,17 @@ GET /api/hub/users/me
122
158
  GET /api/hub/workspaces
123
159
  → [{ id, name, role }] ← workspaces[0].id if only one
124
160
 
125
- GET /api/hub/profiles?userId={userId}&workspaceId={workspaceId}
126
- → [{ slug, displayName, entityScope, properties }]
161
+ GET /api/hub/discover?userId={userId}&workspaceId={workspaceId}
162
+ → { profiles: [{ slug, displayName, scope, properties, createCommand }], commands: {...} }
163
+ ← replaces /profiles — includes property schemas + custom workspace profiles
127
164
  ```
128
165
 
129
- `entityScope: "pod"` = visible across all workspaces (note, task, project, person, company, bookmark, event, contact, article, website).
130
- `entityScope: "workspace"` = scoped to one workspace (deal, file, capture, custom profiles).
166
+ `scope: "pod"` = visible across all workspaces (note, task, project, person, company, bookmark, event, contact, article, website).
167
+ `scope: "workspace"` = scoped to one workspace (deal, file, capture, custom profiles).
168
+ Each profile includes its full property schema. Use `createCommand` as a template.
131
169
 
132
- **2. Search before answering**
133
- 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.
134
172
 
135
173
  **3. Save proactively — without waiting to be asked**
136
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:
@@ -157,8 +195,7 @@ The CLI reads the active pod and workspace from `~/.synap/config.json`. Set cont
157
195
 
158
196
  ```bash
159
197
  synap pods use <profile-name> # switch active pod
160
- synap use <workspace-id> # switch active workspace
161
- 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)
162
199
  ```
163
200
 
164
201
  **Always orient first:**
@@ -169,14 +206,14 @@ synap orient --json
169
206
  # Never hardcode workspace IDs — discover them here.
170
207
  ```
171
208
 
172
- **Search (Typesense-powered, cross-collection):**
209
+ **Ask (the one read verb — routes across all substrates):**
173
210
 
174
211
  ```bash
175
- synap search "project ideas" --json
176
- synap search "Antoine" --type=entity --workspace=<id> --json
177
- synap search "meeting notes" --type=doc --limit=5 --json
178
- # Omit --workspace to search pod-wide. Include it to scope to one workspace.
179
- # 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.
180
217
  ```
181
218
 
182
219
  **Read entities:**
@@ -188,17 +225,19 @@ synap list entities --profile=task --workspace=<id> --json
188
225
  synap get entity <id> --json
189
226
  ```
190
227
 
191
- **Episodic memory (session facts, loose context):**
228
+ **Capturing a decision (the AI structures — it never `note`s):**
192
229
 
193
230
  ```bash
194
- synap remember "Key decision: use Typesense for search" --json
195
- 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"
196
233
  ```
197
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
+
198
237
  **Structured knowledge (durable, typed, searchable — preferred for engineering learnings):**
199
238
 
200
239
  ```bash
201
- # 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
202
241
  synap capture --type gotcha --claim "Hono static routes must come before /:id" \
203
242
  --why "First-match routing; dynamic routes eat static ones" \
204
243
  --tags "repo:synap-backend,layer:routing" --json
@@ -206,17 +245,20 @@ synap capture --type gotcha --claim "Hono static routes must come before /:id" \
206
245
  synap capture --type lesson --claim "code-read ≠ runtime-true for library APIs" \
207
246
  --evidence "tldraw 2.4.6 binding API changed silently from props.start.boundShapeId"
208
247
 
209
- # Recall across your knowledge base with full-text search
210
- synap recall "hono routing" --structured --json
211
- 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
212
251
 
213
- # Prerequisite (run once): provision your agent workspace and set it active
214
- 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
215
258
  ```
216
259
 
217
- `synap capture` / `synap recall --structured` uses the `engineering_knowledge` entity profile.
218
- `synap remember` / `synap recall` (without `--structured`) uses the ephemeral `/memory` store.
219
- 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.
220
262
 
221
263
  **Write:**
222
264
 
@@ -232,7 +274,7 @@ synap set entity <id> --props='{"status":"done"}' --json
232
274
  - Always use `--json` when calling from code — clean stdout, no spinners, machine-parseable
233
275
  - Run `synap orient` first to discover workspace IDs — never hardcode them
234
276
  - Omit `--workspace` to operate pod-wide; include it to scope to a specific workspace
235
- - `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.
236
278
 
237
279
  ---
238
280
 
@@ -418,6 +460,47 @@ Every write returns a `status` field:
418
460
  2. Give them the link to review — `reviewUrl` opens the proposal in Synap Studio. Show the link as-is.
419
461
  3. Move on with the conversation. Don't wait or poll.
420
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
+
421
504
  Example response to the user:
422
505
 
423
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
@@ -566,7 +649,20 @@ POST /api/hub/threads/{threadId}/messages
566
649
 
567
650
  ## Reading
568
651
 
569
- 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):
570
666
 
571
667
  ```
572
668
  # Keyword search across everything (entities, documents, views, threads)
@@ -799,6 +895,52 @@ When the user is interacting with Synap's AI Companion (the in-browser chat pane
799
895
  - **Only in Companion replies.** These patterns are silently ignored in non-companion channels, documents, and memory. Do not use them there.
800
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]]"`
801
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
+
802
944
  ## When you need more
803
945
 
804
946
  - Linking conventions, auto-sync table, relation types → **`linking.md`**
@@ -821,87 +963,225 @@ ViewFrame is the standard way to create custom data visualizations in Synap. Use
821
963
 
822
964
  ### What ViewFrame Is
823
965
 
824
- - A sandboxed iframe that renders any ES module (React component or plain JS module)
825
- - Dependencies resolved at runtime via **esm.sh import maps** — no build step
826
- - 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
827
969
  - Security: `sandbox="allow-scripts allow-modals allow-popups"`, no `allow-same-origin`, no cookies, no pod token
828
970
 
829
- ### Register a Widget via the Hub Protocol
971
+ ### Authoring contract
830
972
 
831
- List installed widgets:
973
+ A ViewFrame cell is **one self-contained ES module** (inline in `rendererSource`):
832
974
 
833
- ```
834
- GET /api/hub/widget-definitions?workspaceId={workspaceId}
835
- Authorization: Bearer {SYNAP_HUB_API_KEY}
836
- ```
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.
837
979
 
838
- 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.
839
985
 
840
986
  ```
841
- POST /api/hub/widget-definitions
987
+ POST /api/hub/cells/define
842
988
  Authorization: Bearer {SYNAP_HUB_API_KEY}
843
989
  Content-Type: application/json
844
990
 
845
991
  {
846
- "userId": "{SYNAP_USER_ID}",
847
- "workspaceId": "{workspaceId}",
848
- "typeKey": "deal-stage-funnel",
849
992
  "name": "Deal Stage Funnel",
850
- "description": "Funnel chart of deal pipeline stages",
851
- "rendererType": "iframe",
852
- "rendererSource": "<full HTML document — see template below>",
853
- "defaultSize": { "w": 8, "h": 6 },
854
- "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
+ }
855
1001
  }
856
1002
  ```
857
1003
 
858
- `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
+ ```
1031
+
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)
859
1058
 
860
- The response carries `status: "ok"` (installed immediately) or `status: "proposed"` (queued for user review — surface `reviewUrl` to the user).
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
+ ```
861
1079
 
862
1080
  ### The SynapWidget Bridge (inside the iframe)
863
1081
 
864
1082
  `window.SynapWidget` is injected automatically — do NOT import or `<script>` it.
865
1083
 
1084
+ #### Queries (read-only, always approved)
1085
+
866
1086
  ```js
867
1087
  SynapWidget.onInit(async ({ config, context }) => {
868
1088
  // context: { workspaceId, viewId?, entityId?, sdkVersion }
869
- const items = await SynapWidget.query("list_entities", {
870
- profileSlug: "deal", // or 'task', 'person', 'company', any custom slug
1089
+
1090
+ // List entities
1091
+ const deals = await SynapWidget.query("entities.list", {
1092
+ profileSlug: "deal",
871
1093
  limit: 200,
872
1094
  });
873
- 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 ?? []);
874
1108
  SynapWidget.resize(document.body.scrollHeight);
875
1109
  });
876
1110
  ```
877
1111
 
878
1112
  All `query()` calls return a Promise. Entity shape: `{ id, title, profileSlug, properties, createdAt, … }`.
879
1113
 
880
- 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
881
1146
 
882
1147
  ```js
883
- SynapWidget.navigate({ entityId: "entity-uuid" }); // opens entity detail
884
- 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
+ });
885
1158
  ```
886
1159
 
887
1160
  ### Common Dependency Patterns (esm.sh import map)
888
1161
 
889
- ```html
890
- <script type="importmap">
891
- {
892
- "imports": {
893
- "react": "https://esm.sh/react@19",
894
- "react-dom/client": "https://esm.sh/react-dom@19/client",
895
- "react/jsx-runtime": "https://esm.sh/react@19/jsx-runtime",
896
- "recharts": "https://esm.sh/recharts@2.12.0"
897
- }
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"
898
1176
  }
899
- </script>
1177
+ }
900
1178
  ```
901
1179
 
1180
+ React 19 core entries are always injected by the host — never put them in `deps`.
1181
+
902
1182
  Common library choices:
903
1183
 
904
- | Category | Packages |
1184
+ | Category | Packages (put in `deps`) |
905
1185
  | -------- | -------------------------------------------------------------- |
906
1186
  | Data viz | `recharts@2.12.0`, `d3@7`, `chart.js@4`, `observable-plot@0.6` |
907
1187
  | Tables | `@tanstack/react-table@8` |
@@ -926,30 +1206,23 @@ Common library choices:
926
1206
  background: transparent;
927
1207
  }
928
1208
  </style>
929
- <script type="importmap">
930
- {
931
- "imports": {
932
- "react": "https://esm.sh/react@19",
933
- "react-dom/client": "https://esm.sh/react-dom@19/client",
934
- "react/jsx-runtime": "https://esm.sh/react@19/jsx-runtime"
935
- }
936
- }
937
- </script>
1209
+ <!-- importmap is injected by the host from deps — do not write one manually -->
938
1210
  </head>
939
1211
  <body>
940
1212
  <div id="root"></div>
941
1213
  <script type="module">
942
1214
  import { createRoot } from "react-dom/client";
1215
+ import { createElement as h, useState } from "react";
943
1216
 
944
1217
  SynapWidget.onInit(async ({ context }) => {
945
- const items = await SynapWidget.query("list_entities", {
1218
+ const items = await SynapWidget.query("entities.list", {
946
1219
  profileSlug: "deal",
947
1220
  limit: 200,
948
1221
  }).catch(() => []);
949
1222
 
950
- // Build your UI here. Plain DOM or React both work.
951
- document.getElementById("root").textContent =
952
- `Loaded ${(items ?? []).length} deals`;
1223
+ createRoot(document.getElementById("root")).render(
1224
+ h("p", null, `Loaded ${(items ?? []).length} deals`)
1225
+ );
953
1226
 
954
1227
  SynapWidget.resize(document.body.scrollHeight);
955
1228
  });
@@ -962,10 +1235,11 @@ Common library choices:
962
1235
 
963
1236
  - **Always call `SynapWidget.onInit()`** — the host will not send data until you register this handler.
964
1237
  - **Call `SynapWidget.resize()`** after rendering to prevent clipping.
965
- - **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`.
966
1240
  - **Transparent background** — `background: transparent` on `body` inherits the host surface color.
967
1241
  - **No external fetch** — the sandbox has no cross-origin access; all data must go through `SynapWidget`.
968
- - **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.
969
1243
 
970
1244
  ---
971
1245