@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.
- package/dist/commands/agent-run.d.ts +47 -0
- package/dist/commands/agent-run.js +302 -0
- package/dist/commands/agent-run.js.map +1 -0
- package/dist/commands/artifact.d.ts +26 -0
- package/dist/commands/artifact.js +76 -0
- package/dist/commands/artifact.js.map +1 -0
- package/dist/commands/automation.d.ts +47 -0
- package/dist/commands/automation.js +600 -0
- package/dist/commands/automation.js.map +1 -0
- package/dist/commands/cell.d.ts +55 -0
- package/dist/commands/cell.js +277 -0
- package/dist/commands/cell.js.map +1 -0
- package/dist/commands/connect.js +41 -7
- package/dist/commands/connect.js.map +1 -1
- package/dist/commands/connections.js +0 -16
- package/dist/commands/connections.js.map +1 -1
- package/dist/commands/connectors.d.ts +31 -0
- package/dist/commands/connectors.js +276 -0
- package/dist/commands/connectors.js.map +1 -0
- package/dist/commands/context.d.ts +22 -0
- package/dist/commands/context.js +151 -0
- package/dist/commands/context.js.map +1 -0
- package/dist/commands/data-extra.d.ts +13 -0
- package/dist/commands/data-extra.js +161 -0
- package/dist/commands/data-extra.js.map +1 -0
- package/dist/commands/data.d.ts +13 -9
- package/dist/commands/data.js +213 -109
- package/dist/commands/data.js.map +1 -1
- package/dist/commands/doc.d.ts +39 -0
- package/dist/commands/doc.js +130 -0
- package/dist/commands/doc.js.map +1 -0
- package/dist/commands/events.d.ts +26 -0
- package/dist/commands/events.js +68 -0
- package/dist/commands/events.js.map +1 -0
- package/dist/commands/explain.d.ts +21 -0
- package/dist/commands/explain.js +248 -0
- package/dist/commands/explain.js.map +1 -0
- package/dist/commands/graph.d.ts +18 -0
- package/dist/commands/graph.js +73 -0
- package/dist/commands/graph.js.map +1 -0
- package/dist/commands/home.d.ts +12 -0
- package/dist/commands/home.js +54 -0
- package/dist/commands/home.js.map +1 -0
- package/dist/commands/init.d.ts +0 -2
- package/dist/commands/init.js +62 -102
- package/dist/commands/init.js.map +1 -1
- package/dist/commands/keys.d.ts +9 -0
- package/dist/commands/keys.js +79 -0
- package/dist/commands/keys.js.map +1 -0
- package/dist/commands/knowledge.d.ts +32 -18
- package/dist/commands/knowledge.js +301 -55
- package/dist/commands/knowledge.js.map +1 -1
- package/dist/commands/observe.d.ts +9 -0
- package/dist/commands/observe.js +90 -0
- package/dist/commands/observe.js.map +1 -0
- package/dist/commands/open.d.ts +15 -0
- package/dist/commands/open.js +44 -0
- package/dist/commands/open.js.map +1 -0
- package/dist/commands/pods.d.ts +5 -0
- package/dist/commands/pods.js +85 -37
- package/dist/commands/pods.js.map +1 -1
- package/dist/commands/proposals-actions.d.ts +7 -0
- package/dist/commands/proposals-actions.js +40 -0
- package/dist/commands/proposals-actions.js.map +1 -0
- package/dist/commands/providers.d.ts +38 -0
- package/dist/commands/providers.js +170 -0
- package/dist/commands/providers.js.map +1 -0
- package/dist/commands/sessions.d.ts +31 -0
- package/dist/commands/sessions.js +224 -0
- package/dist/commands/sessions.js.map +1 -0
- package/dist/commands/skills-commands.d.ts +26 -0
- package/dist/commands/skills-commands.js +186 -0
- package/dist/commands/skills-commands.js.map +1 -0
- package/dist/commands/skills.d.ts +26 -0
- package/dist/commands/skills.js +195 -0
- package/dist/commands/skills.js.map +1 -0
- package/dist/commands/status.js +68 -30
- package/dist/commands/status.js.map +1 -1
- package/dist/commands/subscribe.d.ts +19 -0
- package/dist/commands/subscribe.js +91 -0
- package/dist/commands/subscribe.js.map +1 -0
- package/dist/commands/view.d.ts +57 -0
- package/dist/commands/view.js +194 -0
- package/dist/commands/view.js.map +1 -0
- package/dist/index.d.ts +3 -4
- package/dist/index.js +812 -206
- package/dist/index.js.map +1 -1
- package/dist/lib/agent-templates.d.ts +25 -0
- package/dist/lib/agent-templates.js +148 -0
- package/dist/lib/agent-templates.js.map +1 -0
- package/dist/lib/agents-config.d.ts +4 -1
- package/dist/lib/agents-config.js +19 -2
- package/dist/lib/agents-config.js.map +1 -1
- package/dist/lib/capture-lane.d.ts +49 -0
- package/dist/lib/capture-lane.js +79 -0
- package/dist/lib/capture-lane.js.map +1 -0
- package/dist/lib/hub-client.d.ts +6 -0
- package/dist/lib/hub-client.js +31 -3
- package/dist/lib/hub-client.js.map +1 -1
- package/dist/lib/pod.d.ts +14 -1
- package/dist/lib/pod.js +24 -2
- package/dist/lib/pod.js.map +1 -1
- package/dist/lib/skills-installer.d.ts +1 -1
- package/dist/lib/skills-installer.js +1 -1
- package/dist/lib/skills-installer.js.map +1 -1
- package/dist/lib/targets.d.ts +17 -1
- package/dist/lib/targets.js +713 -63
- package/dist/lib/targets.js.map +1 -1
- package/package.json +2 -1
- package/skills/synap/SKILL.md +357 -82
- package/skills/synap-ui/SKILL.md +12 -9
- package/dist/lib/templates.d.ts +0 -11
- package/dist/lib/templates.js +0 -13
- package/dist/lib/templates.js.map +0 -1
- package/dist/templates/agent-os.json +0 -3090
package/skills/synap/SKILL.md
CHANGED
|
@@ -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.
|
|
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
|
|
60
|
-
synap
|
|
61
|
-
synap
|
|
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.
|
|
132
|
-
Before answering any question about the user's projects, tasks, contacts, decisions, or anything they might have captured —
|
|
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
|
-
**
|
|
209
|
+
**Ask (the one read verb — routes across all substrates):**
|
|
172
210
|
|
|
173
211
|
```bash
|
|
174
|
-
synap
|
|
175
|
-
synap
|
|
176
|
-
synap
|
|
177
|
-
# Omit --workspace
|
|
178
|
-
#
|
|
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
|
-
**
|
|
228
|
+
**Capturing a decision (the AI structures — it never `note`s):**
|
|
191
229
|
|
|
192
230
|
```bash
|
|
193
|
-
synap
|
|
194
|
-
synap
|
|
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
|
-
#
|
|
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
|
-
#
|
|
209
|
-
synap
|
|
210
|
-
|
|
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
|
-
#
|
|
213
|
-
synap
|
|
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`
|
|
217
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
824
|
-
- Dependencies resolved at runtime via **esm.sh import maps** — no build step
|
|
825
|
-
- The host injects a `SynapWidget` bridge for data access
|
|
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
|
-
###
|
|
971
|
+
### Authoring contract
|
|
829
972
|
|
|
830
|
-
|
|
973
|
+
A ViewFrame cell is **one self-contained ES module** (inline in `rendererSource`):
|
|
831
974
|
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
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
|
-
|
|
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/
|
|
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
|
-
"
|
|
850
|
-
"
|
|
851
|
-
"
|
|
852
|
-
"defaultSize": { "w": 8, "h": 6 },
|
|
853
|
-
"
|
|
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
|
-
`
|
|
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
|
-
|
|
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
|
-
|
|
869
|
-
|
|
1089
|
+
|
|
1090
|
+
// List entities
|
|
1091
|
+
const deals = await SynapWidget.query("entities.list", {
|
|
1092
|
+
profileSlug: "deal",
|
|
870
1093
|
limit: 200,
|
|
871
1094
|
});
|
|
872
|
-
|
|
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
|
-
|
|
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" }); //
|
|
883
|
-
SynapWidget.
|
|
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
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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("
|
|
1218
|
+
const items = await SynapWidget.query("entities.list", {
|
|
945
1219
|
profileSlug: "deal",
|
|
946
1220
|
limit: 200,
|
|
947
1221
|
}).catch(() => []);
|
|
948
1222
|
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
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
|
-
- **
|
|
1242
|
+
- **Declare all non-React imports in `deps`** — the host generates the import map from that field.
|
|
968
1243
|
|
|
969
1244
|
---
|
|
970
1245
|
|