@synap-core/cli 1.7.0 → 1.10.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-chat.d.ts +47 -0
- package/dist/commands/agent-chat.js +250 -0
- package/dist/commands/agent-chat.js.map +1 -0
- package/dist/commands/agent-run.d.ts +47 -0
- package/dist/commands/agent-run.js +301 -0
- package/dist/commands/agent-run.js.map +1 -0
- package/dist/commands/agents.js +85 -46
- package/dist/commands/agents.js.map +1 -1
- 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 +601 -0
- package/dist/commands/automation.js.map +1 -0
- package/dist/commands/bridge-setup.d.ts +66 -0
- package/dist/commands/bridge-setup.js +1112 -0
- package/dist/commands/bridge-setup.js.map +1 -0
- package/dist/commands/cap-connections.d.ts +28 -0
- package/dist/commands/cap-connections.js +224 -0
- package/dist/commands/cap-connections.js.map +1 -0
- package/dist/commands/capability.d.ts +89 -0
- package/dist/commands/capability.js +1523 -0
- package/dist/commands/capability.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/centrality.d.ts +20 -0
- package/dist/commands/centrality.js +94 -0
- package/dist/commands/centrality.js.map +1 -0
- package/dist/commands/connect.d.ts +2 -0
- package/dist/commands/connect.js +25 -10
- 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/context.d.ts +22 -0
- package/dist/commands/context.js +149 -0
- package/dist/commands/context.js.map +1 -0
- package/dist/commands/data-extra.d.ts +14 -0
- package/dist/commands/data-extra.js +185 -0
- package/dist/commands/data-extra.js.map +1 -0
- package/dist/commands/data.d.ts +14 -10
- package/dist/commands/data.js +353 -192
- package/dist/commands/data.js.map +1 -1
- package/dist/commands/diagnose.d.ts +20 -0
- package/dist/commands/diagnose.js +143 -0
- package/dist/commands/diagnose.js.map +1 -0
- package/dist/commands/digest.d.ts +17 -0
- package/dist/commands/digest.js +121 -0
- package/dist/commands/digest.js.map +1 -0
- package/dist/commands/discover.d.ts +2 -0
- package/dist/commands/discover.js +25 -8
- package/dist/commands/discover.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 +69 -0
- package/dist/commands/events.js.map +1 -0
- package/dist/commands/explain.d.ts +21 -0
- package/dist/commands/explain.js +251 -0
- package/dist/commands/explain.js.map +1 -0
- package/dist/commands/facet.d.ts +29 -0
- package/dist/commands/facet.js +113 -0
- package/dist/commands/facet.js.map +1 -0
- package/dist/commands/graph.d.ts +18 -0
- package/dist/commands/graph.js +74 -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/import.d.ts +38 -0
- package/dist/commands/import.js +440 -0
- package/dist/commands/import.js.map +1 -0
- package/dist/commands/init.d.ts +0 -2
- package/dist/commands/init.js +97 -118
- package/dist/commands/init.js.map +1 -1
- package/dist/commands/keys.d.ts +9 -0
- package/dist/commands/keys.js +78 -0
- package/dist/commands/keys.js.map +1 -0
- package/dist/commands/knowledge.d.ts +34 -18
- package/dist/commands/knowledge.js +355 -57
- package/dist/commands/knowledge.js.map +1 -1
- package/dist/commands/launch.d.ts +50 -0
- package/dist/commands/launch.js +790 -0
- package/dist/commands/launch.js.map +1 -0
- package/dist/commands/lens.d.ts +45 -0
- package/dist/commands/lens.js +386 -0
- package/dist/commands/lens.js.map +1 -0
- package/dist/commands/market.d.ts +209 -0
- package/dist/commands/market.js +1368 -0
- package/dist/commands/market.js.map +1 -0
- package/dist/commands/mcp.d.ts +42 -0
- package/dist/commands/mcp.js +184 -0
- package/dist/commands/mcp.js.map +1 -0
- package/dist/commands/observe.d.ts +9 -0
- package/dist/commands/observe.js +93 -0
- package/dist/commands/observe.js.map +1 -0
- package/dist/commands/open.d.ts +32 -0
- package/dist/commands/open.js +85 -0
- package/dist/commands/open.js.map +1 -0
- package/dist/commands/pod.d.ts +100 -0
- package/dist/commands/pod.js +359 -0
- package/dist/commands/pod.js.map +1 -0
- package/dist/commands/pods.d.ts +6 -0
- package/dist/commands/pods.js +160 -50
- package/dist/commands/pods.js.map +1 -1
- package/dist/commands/proposals-actions.d.ts +7 -0
- package/dist/commands/proposals-actions.js +77 -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/purge.d.ts +23 -0
- package/dist/commands/purge.js +148 -0
- package/dist/commands/purge.js.map +1 -0
- package/dist/commands/raycast.d.ts +15 -0
- package/dist/commands/raycast.js +153 -0
- package/dist/commands/raycast.js.map +1 -0
- package/dist/commands/sessions.d.ts +31 -0
- package/dist/commands/sessions.js +248 -0
- package/dist/commands/sessions.js.map +1 -0
- package/dist/commands/skill-manage.d.ts +21 -0
- package/dist/commands/skill-manage.js +195 -0
- package/dist/commands/skill-manage.js.map +1 -0
- package/dist/commands/skills-commands.d.ts +26 -0
- package/dist/commands/skills-commands.js +187 -0
- package/dist/commands/skills-commands.js.map +1 -0
- package/dist/commands/skills.d.ts +26 -0
- package/dist/commands/skills.js +196 -0
- package/dist/commands/skills.js.map +1 -0
- package/dist/commands/status.d.ts +3 -1
- package/dist/commands/status.js +231 -41
- package/dist/commands/status.js.map +1 -1
- package/dist/commands/statusline.d.ts +24 -0
- package/dist/commands/statusline.js +450 -0
- package/dist/commands/statusline.js.map +1 -0
- 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/tools.d.ts +30 -0
- package/dist/commands/tools.js +315 -0
- package/dist/commands/tools.js.map +1 -0
- package/dist/commands/versions.d.ts +27 -0
- package/dist/commands/versions.js +277 -0
- package/dist/commands/versions.js.map +1 -0
- package/dist/commands/view.d.ts +57 -0
- package/dist/commands/view.js +195 -0
- package/dist/commands/view.js.map +1 -0
- package/dist/commands/whoami.d.ts +21 -0
- package/dist/commands/whoami.js +129 -0
- package/dist/commands/whoami.js.map +1 -0
- package/dist/index.d.ts +3 -4
- package/dist/index.js +1524 -202
- 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/approval-poll.d.ts +42 -0
- package/dist/lib/approval-poll.js +52 -0
- package/dist/lib/approval-poll.js.map +1 -0
- package/dist/lib/auth.d.ts +29 -6
- package/dist/lib/auth.js +105 -94
- package/dist/lib/auth.js.map +1 -1
- package/dist/lib/bundle-version.d.ts +11 -0
- package/dist/lib/bundle-version.js +33 -0
- package/dist/lib/bundle-version.js.map +1 -0
- package/dist/lib/capture-lane.d.ts +67 -0
- package/dist/lib/capture-lane.js +136 -0
- package/dist/lib/capture-lane.js.map +1 -0
- package/dist/lib/cp-packages.d.ts +121 -0
- package/dist/lib/cp-packages.js +218 -0
- package/dist/lib/cp-packages.js.map +1 -0
- package/dist/lib/describe-lens.d.ts +46 -0
- package/dist/lib/describe-lens.js +33 -0
- package/dist/lib/describe-lens.js.map +1 -0
- package/dist/lib/hub-client.d.ts +84 -1
- package/dist/lib/hub-client.js +502 -22
- package/dist/lib/hub-client.js.map +1 -1
- package/dist/lib/installed.d.ts +51 -0
- package/dist/lib/installed.js +70 -0
- package/dist/lib/installed.js.map +1 -0
- package/dist/lib/next-steps.d.ts +61 -0
- package/dist/lib/next-steps.js +146 -0
- package/dist/lib/next-steps.js.map +1 -0
- package/dist/lib/pod.d.ts +104 -19
- package/dist/lib/pod.js +245 -57
- package/dist/lib/pod.js.map +1 -1
- package/dist/lib/project-ref.d.ts +95 -0
- package/dist/lib/project-ref.js +144 -0
- package/dist/lib/project-ref.js.map +1 -0
- package/dist/lib/project.d.ts +35 -0
- package/dist/lib/project.js +39 -0
- package/dist/lib/project.js.map +1 -0
- package/dist/lib/session-lens.d.ts +33 -0
- package/dist/lib/session-lens.js +76 -0
- package/dist/lib/session-lens.js.map +1 -0
- package/dist/lib/skill-import.d.ts +40 -0
- package/dist/lib/skill-import.js +203 -0
- package/dist/lib/skill-import.js.map +1 -0
- package/dist/lib/skills-installer.d.ts +16 -5
- package/dist/lib/skills-installer.js +132 -18
- package/dist/lib/skills-installer.js.map +1 -1
- package/dist/lib/suite.d.ts +28 -0
- package/dist/lib/suite.js +29 -0
- package/dist/lib/suite.js.map +1 -0
- package/dist/lib/superwhisper-import.d.ts +40 -0
- package/dist/lib/superwhisper-import.js +389 -0
- package/dist/lib/superwhisper-import.js.map +1 -0
- package/dist/lib/targets.d.ts +66 -2
- package/dist/lib/targets.js +914 -132
- package/dist/lib/targets.js.map +1 -1
- package/dist/lib/unwrapList.d.ts +1 -0
- package/dist/lib/unwrapList.js +20 -0
- package/dist/lib/unwrapList.js.map +1 -0
- package/dist/utils/logger.d.ts +2 -0
- package/dist/utils/logger.js +10 -2
- package/dist/utils/logger.js.map +1 -1
- package/package.json +10 -4
- package/skills/agent-os/SKILL.md +212 -0
- package/skills/manifest.json +5 -0
- package/skills/onboard/SKILL.md +186 -0
- package/skills/synap/SKILL.md +1524 -104
- package/skills/synap/_order.txt +37 -0
- package/skills/synap/_skill.yaml +24 -0
- package/skills/synap/authentication.md +10 -0
- package/skills/synap/automations.md +264 -0
- package/skills/synap/capabilities.md +107 -0
- package/skills/synap/capture.md +10 -0
- package/skills/synap/cli-operations.md +107 -0
- package/skills/synap/common-mistakes.md +12 -0
- package/skills/synap/creative-loop.md +43 -0
- package/skills/synap/crm.md +117 -0
- package/skills/synap/diagnostics.md +35 -0
- package/skills/synap/digesting-a-channel.md +38 -0
- package/skills/synap/document-embeds.md +123 -0
- package/skills/synap/escalation-ladder.md +71 -0
- package/skills/synap/focus-sessions.md +47 -0
- package/skills/synap/governance-rules.md +64 -0
- package/skills/synap/governance.md +6 -6
- package/skills/synap/graph-gardening.md +21 -0
- package/skills/synap/inline-patterns.md +38 -0
- package/skills/synap/lenses.md +38 -0
- package/skills/synap/linking-principle.md +39 -0
- package/skills/synap/linking.md +1 -0
- package/skills/synap/mental-model.md +45 -0
- package/skills/synap/more.md +7 -0
- package/skills/synap/multi-entity-capture.md +10 -0
- package/skills/synap/next-action.md +37 -0
- package/skills/synap/north-star.md +17 -0
- package/skills/synap/operating-mode-rules.md +45 -0
- package/skills/synap/operating-mode.md +9 -0
- package/skills/synap/quick-reference.md +67 -0
- package/skills/synap/reading.md +43 -0
- package/skills/synap/recap.md +21 -0
- package/skills/synap/reflexes.md +46 -0
- package/skills/synap/response-style.md +21 -0
- package/skills/synap/scope.md +13 -0
- package/skills/synap/showing.md +17 -0
- package/skills/synap/user-model.md +25 -0
- package/skills/synap/viewframe-cells.md +290 -0
- package/skills/synap/work-flow.md +113 -0
- package/skills/synap/worked-examples.md +53 -0
- package/skills/synap/workspace-design.md +45 -0
- package/skills/synap/workspace-edges.md +49 -0
- package/skills/synap/writes.md +156 -0
- package/skills/synap-schema/SKILL.md +97 -6
- package/skills/synap-schema/_order.txt +13 -0
- package/skills/synap-schema/_skill.yaml +22 -0
- package/skills/synap-schema/before-you-touch.md +14 -0
- package/skills/synap-schema/common-mistakes.md +9 -0
- package/skills/synap-schema/creating-profiles.md +17 -0
- package/skills/synap-schema/discover-profiles.md +24 -0
- package/skills/synap-schema/entity-scope.md +8 -0
- package/skills/synap-schema/extend-vs-create.md +31 -0
- package/skills/synap-schema/more.md +6 -0
- package/skills/synap-schema/overlay-properties.md +22 -0
- package/skills/synap-schema/profiles.md +3 -0
- package/skills/synap-schema/property-defs.md +29 -0
- package/skills/synap-schema/read-before-write.md +50 -0
- package/skills/synap-schema/relations.md +18 -0
- package/skills/synap-schema/worked-example.md +30 -0
- package/skills/synap-ui/SKILL.md +38 -11
- package/skills/synap-ui/_order.txt +13 -0
- package/skills/synap-ui/_skill.yaml +23 -0
- package/skills/synap-ui/ai-companion.md +27 -0
- package/skills/synap-ui/arranging-bento.md +8 -0
- package/skills/synap-ui/before-you-build.md +20 -0
- package/skills/synap-ui/bento-layouts.md +35 -0
- package/skills/synap-ui/common-mistakes.md +10 -0
- package/skills/synap-ui/creating-views.md +26 -0
- package/skills/synap-ui/four-builds.md +24 -0
- package/skills/synap-ui/more.md +7 -0
- package/skills/synap-ui/proposing-vs-extending.md +11 -0
- package/skills/synap-ui/view-list.md +24 -0
- package/skills/synap-ui/views.md +3 -0
- package/skills/synap-ui/worked-example.md +18 -0
- package/skills/synap-ui/workspaces.md +47 -0
- package/templates/automations/bookmark-enrichment.automation.json +44 -0
- package/templates/automations/mail-feed.automation.json +86 -0
- package/templates/automations/url-bookmark-capture.automation.json +70 -0
- package/templates/skills/README.md +21 -0
- package/templates/skills/authoring-automations/SKILL.md +95 -0
- package/templates/skills/authoring-automations/reference/node-matrix.md +49 -0
- package/templates/skills/authoring-capabilities/SKILL.md +93 -0
- package/templates/skills/authoring-capabilities/reference/template-anatomy.md +70 -0
- package/templates/skills/authoring-playbooks/SKILL.md +102 -0
- package/templates/skills/authoring-playbooks/reference/stages-contract.md +35 -0
- package/templates/skills/authoring-playbooks/reference/worked-example.md +64 -0
- package/templates/skills/bookmark-enrichment/SKILL.md +40 -0
- package/templates/skills/coordinating-agents/SKILL.md +32 -0
- package/templates/skills/coordinating-branches/SKILL.md +27 -0
- package/templates/skills/mapping-the-server/SKILL.md +54 -0
- package/templates/skills/reading-client-progress/SKILL.md +65 -0
- package/templates/skills/routing-to-channels/SKILL.md +54 -0
- package/templates/skills/stellar-scf-grants-advisor/ASSOCIATE-CHEATSHEET.md +133 -0
- package/templates/skills/stellar-scf-grants-advisor/ASSOCIATE-ONBOARDING-GUIDE.md +761 -0
- package/templates/skills/stellar-scf-grants-advisor/CLIENT-FAQ.md +181 -0
- package/templates/skills/stellar-scf-grants-advisor/PARTNER-HANDOVER.md +130 -0
- package/templates/skills/stellar-scf-grants-advisor/SKILL.md +102 -0
- package/templates/skills/stellar-scf-grants-advisor/examples/README.md +43 -0
- package/templates/skills/stellar-scf-grants-advisor/examples/chat-summary-templates.md +158 -0
- package/templates/skills/stellar-scf-grants-advisor/examples/corpus-comparable-rows.md +136 -0
- package/templates/skills/stellar-scf-grants-advisor/examples/q10-team-rewrites.md +109 -0
- package/templates/skills/stellar-scf-grants-advisor/examples/q2-description-rewrites.md +88 -0
- package/templates/skills/stellar-scf-grants-advisor/examples/q6-integration-rewrites.md +107 -0
- package/templates/skills/stellar-scf-grants-advisor/examples/tranche-structures.md +176 -0
- package/templates/skills/stellar-scf-grants-advisor/reference/01-methodology.md +96 -0
- package/templates/skills/stellar-scf-grants-advisor/reference/02-scoring-framework.md +199 -0
- package/templates/skills/stellar-scf-grants-advisor/reference/03-rejection-patterns.md +241 -0
- package/templates/skills/stellar-scf-grants-advisor/reference/04-winner-patterns.md +120 -0
- package/templates/skills/stellar-scf-grants-advisor/reference/05-comeback-patterns.md +92 -0
- package/templates/skills/stellar-scf-grants-advisor/reference/06-budget-benchmarks.md +125 -0
- package/templates/skills/stellar-scf-grants-advisor/reference/07-integration-list.md +147 -0
- package/templates/skills/stellar-scf-grants-advisor/reference/08-abstract-fields.md +258 -0
- package/templates/skills/stellar-scf-grants-advisor/reference/09-build-fields.md +259 -0
- package/templates/skills/stellar-scf-grants-advisor/reference/10-corpus-winners.md +135 -0
- package/templates/skills/stellar-scf-grants-advisor/reference/11-corpus-rejects.md +267 -0
- package/templates/skills/stellar-scf-grants-advisor/reference/12-client-communication.md +131 -0
- package/templates/skills/stellar-scf-grants-advisor/reference/13-excel-output-spec.md +199 -0
- package/templates/skills/stellar-scf-grants-advisor/reference/14-worked-examples.md +160 -0
- package/templates/skills/stellar-scf-grants-advisor/templates/abstract-review-builder.py +253 -0
- package/templates/skills/stellar-scf-grants-advisor/templates/build-review-builder.py +213 -0
- package/dist/commands/finish.d.ts +0 -25
- package/dist/commands/finish.js +0 -274
- package/dist/commands/finish.js.map +0 -1
- package/dist/commands/openclaw.d.ts +0 -40
- package/dist/commands/openclaw.js +0 -1148
- package/dist/commands/openclaw.js.map +0 -1
- package/dist/commands/security-audit.d.ts +0 -12
- package/dist/commands/security-audit.js +0 -100
- package/dist/commands/security-audit.js.map +0 -1
- package/dist/lib/hardening.d.ts +0 -18
- package/dist/lib/hardening.js +0 -214
- package/dist/lib/hardening.js.map +0 -1
- 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/node_modules/@synap/hub-rest-client/dist/index.cjs +0 -881
- package/node_modules/@synap/hub-rest-client/dist/index.d.cts +0 -907
- package/node_modules/@synap/hub-rest-client/dist/index.d.ts +0 -907
- package/node_modules/@synap/hub-rest-client/dist/index.js +0 -851
- package/node_modules/@synap/hub-rest-client/package.json +0 -45
- package/node_modules/@synap/hub-rest-client/src/client.ts +0 -1143
- package/node_modules/@synap/hub-rest-client/src/errors.ts +0 -30
- package/node_modules/@synap/hub-rest-client/src/index.ts +0 -111
- package/node_modules/@synap/hub-rest-client/src/setup.ts +0 -77
- package/node_modules/@synap/hub-rest-client/src/types.ts +0 -639
package/skills/synap/SKILL.md
CHANGED
|
@@ -35,41 +35,215 @@ You are connected to a **Synap Data Pod** at `{SYNAP_POD_URL}`. All requests use
|
|
|
35
35
|
|
|
36
36
|
Your job is to turn unstructured input into a **connected** knowledge graph. Isolated entities are anti-value. Every entity you create should link to at least one other entity.
|
|
37
37
|
|
|
38
|
+
---
|
|
39
|
+
|
|
40
|
+
## Reflexes — the two habits, every session
|
|
41
|
+
|
|
42
|
+
> Canonical source — MCP instructions and other surfaces derive from this file.
|
|
43
|
+
|
|
44
|
+
You are connected to the user's Synap pod — their sovereign personal data brain
|
|
45
|
+
(the source of truth about their life, work, projects, people, and preferences).
|
|
46
|
+
Two reflexes hold on every session, on every door (MCP, IS, CLI, HTTP):
|
|
47
|
+
|
|
48
|
+
- **Recall — BEFORE any non-trivial task.** Before answering anything about the
|
|
49
|
+
user's own world, and before creating anything, call `synap_ask` (CLI:
|
|
50
|
+
`synap ask`, REST: `POST /api/hub/knowledge/ask`) to recall what the pod
|
|
51
|
+
already knows. Prefer it over your own assumptions or training data — asking
|
|
52
|
+
first also avoids duplicate creates.
|
|
53
|
+
- **Capture — AFTER you learn something durable.** A fact, a decision, a new
|
|
54
|
+
person/company/task, a stated preference — call `synap_capture` (CLI:
|
|
55
|
+
`synap capture`) to write it back. Don't wait to be asked; this is how the
|
|
56
|
+
second brain grows.
|
|
57
|
+
|
|
58
|
+
Run `synap_orient` (CLI: `synap orient`) once per session to see the available
|
|
59
|
+
workspaces, projects, and entity types before acting.
|
|
60
|
+
|
|
61
|
+
**Writes are governed: a `"proposed"` response is normal, never an error.** It
|
|
62
|
+
means the write is queued for the user's review — like a PR, not a failure. Keep
|
|
63
|
+
working; see `writes.md` for the full governance contract and `inline-patterns.md`
|
|
64
|
+
for how to surface a proposal's review link in a Companion reply.
|
|
65
|
+
|
|
66
|
+
**No private scratchpad.** Everything you learn goes into the shared graph, not a hidden note. Capture a proven tool-fact into `knowledge` immediately; PROMOTE it into a curated skill only once it's proven reusable — a skill is a versioned artifact (one capability, when-to-use + do/don't), never an append-anything log.
|
|
67
|
+
|
|
68
|
+
## Escalation ladder (keep in a corner of your head)
|
|
69
|
+
|
|
70
|
+
You can always escalate — never dead-end on "I can't." Full detail: `escalation-ladder.md`.
|
|
71
|
+
|
|
72
|
+
- **L0 Reflexes** — recall before, capture after, proposed ≠ error
|
|
73
|
+
- **L1 OPERATE on data** — capture, create_entity, link, attach KNOWN facets, sessions
|
|
74
|
+
- **L2 DISCOVER before invent** — list_profiles, list_capabilities, market.search (capability|template|automation|cell)
|
|
75
|
+
- **L3 MUTATE meta-model (proposal-gated)** — only if L2 empty for the need:
|
|
76
|
+
define_role, create_property_def/profile, create_view, create_workspace, market.install.
|
|
77
|
+
**Template FIRST for new domains:** market.search(kind:template) before freehand create_workspace
|
|
78
|
+
- **L4 CRYSTALLIZE after proof** — promote_session_to_playbook, promote_cell_to_renderer, create_playbook.
|
|
79
|
+
Never crystallize a one-off that hasn't succeeded once
|
|
80
|
+
|
|
81
|
+
**Gates:**
|
|
82
|
+
|
|
83
|
+
- Blocked / can't express need → L2 then L3 propose (never dead-end error; never silent invent)
|
|
84
|
+
- Success / repeatable pattern → one structural suggestion (question first if speculative)
|
|
85
|
+
- Capture placement routes to EXISTING lenses only — never invent a workspace from capture
|
|
86
|
+
|
|
87
|
+
---
|
|
88
|
+
|
|
89
|
+
# Escalation ladder — discover → invent under proposal → crystallize after proof
|
|
90
|
+
|
|
91
|
+
The always-on brief lives in `reflexes.md`. This file is the full HOW when you need more than the corner-of-your-head reminder.
|
|
92
|
+
|
|
93
|
+
## Why it exists
|
|
94
|
+
|
|
95
|
+
Agents fail in two ways: (1) dead-end ("I can't do that") when the substrate could express the need after discovery or a proposed meta change, and (2) silent invent (minting workspaces/profiles/views without checking what already exists). The ladder is the habit that prevents both. Soft teaching only — no hard tool filtering by tier.
|
|
96
|
+
|
|
97
|
+
## Levels
|
|
98
|
+
|
|
99
|
+
### L0 — Reflexes (always)
|
|
100
|
+
|
|
101
|
+
Recall before non-trivial work (`synap_ask` / search). Capture durable learning after. Treat `"proposed"` as success-in-review, not an error. Orient once per session.
|
|
102
|
+
|
|
103
|
+
### L1 — OPERATE on data
|
|
104
|
+
|
|
105
|
+
Default mode: work with what already exists.
|
|
106
|
+
|
|
107
|
+
- Capture free text, create/update entities, link, attach **known** facets
|
|
108
|
+
- Start/update sessions when the work is a unit with a deliverable
|
|
109
|
+
- Prefer existing profiles, views, capabilities, playbooks over inventing structure
|
|
110
|
+
|
|
111
|
+
### L2 — DISCOVER before invent
|
|
112
|
+
|
|
113
|
+
When the tool list or current schema doesn't express the need — **search before minting**:
|
|
114
|
+
|
|
115
|
+
1. `list_profiles` / `list_views` / `list_capabilities({query})` in the active lenses
|
|
116
|
+
2. `market.search({query, kind?})` over `capability` | `template` | `automation` | `cell`
|
|
117
|
+
3. Load the relevant skill (`load_skill` / discover_tools) if the HOW is unclear
|
|
118
|
+
|
|
119
|
+
Only if L2 returns empty for the real need do you climb to L3.
|
|
120
|
+
|
|
121
|
+
### L3 — MUTATE the meta-model (proposal-gated)
|
|
122
|
+
|
|
123
|
+
Extend the substrate so the need becomes expressible. Always governed — expect `"proposed"`.
|
|
124
|
+
|
|
125
|
+
| Need | Prefer | Tool sketch |
|
|
126
|
+
| ------------------ | -------------------- | --------------------------------------------------------------------------------------- |
|
|
127
|
+
| Role/hat missing | Existing role attach | `define_role` only after `list_profiles` empty for that role |
|
|
128
|
+
| Field missing | Existing property | `create_property_def` |
|
|
129
|
+
| Kind missing | Closest parent kind | `create_profile` (extend, don't fork) |
|
|
130
|
+
| View missing | Existing view | `list_views` first, then `create_view` (recovery or proactive) |
|
|
131
|
+
| Domain missing | **Template** | `market.search(kind:template)` → install/propose **before** freehand `create_workspace` |
|
|
132
|
+
| Capability missing | Marketplace | `market.install` (always proposes for agents) |
|
|
133
|
+
|
|
134
|
+
**Template-before-workspace (hard rule in teaching):** new operational domains start as marketplace templates when one fits. Freehand workspace creation is last resort after the four workspace-design conditions hold (`workspace-design.md`). Capture never invents a workspace — placement only routes into existing lenses.
|
|
135
|
+
|
|
136
|
+
### L4 — CRYSTALLIZE after proof
|
|
137
|
+
|
|
138
|
+
After a one-off has succeeded and is clearly repeatable:
|
|
139
|
+
|
|
140
|
+
- Session that worked → `promote_session_to_playbook`
|
|
141
|
+
- Cell that presents well for a type/slot → `promote_cell_to_renderer`
|
|
142
|
+
- Repeatable process authored deliberately → `create_playbook`
|
|
143
|
+
|
|
144
|
+
Never crystallize a speculative or failed one-off. One structural suggestion at a time; if speculative, ask first (`creative-loop.md`).
|
|
145
|
+
|
|
146
|
+
## Decision gates (cheat sheet)
|
|
147
|
+
|
|
148
|
+
```
|
|
149
|
+
Can I do it with existing data/tools?
|
|
150
|
+
yes → L1
|
|
151
|
+
no → L2 discover
|
|
152
|
+
found → use / install (propose) / enable
|
|
153
|
+
empty → L3 propose meta change (never silent invent)
|
|
154
|
+
Did a one-off just succeed and will recur?
|
|
155
|
+
yes → offer L4 (question if speculative)
|
|
156
|
+
no → leave it as a one-off
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
Blocked path: **never** invent silently; **never** stop at a dead-end error — climb L2→L3 and propose. Success path: one clear structural nudge, not a cascade of schema changes.
|
|
160
|
+
|
|
161
|
+
---
|
|
162
|
+
|
|
38
163
|
## Mental model
|
|
39
164
|
|
|
40
|
-
Synap is a typed knowledge graph.
|
|
165
|
+
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.
|
|
166
|
+
|
|
167
|
+
### Where to write what — the three lanes (decide by KIND)
|
|
168
|
+
|
|
169
|
+
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.
|
|
170
|
+
|
|
171
|
+
**Known fields → typed create.** If you already know the profileSlug and the values, use `synap create entity` / `synap_create_entity` (or typed `capture --type` for a knowledge entry). Reach for free-text `capture "…"` only for an unstructured blob you haven't parsed — it runs an AI pipeline that can degrade to one flat `note`. 'Always capture into a lane' means _don't leave it unstructured_, not _always use the free-text pipeline_.
|
|
172
|
+
|
|
173
|
+
| If it… | Lane | Where it goes | Governance |
|
|
174
|
+
| ------------------------------------------------------------------------------------------------------------------------------- | -------------------- | ---------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------- |
|
|
175
|
+
| **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) |
|
|
176
|
+
| **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 |
|
|
177
|
+
| **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 |
|
|
178
|
+
|
|
179
|
+
> **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.
|
|
180
|
+
|
|
181
|
+
> **Read the write outcome — it guides your next move (it never blocks you).** Every write (`capture`, `observe`, `create entity`, `create relation`, `note`) reports one of two outcomes (and `--json` carries `"outcome"`):
|
|
182
|
+
>
|
|
183
|
+
> - **`stored`** → it's **live now**, recallable via `synap ask`.
|
|
184
|
+
> - **`proposed`** → queued for the human's review, **like a git PR — not a failure, not a block.** Keep working: compose a whole graph of proposed changes in one session (reference the proposed entities, link them, add more) — they're staged together and go live when the human approves the batch. The only thing to remember: it's _under review_, so don't tell the user it's already applied. (Inferences about the user and writes to real workspaces are gated by design — expected, normal.)
|
|
185
|
+
|
|
186
|
+
> **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.
|
|
187
|
+
|
|
188
|
+
### Data layers — the graph itself
|
|
41
189
|
|
|
42
190
|
| Layer | What it is | When to use |
|
|
43
191
|
| ------------- | ---------------------------------------------- | -------------------------------------------------------- |
|
|
44
192
|
| **Entities** | Typed structured nodes (task, person, …) | Anything worth filtering, sorting, or linking |
|
|
45
193
|
| **Relations** | Typed edges between entities | Making the graph traversable |
|
|
46
194
|
| **Documents** | Long-form markdown attached to an entity | Meeting notes, research writeups, articles |
|
|
47
|
-
| **Memory** | Atomic facts, no structure | Preferences, context, ephemeral notes |
|
|
48
195
|
| **Threads** | Channel conversations, optional entity context | Posting to the user's personal AI channel |
|
|
49
196
|
| **Proposals** | Writes queued for human approval | Governance for some mutations (not an error — see below) |
|
|
50
197
|
|
|
198
|
+
### Key profiles for AI use
|
|
199
|
+
|
|
200
|
+
| Profile slug | Scope | Who writes | Purpose |
|
|
201
|
+
| ------------------ | --------- | -------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
202
|
+
| `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. |
|
|
203
|
+
| `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`. |
|
|
204
|
+
| `user_observation` | pod | AI only | Durable user model — habits, communication style, preferences |
|
|
205
|
+
| `decision` | pod | human + AI | Architectural decisions with rationale |
|
|
206
|
+
| `research` | pod | AI | Investigation with sources + conclusion |
|
|
207
|
+
| `question` | pod | human + AI | Open inquiry, closed when a decision answers it |
|
|
208
|
+
|
|
209
|
+
---
|
|
210
|
+
|
|
51
211
|
## Quick reference — 90% of tasks in 30 lines
|
|
52
212
|
|
|
53
213
|
```bash
|
|
54
214
|
# CLI (preferred — auth automatic, --json = clean output)
|
|
55
|
-
synap orient --json # discover userId + workspaces
|
|
56
|
-
synap
|
|
215
|
+
synap orient --json # discover userId + workspaces + projects
|
|
216
|
+
synap lens # where am I? workspace + project + session (this Claude session)
|
|
217
|
+
synap use <workspace-name-or-id> # focus a workspace (this session)
|
|
57
218
|
synap create entity --profile=task --name="…" --props='{"status":"todo","priority":"high"}' --json
|
|
58
219
|
synap set entity <id> --props='{"status":"done"}' --json # merge-patch (only changed keys)
|
|
59
|
-
synap
|
|
60
|
-
synap
|
|
61
|
-
synap
|
|
220
|
+
synap ask "your question" --json # THE read verb — routes to the right store(s) + shows which answered
|
|
221
|
+
synap capture --type=lesson --claim="…" --json # Work lane (default) — domain knowledge → active workspace
|
|
222
|
+
synap capture --global --type=reference --claim="…" --json # Global lane — pod-wide cross-cutting runbook (knowledge_keys)
|
|
223
|
+
synap observe write "…" --json # User lane — durable user model (inferences proposed)
|
|
62
224
|
```
|
|
63
225
|
|
|
226
|
+
**The canonical verbs:** `ask` (read) · `capture` (structured write — pick a lane:
|
|
227
|
+
Work default / `--global` / `observe` for User) · `orient` (bootstrap). `note` exists
|
|
228
|
+
for the HUMAN's raw "dump now, structure later" inbox — **the AI always `capture`s
|
|
229
|
+
instead.** **Reading is one verb: `ask`** — it classifies your
|
|
230
|
+
question and routes across the three memory substrates (semantic = the typed entity
|
|
231
|
+
graph, procedural = how-to docs, episodic = raw captures), returning one answer
|
|
232
|
+
tagged with which substrate(s) answered (and which, if any, were unavailable). Don't
|
|
233
|
+
pick a store; `ask` picks for you and tells you what it did. (`graph` for an explicit
|
|
234
|
+
traversal and `get`/`show`/`browse` for direct lookups remain; there is no `search`
|
|
235
|
+
or `recall` — `ask` is the door.)
|
|
236
|
+
|
|
64
237
|
```bash
|
|
65
238
|
# REST (when no Bash access)
|
|
66
|
-
POST /api/hub/entities body: { userId, workspaceId?, profileSlug, title, properties }
|
|
239
|
+
POST /api/hub/entities body: { userId, workspaceId?, profileSlug, title, description?, properties?, content?, projectId?, facets?, source? }
|
|
67
240
|
PATCH /api/hub/entities/{id} body: { userId, properties } ← deep-merges, send only changed keys
|
|
68
241
|
POST /api/hub/documents body: { userId, workspaceId?, title, content, entityId? }
|
|
69
242
|
PATCH /api/hub/documents/{id} body: { userId, title?, content? } ← full content replacement
|
|
70
243
|
POST /api/hub/relations body: { userId, sourceEntityId, targetEntityId, type }
|
|
71
244
|
GET /api/hub/entities?q=…&profileSlug=task&workspaceId=…
|
|
72
245
|
GET /api/hub/entities/{id}/connections?userId=…
|
|
246
|
+
POST /api/hub/knowledge/ask body: { query, workspaceId?, limit? } ← ONE read door, routes across substrates
|
|
73
247
|
POST /api/hub/memory body: { userId, fact }
|
|
74
248
|
GET /api/hub/memory?userId=…&query=…
|
|
75
249
|
```
|
|
@@ -82,11 +256,13 @@ synap discover --profiles --json # CLI: profiles only
|
|
|
82
256
|
```
|
|
83
257
|
|
|
84
258
|
```
|
|
85
|
-
GET /api/hub/discover?userId={userId}&
|
|
86
|
-
→ { profiles: [{ slug, displayName, scope, properties: [{ slug, type,
|
|
259
|
+
GET /api/hub/discover?userId={userId}&profileSlugs=task
|
|
260
|
+
→ { profiles: [{ slug, displayName, scope, properties: [{ slug, type, required, defaultValue?, constraints?, targetProfileSlug? }], createCommand }], commands: {...} }
|
|
87
261
|
```
|
|
88
262
|
|
|
89
|
-
Call
|
|
263
|
+
Call the summary tier once at session start, then load only the profile schemas
|
|
264
|
+
needed for a write. Omit `workspaceId` for base/pod schema; add it only to see
|
|
265
|
+
that workspace's overlays. Do not rely on a static property list — it will drift.
|
|
90
266
|
|
|
91
267
|
**Load more detail on demand** (`GET /api/hub/skills/system?sections=<id>`):
|
|
92
268
|
|
|
@@ -100,13 +276,56 @@ Call this once at session start. The response includes every system profile and
|
|
|
100
276
|
| `synap-ui:widget-catalog` | Available widget kinds and their configSchema |
|
|
101
277
|
| `synap-schema:SKILL` | Creating custom profiles or property definitions |
|
|
102
278
|
|
|
279
|
+
---
|
|
280
|
+
|
|
281
|
+
## Lenses — where you are vs. what you can reach
|
|
282
|
+
|
|
283
|
+
You don't work "inside a workspace" the way you'd work inside a folder. You operate **across the whole pod**, and you **focus** through up to three composable lenses. **Lenses narrow; they never silo.** Omitting them is legal and common — that's pod-wide.
|
|
284
|
+
|
|
285
|
+
| Lens | What it is | How to set (this session) |
|
|
286
|
+
| ------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------ |
|
|
287
|
+
| **Project** | a **company or initiative** — the thing that ties the work together (Synap, a client, a launch). The lens you usually _organize by_. | `synap project use <id>` / `clear` |
|
|
288
|
+
| **Workspace** | an **operational domain** — where data lives (Foundation, CRM, Marketing, Finance, Builder). How the work is separated; the default home for writes. | `synap use <name-or-id>` |
|
|
289
|
+
| **Session** | the **work room** for the current goal (holds goal, deliverables, progress) | `synap session start --goal "…"` / `attach <id>` |
|
|
290
|
+
|
|
291
|
+
**How they compose — this is the whole model:**
|
|
292
|
+
|
|
293
|
+
- A **project spans workspaces**: one company/initiative has a Foundation, a CRM, a Marketing, a Finance… each a different operational lens on the _same_ project.
|
|
294
|
+
- A **workspace spans projects**: the Marketing workspace can hold work for several clients/projects at once.
|
|
295
|
+
- **Membership is per-entity, filed on write.** An entity belongs to a project because it was created/filed **under the project lens** — not because its workspace is "in" the project (there is no workspace→project link). So **set the project lens before writing** work that belongs to an initiative, and it composes into that project from any workspace.
|
|
296
|
+
- Compose either way, or both. That's why they're lenses, not folders: **workspaces exist so that development, finance, marketing, and operations don't pile into one undifferentiated place** — they're the separation that makes the work legible.
|
|
297
|
+
|
|
298
|
+
- **The connection is pod-wide by design.** Your MCP/CLI link is _not_ welded to a workspace — reads default pod-wide, writes default to a sensible workspace. Pass a lens to narrow a single call; the lens is a focus, not a fence.
|
|
299
|
+
- **These are per-Claude-session.** Two concurrent Claude sessions can sit on different projects/workspaces/sessions without colliding. `synap use` here rebinds **this** session only.
|
|
300
|
+
- **Inspect anytime:** `synap lens` → the project + workspace + session this session resolves to.
|
|
301
|
+
|
|
302
|
+
### The "am I in the right place?" reflex
|
|
303
|
+
|
|
304
|
+
**Before the FIRST write of a new unit of work**, check your lens and orient if you're unsure:
|
|
305
|
+
|
|
306
|
+
1. `synap lens` — am I scoped where this work belongs?
|
|
307
|
+
2. If unsure what exists → `synap orient` — it returns a **light lens map**: the projects and the workspaces (names + ids), so you see the shape without a data dump. Never guess IDs. Drill into a workspace's profiles or a project's contents only when you actually need them.
|
|
308
|
+
3. **Connect or create:** if the right project / workspace / session doesn't exist yet, create it. A **session is the normal per-task move**. Creating a **workspace (a new operational domain) is a deliberate, expected move as the work grows** — not something to avoid. A **project, though, is a COMMITMENT WITH GRAVITY**: search existing projects first (`synap orient`) and prefer **linking into an existing one** via `belongs_to_project`. Only create a new project for a real initiative that ties work together — never for a task, plan, repo, or theme (those are entities), and **never for the pod owner's own company** (the company _is_ the pod, not a project inside it). An agent-created project must cite **≥5 existing entities** as evidence or the backend rejects it, and near-duplicate names are rejected with the existing candidates.
|
|
309
|
+
|
|
310
|
+
**Don't re-orient mid-flow.** Once you've oriented and you're in a run of related writes, keep going — re-check only when you **start a new piece of work** or switch domains. The reflex guards the _start_ of work, not every call.
|
|
311
|
+
|
|
312
|
+
### Notice a missing domain — and offer it
|
|
313
|
+
|
|
314
|
+
Because workspaces are how a company separates its operations, a project is sometimes **missing an operational domain it clearly needs**. If the conversation is squarely about an area — sales, content, finance, hiring, ops — and the active project has **no workspace for it**, say so **once, at the end, in one line**, and offer to set it up:
|
|
315
|
+
|
|
316
|
+
> _"This project doesn't have a Marketing workspace yet — want me to spin one up and capture the essentials?"_
|
|
317
|
+
|
|
318
|
+
If they say yes, provision that **one** domain and run its onboarding interview **with the project lens active** (so its entities file into the project) (see the `agent-os` skill — it handles both the whole-company setup and adding a single domain to an existing project). **Offer, don't auto-build.** One nudge per response, and only when the gap is real — never a checklist of everything the project "could" have. **If the user has already declined a domain (this session or before), drop it — don't re-offer.**
|
|
319
|
+
|
|
320
|
+
---
|
|
321
|
+
|
|
103
322
|
## Synap-first operating mode
|
|
104
323
|
|
|
105
324
|
> **MCP clients** (Claude Desktop, Raycast, OpenClaw with MCP): use `synap_*` tool names — they wrap auth and governance automatically. **REST / HTTP clients**: use the endpoints below.
|
|
106
325
|
|
|
107
326
|
These five rules override default assistant behavior when connected to a Synap pod:
|
|
108
327
|
|
|
109
|
-
**1. Orient before acting**
|
|
328
|
+
**1. Orient before acting** _(and check your lens — see "am I in the right place?" above)_
|
|
110
329
|
Run `scripts/orient.sh` or call these endpoints at the start of every session — before searching, before creating, before answering any question about the user's data:
|
|
111
330
|
|
|
112
331
|
```
|
|
@@ -128,8 +347,8 @@ GET /api/hub/discover?userId={userId}&workspaceId={workspaceId}
|
|
|
128
347
|
`scope: "workspace"` = scoped to one workspace (deal, file, capture, custom profiles).
|
|
129
348
|
Each profile includes its full property schema. Use `createCommand` as a template.
|
|
130
349
|
|
|
131
|
-
**2.
|
|
132
|
-
Before answering any question about the user's projects, tasks, contacts, decisions, or anything they might have captured —
|
|
350
|
+
**2. Ask before answering**
|
|
351
|
+
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
352
|
|
|
134
353
|
**3. Save proactively — without waiting to be asked**
|
|
135
354
|
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:
|
|
@@ -146,18 +365,21 @@ Facts about the user — preferences, team, working style, recurring context —
|
|
|
146
365
|
|
|
147
366
|
Properties with `valueType: "entity_id"` are typed links to other entities — see **Linking** below.
|
|
148
367
|
|
|
368
|
+
---
|
|
369
|
+
|
|
149
370
|
## CLI Data Operations (Bash tool)
|
|
150
371
|
|
|
151
372
|
When Claude Code (or any agent with Bash access) is using this skill, prefer the `synap` CLI over raw HTTP calls — auth is automatic, output is clean JSON, no spinners in `--json` mode.
|
|
152
373
|
|
|
153
374
|
**Session context — set once, never repeat:**
|
|
154
375
|
|
|
155
|
-
The CLI
|
|
376
|
+
The CLI inherits your pod + lens automatically; set them once and every later command picks them up. Do NOT pass `--pod-url`, `--api-key`, or `--workspace` on every command. Inside a Claude session, `synap use` / `synap project use` bind **this session's lens** (`~/.synap/lenses/<session_id>.json`) — so concurrent sessions stay independent; outside one, they set the global default (`~/.synap/config.json`).
|
|
156
377
|
|
|
157
378
|
```bash
|
|
158
379
|
synap pods use <profile-name> # switch active pod
|
|
159
|
-
synap use <workspace-id> #
|
|
160
|
-
synap
|
|
380
|
+
synap use <workspace-id> # focus a workspace (this session) — captures land here; it IS the domain
|
|
381
|
+
synap project use <id> # add the project lens (composable)
|
|
382
|
+
synap lens # inspect: workspace + project + session this session resolves to
|
|
161
383
|
```
|
|
162
384
|
|
|
163
385
|
**Always orient first:**
|
|
@@ -168,14 +390,14 @@ synap orient --json
|
|
|
168
390
|
# Never hardcode workspace IDs — discover them here.
|
|
169
391
|
```
|
|
170
392
|
|
|
171
|
-
**
|
|
393
|
+
**Ask (the one read verb — routes across all substrates):**
|
|
172
394
|
|
|
173
395
|
```bash
|
|
174
|
-
synap
|
|
175
|
-
synap
|
|
176
|
-
synap
|
|
177
|
-
# Omit --workspace
|
|
178
|
-
#
|
|
396
|
+
synap ask "project ideas" --json
|
|
397
|
+
synap ask "what did Antoine decide about auth" --workspace=<id> --json
|
|
398
|
+
synap ask "how do I deploy the backend" --json # routes to procedural how-to docs
|
|
399
|
+
# Omit --workspace for pod-wide; include it to scope to one workspace.
|
|
400
|
+
# `ask` classifies intent and unions the right substrate(s) — it replaces search/recall.
|
|
179
401
|
```
|
|
180
402
|
|
|
181
403
|
**Read entities:**
|
|
@@ -187,17 +409,19 @@ synap list entities --profile=task --workspace=<id> --json
|
|
|
187
409
|
synap get entity <id> --json
|
|
188
410
|
```
|
|
189
411
|
|
|
190
|
-
**
|
|
412
|
+
**Capturing a decision (the AI structures — it never `note`s):**
|
|
191
413
|
|
|
192
414
|
```bash
|
|
193
|
-
synap
|
|
194
|
-
synap
|
|
415
|
+
synap capture --type decision --claim "Use Typesense for entity search" --json
|
|
416
|
+
# Retrieve later with the one read verb: synap ask "Typesense decision"
|
|
195
417
|
```
|
|
196
418
|
|
|
419
|
+
> `synap note` is the HUMAN's raw "dump now, structure later" inbox. As the AI, always `capture` into a lane — structuring is your job.
|
|
420
|
+
|
|
197
421
|
**Structured knowledge (durable, typed, searchable — preferred for engineering learnings):**
|
|
198
422
|
|
|
199
423
|
```bash
|
|
200
|
-
#
|
|
424
|
+
# Work lane (default): a domain gotcha/lesson/decision → knowledge entity in the ACTIVE workspace
|
|
201
425
|
synap capture --type gotcha --claim "Hono static routes must come before /:id" \
|
|
202
426
|
--why "First-match routing; dynamic routes eat static ones" \
|
|
203
427
|
--tags "repo:synap-backend,layer:routing" --json
|
|
@@ -205,17 +429,33 @@ synap capture --type gotcha --claim "Hono static routes must come before /:id" \
|
|
|
205
429
|
synap capture --type lesson --claim "code-read ≠ runtime-true for library APIs" \
|
|
206
430
|
--evidence "tldraw 2.4.6 binding API changed silently from props.start.boundShapeId"
|
|
207
431
|
|
|
208
|
-
#
|
|
209
|
-
synap
|
|
210
|
-
|
|
432
|
+
# A quick decision-note is a typed knowledge entry (ek_type=decision):
|
|
433
|
+
synap capture --type decision --claim "Use Typesense for entity search" \
|
|
434
|
+
--why "pgvector deferred to V1; Typesense ships now" --json
|
|
211
435
|
|
|
212
|
-
#
|
|
213
|
-
synap
|
|
436
|
+
# Global lane: a runbook/best-practice that holds across ALL projects → pod-wide knowledge_keys
|
|
437
|
+
synap capture --global --type reference --claim "Always fix the canonical path, never a workaround" \
|
|
438
|
+
--key "principle:root-cause" --json
|
|
439
|
+
|
|
440
|
+
# Retrieve any of it later with the one read verb (it spans every lane):
|
|
441
|
+
synap ask "hono routing gotcha" --json
|
|
442
|
+
```
|
|
443
|
+
|
|
444
|
+
`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`.
|
|
445
|
+
Use `capture` for anything worth remembering across sessions and projects.
|
|
446
|
+
|
|
447
|
+
**Open (the one display door):**
|
|
448
|
+
|
|
449
|
+
```bash
|
|
450
|
+
synap open <id> # resolves type automatically, opens in browser
|
|
451
|
+
synap open entity <id> # open entity detail
|
|
452
|
+
synap open proposal <id> # open proposal review
|
|
453
|
+
synap open view <id> # open a view
|
|
454
|
+
synap open cell <typeKey> # open a registered cell by typeKey
|
|
455
|
+
synap open document <id> # open a document
|
|
214
456
|
```
|
|
215
457
|
|
|
216
|
-
|
|
217
|
-
`synap remember` / `synap recall` (without `--structured`) uses the ephemeral `/memory` store.
|
|
218
|
-
Use structured knowledge for anything worth remembering across sessions and projects.
|
|
458
|
+
The bare-ID form calls `GET /api/hub/resolve/:id` to determine the type before dispatching. Use this when you don't know what type a UUID is — `synap open <id>` always works.
|
|
219
459
|
|
|
220
460
|
**Write:**
|
|
221
461
|
|
|
@@ -231,7 +471,9 @@ synap set entity <id> --props='{"status":"done"}' --json
|
|
|
231
471
|
- Always use `--json` when calling from code — clean stdout, no spinners, machine-parseable
|
|
232
472
|
- Run `synap orient` first to discover workspace IDs — never hardcode them
|
|
233
473
|
- Omit `--workspace` to operate pod-wide; include it to scope to a specific workspace
|
|
234
|
-
- `synap
|
|
474
|
+
- `synap ask` is the one read verb — it routes keyword + semantic + procedural automatically; you never choose a search backend.
|
|
475
|
+
|
|
476
|
+
---
|
|
235
477
|
|
|
236
478
|
---
|
|
237
479
|
|
|
@@ -249,6 +491,8 @@ synap set entity <id> --props='{"status":"done"}' --json
|
|
|
249
491
|
|
|
250
492
|
When you do scope to a workspace, pass `workspaceId` in the create body — the backend respects it. Never pass `workspaceId: null` explicitly to force pod-wide; the profile's `entityScope` decides.
|
|
251
493
|
|
|
494
|
+
---
|
|
495
|
+
|
|
252
496
|
## The work flow — question → research → decision → action
|
|
253
497
|
|
|
254
498
|
AI-assisted work has a shape. When the user is actually _thinking about something_, it flows through four structural nodes. Each is a first-class entity. None of these are optional "nice-to-have" labels — they're the graph that makes the work _durable_ and transferrable between AIs.
|
|
@@ -357,11 +601,13 @@ User: _"I'm trying to figure out whether we should build our own orchestrator or
|
|
|
357
601
|
|
|
358
602
|
Don't interrupt the conversation to ask "should I log this as a question?" — just do it and add a one-line trailer at the end of your response:
|
|
359
603
|
|
|
360
|
-
> (Logged as question on Project Eve. Review:
|
|
604
|
+
> (Logged as question on Project Eve. Review: synap://open/proposal/…)
|
|
361
605
|
|
|
362
606
|
If the creation was auto-approved (entity.create is on the whitelist), there's no proposal; just show a link to the entity:
|
|
363
607
|
|
|
364
|
-
> (Logged as question →
|
|
608
|
+
> (Logged as question → synap://open/entity/ent_question_1)
|
|
609
|
+
|
|
610
|
+
---
|
|
365
611
|
|
|
366
612
|
## Linking — the core principle
|
|
367
613
|
|
|
@@ -399,8 +645,12 @@ POST /api/hub/relations
|
|
|
399
645
|
}
|
|
400
646
|
```
|
|
401
647
|
|
|
648
|
+
**Link entities you reference back to the thread.** When your reply cites an entity or document, connect it to the current conversation with `link_entity_to_thread` / `link_document_to_thread` — one line of why ("linking this because it's directly relevant to what you're building"). It should feel like keeping notes, not running a pipeline.
|
|
649
|
+
|
|
402
650
|
For auto-sync mapping, conventional relation types, and edge cases, read **`linking.md`**.
|
|
403
651
|
|
|
652
|
+
---
|
|
653
|
+
|
|
404
654
|
## Writing — governance in one paragraph
|
|
405
655
|
|
|
406
656
|
Every write returns a `status` field:
|
|
@@ -417,29 +667,201 @@ Every write returns a `status` field:
|
|
|
417
667
|
2. Give them the link to review — `reviewUrl` opens the proposal in Synap Studio. Show the link as-is.
|
|
418
668
|
3. Move on with the conversation. Don't wait or poll.
|
|
419
669
|
|
|
670
|
+
### The `reasoning` field — required, structured, contextual
|
|
671
|
+
|
|
672
|
+
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**.
|
|
673
|
+
|
|
674
|
+
Use this exact structure:
|
|
675
|
+
|
|
676
|
+
```
|
|
677
|
+
Context: [what the user said or what event triggered this write — one sentence]
|
|
678
|
+
Intent: [what this entity or change accomplishes — one sentence]
|
|
679
|
+
Links: [actual entity IDs or slugs this relates to, e.g. "ent_abc, ent_xyz"]
|
|
680
|
+
```
|
|
681
|
+
|
|
682
|
+
For updates, add:
|
|
683
|
+
|
|
684
|
+
```
|
|
685
|
+
Changed: [field] [old value] → [new value]
|
|
686
|
+
```
|
|
687
|
+
|
|
688
|
+
**Example (create):**
|
|
689
|
+
|
|
690
|
+
```
|
|
691
|
+
Context: User asked to track the Acme deal they mentioned in today's call.
|
|
692
|
+
Intent: Creates a deal entity for Acme at lead stage linked to Alice Johnson.
|
|
693
|
+
Links: ent_person_alice_johnson, ent_company_acme
|
|
694
|
+
```
|
|
695
|
+
|
|
696
|
+
**Example (update):**
|
|
697
|
+
|
|
698
|
+
```
|
|
699
|
+
Context: User confirmed the Acme deal moved to proposal stage.
|
|
700
|
+
Intent: Advances the deal through the pipeline so it appears in the proposal view.
|
|
701
|
+
Links: ent_deal_acme, ent_person_alice_johnson
|
|
702
|
+
Changed: dealStage lead → proposal
|
|
703
|
+
```
|
|
704
|
+
|
|
705
|
+
Rules:
|
|
706
|
+
|
|
707
|
+
- One sentence per field. No padding.
|
|
708
|
+
- `Links` must reference real entity IDs or slugs visible in the current context — not descriptions like "the related project".
|
|
709
|
+
- **"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.
|
|
710
|
+
|
|
420
711
|
Example response to the user:
|
|
421
712
|
|
|
422
|
-
> I queued **Delete task "Q2 plan review"** for your review. Destructive actions need your approval. Open it:
|
|
713
|
+
> I queued **Delete task "Q2 plan review"** for your review. Destructive actions need your approval. Open it: synap://open/proposal/prp_abc
|
|
423
714
|
|
|
424
715
|
Auto-approved by default (for agent API keys): `entity.create`, `entity.update`, `document.create`, `relation.create`, `view.create`, `profile.create`, `property_def.create`, `channel.create`, `memory.*`, all reads. Destructive actions (`delete`, `archive`, `purge`) always propose in agent-owned workspaces.
|
|
425
716
|
|
|
426
717
|
For the full whitelist, agent-user semantics, and workspace overrides, read **`governance.md`**.
|
|
427
718
|
|
|
719
|
+
---
|
|
720
|
+
|
|
721
|
+
# Capabilities — discover, run, and the when-blocked reflex
|
|
722
|
+
|
|
723
|
+
Capabilities are the verbs a workspace's connected services and applied
|
|
724
|
+
templates unlock — `gmail_send`, `gmail_search`, `calendar_create`,
|
|
725
|
+
`drive_search`, and so on. They are the bridge between "I can talk about it"
|
|
726
|
+
and "I can actually do it."
|
|
727
|
+
|
|
728
|
+
## Discover: `list_capabilities`
|
|
729
|
+
|
|
730
|
+
MCP: `synap_list_capabilities`. IS: `list_capabilities`. Takes
|
|
731
|
+
`{ query, kind?, limit? }` (plus `workspaceId`).
|
|
732
|
+
|
|
733
|
+
**Search first — never dump-and-eyeball.** A workspace can carry 100+
|
|
734
|
+
capability entries; call with a `query` describing the action you're after
|
|
735
|
+
("send email", "search calendar") and read the ranked, compact results. Don't
|
|
736
|
+
fetch the unfiltered list and scan it yourself.
|
|
737
|
+
|
|
738
|
+
Each entry carries:
|
|
739
|
+
|
|
740
|
+
- `name` (the `verbId` you pass to `run_capability`), `label`, backing tool
|
|
741
|
+
- `paramsSchema` — the shape `parameters` must satisfy; check it before calling
|
|
742
|
+
- `enabled` — `true` means it will run right now. `false`/DRAFT means it's
|
|
743
|
+
installed but the user hasn't approved it yet (Settings → Capabilities)
|
|
744
|
+
|
|
745
|
+
## Run: `run_capability`
|
|
746
|
+
|
|
747
|
+
MCP: `synap_run_capability`. IS: `run_capability`. Pass `verbId` (or
|
|
748
|
+
`skillId`) + `parameters` + `workspaceId`.
|
|
749
|
+
|
|
750
|
+
```json
|
|
751
|
+
{
|
|
752
|
+
"verbId": "gmail_send",
|
|
753
|
+
"parameters": { "to": "…", "subject": "…", "body": "…" },
|
|
754
|
+
"workspaceId": "{workspaceId}"
|
|
755
|
+
}
|
|
756
|
+
```
|
|
757
|
+
|
|
758
|
+
Check `paramsSchema` from discovery before calling — a missing required
|
|
759
|
+
parameter is refused, not guessed.
|
|
760
|
+
|
|
761
|
+
**`proposed` is a normal outcome, not a failure.** A capability run can land
|
|
762
|
+
as a proposal exactly like any other governed write (see `governance.md`):
|
|
763
|
+
tell the user why, share the `reviewUrl`, and don't retry.
|
|
764
|
+
|
|
765
|
+
**Provider results can be 200-with-error-body.** A successful HTTP call to
|
|
766
|
+
Gmail/Calendar/Drive can still carry `result.success: false` +
|
|
767
|
+
`result.error` — an auth expiry, a bad recipient, a quota limit. **Always
|
|
768
|
+
check `result.success`/`result.error` in the response before telling the
|
|
769
|
+
user the action worked.** A 200 status is not proof of success here.
|
|
770
|
+
|
|
771
|
+
## The when-blocked reflex
|
|
772
|
+
|
|
773
|
+
When you cannot do something the user asked for — no matching tool, a
|
|
774
|
+
"not found" verb, "no connection", "not enabled" — do not fabricate a result
|
|
775
|
+
and do not silently give up. Follow this order:
|
|
776
|
+
|
|
777
|
+
1. **Search first.** `list_capabilities({ query })` for what the user actually
|
|
778
|
+
wants. Capabilities are added over time; don't assume today's tool list is
|
|
779
|
+
the ceiling.
|
|
780
|
+
2. **Found but blocked?** If the verb exists but is DRAFT (not enabled) or its
|
|
781
|
+
backing connection is missing, tell the user exactly what to do and where:
|
|
782
|
+
"This needs Gmail connected — enable it in Settings → Capabilities" (or
|
|
783
|
+
the equivalent connect deep-link the error hands you). Don't attempt the
|
|
784
|
+
run again until they've acted.
|
|
785
|
+
3. **Still nothing? Search the marketplace.** `market.search({query, kind?})`
|
|
786
|
+
over what could be _installed_ (capabilities, automations, workspace
|
|
787
|
+
templates, cells) — a cache read, not a live fetch, so it's always fast.
|
|
788
|
+
4. **Found in the marketplace?** `market.install({slug, kind, version?})`. As
|
|
789
|
+
an agent this ALWAYS lands as a reviewable proposal — never auto-installs,
|
|
790
|
+
even with a grant on the verb itself. Share the `reviewUrl`; don't retry.
|
|
791
|
+
5. **Truly nothing, anywhere?** That is escalation-ladder **L2 empty → L3**:
|
|
792
|
+
say precisely what's missing (the action, not "I can't"), offer to capture
|
|
793
|
+
the gap, and if the need is structural (new capability package, template,
|
|
794
|
+
automation), propose via marketplace/install or meta tools — never
|
|
795
|
+
dead-end and never fabricate a result.
|
|
796
|
+
|
|
797
|
+
<!-- brief:start -->
|
|
798
|
+
|
|
799
|
+
When blocked (ladder L2→L3): (1) `list_capabilities({query})` first — never
|
|
800
|
+
assume today's list is the ceiling. (2) Found but DRAFT/no connection → tell
|
|
801
|
+
the user exactly what to enable/connect and where; don't retry until they've
|
|
802
|
+
acted. (3) Still nothing → `market.search({query, kind?})`; `market.install`
|
|
803
|
+
on a hit always proposes for an agent. (4) Truly nothing → say precisely
|
|
804
|
+
what's missing, offer to capture the gap or propose L3 structure — never
|
|
805
|
+
fabricate, never silent give-up. Provider 200-with-error-body: always check
|
|
806
|
+
`result.success`/`result.error` before claiming success.
|
|
807
|
+
|
|
808
|
+
<!-- brief:end -->
|
|
809
|
+
|
|
810
|
+
## Errors — what they mean, what to do next
|
|
811
|
+
|
|
812
|
+
| You see roughly... | Meaning | Next step |
|
|
813
|
+
| ------------------------------------------- | ------------------------------------------------------------------------- | ------------------------------------------------------------------------------- |
|
|
814
|
+
| verb/skill "not found" in this workspace | No capability by that name is registered here | `list_capabilities({query})`; if nothing, tell the user what's missing |
|
|
815
|
+
| capability "not approved" / DRAFT | It exists but the user hasn't enabled it yet | Tell the user to enable it (Settings → Capabilities); don't retry |
|
|
816
|
+
| a required parameter is missing | Your `parameters` didn't satisfy the verb's `paramsSchema` | Re-check `paramsSchema` from discovery, fill the gap, retry once |
|
|
817
|
+
| no connection / credential for this service | The verb needs a connected account (Gmail, Calendar, …) that isn't set up | Hand the user the connect link; don't retry until connected |
|
|
818
|
+
| `status: "proposed"` | Normal governed outcome — this run needs human approval | Share `summary` + `reviewUrl`; don't retry (see `governance.md`) |
|
|
819
|
+
| `status: "denied"` | Workspace policy blocked it outright | Explain the reason; don't retry |
|
|
820
|
+
| provider result with `success: false` | The call reached the provider but the provider itself rejected it | Read `result.error`; tell the user what actually happened, don't report success |
|
|
821
|
+
|
|
822
|
+
## What NOT to do
|
|
823
|
+
|
|
824
|
+
- Don't dump the full unfiltered capability list and eyeball it — search.
|
|
825
|
+
- Don't retry a `proposed` or `denied` result as if it were a transient error.
|
|
826
|
+
- Don't tell the user an action "worked" without checking `result.success`.
|
|
827
|
+
- Don't invent a capability, verb, or connection that discovery didn't return.
|
|
828
|
+
|
|
829
|
+
---
|
|
830
|
+
|
|
428
831
|
## Core writes
|
|
429
832
|
|
|
430
|
-
### Create an entity (
|
|
833
|
+
### Create an entity (schema-first, complete intent)
|
|
834
|
+
|
|
835
|
+
Before this call, use `/discover?userId=…&profileSlugs=<kind>` to read the
|
|
836
|
+
real fields, required/default values, constraints and reference targets. Omit
|
|
837
|
+
`workspaceId` for the pod/base schema and normal profile placement; pass it
|
|
838
|
+
only when the user or routing decision explicitly selected that workspace.
|
|
431
839
|
|
|
432
840
|
```json
|
|
433
841
|
POST /api/hub/entities
|
|
434
842
|
{
|
|
435
843
|
"userId": "{userId}",
|
|
436
|
-
"
|
|
437
|
-
"profileSlug": "task", // from /profiles — never guess
|
|
844
|
+
"profileSlug": "task", // from /discover — never guess
|
|
438
845
|
"title": "Weekly team sync",
|
|
439
|
-
"
|
|
846
|
+
"description": "Recurring planning sync",
|
|
847
|
+
"properties": { "status": "todo", "dueDate": "2026-07-21" },
|
|
848
|
+
"content": "# Agenda\n- Priorities\n- Risks",
|
|
849
|
+
"projectId": "{existingProjectId}",
|
|
850
|
+
"source": "agent"
|
|
440
851
|
}
|
|
441
852
|
```
|
|
442
853
|
|
|
854
|
+
The response has legacy `status`/`id` fields plus `writeReceipt`:
|
|
855
|
+
`pending` means a proposal exists and no entity is live; `applied` means the
|
|
856
|
+
reported direct write completed; `partial` means a follow-up (for example a
|
|
857
|
+
facet) failed after the entity applied. Never claim completion from `pending`,
|
|
858
|
+
and only enrich again when the receipt identifies a real missing fact.
|
|
859
|
+
|
|
860
|
+
For several entities, creation-time roles/facets, or relations that need one
|
|
861
|
+
review, submit one `POST /api/hub/capture/graph` plan instead of sequencing
|
|
862
|
+
independent creates. It returns a pending receipt and materializes on human
|
|
863
|
+
approval.
|
|
864
|
+
|
|
443
865
|
### Update an entity
|
|
444
866
|
|
|
445
867
|
```json
|
|
@@ -563,9 +985,260 @@ POST /api/hub/threads/{threadId}/messages
|
|
|
563
985
|
{ "userId": "{userId}", "role": "user", "content": "…" }
|
|
564
986
|
```
|
|
565
987
|
|
|
988
|
+
---
|
|
989
|
+
|
|
990
|
+
## Document embeds — live entities/views/cells inside markdown
|
|
991
|
+
|
|
992
|
+
Documents (`type: "markdown"`) can embed **live, rendering** Synap objects inline, not
|
|
993
|
+
just links. The browser's markdown engine parses a small set of remark **container
|
|
994
|
+
directives** and swaps them for real components — an entity card, a view, or a cell
|
|
995
|
+
— wherever they appear in the prose.
|
|
996
|
+
|
|
997
|
+
**This is a DOCUMENTS-only mechanism.** It is unrelated to the `[[kind:id|label]]`
|
|
998
|
+
inline chips described in `inline-patterns.md` — those render **only** in Companion
|
|
999
|
+
chat replies. Never put a `:::synap-*` directive in a chat reply, and never put a
|
|
1000
|
+
`[[…]]` chip in a document's `content`. Different surface, different grammar.
|
|
1001
|
+
|
|
1002
|
+
### Syntax
|
|
1003
|
+
|
|
1004
|
+
<!-- brief:start -->
|
|
1005
|
+
|
|
1006
|
+
A container directive: three colons, the directive name, `{attrs}` on the opening
|
|
1007
|
+
line, three colons alone on the closing line.
|
|
1008
|
+
|
|
1009
|
+
```
|
|
1010
|
+
:::synap-entity{id="ent_abc123"}
|
|
1011
|
+
:::
|
|
1012
|
+
```
|
|
1013
|
+
|
|
1014
|
+
| Directive | Required attrs | Optional attrs | Renders |
|
|
1015
|
+
| -------------- | --------------------------------------------- | -------------- | ------------------------------------------------- |
|
|
1016
|
+
| `synap-entity` | `id` (entity UUID) | — | Compact entity card (`__entity-block` cell) |
|
|
1017
|
+
| `synap-view` | `viewId` (view UUID) | — | Embedded, read-only view (`__embedded-view` cell) |
|
|
1018
|
+
| `synap-cell` | `instanceId` **OR** (`cellKey` + `cellProps`) | — | A persisted cell instance, or an inline cell ref |
|
|
1019
|
+
|
|
1020
|
+
Only real IDs from prior tool results — never invent one. This is a
|
|
1021
|
+
DOCUMENTS-only grammar: never use it in a chat reply, and never use a
|
|
1022
|
+
`[[kind:id|label]]` chip inside a document's `content`.
|
|
1023
|
+
|
|
1024
|
+
<!-- brief:end -->
|
|
1025
|
+
|
|
1026
|
+
For `synap-cell`: an explicit `instanceId` always wins if present — it renders a
|
|
1027
|
+
persisted cell instance from `/api/hub/cells`. Otherwise the pair `cellKey` +
|
|
1028
|
+
`cellProps` builds an inline ref (`cellRefFromLegacy`). `cellProps` is a JSON string,
|
|
1029
|
+
e.g. `cellProps='{"profileSlug":"task"}'`.
|
|
1030
|
+
|
|
1031
|
+
When you write a document's `content` directly (via `synap_create_document` /
|
|
1032
|
+
`POST /api/hub/documents`), you're writing raw markdown text — quote `cellProps`'
|
|
1033
|
+
JSON with single quotes (`cellProps='{"profileSlug":"task"}'`) so the inner `"`
|
|
1034
|
+
characters don't collide with the directive's own `key="value"` quoting; the
|
|
1035
|
+
markdown renderer (remark-directive) parses this directly, no escaping needed.
|
|
1036
|
+
|
|
1037
|
+
One caveat: if a human later opens the document in the rich-text editor, its
|
|
1038
|
+
Tiptap round-trip serializer re-emits every directive as `key="value"` and
|
|
1039
|
+
**drops any attribute value containing `"` or `}`** (it would otherwise corrupt
|
|
1040
|
+
the directive) — so an inline `cellProps` blob can be silently lost on the next
|
|
1041
|
+
editor save. For a cell you expect to survive editing, create it first
|
|
1042
|
+
(`synap_create_cell` / `POST /api/hub/cells`) and embed it by `instanceId`
|
|
1043
|
+
instead of inlining `cellProps`.
|
|
1044
|
+
|
|
1045
|
+
### Rules
|
|
1046
|
+
|
|
1047
|
+
- **Only real IDs from prior tool results.** Never invent an entity/view/instance
|
|
1048
|
+
ID. Create or look it up first (`synap_create_entity`, `synap_get_entities`,
|
|
1049
|
+
`synap_create_view`, `synap_create_cell`), then embed the ID you got back.
|
|
1050
|
+
- **Embeds are for DOCUMENTS.** The `[[…]]` inline chips are for Companion chat
|
|
1051
|
+
replies. Do not mix the two grammars across surfaces.
|
|
1052
|
+
- **Embed vs. link:** embed when the reader benefits from seeing the live
|
|
1053
|
+
object in place — a stat card inside a report, the linked meeting entity inside
|
|
1054
|
+
meeting notes, a pipeline view inside a status update. Link (`entities WHERE
|
|
1055
|
+
documentId = ?` attachment, or a plain reference to the ID) when you just need
|
|
1056
|
+
traceability and the reader doesn't need to see it rendered inline — most
|
|
1057
|
+
documents should still be _attached_ to one entity (see `writes.md`) regardless
|
|
1058
|
+
of whether they also embed others inline.
|
|
1059
|
+
- A directive with a missing/invalid required attribute renders a visible error
|
|
1060
|
+
block in the browser (`Error: View ID is required` / `Error: Cell type is
|
|
1061
|
+
required`) — always double-check the ID before writing the directive.
|
|
1062
|
+
|
|
1063
|
+
### Worked example 1 — meeting notes embedding the meeting entity
|
|
1064
|
+
|
|
1065
|
+
```json
|
|
1066
|
+
POST /api/hub/documents
|
|
1067
|
+
{
|
|
1068
|
+
"userId": "{userId}",
|
|
1069
|
+
"workspaceId": "{workspaceId}",
|
|
1070
|
+
"title": "Meeting notes — 2026-07-12",
|
|
1071
|
+
"type": "markdown",
|
|
1072
|
+
"entityId": "ent_event_kickoff",
|
|
1073
|
+
"content": "# Kickoff meeting\n\n:::synap-entity{id=\"ent_event_kickoff\"}\n:::\n\n## Decisions\n- Ship the pilot by August 1\n\n## Action items\n- [ ] Draft the rollout plan"
|
|
1074
|
+
}
|
|
1075
|
+
```
|
|
1076
|
+
|
|
1077
|
+
The event entity renders as a live card at the top of the notes — attendees,
|
|
1078
|
+
time, status stay current even if the entity changes later.
|
|
1079
|
+
|
|
1080
|
+
### Worked example 2 — status report embedding a pipeline view
|
|
1081
|
+
|
|
1082
|
+
```json
|
|
1083
|
+
POST /api/hub/documents
|
|
1084
|
+
{
|
|
1085
|
+
"userId": "{userId}",
|
|
1086
|
+
"workspaceId": "{workspaceId}",
|
|
1087
|
+
"title": "Weekly deals update",
|
|
1088
|
+
"type": "markdown",
|
|
1089
|
+
"entityId": "ent_project_eve",
|
|
1090
|
+
"content": "# Weekly update\n\nThree deals moved to negotiating this week.\n\n:::synap-view{viewId=\"view_deals_pipeline\"}\n:::\n\nSee the board above for the live state."
|
|
1091
|
+
}
|
|
1092
|
+
```
|
|
1093
|
+
|
|
1094
|
+
### Worked example 3 — report embedding an inline stat cell
|
|
1095
|
+
|
|
1096
|
+
```json
|
|
1097
|
+
POST /api/hub/documents
|
|
1098
|
+
{
|
|
1099
|
+
"userId": "{userId}",
|
|
1100
|
+
"workspaceId": "{workspaceId}",
|
|
1101
|
+
"title": "Q2 task summary",
|
|
1102
|
+
"type": "markdown",
|
|
1103
|
+
"entityId": "ent_project_eve",
|
|
1104
|
+
"content": "# Q2 summary\n\n:::synap-cell{cellKey=\"stat-card\" cellProps='{\"profileSlug\":\"task\"}'}\n:::\n\nOpen tasks are trending down."
|
|
1105
|
+
}
|
|
1106
|
+
```
|
|
1107
|
+
|
|
1108
|
+
Note the mixed quoting: the directive's own attribute values use double quotes
|
|
1109
|
+
(`cellKey="stat-card"`), so the outer `cellProps` value uses single quotes to hold
|
|
1110
|
+
its JSON — the JSON itself must not contain any `"` once flattened into the
|
|
1111
|
+
attribute string, or the serializer will drop it. When in doubt, prefer a
|
|
1112
|
+
persisted `instanceId` over inline `cellProps`.
|
|
1113
|
+
|
|
1114
|
+
---
|
|
1115
|
+
|
|
1116
|
+
## The Creative Loop — do once, then crystallize to reusable config
|
|
1117
|
+
|
|
1118
|
+
The core rhythm of real work in Synap: **do the thing once by hand, then crystallize what worked into reusable configuration.** You author; the user curates. Every crystallization is governed — a `proposed` result is the normal, successful outcome, never an error.
|
|
1119
|
+
|
|
1120
|
+
The loop has three moves that compound. Each takes a one-off act and, if it's worth repeating, turns it into standing structure:
|
|
1121
|
+
|
|
1122
|
+
| Do-once (author) | → Crystallize (curate) | Tool |
|
|
1123
|
+
| --------------------------------------- | ----------------------------------- | ----------------------------- |
|
|
1124
|
+
| Work a multi-step goal in a **session** | → a **playbook** (the process) | `promote_session_to_playbook` |
|
|
1125
|
+
| Show a result in a **cell** | → a **renderer** for an entity type | `promote_cell_to_renderer` |
|
|
1126
|
+
|
|
1127
|
+
### 1. Open a session for real work
|
|
1128
|
+
|
|
1129
|
+
When the task is a unit of work with a deliverable — research, a build, an investigation, a sprint — and there's no active session, **`start_session`** with a clear `goal` and `expectedOutputs`. The session is the spine that accrues results (see the focus-sessions skill). Don't open one for a one-shot lookup or a casual reply.
|
|
1130
|
+
|
|
1131
|
+
### 2. Create a cell to REPORT — don't dump data into chat
|
|
1132
|
+
|
|
1133
|
+
When you have something to _show_ the user — a list of leads, a summary, a comparison, a chart — author a **cell** with `create_cell` instead of pasting rows into the message.
|
|
1134
|
+
|
|
1135
|
+
- **Declare the data intent in `rendererSource`; do NOT pre-fetch.** Cells use a dynamic data-binding SDK: you describe what the cell needs (e.g. "the open leads in this workspace"), and the runtime binds the live data at render time. Never fetch rows yourself and inline them — that snapshot goes stale and defeats the cell.
|
|
1136
|
+
- Keep one cell to one job. A good, focused cell is the raw material for the next move.
|
|
1137
|
+
|
|
1138
|
+
### 3. Promote a good cell to a renderer — recurring presentation
|
|
1139
|
+
|
|
1140
|
+
When a cell is a _good, recurring way to present a whole entity type or step_ — e.g. every `bookmark`'s detail view, every `lead`'s list row — promote it with `promote_cell_to_renderer`:
|
|
1141
|
+
|
|
1142
|
+
- Pick the `profileSlug` (the entity type), the `slot` (`list` | `detail` | `dashboard`), and the `cellKey` from `create_cell`.
|
|
1143
|
+
- This is **governed**: for you it returns `{ status: "proposed", proposalId }`. That is the point — you author the renderer, the user reviews and curates it before it becomes every entity's view. Surface the proposal plainly ("I've proposed this as the detail view for bookmarks — review it when you like"), don't treat it as a failure.
|
|
1144
|
+
- Use `scope: "pod"` only when the presentation should apply in every workspace; default to workspace scope.
|
|
1145
|
+
|
|
1146
|
+
### 4. Promote a finished session to a playbook — recurring process
|
|
1147
|
+
|
|
1148
|
+
When the session is done **and the work was a repeatable process** (not a one-off), promote it with `promote_session_to_playbook({ sessionId })`. This captures the goal, tasks, expected outputs, and steps as a reusable session template — so next time the process starts pre-built instead of from scratch.
|
|
1149
|
+
|
|
1150
|
+
- Do this at the _end_, once the promised outputs are produced and verified.
|
|
1151
|
+
- Judge repeatability honestly: a bespoke, never-again investigation is not a playbook. A "weekly competitor scan" or "new-client onboarding" is.
|
|
1152
|
+
- Governed like the others — `promoted` (applied) or `proposed` (awaiting review) are both normal.
|
|
1153
|
+
|
|
1154
|
+
### The symmetry
|
|
1155
|
+
|
|
1156
|
+
Sessions and cells are the two things you _do_; playbooks and renderers are the two things you _keep_. The instinct to build: **first do it once concretely, watch it work, then offer to crystallize it** — and let the user decide what becomes standing config. Never crystallize speculatively before the one-off has proven itself.
|
|
1157
|
+
|
|
1158
|
+
This is escalation ladder **L4**: crystallize only after proof. Blocked/missing structure climbs L2→L3 first (`escalation-ladder.md`); L4 is the success path, not a substitute for discovery.
|
|
1159
|
+
|
|
1160
|
+
---
|
|
1161
|
+
|
|
1162
|
+
## Garden the graph — writes tell you their impact (the circling of action)
|
|
1163
|
+
|
|
1164
|
+
A write is no longer blind. When you create or update an entity, the response tells you what it did to the graph — **READ it and act**, so the graph stays connected instead of accumulating isolated, duplicated nodes.
|
|
1165
|
+
|
|
1166
|
+
**On create**, the response carries a `resolution` block:
|
|
1167
|
+
|
|
1168
|
+
- `existingSameProfile` — an entity with this exact name ALREADY exists as the same profile. Prefer **updating** it (`synap set entity <id>` · `PATCH /api/hub/entities/:id` · `update_entity`) over creating a duplicate.
|
|
1169
|
+
- `autoConnected` — same-name entities of a DIFFERENT profile are facets of one real thing, and the system has already woven them together with a `same_subject` relation. Acknowledge it; add a more specific relation if the real relationship is narrower than "same subject".
|
|
1170
|
+
- `suggestions` — entities worth linking. Create the relations that genuinely apply (`synap create relation` · `POST /api/hub/relations` · `create_relation`).
|
|
1171
|
+
|
|
1172
|
+
**On update**, the response carries an `impact` block — the entity's immediate relation neighbors. Read it and resolve secondary effects: supersede a now-stale entity, update its dependents, re-link.
|
|
1173
|
+
|
|
1174
|
+
**The circling of action** for any structural write (updating the vision, the architecture, a decision record, a codebase map):
|
|
1175
|
+
|
|
1176
|
+
1. **Write** — the response shows collisions + connections; don't ignore it.
|
|
1177
|
+
2. **If it already exists** (`existingSameProfile`) → update it, don't duplicate.
|
|
1178
|
+
3. **The same-name facets are auto-woven** (`autoConnected`) → extend the links where the relationship is more specific than `same_subject`.
|
|
1179
|
+
4. **Act on `suggestions`** → link what genuinely relates.
|
|
1180
|
+
5. **On update** → resolve the `impact` neighbors (supersede / update / re-link).
|
|
1181
|
+
|
|
1182
|
+
The principle: **more data is better when it is structured.** The graph now helps you keep it structured — never leave an entity isolated, never blindly overwrite. (Matching is exact-name today; it will deepen over time — so still link deliberately when you know two things relate but their names differ.)
|
|
1183
|
+
|
|
1184
|
+
---
|
|
1185
|
+
|
|
1186
|
+
## Decide the next action — advance the goal, don't just answer
|
|
1187
|
+
|
|
1188
|
+
You are an adjunct, not a reply box. A good assistant doesn't wait to be told the next step — after you answer, you ask **what is the highest-leverage next move toward the goal, and should I make it now?** — then you make it. Proactivity is this habit, run every turn; it is not a tone.
|
|
1189
|
+
|
|
1190
|
+
### The session is the spine of real work
|
|
1191
|
+
|
|
1192
|
+
A **focus session** is a goal-bound work room. It holds the goal, the promised outputs (deliverables), the tools/skills in play, and what's been produced so far — so it answers both _"where am I"_ and _"what am I working toward"_. Operate it with the triplet:
|
|
1193
|
+
|
|
1194
|
+
| Move | CLI | REST | IS tool |
|
|
1195
|
+
| ------------------------- | ---------------------------------------- | ------------------------------------------- | ---------------- |
|
|
1196
|
+
| open a session | `synap session start --goal "…"` | `POST /api/hub/focus-sessions` | `start_session` |
|
|
1197
|
+
| read it | `synap session get <id>` | `GET /api/hub/focus-sessions/:id` | `get_session` |
|
|
1198
|
+
| see active ones | `synap session list` | `GET /api/hub/focus-sessions?status=active` | `list_sessions` |
|
|
1199
|
+
| record progress / outputs | `synap session update <id> --progress N` | `PATCH /api/hub/focus-sessions/:id` | `update_session` |
|
|
1200
|
+
|
|
1201
|
+
- If you're already in a session (or a `## Active Session` block is in your context) → that's your frame. Read its goal and the gap between _promised outputs_ and _what's produced_, and pursue that gap.
|
|
1202
|
+
- If the user's intent is a **unit of work with a deliverable** (research, a plan, a build, an investigation) and no session is active → **open one** with a clear goal + expected outputs, so the work has a spine that accrues results. Don't open a session for a one-shot lookup or a casual reply.
|
|
1203
|
+
|
|
1204
|
+
### Decide ONE move, then make it
|
|
1205
|
+
|
|
1206
|
+
Pick the single highest-leverage next action toward the goal — not a checklist: answer · capture/structure what was said · create a task · link entities · advance or produce a promised deliverable · update the session's progress · propose an automation for a repeating pattern.
|
|
1207
|
+
|
|
1208
|
+
**Spin off a branch / sub-agent** when there's side-work that advances the goal but would **bloat the main channel** — e.g. "go find best practices for X", a deep research dive, a parallel investigation. Keep the main thread clean and let the branch do the heavy lifting and report back. Judge by complexity + channel hygiene, not by a fixed category.
|
|
1209
|
+
|
|
1210
|
+
**Land it through governance — act, don't just suggest.** When the move is clear, _make_ it: create the task, the research entity, the branch, the link, the session update. Every write passes through governance — it either auto-applies (safe, whitelisted writes) or becomes a one-click review. Either way, making the move is the safe path. Don't downgrade a clear next action into "you might want to…". Say _why_ in one line, then act.
|
|
1211
|
+
|
|
1212
|
+
**Reflect back into the session.** When work lands, update the session's progress and link what you produced — so the next turn reads a richer state and the loop compounds.
|
|
1213
|
+
|
|
1214
|
+
**Know when to stop.** When the promised outputs are produced and verified, the goal is done — say so and stop. Surface a _real_ next move when there is one; stay quiet when there isn't. Never manufacture busywork to seem active.
|
|
1215
|
+
|
|
1216
|
+
### The nudge vs. propose line
|
|
1217
|
+
|
|
1218
|
+
A concrete next action toward a _known_ goal → propose it (gated). A _speculative_ restructuring the user hasn't asked for (a new profile, a new view, splitting a workspace) → raise it as a question and let them decide.
|
|
1219
|
+
|
|
1220
|
+
### If you are a coding / terminal agent working _in_ a repo
|
|
1221
|
+
|
|
1222
|
+
Same habit, one addition: before you finish a piece of work, ask **"what's the next action, and what belongs in Synap?"** A decision you made, a gotcha you hit, a follow-up the work revealed, a task it spun off — capture it (`synap capture` / `synap note` / a task) and, when the work is a real unit, track it in a session. The point of the second brain is that the _next_ agent (or you, tomorrow) starts from what this turn learned instead of re-deriving it.
|
|
1223
|
+
|
|
1224
|
+
---
|
|
1225
|
+
|
|
566
1226
|
## Reading
|
|
567
1227
|
|
|
568
|
-
|
|
1228
|
+
**Start with `ask` — the one routed read door.** It classifies the question and
|
|
1229
|
+
queries the right substrate(s) for you, returning a glass-box answer (which
|
|
1230
|
+
substrates answered, which were unavailable, plus the engine's verdict). Reach for
|
|
1231
|
+
the low-level doors below only when you deliberately want a single substrate or a
|
|
1232
|
+
specific shape (a graph traversal, a typed-entity filter, an entity's neighborhood).
|
|
1233
|
+
|
|
1234
|
+
```
|
|
1235
|
+
# THE read door — routes across semantic / procedural / episodic, tells you which answered
|
|
1236
|
+
POST /api/hub/knowledge/ask body: { query, workspaceId?, limit? }
|
|
1237
|
+
→ { query, routedTo: [...], primary, answers: [{ substrate, items, status }], degraded, understanding, verdict }
|
|
1238
|
+
```
|
|
1239
|
+
|
|
1240
|
+
Low-level doors (`ask` routes to these — graph-based, not semantic; type filter →
|
|
1241
|
+
relations → neighborhood):
|
|
569
1242
|
|
|
570
1243
|
```
|
|
571
1244
|
# Keyword search across everything (entities, documents, views, threads)
|
|
@@ -590,8 +1263,12 @@ GET /api/hub/graph/traverse?entityId={id}&maxDepth=2&workspaceId={id}
|
|
|
590
1263
|
GET /api/hub/memory?userId={userId}&query={keywords}
|
|
591
1264
|
```
|
|
592
1265
|
|
|
1266
|
+
**Never claim absence without searching this turn.** Asked "what do you know about X", "is there an X", "anything on X" — you MUST `ask`/`search_unified` for X first (and `list_entities` on the matching profile for "how many / list all X"). Only after a search returns nothing may you say "I didn't find anything matching X" — never assert "X does not exist." A just-created entity is searchable within seconds, so a confident "nothing exists" without a search this turn is a hard failure.
|
|
1267
|
+
|
|
593
1268
|
No SQL joins. The graph is the join.
|
|
594
1269
|
|
|
1270
|
+
---
|
|
1271
|
+
|
|
595
1272
|
## Multi-entity capture from free-form text
|
|
596
1273
|
|
|
597
1274
|
When the user pastes a block of unstructured content (a meeting transcript, an email, a LinkedIn bio), use the capture pipeline instead of chaining manual creates:
|
|
@@ -603,6 +1280,8 @@ POST /api/hub/capture/execute → commits (after user confirms)
|
|
|
603
1280
|
|
|
604
1281
|
The pipeline extracts multiple entities with their relations in one LLM call. Read **`capture.md`** for the full flow.
|
|
605
1282
|
|
|
1283
|
+
---
|
|
1284
|
+
|
|
606
1285
|
## Worked examples
|
|
607
1286
|
|
|
608
1287
|
### Example 1 — "Remind me to send the proposal to Acme on Friday"
|
|
@@ -657,6 +1336,8 @@ The pipeline extracts multiple entities with their relations in one LLM call. Re
|
|
|
657
1336
|
|
|
658
1337
|
3. If the user said why ("interesting for the onboarding project"), also create a relation to that project — never drop the reason as a plain comment, turn it into a link.
|
|
659
1338
|
|
|
1339
|
+
---
|
|
1340
|
+
|
|
660
1341
|
## CRM Workspaces — 4-Entity Model
|
|
661
1342
|
|
|
662
1343
|
Some workspaces use a CRM data structure with four entities: `person` and `company` (identity records), `deal` (pipeline record), and `client` (post-win relationship marker). Understand this pattern when proposing lead captures, deal updates, or campaign membership.
|
|
@@ -763,7 +1444,21 @@ POST /api/hub/relations
|
|
|
763
1444
|
|
|
764
1445
|
This model enables renewals (new deal linking to existing client), multi-stakeholder deals (multiple `linked_to_deal` relations per deal), campaigns with mixed entity types (persons + companies + deals as members), and clean churn tracking. It matches Synap's core pattern: entities + relations = graph.
|
|
765
1446
|
|
|
766
|
-
##
|
|
1447
|
+
## Resolution discipline — resolve BEFORE you create
|
|
1448
|
+
|
|
1449
|
+
Extracting people/companies from a digest, an email/DM thread, or notes is a RESOLUTION problem, not a creation problem. Follow this method every time (it is generic — the specific team roster and known aliases are DATA you fetch, never hardcoded):
|
|
1450
|
+
|
|
1451
|
+
1. **Search first, once (batched).** Before proposing any person/company, run a SINGLE batched query (`search_unified`, plus `list_entities` if needed) that covers ALL candidate names, handles, emails and aliases at once. Never run one query per candidate — that is a cost failure.
|
|
1452
|
+
2. **Reuse on match → add an alias.** If a candidate matches an existing entity, reference the existing entity by its real id (e.g. `existingEntityId` on `propose_entity_graph`) and add the newly-seen surface form (a handle, an alternate spelling) to the entity's `aliases` — do NOT create a second `person`/`company`. The `person` profile carries `email`, `discord-handle` and `aliases` for exactly this.
|
|
1453
|
+
3. **Never placeholder.** Never mint an entity whose identity is unstated — "Not publicly disclosed", "unknown", "TBD", an empty name, or a bare handle with no person behind it. Fold that unstated thing into the description of a related entity instead.
|
|
1454
|
+
4. **Team is not a contact.** Internal senders — your own side of a `client-comms` thread (the agency's own team) — are NEVER captured as the client's contacts. Only the external party becomes a person/company/deal. Team roster may appear in `orient.teamRoster` — treat as internal.
|
|
1455
|
+
5. **Connect the graph.** Every entity you propose should attach via at least one relation (person `works_at` company, person `linked_to_deal` deal, …). A name mentioned only in passing, with no relation and no stated identity, is not worth capturing as its own entity.
|
|
1456
|
+
|
|
1457
|
+
An extraction that creates a duplicate "Sarah Chen", or a `person` titled "Unknown sender", is a failure — resolve, reuse, and alias instead.
|
|
1458
|
+
|
|
1459
|
+
---
|
|
1460
|
+
|
|
1461
|
+
## Common mistakes — core data operations
|
|
767
1462
|
|
|
768
1463
|
1. **Creating orphan entities.** Always connect to at least one other entity on creation. Search first; if nothing links, reconsider whether this should be memory.
|
|
769
1464
|
2. **Guessing profile slugs.** Always `GET /profiles` first. `deal`, `capture`, and custom profiles may not exist in this workspace.
|
|
@@ -773,6 +1468,10 @@ This model enables renewals (new deal linking to existing client), multi-stakeho
|
|
|
773
1468
|
6. **Not knowing your userId.** Use `{SYNAP_USER_ID}` from the env (set by `synap connect`). Or call `GET /api/hub/users/me` → `.id` once and cache it. Never hardcode or guess.
|
|
774
1469
|
7. **Skipping the search step.** Duplicates degrade the graph more than missing data.
|
|
775
1470
|
8. **Forgetting that `GET /channels/personal` needs `hub-protocol.write`** scope — it's get-or-create, not a pure read.
|
|
1471
|
+
9. **Routing known-structure data through free-text capture.** If you already know the profileSlug + fields, create the entity directly — smart capture can degrade to a single flat note.
|
|
1472
|
+
10. **Paragraph session goals.** The goal is one line; put detail and deliverables in expectedOutputs.
|
|
1473
|
+
|
|
1474
|
+
---
|
|
776
1475
|
|
|
777
1476
|
## AI Inline Patterns — reference entities in your replies
|
|
778
1477
|
|
|
@@ -798,13 +1497,487 @@ When the user is interacting with Synap's AI Companion (the in-browser chat pane
|
|
|
798
1497
|
- **Only in Companion replies.** These patterns are silently ignored in non-companion channels, documents, and memory. Do not use them there.
|
|
799
1498
|
- **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
1499
|
|
|
801
|
-
|
|
1500
|
+
### Proposals
|
|
802
1501
|
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
1502
|
+
There is no `[[open:…|proposal:…]]` chip — `open`'s `resourceType` only accepts
|
|
1503
|
+
`entity`, `view`, `doc`, `cell`, `channel`. A proposal is not one of those, so
|
|
1504
|
+
never invent that form.
|
|
1505
|
+
|
|
1506
|
+
When a write returns `status: "proposed"`, the response also carries a
|
|
1507
|
+
`reviewUrl` (a full Synap Studio URL). Surface it as a plain markdown link, and
|
|
1508
|
+
add one sentence explaining why the write was proposed instead of auto-applied:
|
|
1509
|
+
|
|
1510
|
+
> Queued the task deletion for your review — destructive actions always need approval: [Review proposal](https://pod.example.com/proposals/prp_abc)
|
|
1511
|
+
|
|
1512
|
+
`"proposed"` is normal, not an error — don't apologize for it or wait for the
|
|
1513
|
+
user to approve before continuing the conversation.
|
|
1514
|
+
|
|
1515
|
+
---
|
|
1516
|
+
|
|
1517
|
+
## Focus Sessions — Goal-Bound Work Rooms
|
|
1518
|
+
|
|
1519
|
+
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.
|
|
1520
|
+
|
|
1521
|
+
**When to propose a session** (via the proposal system — always ask first):
|
|
1522
|
+
|
|
1523
|
+
- Research with 5+ sources → decision memo
|
|
1524
|
+
- Lead generation sprint → qualified list + outreach drafts
|
|
1525
|
+
- Incident investigation → postmortem doc
|
|
1526
|
+
- Data import → structured knowledge base
|
|
1527
|
+
- Any task you'd naturally call "a project" rather than "a question"
|
|
1528
|
+
|
|
1529
|
+
**How the AI proposes a session:**
|
|
1530
|
+
|
|
1531
|
+
```
|
|
1532
|
+
create_proposal with targetType: "focus_session"
|
|
1533
|
+
→ user reviews goal + rationale + expected outputs in ProposalReviewBoard
|
|
1534
|
+
→ on approval, session is created in focus_sessions table
|
|
1535
|
+
→ AI updates progress (0→100) via PATCH /api/hub/focus-sessions/:id { workspaceId, progress: N }
|
|
1536
|
+
→ session auto-surfaces in the Active Sessions bento widget on the user's home
|
|
1537
|
+
```
|
|
1538
|
+
|
|
1539
|
+
**Session templates** (pass as `templateId`):
|
|
1540
|
+
`research-room` · `lead-sprint` · `decision-memo` · `import-cleanup` · `incident-room` · `campaign-intel`
|
|
1541
|
+
|
|
1542
|
+
**Hub Protocol REST** (for IS → backend; always include `workspaceId`):
|
|
1543
|
+
|
|
1544
|
+
- `POST /api/hub/focus-sessions` — create (include `correlationId` for idempotency)
|
|
1545
|
+
- `GET /api/hub/focus-sessions/:id?workspaceId=<id>` — read
|
|
1546
|
+
- `PATCH /api/hub/focus-sessions/:id` — update `{ workspaceId, progress, status, goal, agentIds }`
|
|
1547
|
+
|
|
1548
|
+
**CLI** (use when running as Claude Code / OpenClaw agent):
|
|
1549
|
+
|
|
1550
|
+
```bash
|
|
1551
|
+
synap session start --goal "<goal>" [--workspace <id>] # create + start a session
|
|
1552
|
+
synap session list [--workspace <id>] [--status active|paused|closed] # list sessions
|
|
1553
|
+
synap session get <id> [--workspace <id>] # read a session
|
|
1554
|
+
synap session update <id> --workspace <id> --progress 50 # report progress
|
|
1555
|
+
synap session update <id> --workspace <id> --status paused # pause
|
|
1556
|
+
synap session close <id> --workspace <id> [--recap "what was done"] # close + recap
|
|
1557
|
+
```
|
|
1558
|
+
|
|
1559
|
+
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.
|
|
1560
|
+
|
|
1561
|
+
**MCP door**: after `synap_start_session` returns, call `synap_get_channel` to get a personal channel for the session, then `synap_post_message` with `triggerAI:true` to dispatch the IS agent for autonomous work on the goal. The agent's produced entities link back to the session via the graph.
|
|
1562
|
+
|
|
1563
|
+
**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.
|
|
1564
|
+
|
|
1565
|
+
---
|
|
1566
|
+
|
|
1567
|
+
# Automations
|
|
1568
|
+
|
|
1569
|
+
Create workflow automations that trigger automatically based on events, schedules, or webhooks.
|
|
1570
|
+
|
|
1571
|
+
## Automation Structure
|
|
1572
|
+
|
|
1573
|
+
Every automation has:
|
|
1574
|
+
|
|
1575
|
+
1. **Trigger** (exactly one) — what starts the automation
|
|
1576
|
+
2. **Steps** (one or more) — commands, conditions, delays, outputs connected in a flow
|
|
1577
|
+
|
|
1578
|
+
## Trigger Types
|
|
1579
|
+
|
|
1580
|
+
| Type | Config | Example |
|
|
1581
|
+
| --------- | ---------------------------- | ------------------------------------ |
|
|
1582
|
+
| `event` | `{ eventPattern, filters? }` | Entity created with specific profile |
|
|
1583
|
+
| `cron` | `{ expression }` | Daily at 9am: `"0 9 * * *"` |
|
|
1584
|
+
| `webhook` | `{ webhookSubscriptionId }` | External service sends data |
|
|
1585
|
+
| `manual` | `{}` | User-triggered from UI |
|
|
1586
|
+
|
|
1587
|
+
### Event Patterns
|
|
1588
|
+
|
|
1589
|
+
Format: `{subjectType}.{action}.completed`
|
|
1590
|
+
|
|
1591
|
+
Common patterns:
|
|
1592
|
+
|
|
1593
|
+
- `entity.create.completed` — entity created and persisted
|
|
1594
|
+
- `entity.update.completed` — entity updated
|
|
1595
|
+
- `entity.delete.completed` — entity deleted
|
|
1596
|
+
- `document.create.completed` — document created
|
|
1597
|
+
- `document.update.completed` — document updated
|
|
1598
|
+
|
|
1599
|
+
Filters narrow the event to specific conditions:
|
|
1600
|
+
|
|
1601
|
+
```json
|
|
1602
|
+
{
|
|
1603
|
+
"eventPattern": "entity.create.completed",
|
|
1604
|
+
"filters": { "profileSlug": "task", "metadata.priority": "high" }
|
|
1605
|
+
}
|
|
1606
|
+
```
|
|
1607
|
+
|
|
1608
|
+
### Cron Expressions
|
|
1609
|
+
|
|
1610
|
+
Standard 5-field cron (minute hour day month weekday):
|
|
1611
|
+
|
|
1612
|
+
- `"0 9 * * *"` — daily at 9am
|
|
1613
|
+
- `"0 9 * * MON"` — every Monday at 9am
|
|
1614
|
+
- `"*/30 * * * *"` — every 30 minutes
|
|
1615
|
+
- `"0 0 1 * *"` — first day of month at midnight
|
|
1616
|
+
|
|
1617
|
+
## Step Types
|
|
1618
|
+
|
|
1619
|
+
### command
|
|
1620
|
+
|
|
1621
|
+
Execute an intelligence command. Reference by `commandId` or describe inline.
|
|
1622
|
+
|
|
1623
|
+
```json
|
|
1624
|
+
{
|
|
1625
|
+
"id": "extract",
|
|
1626
|
+
"type": "command",
|
|
1627
|
+
"data": {
|
|
1628
|
+
"commandTitle": "Extract key entities",
|
|
1629
|
+
"inputMapping": {
|
|
1630
|
+
"content": "{{trigger.payload.entity.content}}",
|
|
1631
|
+
"context": "{{trigger.payload.entity.name}}"
|
|
1632
|
+
}
|
|
1633
|
+
}
|
|
1634
|
+
}
|
|
1635
|
+
```
|
|
1636
|
+
|
|
1637
|
+
**Input mapping** uses template syntax:
|
|
1638
|
+
|
|
1639
|
+
- `{{trigger.payload.*}}` — data from the triggering event
|
|
1640
|
+
- `{{steps.<stepId>.output.*}}` — output from a prior step
|
|
1641
|
+
- `{{loop.item}}` — current item in a loop
|
|
1642
|
+
|
|
1643
|
+
### condition
|
|
1644
|
+
|
|
1645
|
+
Branch the flow based on a boolean expression.
|
|
1646
|
+
|
|
1647
|
+
```json
|
|
1648
|
+
{
|
|
1649
|
+
"id": "check-priority",
|
|
1650
|
+
"type": "condition",
|
|
1651
|
+
"data": {
|
|
1652
|
+
"label": "High priority?",
|
|
1653
|
+
"expression": "trigger.payload.entity.metadata.priority === 'high'",
|
|
1654
|
+
"trueLabel": "Yes",
|
|
1655
|
+
"falseLabel": "No"
|
|
1656
|
+
}
|
|
1657
|
+
}
|
|
1658
|
+
```
|
|
1659
|
+
|
|
1660
|
+
Conditions have two output handles: `yes` and `no`. Connect subsequent steps to the appropriate handle.
|
|
1661
|
+
|
|
1662
|
+
### delay
|
|
1663
|
+
|
|
1664
|
+
Wait before continuing.
|
|
1665
|
+
|
|
1666
|
+
```json
|
|
1667
|
+
{
|
|
1668
|
+
"id": "wait",
|
|
1669
|
+
"type": "delay",
|
|
1670
|
+
"data": { "duration": "5m", "label": "Cool down" }
|
|
1671
|
+
}
|
|
1672
|
+
```
|
|
1673
|
+
|
|
1674
|
+
Supported durations: `30s`, `5m`, `1h`, `1d`, `1w`.
|
|
1675
|
+
|
|
1676
|
+
### output
|
|
1677
|
+
|
|
1678
|
+
Terminal action — the end result of the automation.
|
|
1679
|
+
|
|
1680
|
+
```json
|
|
1681
|
+
{
|
|
1682
|
+
"id": "notify",
|
|
1683
|
+
"type": "output",
|
|
1684
|
+
"data": {
|
|
1685
|
+
"label": "Send notification",
|
|
1686
|
+
"outputType": "notification",
|
|
1687
|
+
"config": {
|
|
1688
|
+
"message": "New high-priority task: {{trigger.payload.entity.name}}"
|
|
1689
|
+
}
|
|
1690
|
+
}
|
|
1691
|
+
}
|
|
1692
|
+
```
|
|
1693
|
+
|
|
1694
|
+
Output types:
|
|
1695
|
+
|
|
1696
|
+
- `notification` — in-app notification to the user
|
|
1697
|
+
- `entity_create` — create a new entity (config: `{ profileSlug, title, properties }`)
|
|
1698
|
+
- `entity_update` — update an existing entity (config: `{ entityId, properties }`)
|
|
1699
|
+
- `webhook` — POST to external URL (config: `{ url, headers?, body }`)
|
|
1700
|
+
- `channel_message` — post a message to a channel (config: `{ channelId, content }`)
|
|
1701
|
+
|
|
1702
|
+
### loop
|
|
1703
|
+
|
|
1704
|
+
Iterate over a collection from a prior step.
|
|
1705
|
+
|
|
1706
|
+
```json
|
|
1707
|
+
{
|
|
1708
|
+
"id": "for-each-result",
|
|
1709
|
+
"type": "loop",
|
|
1710
|
+
"data": {
|
|
1711
|
+
"label": "For each search result",
|
|
1712
|
+
"iteratorExpression": "steps.search.output.results",
|
|
1713
|
+
"itemVariable": "item"
|
|
1714
|
+
}
|
|
1715
|
+
}
|
|
1716
|
+
```
|
|
1717
|
+
|
|
1718
|
+
Inside the loop, reference `{{loop.item}}` for the current element.
|
|
1719
|
+
|
|
1720
|
+
## Connecting Steps
|
|
1721
|
+
|
|
1722
|
+
Steps are connected via `dependsOn` (which step must complete first) and optional `conditionBranch` (which branch to follow from a condition).
|
|
1723
|
+
|
|
1724
|
+
```json
|
|
1725
|
+
{
|
|
1726
|
+
"steps": [
|
|
1727
|
+
{ "id": "check", "type": "condition", "data": {...} },
|
|
1728
|
+
{ "id": "notify-high", "type": "output", "data": {...}, "dependsOn": ["check"], "conditionBranch": "yes" },
|
|
1729
|
+
{ "id": "log-normal", "type": "output", "data": {...}, "dependsOn": ["check"], "conditionBranch": "no" }
|
|
1730
|
+
]
|
|
1731
|
+
}
|
|
1732
|
+
```
|
|
1733
|
+
|
|
1734
|
+
## Discovering Commands
|
|
1735
|
+
|
|
1736
|
+
Before creating automations with command steps, call `list_commands` to discover available intelligence commands in the workspace. Use the command `id` in the step's `commandId` field.
|
|
1737
|
+
|
|
1738
|
+
If no suitable command exists, you can leave `commandId` empty and set `commandTitle` + `inputMapping` — the execution engine will use the title as a prompt template.
|
|
1739
|
+
|
|
1740
|
+
## Vault References
|
|
1741
|
+
|
|
1742
|
+
Automation configs that need secrets (API keys, auth tokens) should use vault references instead of hardcoded values:
|
|
1743
|
+
|
|
1744
|
+
- `vault://secret-uuid` — resolves to the full secret value at runtime
|
|
1745
|
+
- `vault://secret-uuid/field-name` — resolves a specific field from a JSON secret
|
|
1746
|
+
|
|
1747
|
+
Only server-encrypted secrets can be resolved by the automation engine. The user must store the credential in the vault first.
|
|
1748
|
+
|
|
1749
|
+
## Best Practices
|
|
1750
|
+
|
|
1751
|
+
1. **Keep it simple** — Start with trigger → command → output. Add complexity only when needed.
|
|
1752
|
+
2. **Name clearly** — Use descriptive labels: "When task created with high priority" not "Trigger 1".
|
|
1753
|
+
3. **One purpose** — Each automation should do one thing well. Compose multiple automations rather than building one complex flow.
|
|
1754
|
+
4. **Filter early** — Use trigger filters to avoid unnecessary execution. Don't use a condition step when a trigger filter suffices.
|
|
1755
|
+
5. **Test first** — Create automations as `draft` status. Let the user review the flow visualization before activating.
|
|
1756
|
+
|
|
1757
|
+
## Example: Auto-archive completed tasks
|
|
1758
|
+
|
|
1759
|
+
```json
|
|
1760
|
+
{
|
|
1761
|
+
"name": "Auto-archive completed tasks",
|
|
1762
|
+
"description": "When a task status changes to 'done', archive it after 24 hours",
|
|
1763
|
+
"trigger": {
|
|
1764
|
+
"type": "event",
|
|
1765
|
+
"config": {
|
|
1766
|
+
"eventPattern": "entity.update.completed",
|
|
1767
|
+
"filters": { "profileSlug": "task", "metadata.status": "done" }
|
|
1768
|
+
}
|
|
1769
|
+
},
|
|
1770
|
+
"steps": [
|
|
1771
|
+
{
|
|
1772
|
+
"id": "wait-24h",
|
|
1773
|
+
"type": "delay",
|
|
1774
|
+
"data": { "duration": "1d", "label": "Wait 24h" }
|
|
1775
|
+
},
|
|
1776
|
+
{
|
|
1777
|
+
"id": "archive",
|
|
1778
|
+
"type": "output",
|
|
1779
|
+
"data": {
|
|
1780
|
+
"label": "Archive task",
|
|
1781
|
+
"outputType": "entity_update",
|
|
1782
|
+
"config": {
|
|
1783
|
+
"entityId": "{{trigger.payload.entity.id}}",
|
|
1784
|
+
"properties": { "archived": true }
|
|
1785
|
+
}
|
|
1786
|
+
},
|
|
1787
|
+
"dependsOn": ["wait-24h"]
|
|
1788
|
+
}
|
|
1789
|
+
]
|
|
1790
|
+
}
|
|
1791
|
+
```
|
|
1792
|
+
|
|
1793
|
+
## Example: Notify on high-priority tasks
|
|
1794
|
+
|
|
1795
|
+
```json
|
|
1796
|
+
{
|
|
1797
|
+
"name": "High-priority task alerts",
|
|
1798
|
+
"description": "Send a notification when a high-priority task is created",
|
|
1799
|
+
"trigger": {
|
|
1800
|
+
"type": "event",
|
|
1801
|
+
"config": {
|
|
1802
|
+
"eventPattern": "entity.create.completed",
|
|
1803
|
+
"filters": { "profileSlug": "task" }
|
|
1804
|
+
}
|
|
1805
|
+
},
|
|
1806
|
+
"steps": [
|
|
1807
|
+
{
|
|
1808
|
+
"id": "check-priority",
|
|
1809
|
+
"type": "condition",
|
|
1810
|
+
"data": {
|
|
1811
|
+
"label": "High priority?",
|
|
1812
|
+
"expression": "trigger.payload.entity.metadata.priority === 'high'"
|
|
1813
|
+
}
|
|
1814
|
+
},
|
|
1815
|
+
{
|
|
1816
|
+
"id": "notify",
|
|
1817
|
+
"type": "output",
|
|
1818
|
+
"data": {
|
|
1819
|
+
"label": "Alert: high-priority task",
|
|
1820
|
+
"outputType": "notification",
|
|
1821
|
+
"config": {
|
|
1822
|
+
"message": "New urgent task: {{trigger.payload.entity.name}}"
|
|
1823
|
+
}
|
|
1824
|
+
},
|
|
1825
|
+
"dependsOn": ["check-priority"],
|
|
1826
|
+
"conditionBranch": "yes"
|
|
1827
|
+
}
|
|
1828
|
+
]
|
|
1829
|
+
}
|
|
1830
|
+
```
|
|
1831
|
+
|
|
1832
|
+
---
|
|
1833
|
+
|
|
1834
|
+
# Diagnosing what an AI did — the runs door
|
|
1835
|
+
|
|
1836
|
+
When a capture, automation, playbook, or session **didn't do what you (or the user) expected** — a facet wasn't attached, an entity landed in the wrong workspace, a step failed silently — do **not** guess or apologize. Every AI run leaves a trace. Read it.
|
|
1837
|
+
|
|
1838
|
+
## The one tool
|
|
1839
|
+
|
|
1840
|
+
`synap_diagnose` — the unified view of what an AI did across flows.
|
|
1841
|
+
|
|
1842
|
+
- **No args** → the recent run feed (automation · playbook · capture · session), newest first. Each run has an `id`, `flowType`, `status`, and `flowName`.
|
|
1843
|
+
- **`runId` + `flowType`** → that run's **activity timeline**. For a **capture** run this is its decision + trace events: for each thing that was dropped/coerced, a machine-readable `reason` **and an actionable `fixHint`**.
|
|
1844
|
+
|
|
1845
|
+
(CLI equivalent for the operator: `synap diagnose` and `synap diagnose <captureId> --flow capture`.)
|
|
1846
|
+
|
|
1847
|
+
## The reflex
|
|
1848
|
+
|
|
1849
|
+
> "The capture didn't attach the client role" → `synap_diagnose({ runId: <captureId>, flowType: "capture" })` → read the `capture_trace` rows → act on the `fixHint`.
|
|
1850
|
+
|
|
1851
|
+
A capture's `id` **is** its correlationId — the same id stamped on the entities it created and returned to you as `captureId`. So you always have the key to diagnose your own last capture.
|
|
1852
|
+
|
|
1853
|
+
## Reading a capture trace
|
|
1854
|
+
|
|
1855
|
+
Each `capture_trace` activity item names a pipeline stage (`component`), why it stopped (`reason`), and what to do (`fixHint`). The common ones:
|
|
1856
|
+
|
|
1857
|
+
| reason | what happened | what to do |
|
|
1858
|
+
| -------------------------- | ----------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------- |
|
|
1859
|
+
| `not_in_creatable_catalog` | a role/kind you asked to create isn't a creatable entity kind — it's a **facet** (client, partner, prospect, investor…) | resolve the real entity first, then `attach_facet` for the role — never a second entity for a hat |
|
|
1860
|
+
| `kind_mismatch` | the facet's `applicableKinds` didn't include the target entity's kind | attach the role to an entity of a kind the role applies to |
|
|
1861
|
+
| `slug_coerce` | a profileSlug was normalized/renamed to the canonical one | use the canonical slug next time (see `list_profiles`) |
|
|
1862
|
+
| `materialize_skip` | an operation was skipped (dedup or a missing dependency) | check the dedup match; re-capture only what's genuinely new |
|
|
1863
|
+
|
|
1864
|
+
If a run carries a `channelId` (playbook / session / automation), its message-level story lives in that channel — the timeline points you there rather than duplicating it.
|
|
1865
|
+
|
|
1866
|
+
## Why this matters
|
|
1867
|
+
|
|
1868
|
+
The point of the flywheel is that mistakes are **visible and fixable**, not silent. If you can see what happened, you can correct it — and every correction teaches the routing. Reach for `synap_diagnose` before you conclude "it didn't work."
|
|
1869
|
+
|
|
1870
|
+
---
|
|
1871
|
+
|
|
1872
|
+
## Workspace design — is this concern a WORKSPACE, or something smaller?
|
|
1873
|
+
|
|
1874
|
+
Before you create a workspace, run the decision rule. A workspace (an operational **domain**) is the heaviest structure in the pod — it owns kinds, confers roles, carries its own team and automations. Most new concerns are NOT domains; they are a **hat**, an **initiative**, or a **stage**. Creating a workspace for one of those is the anti-pattern that fragments the graph. Decide first, then create.
|
|
1875
|
+
|
|
1876
|
+
## The decision rule — a concern earns a workspace ONLY if ALL FOUR hold
|
|
1877
|
+
|
|
1878
|
+
1. **Owns kinds** — it is source-of-truth for a noun nothing else owns (CRM owns `person`/`company`; Operations owns `engagement`/`deliverable`). If it only _reads_ or _annotates_ another domain's kinds, it is not a domain.
|
|
1879
|
+
2. **Own team** — a distinct set of operators/collaborators works it (separation of _who_, not just _what_).
|
|
1880
|
+
3. **Native automations/tools** — it runs behavior its neighbors don't (its own capabilities, playbooks, triggers).
|
|
1881
|
+
4. **Stable** — it persists across clients and campaigns. If it is per-client or per-campaign, it is time-bound, not a domain.
|
|
1882
|
+
|
|
1883
|
+
**All four, or it is not a workspace.** Then fork it to the right lighter structure:
|
|
1884
|
+
|
|
1885
|
+
| If the concern is… | It is a… | Substrate | Example |
|
|
1886
|
+
| ----------------------------------------------------------- | -------------- | -------------------------------------- | ------------------------------------------- |
|
|
1887
|
+
| a **role/hat** an existing entity wears in a domain | **Facet** | `attach_facet` (`profileKind: "role"`) | `client`, `sponsor`, `prospect`, `investor` |
|
|
1888
|
+
| a **cross-cutting, time-bound initiative** spanning domains | **Project** | `create_project` (a lens) | a campaign, an engagement, a launch |
|
|
1889
|
+
| a **stage/filter WITHIN a domain** | **State/View** | a `status` property def + a view | pipeline stage, "active"/"archived" |
|
|
1890
|
+
|
|
1891
|
+
## The decision procedure (follow in order)
|
|
1892
|
+
|
|
1893
|
+
1. **Name the source-of-truth noun.** What kind would this workspace _own_ that no existing workspace owns? Run `list_profiles` — if the noun already lives in another domain, you have a facet or a project, not a domain. STOP.
|
|
1894
|
+
2. **Test all four conditions.** Owns kinds AND own team AND native automations AND stable. Any one fails → fork below.
|
|
1895
|
+
3. **If it's a hat** (a status/role on an entity that already exists elsewhere) → resolve the entity, `attach_facet`. Never a workspace, never a second entity.
|
|
1896
|
+
4. **If it's time-bound work across domains** → `create_project` and set it as the lens; the work files into it from whatever workspace holds the data. **A project is a COMMITMENT WITH GRAVITY** — a real initiative that ties work together (a campaign, an engagement, a client, a launch). Tasks, plans, repos, themes, and topics are **entities**, never projects. Before you create one: (a) **search existing projects first** (`synap orient` / `GET /api/hub/projects`) and prefer **linking into an existing project** via `belongs_to_project` — near-duplicate names are rejected with the existing candidates; (b) an agent-created project must cite **≥5 existing entities** that would belong to it as `evidenceEntityIds` — the backend rejects a project with no gravity and tells you to store it as an entity or reuse an existing project instead; (c) **never create a project for the pod owner's own company** — the company _is_ the pod, not a project inside it.
|
|
1897
|
+
5. **If it's a stage inside a domain** → add a `status` property def (`create_property_def`) and a view; don't split the stage into its own space.
|
|
1898
|
+
6. **Only if all four held** → **template first** (escalation ladder L3):
|
|
1899
|
+
`market.search({query, kind: "template"})` and propose install of a matching
|
|
1900
|
+
template before freehand `create_workspace`. Freehand create is last resort
|
|
1901
|
+
and always proposed — a deliberate move, offer it to the user (see
|
|
1902
|
+
`lenses.md`). Then declare how it lives in the graph (see `workspace-edges.md`).
|
|
1903
|
+
|
|
1904
|
+
## The CRM corollary — the load-bearing example
|
|
1905
|
+
|
|
1906
|
+
Operational state — **prospect → client → delivered** — is a **FLOW across domains**, expressed as **facets + a triggered project**, NEVER as workspaces and NEVER by bolting delivery onto the identity domain.
|
|
1907
|
+
|
|
1908
|
+
- CRM = **who** (owns `person`/`company`, confers the `lead`/`client` facets).
|
|
1909
|
+
- Operations = **what we do for them** (owns `engagement`/`contract`/`deliverable`).
|
|
1910
|
+
- The bridge: attaching the `client` facet in CRM **triggers** an engagement project in Operations (see the _triggers_ edge in `workspace-edges.md`).
|
|
1911
|
+
|
|
1912
|
+
Bolting delivery-ops onto CRM was the anti-pattern: it made one workspace own two unrelated source-of-truth concerns and blurred _who_ the entity is with _what work_ is happening. Split by ownership; bridge by facet + trigger.
|
|
1913
|
+
|
|
1914
|
+
## Why this matters
|
|
1915
|
+
|
|
1916
|
+
A workspace is a boundary; a facet/project/state is a connection. Boundaries fragment the graph — they should be rare and earned. When you catch yourself about to create a workspace, check the four conditions: nine times out of ten the honest answer is a facet on an entity that already exists, a project that spans what's already there, or a status field on a kind you already own.
|
|
1917
|
+
|
|
1918
|
+
---
|
|
1919
|
+
|
|
1920
|
+
## Workspace edges — how a domain LIVES IN THE GRAPH
|
|
1921
|
+
|
|
1922
|
+
A workspace is never an island. Before you create one (or reason about an existing one), map its **edges**: what it consumes, what it provides, what it triggers, what subject it shares, what spans it. Domains are wired together by a small, fixed taxonomy — and each edge type maps to a specific substrate. Knowing the taxonomy is what lets you reason about a new domain's _position_ instead of dropping it in disconnected.
|
|
1923
|
+
|
|
1924
|
+
> The two graphs are orthogonal. This is the **data-flow graph** (what reads/writes/triggers what) — the one we model. The **org graph** (who owns/operates a domain) is workspace membership only. "Comms contains Marketing" is org; the _data_ edge is "Marketing **consumes** Comms' brand." Keep them separate so a team reorg never rewires the data graph.
|
|
1925
|
+
|
|
1926
|
+
## The four edge types (and their substrate)
|
|
1927
|
+
|
|
1928
|
+
| Edge | Meaning | Direction | Substrate | Example |
|
|
1929
|
+
| ----------------------- | ------------------------------------------------ | --------- | ------------------------------------------------------------- | ----------------------------------------------------- |
|
|
1930
|
+
| **Provides / Consumes** | a domain _reads_ another's data (read redirect) | A ← B | `defaultSources` / `sourceRoles` on the consuming workspace | Content **consumes** Comms' voice/ICP |
|
|
1931
|
+
| **Triggers** | an event in A causes _work written_ into B | A ⇒ B | automation + `resolveWorkspacePlacement` (run-in-A → write-B) | a `client` facet in CRM ⇒ an engagement in Operations |
|
|
1932
|
+
| **Shares subject** | the same atom wears a different facet per domain | A ⟷ B | `entity_facets` (one entity, per-domain roles) | one company is `lead` in CRM, `client` in Ops |
|
|
1933
|
+
| **Spans** | a time-bound initiative crosses domains | A—B—C | `projects` (a cross-cutting lens) | one campaign spans Marketing + Content + Social |
|
|
1934
|
+
|
|
1935
|
+
Read the whole graph as: **Provides/Consumes** = the read wiring · **Triggers** = the write/event wiring · **Shares subject** = shared identity · **Spans** = shared initiative.
|
|
1936
|
+
|
|
1937
|
+
## Reason about position BEFORE you create
|
|
1938
|
+
|
|
1939
|
+
When `workspace-design.md`'s rule says "yes, this is a domain," don't stop at creating it — place it in the graph:
|
|
1940
|
+
|
|
1941
|
+
1. **What does it consume?** Which existing domains' data does it read? Those become its `defaultSources` (provides/consumes edges).
|
|
1942
|
+
2. **What does it provide?** Which domains will read _its_ output? (Declared on the consumer's side, but know the answer.)
|
|
1943
|
+
3. **What does it trigger — and what triggers it?** Which facet/event in a neighbor should spin up work here, or here into a neighbor? That's the automation + placement wiring (Wave 2 processor behavior).
|
|
1944
|
+
4. **What subject does it share?** Which atoms already exist elsewhere that this domain will confer a new facet on? (Resolve identity first, `attach_facet` — never a duplicate.)
|
|
1945
|
+
5. **What projects span it?** Which cross-cutting initiatives will pull its data alongside other domains'?
|
|
1946
|
+
|
|
1947
|
+
A domain that consumes nothing and provides nothing is a smell — re-check the decision rule; it may be a facet or a project after all.
|
|
1948
|
+
|
|
1949
|
+
## Declaring provides/consumes on an existing workspace
|
|
1950
|
+
|
|
1951
|
+
Edges used to be settable only at template-authoring time or through the tRPC UI. The agnostic door for setting them on a live workspace is the governed MCP tool **`synap_declare_workspace_source`** (Hub REST: `PATCH /workspaces/:id/source-edges`): it merges `defaultSources` / `sourceRoles` on an existing workspace so the generic edge-resolver can redirect its reads to the providing domain, and it materializes the `feeds` link the placement ladder reads.
|
|
1952
|
+
|
|
1953
|
+
- Use it when a domain should start reading another's data (e.g. point Marketing at Comms for brand/ICP).
|
|
1954
|
+
- It is a **governed write** — a `{ status: "proposed", proposalId }` response is NORMAL, not an error. Because declaring an edge rewires where the pod's cross-workspace reads land, it goes through review: when you (an agent) call it, it is **proposed for a human to approve**, and the edge only goes live on approval (the `workspace/declare_source` proposal executor then runs the same merge). Tell the user it's **proposed for review** and share the review link — don't claim the edge is already live. (An operator calling it directly with their own authority applies immediately and gets `{ status: "updated" }`.)
|
|
1955
|
+
- Setting the edge is what makes cross-workspace reads resolve generically, instead of each domain re-deriving its sources by hand.
|
|
1956
|
+
|
|
1957
|
+
## The reference wiring (worked example)
|
|
1958
|
+
|
|
1959
|
+
The 6-domain reference enterprise, read as edges:
|
|
1960
|
+
|
|
1961
|
+
- **Communication** owns voice/narrative/ICP/assets — _provides_ → Marketing, Content.
|
|
1962
|
+
- **Content** owns asset/carousel/video — _consumes_ Comms; _provides_ → Social, Marketing.
|
|
1963
|
+
- **Marketing** owns campaign/funnel — _consumes_ Comms + Content; _provides_ brief → Social; ⇒ _triggers_ leads → CRM.
|
|
1964
|
+
- **Social** owns channel/post/schedule — _consumes_ Content + Marketing; _provides_ signals → Marketing/CRM.
|
|
1965
|
+
- **CRM** owns person/company, confers `lead`/`client` — _consumes_ Social signals; `client` facet ⇒ _triggers_ → Operations.
|
|
1966
|
+
- **Operations** owns engagement/contract/deliverable — _consumes_ CRM; delivery proof ⇒ _feeds_ → Content/Marketing.
|
|
1967
|
+
|
|
1968
|
+
Every arrow above is one of the four edge types. That is the whole model: name the arrows, pick the substrate, wire it — then the domain is a citizen of the graph, not an island.
|
|
1969
|
+
|
|
1970
|
+
---
|
|
1971
|
+
|
|
1972
|
+
## When you need more — core data operations
|
|
1973
|
+
|
|
1974
|
+
- Linking conventions, auto-sync table, relation types → **`linking.md`**
|
|
1975
|
+
- Full governance whitelist, proposal lifecycle, agent users → **`governance.md`**
|
|
1976
|
+
- Unstructured capture pipeline → **`capture.md`**
|
|
1977
|
+
- Extending the data model (new profiles, new properties) → install the **`synap-schema`** skill
|
|
1978
|
+
- Building views, dashboards, and bento layouts → install the **`synap-ui`** skill
|
|
1979
|
+
|
|
1980
|
+
---
|
|
808
1981
|
|
|
809
1982
|
## ViewFrame Cells — Custom View Generation
|
|
810
1983
|
|
|
@@ -820,87 +1993,222 @@ ViewFrame is the standard way to create custom data visualizations in Synap. Use
|
|
|
820
1993
|
|
|
821
1994
|
### What ViewFrame Is
|
|
822
1995
|
|
|
823
|
-
- A sandboxed iframe that renders
|
|
824
|
-
-
|
|
825
|
-
|
|
1996
|
+
- A sandboxed iframe that renders **one ES module** that default-exports a React component (or plain JS)
|
|
1997
|
+
- Keep generated cells self-contained. The CLI can analyse bare imports into a
|
|
1998
|
+
`deps` map, but the current Hub `cells/define` persistence path does not yet
|
|
1999
|
+
retain that map, so external runtime dependencies are not a reliable contract.
|
|
2000
|
+
- The host injects a `SynapWidget` bridge for data access and shell actions
|
|
826
2001
|
- Security: `sandbox="allow-scripts allow-modals allow-popups"`, no `allow-same-origin`, no cookies, no pod token
|
|
827
2002
|
|
|
828
|
-
###
|
|
2003
|
+
### Authoring contract
|
|
829
2004
|
|
|
830
|
-
|
|
2005
|
+
A ViewFrame cell is **one self-contained ES module** (inline in `rendererSource`):
|
|
831
2006
|
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
2007
|
+
- The module **default-exports a React component** (or calls `SynapWidget.onInit()` for plain JS).
|
|
2008
|
+
- Bare imports (`"react"`, `"recharts"`, etc.) are resolved via the esm.sh import map generated from `deps`.
|
|
2009
|
+
- **No bundler, no `import` of local files** — everything is either inlined or declared in `deps`.
|
|
2010
|
+
- External CSS is not supported; inline `<style>` tags or CSS-in-JS only.
|
|
836
2011
|
|
|
837
|
-
|
|
2012
|
+
### Register a Cell via the Hub Protocol (canonical path)
|
|
2013
|
+
|
|
2014
|
+
**Use `POST /api/hub/cells/define` — this is the canonical Hub Protocol path for AI-generated cells.**
|
|
2015
|
+
|
|
2016
|
+
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
2017
|
|
|
839
2018
|
```
|
|
840
|
-
POST /api/hub/
|
|
2019
|
+
POST /api/hub/cells/define
|
|
841
2020
|
Authorization: Bearer {SYNAP_HUB_API_KEY}
|
|
842
2021
|
Content-Type: application/json
|
|
843
2022
|
|
|
844
2023
|
{
|
|
845
|
-
"userId": "{SYNAP_USER_ID}",
|
|
846
|
-
"workspaceId": "{workspaceId}",
|
|
847
|
-
"typeKey": "deal-stage-funnel",
|
|
848
2024
|
"name": "Deal Stage Funnel",
|
|
849
|
-
"
|
|
850
|
-
"
|
|
851
|
-
"
|
|
852
|
-
"defaultSize": { "w": 8, "h": 6 },
|
|
853
|
-
"
|
|
2025
|
+
"rendererSource": "<!DOCTYPE html>…</html>",
|
|
2026
|
+
"typeKey": "deal-stage-funnel", // optional — derived from name if omitted
|
|
2027
|
+
"description": "Funnel chart of deal pipeline stages", // optional
|
|
2028
|
+
"defaultSize": { "w": 8, "h": 6 }, // optional
|
|
2029
|
+
"deps": { "recharts": "2.12.0" } // accepted for forward compatibility; not persisted yet
|
|
854
2030
|
}
|
|
855
2031
|
```
|
|
856
2032
|
|
|
857
|
-
`
|
|
2033
|
+
**`deps` status:** the CLI accepts a JSON map for forward compatibility, but
|
|
2034
|
+
the current server stores `{}`. Do not make a generated cell depend on an
|
|
2035
|
+
external package until the persistence contract is upgraded and verified.
|
|
2036
|
+
|
|
2037
|
+
**`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.
|
|
2038
|
+
|
|
2039
|
+
Response: `{ "success": true, "typeKey": "generated:deal-stage-funnel" }`
|
|
2040
|
+
|
|
2041
|
+
The typeKey is auto-prefixed `generated:` when not explicitly provided.
|
|
2042
|
+
|
|
2043
|
+
**List cells (all pod-global + optionally workspace-specific):**
|
|
2044
|
+
|
|
2045
|
+
```
|
|
2046
|
+
GET /api/hub/cells — pod-global only
|
|
2047
|
+
GET /api/hub/cells?workspaceId={id} — pod-global + workspace-scoped
|
|
2048
|
+
Authorization: Bearer {SYNAP_HUB_API_KEY}
|
|
2049
|
+
```
|
|
2050
|
+
|
|
2051
|
+
**Delete a cell:**
|
|
2052
|
+
|
|
2053
|
+
```
|
|
2054
|
+
DELETE /api/hub/cells/{typeKey} — pod-global row
|
|
2055
|
+
DELETE /api/hub/cells/{typeKey}?workspaceId={id} — workspace-scoped row
|
|
2056
|
+
Authorization: Bearer {SYNAP_HUB_API_KEY}
|
|
2057
|
+
```
|
|
2058
|
+
|
|
2059
|
+
**Open the cell in the browser (deep link):**
|
|
2060
|
+
|
|
2061
|
+
```
|
|
2062
|
+
synap://open/cell/{typeKey}
|
|
2063
|
+
```
|
|
2064
|
+
|
|
2065
|
+
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.
|
|
2066
|
+
|
|
2067
|
+
**Full AI artifact workflow:**
|
|
2068
|
+
|
|
2069
|
+
```
|
|
2070
|
+
// 1. Generate the HTML/React cell
|
|
2071
|
+
POST /api/hub/cells/define
|
|
2072
|
+
{ "name": "Q2 Revenue Report", "rendererSource": "<!DOCTYPE html>…</html>",
|
|
2073
|
+
"deps": { "recharts": "2.12.0" } }
|
|
2074
|
+
// → { "success": true, "typeKey": "generated:q2-revenue-report" }
|
|
2075
|
+
|
|
2076
|
+
// 2. Open it in the user's browser
|
|
2077
|
+
synap://open/cell/generated:q2-revenue-report
|
|
2078
|
+
```
|
|
2079
|
+
|
|
2080
|
+
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.
|
|
2081
|
+
|
|
2082
|
+
> **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.
|
|
858
2083
|
|
|
859
|
-
|
|
2084
|
+
### CLI commands (when running as Claude Code / OpenClaw agent)
|
|
2085
|
+
|
|
2086
|
+
```bash
|
|
2087
|
+
# Build a multi-file cell source into a single ES module bundle
|
|
2088
|
+
synap cell build <entry> --out ./dist/my-chart.js
|
|
2089
|
+
# → writes the bundle and prints the inferred deps JSON
|
|
2090
|
+
|
|
2091
|
+
# Push a built cell (source + deps) to the pod
|
|
2092
|
+
synap cell define \
|
|
2093
|
+
--name "My Chart" \
|
|
2094
|
+
--file ./dist/my-chart.js \
|
|
2095
|
+
--deps '{"recharts":"2.12.0"}' \
|
|
2096
|
+
[--type-key my-chart] \
|
|
2097
|
+
[--workspace <id>]
|
|
2098
|
+
|
|
2099
|
+
# Document operations
|
|
2100
|
+
synap doc create --title "Q2 Report" --file ./report.md
|
|
2101
|
+
synap doc update <docId> --file ./updated-report.md
|
|
2102
|
+
|
|
2103
|
+
# Arrange widgets on an existing bento view
|
|
2104
|
+
synap view arrange <viewId> --blocks '[{"id":"b1","kind":"widget","widgetKind":"generated:my-chart","layout":{"x":0,"y":0,"w":8,"h":6}}]'
|
|
2105
|
+
```
|
|
860
2106
|
|
|
861
2107
|
### The SynapWidget Bridge (inside the iframe)
|
|
862
2108
|
|
|
863
2109
|
`window.SynapWidget` is injected automatically — do NOT import or `<script>` it.
|
|
864
2110
|
|
|
2111
|
+
#### Queries (read-only, always approved)
|
|
2112
|
+
|
|
865
2113
|
```js
|
|
866
2114
|
SynapWidget.onInit(async ({ config, context }) => {
|
|
867
2115
|
// context: { workspaceId, viewId?, entityId?, sdkVersion }
|
|
868
|
-
|
|
869
|
-
|
|
2116
|
+
|
|
2117
|
+
// List entities
|
|
2118
|
+
const deals = await SynapWidget.query("entities.list", {
|
|
2119
|
+
profileSlug: "deal",
|
|
870
2120
|
limit: 200,
|
|
871
2121
|
});
|
|
872
|
-
|
|
2122
|
+
|
|
2123
|
+
// Get a single entity
|
|
2124
|
+
const entity = await SynapWidget.query("entities.get", { id: "uuid" });
|
|
2125
|
+
|
|
2126
|
+
// List views
|
|
2127
|
+
const views = await SynapWidget.query("views.list", {
|
|
2128
|
+
workspaceId: context.workspaceId,
|
|
2129
|
+
});
|
|
2130
|
+
|
|
2131
|
+
// List profiles
|
|
2132
|
+
const profiles = await SynapWidget.query("profiles.list", {});
|
|
2133
|
+
|
|
2134
|
+
render(deals ?? []);
|
|
873
2135
|
SynapWidget.resize(document.body.scrollHeight);
|
|
874
2136
|
});
|
|
875
2137
|
```
|
|
876
2138
|
|
|
877
2139
|
All `query()` calls return a Promise. Entity shape: `{ id, title, profileSlug, properties, createdAt, … }`.
|
|
878
2140
|
|
|
879
|
-
|
|
2141
|
+
#### Mutations (governance-gated — return `{ status: "approved" | "proposed" | "denied" }`)
|
|
880
2142
|
|
|
881
2143
|
```js
|
|
882
|
-
|
|
883
|
-
SynapWidget.
|
|
2144
|
+
// Create an entity
|
|
2145
|
+
const result = await SynapWidget.mutate("create_entity", {
|
|
2146
|
+
profileSlug: "task",
|
|
2147
|
+
title: "Follow up",
|
|
2148
|
+
properties: { status: "todo" },
|
|
2149
|
+
});
|
|
2150
|
+
// result.status === "approved" → result.id is the new entity id
|
|
2151
|
+
// result.status === "proposed" → result.proposalId, result.reviewUrl
|
|
2152
|
+
|
|
2153
|
+
// Update an entity
|
|
2154
|
+
await SynapWidget.mutate("update_entity", {
|
|
2155
|
+
id: "uuid",
|
|
2156
|
+
properties: { status: "done" },
|
|
2157
|
+
});
|
|
2158
|
+
|
|
2159
|
+
// Delete an entity (always proposed for agent-generated cells)
|
|
2160
|
+
await SynapWidget.mutate("delete_entity", { id: "uuid" });
|
|
2161
|
+
|
|
2162
|
+
// Create a relation
|
|
2163
|
+
await SynapWidget.mutate("create_relation", {
|
|
2164
|
+
sourceEntityId: "uuid-a",
|
|
2165
|
+
targetEntityId: "uuid-b",
|
|
2166
|
+
type: "related_to",
|
|
2167
|
+
});
|
|
2168
|
+
```
|
|
2169
|
+
|
|
2170
|
+
**Always check `result.status`.** `"proposed"` is not an error — surface `result.reviewUrl` to the user.
|
|
2171
|
+
|
|
2172
|
+
#### Shell actions
|
|
2173
|
+
|
|
2174
|
+
```js
|
|
2175
|
+
SynapWidget.navigate({ entityId: "entity-uuid" }); // open entity detail in side panel
|
|
2176
|
+
SynapWidget.openPanel("entity-detail", { entityId: "uuid" }); // explicit panel open
|
|
2177
|
+
SynapWidget.toast("Saved!", "success"); // 'success' | 'error' | 'info'
|
|
2178
|
+
SynapWidget.resize(document.body.scrollHeight); // resize the iframe to content height
|
|
2179
|
+
SynapWidget.updateContext({ viewId: "uuid" }); // update ambient context
|
|
2180
|
+
|
|
2181
|
+
// Subscribe to live entity changes
|
|
2182
|
+
SynapWidget.subscribe("entity:changed", ({ entityId }) => {
|
|
2183
|
+
// re-fetch and re-render when any entity in the pod changes
|
|
2184
|
+
});
|
|
884
2185
|
```
|
|
885
2186
|
|
|
886
2187
|
### Common Dependency Patterns (esm.sh import map)
|
|
887
2188
|
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
2189
|
+
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>`.
|
|
2190
|
+
|
|
2191
|
+
```json
|
|
2192
|
+
// deps in the define call:
|
|
2193
|
+
{ "recharts": "2.12.0", "d3": "7" }
|
|
2194
|
+
|
|
2195
|
+
// → generates this importmap inside the frame:
|
|
2196
|
+
{
|
|
2197
|
+
"imports": {
|
|
2198
|
+
"react": "https://esm.sh/react@19",
|
|
2199
|
+
"react-dom/client": "https://esm.sh/react-dom@19/client",
|
|
2200
|
+
"react/jsx-runtime": "https://esm.sh/react@19/jsx-runtime",
|
|
2201
|
+
"recharts": "https://esm.sh/recharts@2.12.0",
|
|
2202
|
+
"d3": "https://esm.sh/d3@7"
|
|
897
2203
|
}
|
|
898
|
-
|
|
2204
|
+
}
|
|
899
2205
|
```
|
|
900
2206
|
|
|
2207
|
+
React 19 core entries are always injected by the host — never put them in `deps`.
|
|
2208
|
+
|
|
901
2209
|
Common library choices:
|
|
902
2210
|
|
|
903
|
-
| Category | Packages
|
|
2211
|
+
| Category | Packages (put in `deps`) |
|
|
904
2212
|
| -------- | -------------------------------------------------------------- |
|
|
905
2213
|
| Data viz | `recharts@2.12.0`, `d3@7`, `chart.js@4`, `observable-plot@0.6` |
|
|
906
2214
|
| Tables | `@tanstack/react-table@8` |
|
|
@@ -925,30 +2233,23 @@ Common library choices:
|
|
|
925
2233
|
background: transparent;
|
|
926
2234
|
}
|
|
927
2235
|
</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>
|
|
2236
|
+
<!-- importmap is injected by the host from deps — do not write one manually -->
|
|
937
2237
|
</head>
|
|
938
2238
|
<body>
|
|
939
2239
|
<div id="root"></div>
|
|
940
2240
|
<script type="module">
|
|
941
2241
|
import { createRoot } from "react-dom/client";
|
|
2242
|
+
import { createElement as h, useState } from "react";
|
|
942
2243
|
|
|
943
2244
|
SynapWidget.onInit(async ({ context }) => {
|
|
944
|
-
const items = await SynapWidget.query("
|
|
2245
|
+
const items = await SynapWidget.query("entities.list", {
|
|
945
2246
|
profileSlug: "deal",
|
|
946
2247
|
limit: 200,
|
|
947
2248
|
}).catch(() => []);
|
|
948
2249
|
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
2250
|
+
createRoot(document.getElementById("root")).render(
|
|
2251
|
+
h("p", null, `Loaded ${(items ?? []).length} deals`)
|
|
2252
|
+
);
|
|
952
2253
|
|
|
953
2254
|
SynapWidget.resize(document.body.scrollHeight);
|
|
954
2255
|
});
|
|
@@ -961,10 +2262,13 @@ Common library choices:
|
|
|
961
2262
|
|
|
962
2263
|
- **Always call `SynapWidget.onInit()`** — the host will not send data until you register this handler.
|
|
963
2264
|
- **Call `SynapWidget.resize()`** after rendering to prevent clipping.
|
|
964
|
-
- **Handle errors** — `query()` can fail; always `.catch()`.
|
|
2265
|
+
- **Handle errors** — `query()` and `mutate()` can fail; always `.catch()`.
|
|
2266
|
+
- **Check `result.status` on mutations** — `"proposed"` is governance, not an error; surface `reviewUrl`.
|
|
965
2267
|
- **Transparent background** — `background: transparent` on `body` inherits the host surface color.
|
|
966
2268
|
- **No external fetch** — the sandbox has no cross-origin access; all data must go through `SynapWidget`.
|
|
967
|
-
- **
|
|
2269
|
+
- **Declare all non-React imports in `deps`** — the host generates the import map from that field.
|
|
2270
|
+
|
|
2271
|
+
---
|
|
968
2272
|
|
|
969
2273
|
---
|
|
970
2274
|
|
|
@@ -978,3 +2282,119 @@ Scopes:
|
|
|
978
2282
|
hub-protocol.read → most GET endpoints
|
|
979
2283
|
hub-protocol.write → all writes AND GET /channels/personal
|
|
980
2284
|
```
|
|
2285
|
+
|
|
2286
|
+
---
|
|
2287
|
+
|
|
2288
|
+
## Responding as a co-founder
|
|
2289
|
+
|
|
2290
|
+
You are a strategic partner, not an assistant. The shape of your reply fits the
|
|
2291
|
+
question — there is no fixed template.
|
|
2292
|
+
|
|
2293
|
+
- **Lead with the direct answer.** Always. Asked what tasks they have? List the
|
|
2294
|
+
tasks. No preamble.
|
|
2295
|
+
- **Keep it proportional.** A quick lookup gets a quick answer; a strategic
|
|
2296
|
+
question earns depth. Don't pad simple answers with extra layers.
|
|
2297
|
+
- **Weave in one insight only if it's genuinely useful** — something from your
|
|
2298
|
+
investigation that contradicts prior context, connects two things the user
|
|
2299
|
+
hasn't linked, or changes the picture. If nothing stands out, skip it.
|
|
2300
|
+
- **Push back only for a real reason** — a direction that conflicts with a stated
|
|
2301
|
+
goal, or a clearly better path. Never manufacture skepticism.
|
|
2302
|
+
- **Propose actions only when the request or context warrants it.** Linking,
|
|
2303
|
+
creating a work item, spawning a branch should feel like a natural next step,
|
|
2304
|
+
not a default closing paragraph.
|
|
2305
|
+
|
|
2306
|
+
**Never render "Layer 1 / 2 / 3 / 4" as headers or labels.** Those are mental-model
|
|
2307
|
+
cues, not sections to fill in. A reply that naturally answers, connects, and
|
|
2308
|
+
proposes beats one that mechanically does all four.
|
|
2309
|
+
|
|
2310
|
+
---
|
|
2311
|
+
|
|
2312
|
+
## Recapping tasks
|
|
2313
|
+
|
|
2314
|
+
At session boundaries, keep the user oriented. When you complete a task that took
|
|
2315
|
+
3+ tool calls, end your response with a tight recap block:
|
|
2316
|
+
|
|
2317
|
+
---
|
|
2318
|
+
|
|
2319
|
+
**What I did:** [1-3 bullets: key actions]
|
|
2320
|
+
**Result:** [what was created, found, or changed]
|
|
2321
|
+
**Next steps:** [optional: what the user might do next]
|
|
2322
|
+
|
|
2323
|
+
---
|
|
2324
|
+
|
|
2325
|
+
Keep it to 3-5 lines. Skip it for simple answers, quick lookups, or single-tool
|
|
2326
|
+
tasks. Do NOT create a workspace (or any entity) for the recap — it lives in your
|
|
2327
|
+
response text only.
|
|
2328
|
+
|
|
2329
|
+
**Recap vs. conversational response — pick one.** The recap block is for
|
|
2330
|
+
summarising multi-step tool work. A conversational co-founder reply (see
|
|
2331
|
+
`response-style.md`) is for everything else. Never stack both structures in one
|
|
2332
|
+
reply.
|
|
2333
|
+
|
|
2334
|
+
---
|
|
2335
|
+
|
|
2336
|
+
## Showing on the screen
|
|
2337
|
+
|
|
2338
|
+
You have a screen, not only a memory. When you find, build, or propose something
|
|
2339
|
+
the user would want to SEE, open it with `focus_surface` instead of only
|
|
2340
|
+
describing it:
|
|
2341
|
+
|
|
2342
|
+
- They ask about an entity / view / channel and you found it → `focus_surface` to
|
|
2343
|
+
open it. `kind` = `entity | view | channel | cell | app`; `placement` = `main`
|
|
2344
|
+
to focus it, `side` to keep the conversation in view.
|
|
2345
|
+
- You created a view or generated a widget → open the result, don't hand back a
|
|
2346
|
+
paragraph about it.
|
|
2347
|
+
- You proposed a graph of changes (a PR) → lay them out with
|
|
2348
|
+
`place_on_whiteboard` so the review is spatial.
|
|
2349
|
+
|
|
2350
|
+
**Rules:** show when it genuinely helps the user see or act — not every turn, one
|
|
2351
|
+
surface at a time. Lead with the direct answer, THEN open. `focus_surface` only
|
|
2352
|
+
navigates; it mutates nothing, so it needs no proposal — it runs like a read.
|
|
2353
|
+
|
|
2354
|
+
---
|
|
2355
|
+
|
|
2356
|
+
## Modeling the user
|
|
2357
|
+
|
|
2358
|
+
You keep a structured model of the user across sessions in `user_observation`
|
|
2359
|
+
entities (a pod-scoped profile) — their working style, communication
|
|
2360
|
+
preferences, focus patterns, technical habits.
|
|
2361
|
+
|
|
2362
|
+
**Reading.** The durable model is loaded for you at session start under a
|
|
2363
|
+
"## What I Know About You (durable)" context block. Use it; you don't need to
|
|
2364
|
+
search for it. Inspect `user_observation` entities mid-session only if you need
|
|
2365
|
+
detail.
|
|
2366
|
+
|
|
2367
|
+
**Writing.** When you observe a NEW durable pattern — one that changes how you
|
|
2368
|
+
work with this person across sessions — call `record_observation`:
|
|
2369
|
+
|
|
2370
|
+
- `observation` — plain-language description of the pattern
|
|
2371
|
+
- `category` — `working_style | communication | focus | preferences | habits | technical`
|
|
2372
|
+
- `confidence` — ~0.6 for an inference, 0.9 for an explicit "I always want X"
|
|
2373
|
+
- `validated` — true only if the user explicitly confirmed it
|
|
2374
|
+
|
|
2375
|
+
**Rules:**
|
|
2376
|
+
|
|
2377
|
+
- Write only genuine signal, never one-time behaviour.
|
|
2378
|
+
- Update an existing observation instead of duplicating — search by category first.
|
|
2379
|
+
- Do it silently. Never tell the user "I updated your model" mid-conversation.
|
|
2380
|
+
- On an explicit "I always want X", write it immediately at confidence 0.9.
|
|
2381
|
+
|
|
2382
|
+
---
|
|
2383
|
+
|
|
2384
|
+
## Aligning to the North Star
|
|
2385
|
+
|
|
2386
|
+
If a "## North Star" block appears in your context, treat it as the workspace's
|
|
2387
|
+
anchoring goal:
|
|
2388
|
+
|
|
2389
|
+
- Let it guide your reasoning silently on every response — no need to announce it.
|
|
2390
|
+
- When you're about to create, propose, or initiate an action (not on reads,
|
|
2391
|
+
lookups, or casual replies), state in one line how it advances the North Star
|
|
2392
|
+
before proceeding.
|
|
2393
|
+
- When you create a task or work item, link it to the North Star with the
|
|
2394
|
+
`advances` relation — the North Star id from the context block is the target.
|
|
2395
|
+
- Pressure-test off-goal requests as a co-founder: "This doesn't obviously
|
|
2396
|
+
advance [North Star] — want me to do it anyway, or is there a higher-leverage
|
|
2397
|
+
move?"
|
|
2398
|
+
|
|
2399
|
+
**Never invent a North Star.** If no "## North Star" block is present, work
|
|
2400
|
+
normally and, when the moment is right, suggest the user define one.
|