@synap-core/cli 1.8.0 → 1.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (372) hide show
  1. package/dist/commands/agent-chat.d.ts +47 -0
  2. package/dist/commands/agent-chat.js +250 -0
  3. package/dist/commands/agent-chat.js.map +1 -0
  4. package/dist/commands/agent-run.js +3 -4
  5. package/dist/commands/agent-run.js.map +1 -1
  6. package/dist/commands/agents.js +85 -46
  7. package/dist/commands/agents.js.map +1 -1
  8. package/dist/commands/automation.js +2 -1
  9. package/dist/commands/automation.js.map +1 -1
  10. package/dist/commands/bridge-setup.d.ts +66 -0
  11. package/dist/commands/bridge-setup.js +1112 -0
  12. package/dist/commands/bridge-setup.js.map +1 -0
  13. package/dist/commands/cap-connections.d.ts +28 -0
  14. package/dist/commands/cap-connections.js +224 -0
  15. package/dist/commands/cap-connections.js.map +1 -0
  16. package/dist/commands/capability.d.ts +89 -0
  17. package/dist/commands/capability.js +1594 -0
  18. package/dist/commands/capability.js.map +1 -0
  19. package/dist/commands/centrality.d.ts +20 -0
  20. package/dist/commands/centrality.js +95 -0
  21. package/dist/commands/centrality.js.map +1 -0
  22. package/dist/commands/connect.d.ts +2 -0
  23. package/dist/commands/connect.js +16 -35
  24. package/dist/commands/connect.js.map +1 -1
  25. package/dist/commands/context.js +6 -7
  26. package/dist/commands/context.js.map +1 -1
  27. package/dist/commands/data-extra.d.ts +1 -0
  28. package/dist/commands/data-extra.js +41 -14
  29. package/dist/commands/data-extra.js.map +1 -1
  30. package/dist/commands/data.d.ts +4 -4
  31. package/dist/commands/data.js +254 -187
  32. package/dist/commands/data.js.map +1 -1
  33. package/dist/commands/diagnose.d.ts +20 -0
  34. package/dist/commands/diagnose.js +186 -0
  35. package/dist/commands/diagnose.js.map +1 -0
  36. package/dist/commands/digest.d.ts +17 -0
  37. package/dist/commands/digest.js +122 -0
  38. package/dist/commands/digest.js.map +1 -0
  39. package/dist/commands/discover.d.ts +2 -0
  40. package/dist/commands/discover.js +25 -8
  41. package/dist/commands/discover.js.map +1 -1
  42. package/dist/commands/doc.d.ts +14 -0
  43. package/dist/commands/doc.js +61 -4
  44. package/dist/commands/doc.js.map +1 -1
  45. package/dist/commands/doctor.d.ts +21 -0
  46. package/dist/commands/doctor.js +189 -0
  47. package/dist/commands/doctor.js.map +1 -0
  48. package/dist/commands/events.js +5 -2
  49. package/dist/commands/events.js.map +1 -1
  50. package/dist/commands/explain.d.ts +1 -1
  51. package/dist/commands/explain.js +18 -15
  52. package/dist/commands/explain.js.map +1 -1
  53. package/dist/commands/facet.d.ts +29 -0
  54. package/dist/commands/facet.js +113 -0
  55. package/dist/commands/facet.js.map +1 -0
  56. package/dist/commands/graph.js +7 -4
  57. package/dist/commands/graph.js.map +1 -1
  58. package/dist/commands/import.d.ts +38 -0
  59. package/dist/commands/import.js +471 -0
  60. package/dist/commands/import.js.map +1 -0
  61. package/dist/commands/init.js +35 -16
  62. package/dist/commands/init.js.map +1 -1
  63. package/dist/commands/keys.js +3 -4
  64. package/dist/commands/keys.js.map +1 -1
  65. package/dist/commands/knowledge.d.ts +8 -0
  66. package/dist/commands/knowledge.js +308 -33
  67. package/dist/commands/knowledge.js.map +1 -1
  68. package/dist/commands/launch.d.ts +50 -0
  69. package/dist/commands/launch.js +790 -0
  70. package/dist/commands/launch.js.map +1 -0
  71. package/dist/commands/lens.d.ts +45 -0
  72. package/dist/commands/lens.js +386 -0
  73. package/dist/commands/lens.js.map +1 -0
  74. package/dist/commands/market-authoring.d.ts +45 -0
  75. package/dist/commands/market-authoring.js +225 -0
  76. package/dist/commands/market-authoring.js.map +1 -0
  77. package/dist/commands/market.d.ts +224 -0
  78. package/dist/commands/market.js +1643 -0
  79. package/dist/commands/market.js.map +1 -0
  80. package/dist/commands/mcp.d.ts +64 -0
  81. package/dist/commands/mcp.js +265 -0
  82. package/dist/commands/mcp.js.map +1 -0
  83. package/dist/commands/observe.js +14 -11
  84. package/dist/commands/observe.js.map +1 -1
  85. package/dist/commands/open.d.ts +21 -4
  86. package/dist/commands/open.js +45 -4
  87. package/dist/commands/open.js.map +1 -1
  88. package/dist/commands/pod.d.ts +100 -0
  89. package/dist/commands/pod.js +359 -0
  90. package/dist/commands/pod.js.map +1 -0
  91. package/dist/commands/pods.d.ts +15 -0
  92. package/dist/commands/pods.js +233 -17
  93. package/dist/commands/pods.js.map +1 -1
  94. package/dist/commands/proposals-actions.js +43 -3
  95. package/dist/commands/proposals-actions.js.map +1 -1
  96. package/dist/commands/purge.d.ts +23 -0
  97. package/dist/commands/purge.js +148 -0
  98. package/dist/commands/purge.js.map +1 -0
  99. package/dist/commands/raycast.d.ts +15 -0
  100. package/dist/commands/raycast.js +153 -0
  101. package/dist/commands/raycast.js.map +1 -0
  102. package/dist/commands/sessions.js +47 -22
  103. package/dist/commands/sessions.js.map +1 -1
  104. package/dist/commands/skill-manage.d.ts +21 -0
  105. package/dist/commands/skill-manage.js +195 -0
  106. package/dist/commands/skill-manage.js.map +1 -0
  107. package/dist/commands/skills-commands.js +2 -1
  108. package/dist/commands/skills-commands.js.map +1 -1
  109. package/dist/commands/skills.js +7 -4
  110. package/dist/commands/skills.js.map +1 -1
  111. package/dist/commands/status.d.ts +3 -1
  112. package/dist/commands/status.js +166 -14
  113. package/dist/commands/status.js.map +1 -1
  114. package/dist/commands/statusline.d.ts +24 -0
  115. package/dist/commands/statusline.js +450 -0
  116. package/dist/commands/statusline.js.map +1 -0
  117. package/dist/commands/subscribe.js +2 -2
  118. package/dist/commands/subscribe.js.map +1 -1
  119. package/dist/commands/template.d.ts +42 -0
  120. package/dist/commands/template.js +191 -0
  121. package/dist/commands/template.js.map +1 -0
  122. package/dist/commands/tools.d.ts +30 -0
  123. package/dist/commands/tools.js +315 -0
  124. package/dist/commands/tools.js.map +1 -0
  125. package/dist/commands/upload.d.ts +30 -0
  126. package/dist/commands/upload.js +155 -0
  127. package/dist/commands/upload.js.map +1 -0
  128. package/dist/commands/versions.d.ts +27 -0
  129. package/dist/commands/versions.js +277 -0
  130. package/dist/commands/versions.js.map +1 -0
  131. package/dist/commands/view.js +3 -2
  132. package/dist/commands/view.js.map +1 -1
  133. package/dist/commands/whoami.d.ts +21 -0
  134. package/dist/commands/whoami.js +129 -0
  135. package/dist/commands/whoami.js.map +1 -0
  136. package/dist/index.js +894 -60
  137. package/dist/index.js.map +1 -1
  138. package/dist/lib/approval-poll.d.ts +42 -0
  139. package/dist/lib/approval-poll.js +52 -0
  140. package/dist/lib/approval-poll.js.map +1 -0
  141. package/dist/lib/auth.d.ts +29 -6
  142. package/dist/lib/auth.js +105 -94
  143. package/dist/lib/auth.js.map +1 -1
  144. package/dist/lib/bundle-version.d.ts +11 -0
  145. package/dist/lib/bundle-version.js +33 -0
  146. package/dist/lib/bundle-version.js.map +1 -0
  147. package/dist/lib/capture-lane.d.ts +30 -12
  148. package/dist/lib/capture-lane.js +73 -14
  149. package/dist/lib/capture-lane.js.map +1 -1
  150. package/dist/lib/capture-structure.d.ts +123 -0
  151. package/dist/lib/capture-structure.js +57 -0
  152. package/dist/lib/capture-structure.js.map +1 -0
  153. package/dist/lib/cp-packages.d.ts +177 -0
  154. package/dist/lib/cp-packages.js +389 -0
  155. package/dist/lib/cp-packages.js.map +1 -0
  156. package/dist/lib/describe-lens.d.ts +46 -0
  157. package/dist/lib/describe-lens.js +33 -0
  158. package/dist/lib/describe-lens.js.map +1 -0
  159. package/dist/lib/hub-client.d.ts +91 -1
  160. package/dist/lib/hub-client.js +565 -22
  161. package/dist/lib/hub-client.js.map +1 -1
  162. package/dist/lib/id.d.ts +23 -0
  163. package/dist/lib/id.js +35 -0
  164. package/dist/lib/id.js.map +1 -0
  165. package/dist/lib/installed.d.ts +51 -0
  166. package/dist/lib/installed.js +70 -0
  167. package/dist/lib/installed.js.map +1 -0
  168. package/dist/lib/next-steps.d.ts +61 -0
  169. package/dist/lib/next-steps.js +146 -0
  170. package/dist/lib/next-steps.js.map +1 -0
  171. package/dist/lib/pod.d.ts +144 -22
  172. package/dist/lib/pod.js +302 -64
  173. package/dist/lib/pod.js.map +1 -1
  174. package/dist/lib/project-ref.d.ts +95 -0
  175. package/dist/lib/project-ref.js +144 -0
  176. package/dist/lib/project-ref.js.map +1 -0
  177. package/dist/lib/project.d.ts +35 -0
  178. package/dist/lib/project.js +39 -0
  179. package/dist/lib/project.js.map +1 -0
  180. package/dist/lib/session-lens.d.ts +33 -0
  181. package/dist/lib/session-lens.js +76 -0
  182. package/dist/lib/session-lens.js.map +1 -0
  183. package/dist/lib/skill-import.d.ts +40 -0
  184. package/dist/lib/skill-import.js +203 -0
  185. package/dist/lib/skill-import.js.map +1 -0
  186. package/dist/lib/skills-installer.d.ts +17 -6
  187. package/dist/lib/skills-installer.js +133 -19
  188. package/dist/lib/skills-installer.js.map +1 -1
  189. package/dist/lib/suite.d.ts +28 -0
  190. package/dist/lib/suite.js +29 -0
  191. package/dist/lib/suite.js.map +1 -0
  192. package/dist/lib/superwhisper-import.d.ts +40 -0
  193. package/dist/lib/superwhisper-import.js +389 -0
  194. package/dist/lib/superwhisper-import.js.map +1 -0
  195. package/dist/lib/targets.d.ts +49 -1
  196. package/dist/lib/targets.js +420 -288
  197. package/dist/lib/targets.js.map +1 -1
  198. package/dist/lib/template-file.d.ts +74 -0
  199. package/dist/lib/template-file.js +140 -0
  200. package/dist/lib/template-file.js.map +1 -0
  201. package/dist/lib/unwrapList.d.ts +1 -0
  202. package/dist/lib/unwrapList.js +20 -0
  203. package/dist/lib/unwrapList.js.map +1 -0
  204. package/dist/utils/logger.d.ts +2 -0
  205. package/dist/utils/logger.js +10 -2
  206. package/dist/utils/logger.js.map +1 -1
  207. package/node_modules/@synap/hub-rest-client/dist/index.cjs +414 -34
  208. package/node_modules/@synap/hub-rest-client/dist/index.d.cts +762 -22
  209. package/node_modules/@synap/hub-rest-client/dist/index.d.ts +762 -22
  210. package/node_modules/@synap/hub-rest-client/dist/index.js +414 -34
  211. package/node_modules/@synap/hub-rest-client/package.json +1 -1
  212. package/node_modules/@synap/hub-rest-client/src/client.ts +525 -37
  213. package/node_modules/@synap/hub-rest-client/src/index.ts +42 -0
  214. package/node_modules/@synap/hub-rest-client/src/setup.ts +57 -1
  215. package/node_modules/@synap/hub-rest-client/src/types.ts +697 -37
  216. package/package.json +11 -5
  217. package/skills/agent-os/SKILL.md +218 -0
  218. package/skills/manifest.json +5 -0
  219. package/skills/onboard/SKILL.md +193 -0
  220. package/skills/synap/SKILL.md +1284 -58
  221. package/skills/synap/_order.txt +37 -0
  222. package/skills/synap/_skill.yaml +24 -0
  223. package/skills/synap/authentication.md +10 -0
  224. package/skills/synap/automations.md +264 -0
  225. package/skills/synap/capabilities.md +107 -0
  226. package/skills/synap/capture.md +50 -142
  227. package/skills/synap/cli-operations.md +107 -0
  228. package/skills/synap/common-mistakes.md +13 -0
  229. package/skills/synap/content-os.md +83 -0
  230. package/skills/synap/creative-loop.md +43 -0
  231. package/skills/synap/crm.md +117 -0
  232. package/skills/synap/diagnostics.md +35 -0
  233. package/skills/synap/digesting-a-channel.md +63 -0
  234. package/skills/synap/document-embeds.md +123 -0
  235. package/skills/synap/escalation-ladder.md +71 -0
  236. package/skills/synap/focus-sessions.md +47 -0
  237. package/skills/synap/governance-rules.md +64 -0
  238. package/skills/synap/governance.md +6 -6
  239. package/skills/synap/graph-gardening.md +21 -0
  240. package/skills/synap/inline-patterns.md +38 -0
  241. package/skills/synap/lenses.md +38 -0
  242. package/skills/synap/linking-principle.md +39 -0
  243. package/skills/synap/linking.md +1 -0
  244. package/skills/synap/mental-model.md +45 -0
  245. package/skills/synap/more.md +7 -0
  246. package/skills/synap/multi-entity-capture.md +12 -0
  247. package/skills/synap/next-action.md +37 -0
  248. package/skills/synap/north-star.md +17 -0
  249. package/skills/synap/operating-mode-rules.md +45 -0
  250. package/skills/synap/operating-mode.md +9 -0
  251. package/skills/synap/quick-reference.md +67 -0
  252. package/skills/synap/reading.md +43 -0
  253. package/skills/synap/recap.md +21 -0
  254. package/skills/synap/reflexes.md +46 -0
  255. package/skills/synap/response-style.md +21 -0
  256. package/skills/synap/scope.md +13 -0
  257. package/skills/synap/showing.md +17 -0
  258. package/skills/synap/user-model.md +25 -0
  259. package/skills/synap/viewframe-cells.md +290 -0
  260. package/skills/synap/work-flow.md +113 -0
  261. package/skills/synap/worked-examples.md +74 -0
  262. package/skills/synap/workspace-design.md +45 -0
  263. package/skills/synap/workspace-edges.md +49 -0
  264. package/skills/synap/writes.md +213 -0
  265. package/skills/synap-app/SKILL.md +319 -0
  266. package/skills/synap-app/_order.txt +5 -0
  267. package/skills/synap-app/_skill.yaml +20 -0
  268. package/skills/synap-app/creating-the-base-app.md +64 -0
  269. package/skills/synap-app/governance.md +42 -0
  270. package/skills/synap-app/install-commands.md +55 -0
  271. package/skills/synap-app/overview.md +50 -0
  272. package/skills/synap-app/using-the-sdk.md +73 -0
  273. package/skills/synap-market/SKILL.md +335 -0
  274. package/skills/synap-market/_order.txt +5 -0
  275. package/skills/synap-market/_skill.yaml +24 -0
  276. package/skills/synap-market/author-and-publish.md +75 -0
  277. package/skills/synap-market/discover-and-fetch.md +59 -0
  278. package/skills/synap-market/governance-and-catalog.md +56 -0
  279. package/skills/synap-market/install.md +55 -0
  280. package/skills/synap-market/overview.md +51 -0
  281. package/skills/synap-schema/SKILL.md +97 -6
  282. package/skills/synap-schema/_order.txt +13 -0
  283. package/skills/synap-schema/_skill.yaml +22 -0
  284. package/skills/synap-schema/before-you-touch.md +14 -0
  285. package/skills/synap-schema/common-mistakes.md +9 -0
  286. package/skills/synap-schema/creating-profiles.md +17 -0
  287. package/skills/synap-schema/discover-profiles.md +24 -0
  288. package/skills/synap-schema/entity-scope.md +8 -0
  289. package/skills/synap-schema/extend-vs-create.md +31 -0
  290. package/skills/synap-schema/more.md +6 -0
  291. package/skills/synap-schema/overlay-properties.md +22 -0
  292. package/skills/synap-schema/profiles.md +3 -0
  293. package/skills/synap-schema/property-defs.md +29 -0
  294. package/skills/synap-schema/read-before-write.md +50 -0
  295. package/skills/synap-schema/relations.md +18 -0
  296. package/skills/synap-schema/worked-example.md +30 -0
  297. package/skills/synap-ui/SKILL.md +26 -2
  298. package/skills/synap-ui/_order.txt +13 -0
  299. package/skills/synap-ui/_skill.yaml +23 -0
  300. package/skills/synap-ui/ai-companion.md +27 -0
  301. package/skills/synap-ui/arranging-bento.md +8 -0
  302. package/skills/synap-ui/before-you-build.md +20 -0
  303. package/skills/synap-ui/bento-layouts.md +35 -0
  304. package/skills/synap-ui/common-mistakes.md +10 -0
  305. package/skills/synap-ui/creating-views.md +26 -0
  306. package/skills/synap-ui/four-builds.md +24 -0
  307. package/skills/synap-ui/more.md +7 -0
  308. package/skills/synap-ui/proposing-vs-extending.md +11 -0
  309. package/skills/synap-ui/view-list.md +24 -0
  310. package/skills/synap-ui/views.md +3 -0
  311. package/skills/synap-ui/worked-example.md +18 -0
  312. package/skills/synap-ui/workspaces.md +47 -0
  313. package/templates/automations/bookmark-enrichment.automation.json +44 -0
  314. package/templates/automations/mail-feed.automation.json +86 -0
  315. package/templates/automations/url-bookmark-capture.automation.json +70 -0
  316. package/templates/skills/README.md +21 -0
  317. package/templates/skills/authoring-automations/SKILL.md +95 -0
  318. package/templates/skills/authoring-automations/reference/node-matrix.md +49 -0
  319. package/templates/skills/authoring-capabilities/SKILL.md +93 -0
  320. package/templates/skills/authoring-capabilities/reference/template-anatomy.md +70 -0
  321. package/templates/skills/authoring-playbooks/SKILL.md +102 -0
  322. package/templates/skills/authoring-playbooks/reference/stages-contract.md +35 -0
  323. package/templates/skills/authoring-playbooks/reference/worked-example.md +64 -0
  324. package/templates/skills/bookmark-enrichment/SKILL.md +40 -0
  325. package/templates/skills/coordinating-agents/SKILL.md +32 -0
  326. package/templates/skills/coordinating-branches/SKILL.md +27 -0
  327. package/templates/skills/mapping-the-server/SKILL.md +54 -0
  328. package/templates/skills/reading-client-progress/SKILL.md +65 -0
  329. package/templates/skills/routing-to-channels/SKILL.md +54 -0
  330. package/templates/skills/stellar-scf-grants-advisor/ASSOCIATE-CHEATSHEET.md +133 -0
  331. package/templates/skills/stellar-scf-grants-advisor/ASSOCIATE-ONBOARDING-GUIDE.md +761 -0
  332. package/templates/skills/stellar-scf-grants-advisor/CLIENT-FAQ.md +181 -0
  333. package/templates/skills/stellar-scf-grants-advisor/PARTNER-HANDOVER.md +130 -0
  334. package/templates/skills/stellar-scf-grants-advisor/SKILL.md +102 -0
  335. package/templates/skills/stellar-scf-grants-advisor/examples/README.md +43 -0
  336. package/templates/skills/stellar-scf-grants-advisor/examples/chat-summary-templates.md +158 -0
  337. package/templates/skills/stellar-scf-grants-advisor/examples/corpus-comparable-rows.md +136 -0
  338. package/templates/skills/stellar-scf-grants-advisor/examples/q10-team-rewrites.md +109 -0
  339. package/templates/skills/stellar-scf-grants-advisor/examples/q2-description-rewrites.md +88 -0
  340. package/templates/skills/stellar-scf-grants-advisor/examples/q6-integration-rewrites.md +107 -0
  341. package/templates/skills/stellar-scf-grants-advisor/examples/tranche-structures.md +176 -0
  342. package/templates/skills/stellar-scf-grants-advisor/reference/01-methodology.md +96 -0
  343. package/templates/skills/stellar-scf-grants-advisor/reference/02-scoring-framework.md +199 -0
  344. package/templates/skills/stellar-scf-grants-advisor/reference/03-rejection-patterns.md +241 -0
  345. package/templates/skills/stellar-scf-grants-advisor/reference/04-winner-patterns.md +120 -0
  346. package/templates/skills/stellar-scf-grants-advisor/reference/05-comeback-patterns.md +92 -0
  347. package/templates/skills/stellar-scf-grants-advisor/reference/06-budget-benchmarks.md +125 -0
  348. package/templates/skills/stellar-scf-grants-advisor/reference/07-integration-list.md +147 -0
  349. package/templates/skills/stellar-scf-grants-advisor/reference/08-abstract-fields.md +258 -0
  350. package/templates/skills/stellar-scf-grants-advisor/reference/09-build-fields.md +259 -0
  351. package/templates/skills/stellar-scf-grants-advisor/reference/10-corpus-winners.md +135 -0
  352. package/templates/skills/stellar-scf-grants-advisor/reference/11-corpus-rejects.md +267 -0
  353. package/templates/skills/stellar-scf-grants-advisor/reference/12-client-communication.md +131 -0
  354. package/templates/skills/stellar-scf-grants-advisor/reference/13-excel-output-spec.md +199 -0
  355. package/templates/skills/stellar-scf-grants-advisor/reference/14-worked-examples.md +160 -0
  356. package/templates/skills/stellar-scf-grants-advisor/templates/abstract-review-builder.py +253 -0
  357. package/templates/skills/stellar-scf-grants-advisor/templates/build-review-builder.py +213 -0
  358. package/dist/commands/connectors.d.ts +0 -31
  359. package/dist/commands/connectors.js +0 -276
  360. package/dist/commands/connectors.js.map +0 -1
  361. package/dist/commands/finish.d.ts +0 -25
  362. package/dist/commands/finish.js +0 -274
  363. package/dist/commands/finish.js.map +0 -1
  364. package/dist/commands/openclaw.d.ts +0 -40
  365. package/dist/commands/openclaw.js +0 -1148
  366. package/dist/commands/openclaw.js.map +0 -1
  367. package/dist/commands/security-audit.d.ts +0 -12
  368. package/dist/commands/security-audit.js +0 -100
  369. package/dist/commands/security-audit.js.map +0 -1
  370. package/dist/lib/hardening.d.ts +0 -18
  371. package/dist/lib/hardening.js +0 -214
  372. package/dist/lib/hardening.js.map +0 -1
@@ -35,6 +35,131 @@ 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
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.
@@ -43,6 +168,8 @@ Synap is a typed knowledge graph. **Reading is one verb (`synap ask`) — it rou
43
168
 
44
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.
45
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
+
46
173
  | If it… | Lane | Where it goes | Governance |
47
174
  | ------------------------------------------------------------------------------------------------------------------------------- | -------------------- | ---------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------- |
48
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) |
@@ -51,17 +178,22 @@ Ask yourself: _who does this knowledge serve?_ **There is no private AI scratchp
51
178
 
52
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.
53
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
+
54
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.
55
187
 
56
188
  ### Data layers — the graph itself
57
189
 
58
- | Layer | What it is | When to use |
59
- | ------------- | ---------------------------------------------- | -------------------------------------------------------- |
60
- | **Entities** | Typed structured nodes (task, person, …) | Anything worth filtering, sorting, or linking |
61
- | **Relations** | Typed edges between entities | Making the graph traversable |
62
- | **Documents** | Long-form markdown attached to an entity | Meeting notes, research writeups, articles |
63
- | **Threads** | Channel conversations, optional entity context | Posting to the user's personal AI channel |
64
- | **Proposals** | Writes queued for human approval | Governance for some mutations (not an error — see below) |
190
+ | Layer | What it is | When to use |
191
+ | ------------- | ------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------ |
192
+ | **Entities** | Typed structured nodes (task, person, …) | Anything worth filtering, sorting, or linking |
193
+ | **Relations** | Typed edges between entities | Making the graph traversable |
194
+ | **Documents** | Long-form versioned body attached to an entity — auto-materialized from an entity's `content`, or created standalone via `synap_create_document` | Meeting notes, research writeups, articles — **never** a `file`/`document`-kind entity for text you authored |
195
+ | **Threads** | Channel conversations, optional entity context | Posting to the user's personal AI channel |
196
+ | **Proposals** | Writes queued for human approval | Governance for some mutations (not an error — see below) |
65
197
 
66
198
  ### Key profiles for AI use
67
199
 
@@ -74,12 +206,15 @@ Ask yourself: _who does this knowledge serve?_ **There is no private AI scratchp
74
206
  | `research` | pod | AI | Investigation with sources + conclusion |
75
207
  | `question` | pod | human + AI | Open inquiry, closed when a decision answers it |
76
208
 
209
+ ---
210
+
77
211
  ## Quick reference — 90% of tasks in 30 lines
78
212
 
79
213
  ```bash
80
214
  # CLI (preferred — auth automatic, --json = clean output)
81
- synap orient --json # discover userId + workspaces
82
- synap use <workspace-name-or-id> # set active workspace
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)
83
218
  synap create entity --profile=task --name="…" --props='{"status":"todo","priority":"high"}' --json
84
219
  synap set entity <id> --props='{"status":"done"}' --json # merge-patch (only changed keys)
85
220
  synap ask "your question" --json # THE read verb — routes to the right store(s) + shows which answered
@@ -101,7 +236,7 @@ or `recall` — `ask` is the door.)
101
236
 
102
237
  ```bash
103
238
  # REST (when no Bash access)
104
- 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? }
105
240
  PATCH /api/hub/entities/{id} body: { userId, properties } ← deep-merges, send only changed keys
106
241
  POST /api/hub/documents body: { userId, workspaceId?, title, content, entityId? }
107
242
  PATCH /api/hub/documents/{id} body: { userId, title?, content? } ← full content replacement
@@ -121,11 +256,13 @@ synap discover --profiles --json # CLI: profiles only
121
256
  ```
122
257
 
123
258
  ```
124
- GET /api/hub/discover?userId={userId}&workspaceId={workspaceId}
125
- → { profiles: [{ slug, displayName, scope, properties: [{ slug, type, options? }], createCommand }], commands: {...} }
259
+ GET /api/hub/discover?userId={userId}&profileSlugs=task
260
+ → { profiles: [{ slug, displayName, scope, properties: [{ slug, type, required, defaultValue?, constraints?, targetProfileSlug? }], createCommand }], commands: {...} }
126
261
  ```
127
262
 
128
- Call this once at session start. The response includes every system profile and any custom workspace profiles the user has created, each with its full property schema. Use `createCommand` per profile as a copy-paste template. Do not rely on a static property list — it will drift.
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.
129
266
 
130
267
  **Load more detail on demand** (`GET /api/hub/skills/system?sections=<id>`):
131
268
 
@@ -139,13 +276,56 @@ Call this once at session start. The response includes every system profile and
139
276
  | `synap-ui:widget-catalog` | Available widget kinds and their configSchema |
140
277
  | `synap-schema:SKILL` | Creating custom profiles or property definitions |
141
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
+
142
322
  ## Synap-first operating mode
143
323
 
144
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.
145
325
 
146
326
  These five rules override default assistant behavior when connected to a Synap pod:
147
327
 
148
- **1. Orient before acting**
328
+ **1. Orient before acting** _(and check your lens — see "am I in the right place?" above)_
149
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:
150
330
 
151
331
  ```
@@ -185,17 +365,21 @@ Facts about the user — preferences, team, working style, recurring context —
185
365
 
186
366
  Properties with `valueType: "entity_id"` are typed links to other entities — see **Linking** below.
187
367
 
368
+ ---
369
+
188
370
  ## CLI Data Operations (Bash tool)
189
371
 
190
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.
191
373
 
192
374
  **Session context — set once, never repeat:**
193
375
 
194
- The CLI reads the active pod and workspace from `~/.synap/config.json`. Set context once at the start of a session; all subsequent commands inherit it automatically. Do NOT pass `--pod-url`, `--api-key`, or `--workspace` on every command.
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`).
195
377
 
196
378
  ```bash
197
379
  synap pods use <profile-name> # switch active pod
198
- synap use <workspace-id> # switch active workspace (captures land here — it IS the domain)
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
199
383
  ```
200
384
 
201
385
  **Always orient first:**
@@ -260,6 +444,19 @@ synap ask "hono routing gotcha" --json
260
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`.
261
445
  Use `capture` for anything worth remembering across sessions and projects.
262
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
456
+ ```
457
+
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.
459
+
263
460
  **Write:**
264
461
 
265
462
  ```bash
@@ -278,6 +475,8 @@ synap set entity <id> --props='{"status":"done"}' --json
278
475
 
279
476
  ---
280
477
 
478
+ ---
479
+
281
480
  ## Scope — default pod-wide
282
481
 
283
482
  **Default: pod-wide.** 13 of 17 system profiles (`note`, `task`, `project`, `event`, `person`, `contact`, `company`, `bookmark`, `article`, `website`, `decision`, `question`, `research`) are pod-scoped — entities you create show up in _every_ workspace the user owns. The backend handles this automatically when the profile is pod-scoped: you don't need to pass `workspaceId`.
@@ -292,6 +491,8 @@ synap set entity <id> --props='{"status":"done"}' --json
292
491
 
293
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.
294
493
 
494
+ ---
495
+
295
496
  ## The work flow — question → research → decision → action
296
497
 
297
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.
@@ -400,11 +601,13 @@ User: _"I'm trying to figure out whether we should build our own orchestrator or
400
601
 
401
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:
402
603
 
403
- > (Logged as question on Project Eve. Review: https://studio.synap.live/proposals/…)
604
+ > (Logged as question on Project Eve. Review: synap://open/proposal/…)
404
605
 
405
606
  If the creation was auto-approved (entity.create is on the whitelist), there's no proposal; just show a link to the entity:
406
607
 
407
- > (Logged as question → https://studio.synap.live/entities/ent_question_1)
608
+ > (Logged as question → synap://open/entity/ent_question_1)
609
+
610
+ ---
408
611
 
409
612
  ## Linking — the core principle
410
613
 
@@ -442,8 +645,12 @@ POST /api/hub/relations
442
645
  }
443
646
  ```
444
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
+
445
650
  For auto-sync mapping, conventional relation types, and edge cases, read **`linking.md`**.
446
651
 
652
+ ---
653
+
447
654
  ## Writing — governance in one paragraph
448
655
 
449
656
  Every write returns a `status` field:
@@ -503,27 +710,176 @@ Rules:
503
710
 
504
711
  Example response to the user:
505
712
 
506
- > I queued **Delete task "Q2 plan review"** for your review. Destructive actions need your approval. Open it: https://studio.synap.live/proposals/prp_abc
713
+ > I queued **Delete task "Q2 plan review"** for your review. Destructive actions need your approval. Open it: synap://open/proposal/prp_abc
507
714
 
508
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.
509
716
 
510
717
  For the full whitelist, agent-user semantics, and workspace overrides, read **`governance.md`**.
511
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
+
512
831
  ## Core writes
513
832
 
514
- ### Create an entity (always with links)
833
+ **Two write doors, one gradient.** `create_entity` is for exactly ONE
834
+ fully-structured, typed entity you already have. For anything unstructured,
835
+ several entities, or a graph — or **when in doubt** — use the capture door
836
+ (`synap_capture`, see `capture.md`): precision comes from sending more structure
837
+ in the SAME call, never from picking a different tool or a second commit step.
838
+
839
+ ### Create an entity (one exact typed entity)
840
+
841
+ Before this call, use `/discover?userId=…&profileSlugs=<kind>` to read the
842
+ real fields, required/default values, constraints and reference targets. Omit
843
+ `workspaceId` for the pod/base schema and normal profile placement; pass it
844
+ only when the user or routing decision explicitly selected that workspace.
515
845
 
516
846
  ```json
517
847
  POST /api/hub/entities
518
848
  {
519
849
  "userId": "{userId}",
520
- "workspaceId": "{workspaceId}",
521
- "profileSlug": "task", // from /profiles — never guess
850
+ "profileSlug": "task", // from /discover — never guess
522
851
  "title": "Weekly team sync",
523
- "properties": { "status": "todo", "projectId": "ent_..." }
852
+ "description": "Recurring planning sync",
853
+ "properties": { "status": "todo", "dueDate": "2026-07-21" },
854
+ "content": "# Agenda\n- Priorities\n- Risks",
855
+ "projectId": "{existingProjectId}",
856
+ "source": "agent"
524
857
  }
525
858
  ```
526
859
 
860
+ The response has legacy `status`/`id` fields plus `writeReceipt`:
861
+ `pending`/`proposed` means a proposal exists and no entity is live yet;
862
+ `applied` means the reported direct write completed; `partial` means a follow-up
863
+ (for example a facet) failed after the entity applied. **A `proposed`/`pending`
864
+ receipt is a governed success, not an error** — surface its `reviewUrl`, never
865
+ claim completion, and only enrich again when the receipt identifies a real
866
+ missing fact.
867
+
868
+ For several entities, creation-time roles/facets, or relations that need one
869
+ review, send the whole graph through the capture door (`synap_capture` with
870
+ `entities[]` + `relations[]`) instead of sequencing independent creates. It is
871
+ ONE governed call: policy auto-applies when every op is safe, otherwise the
872
+ whole graph is proposed (atomic). There is no separate commit step.
873
+
874
+ **Name-refs, not UUIDs.** Reference an existing project by name — the server
875
+ resolves it against the caller's own projects (exact match files there; no match
876
+ proposes, never mis-files). Never ask the user for a UUID.
877
+
878
+ **Dedup is advisory across kinds.** Strong signals (`email`/`phone`/`website` —
879
+ not a bare `url`) dedup within a kind; a same-title hit in a _different_ kind
880
+ comes back as an advisory candidate, never an auto-merge. "No exact match" is not
881
+ "safe to create" when advisory candidates are returned — review them first.
882
+
527
883
  ### Update an entity
528
884
 
529
885
  ```json
@@ -533,6 +889,41 @@ PATCH /api/hub/entities/{entityId}
533
889
 
534
890
  **Properties are deep-merged — send only the keys you want to change.** An update with `{ "status": "done" }` leaves all other properties untouched. You never need to re-send the full properties object.
535
891
 
892
+ ### Authored text is content, not a `file`
893
+
894
+ Something **you** author — a pitch deck, a strategic plan, a note body — is
895
+ **never** a `file`- or `document`-kind entity. Create it as the right CONTENT
896
+ kind (`note`, `knowledge`, or a fitting domain kind) with `content` set to the
897
+ Markdown body; Synap **auto-materializes** that `content` into a real
898
+ versioned document behind the scenes (`entities WHERE documentId = ?`) — no
899
+ upload step needed. `file` is reserved for real uploaded bytes you actually
900
+ have (the upload door / `synap upload`) — an agent has no filesystem, so it
901
+ rarely touches `file` at all. Reach for `synap_create_document` /
902
+ `POST /api/hub/documents` directly only for a standalone rich document that
903
+ isn't itself a title-worthy entity (see below) — never as a substitute for an
904
+ entity's `content`.
905
+
906
+ ### "I want to add a real FILE" — the decision tree
907
+
908
+ You CAN store content you **hold** — you send it inline. Pick by what you have:
909
+
910
+ | You have… | Do this | Result |
911
+ | ------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------- | -------------------------------------------------------------------- |
912
+ | **Content in hand** — a report/CSV/image/PDF/etc. you generated or received (text or bytes) | **`synap_store_file`** (`content` for text, `contentBase64` for binary) + `mimeType`/`filename` | a `file` entity, content stored **as-is, never read** (≤10MB inline) |
913
+ | Text you're **authoring as a note/idea** (not "a file") — a plan, a thought | `synap_create_entity` with `content` (a content kind) | body auto-becomes a document — NOT a `file` |
914
+ | Only a **URL/link** (no bytes) — a Google Doc, a PDF url, an article | `synap_create_document` with **`url`**, or attach via `entityId` | an external **reference** document (no bytes) |
915
+ | A **large file on a local disk** you don't hold in context | the CLI `synap upload <path>` (streams it) — an agent can't send bytes it doesn't have | a `file` entity backed by stored bytes |
916
+ | Only a file's **name**, nothing else | you **cannot** invent it — ask the human/client to provide the content or a link | — |
917
+
918
+ **Store ≠ analyze.** `synap_store_file` / `synap_create_document` store content
919
+ **deterministically — the file is NEVER read by an LLM.** Only fetch a document
920
+ and reason over it when the user explicitly says "read/analyze this file."
921
+
922
+ **Do NOT over-structure a file request.** "Store this file" / "how do I add this
923
+ file" is ONE intent → at most one stored file (plus, only if asked, a linking
924
+ relation). Never inflate it into a task + note + placeholder-file scaffold, and
925
+ never dump the request text into a note and call it done.
926
+
536
927
  ### Create a document (attach to an entity)
537
928
 
538
929
  ```json
@@ -590,21 +981,25 @@ Content is a **full replacement**, not a patch. Fetch the current content first
590
981
 
591
982
  The reverse lookup is `entities WHERE documentId = ?`. Always attach the document to a meaningful entity (the meeting event, the project, the person) — a floating document is another orphan.
592
983
 
593
- ### Store a fact (memory) — use sparingly
984
+ ### Remember a fact about the user — use sparingly
594
985
 
595
- ```json
596
- POST /api/hub/memory
597
- { "userId": "{userId}", "fact": "User prefers async communication over meetings" }
598
- ```
986
+ A fact _about the user_ goes through `remember_fact` (CLI: `synap capture --type
987
+ observation`). It writes a governed `user_observation` — not an ungoverned
988
+ throwaway row: a fact the user explicitly stated auto-approves; a fact you
989
+ inferred returns `proposed` (normal — surface the review link). Because it's a
990
+ real entity it's addressable, linkable and revertible.
599
991
 
600
- Always auto-approved. **Memory is for loose, unstructured, hard-to-title facts only.** The seductive thing about memory is it has zero friction — no dedup, no linking, no proposals. That makes it easy to misuse.
992
+ **Use it only for loose, unstructured, hard-to-title facts about the user** — a
993
+ stated preference, a throwaway detail. Everything with a title-worthy noun or
994
+ something to link to is an ENTITY through the capture door, not an observation.
601
995
 
602
- **The test:** if the user later asked "show me all X," can memory answer? Memory can only keyword-match — it has no structure. So:
996
+ **The test:** if the user later asked "show me all X," could a loose observation
997
+ answer? It can only keyword-match — it has no structure. So:
603
998
 
604
999
  | Input | Use |
605
1000
  | ------------------------------------------------------- | --------------------------------------------------------------- |
606
- | "User prefers async communication" | memory — it's a preference |
607
- | "Garage code is 4321" | memory — throwaway fact |
1001
+ | "User prefers async communication" | observation — it's a preference |
1002
+ | "Garage code is 4321" | observation — throwaway fact |
608
1003
  | "Should we use LangGraph or CrewAI for Eve?" | **entity `question`** — substantive inquiry, start of flow |
609
1004
  | "Here's what I found comparing LangGraph and CrewAI…" | **entity `research`** — investigation with sources + conclusion |
610
1005
  | "We decided to use LangGraph over OpenClaude's native…" | **entity `decision`** — has title, rationale, project |
@@ -614,7 +1009,7 @@ Always auto-approved. **Memory is for loose, unstructured, hard-to-title facts o
614
1009
  | "Action item from meeting: ship MVP by Friday" | **entity `task`** linked to the `event` (meeting) |
615
1010
  | "Agreed with Sarah: we'll split backend & frontend" | **entity `decision`** linked to Sarah + the project |
616
1011
 
617
- **Rule of thumb:** if it has a title-worthy noun OR context to link to (a project, a person, a meeting) OR a lifecycle (status/supersession) — it's an entity, not memory. Memory is the fallback, not the default.
1012
+ **Rule of thumb:** if it has a title-worthy noun OR context to link to (a project, a person, a meeting) OR a lifecycle (status/supersession) — it's an entity through the capture door, not an observation. A user observation is the fallback, not the default.
618
1013
 
619
1014
  **For decisions specifically** — use the `decision` system profile:
620
1015
 
@@ -647,6 +1042,244 @@ POST /api/hub/threads/{threadId}/messages
647
1042
  { "userId": "{userId}", "role": "user", "content": "…" }
648
1043
  ```
649
1044
 
1045
+ ---
1046
+
1047
+ ## Document embeds — live entities/views/cells inside markdown
1048
+
1049
+ Documents (`type: "markdown"`) can embed **live, rendering** Synap objects inline, not
1050
+ just links. The browser's markdown engine parses a small set of remark **container
1051
+ directives** and swaps them for real components — an entity card, a view, or a cell
1052
+ — wherever they appear in the prose.
1053
+
1054
+ **This is a DOCUMENTS-only mechanism.** It is unrelated to the `[[kind:id|label]]`
1055
+ inline chips described in `inline-patterns.md` — those render **only** in Companion
1056
+ chat replies. Never put a `:::synap-*` directive in a chat reply, and never put a
1057
+ `[[…]]` chip in a document's `content`. Different surface, different grammar.
1058
+
1059
+ ### Syntax
1060
+
1061
+ <!-- brief:start -->
1062
+
1063
+ A container directive: three colons, the directive name, `{attrs}` on the opening
1064
+ line, three colons alone on the closing line.
1065
+
1066
+ ```
1067
+ :::synap-entity{id="ent_abc123"}
1068
+ :::
1069
+ ```
1070
+
1071
+ | Directive | Required attrs | Optional attrs | Renders |
1072
+ | -------------- | --------------------------------------------- | -------------- | ------------------------------------------------- |
1073
+ | `synap-entity` | `id` (entity UUID) | — | Compact entity card (`__entity-block` cell) |
1074
+ | `synap-view` | `viewId` (view UUID) | — | Embedded, read-only view (`__embedded-view` cell) |
1075
+ | `synap-cell` | `instanceId` **OR** (`cellKey` + `cellProps`) | — | A persisted cell instance, or an inline cell ref |
1076
+
1077
+ Only real IDs from prior tool results — never invent one. This is a
1078
+ DOCUMENTS-only grammar: never use it in a chat reply, and never use a
1079
+ `[[kind:id|label]]` chip inside a document's `content`.
1080
+
1081
+ <!-- brief:end -->
1082
+
1083
+ For `synap-cell`: an explicit `instanceId` always wins if present — it renders a
1084
+ persisted cell instance from `/api/hub/cells`. Otherwise the pair `cellKey` +
1085
+ `cellProps` builds an inline ref (`cellRefFromLegacy`). `cellProps` is a JSON string,
1086
+ e.g. `cellProps='{"profileSlug":"task"}'`.
1087
+
1088
+ When you write a document's `content` directly (via `synap_create_document` /
1089
+ `POST /api/hub/documents`), you're writing raw markdown text — quote `cellProps`'
1090
+ JSON with single quotes (`cellProps='{"profileSlug":"task"}'`) so the inner `"`
1091
+ characters don't collide with the directive's own `key="value"` quoting; the
1092
+ markdown renderer (remark-directive) parses this directly, no escaping needed.
1093
+
1094
+ One caveat: if a human later opens the document in the rich-text editor, its
1095
+ Tiptap round-trip serializer re-emits every directive as `key="value"` and
1096
+ **drops any attribute value containing `"` or `}`** (it would otherwise corrupt
1097
+ the directive) — so an inline `cellProps` blob can be silently lost on the next
1098
+ editor save. For a cell you expect to survive editing, create it first
1099
+ (`synap_create_cell` / `POST /api/hub/cells`) and embed it by `instanceId`
1100
+ instead of inlining `cellProps`.
1101
+
1102
+ ### Rules
1103
+
1104
+ - **Only real IDs from prior tool results.** Never invent an entity/view/instance
1105
+ ID. Create or look it up first (`synap_create_entity`, `synap_get_entities`,
1106
+ `synap_create_view`, `synap_create_cell`), then embed the ID you got back.
1107
+ - **Embeds are for DOCUMENTS.** The `[[…]]` inline chips are for Companion chat
1108
+ replies. Do not mix the two grammars across surfaces.
1109
+ - **Embed vs. link:** embed when the reader benefits from seeing the live
1110
+ object in place — a stat card inside a report, the linked meeting entity inside
1111
+ meeting notes, a pipeline view inside a status update. Link (`entities WHERE
1112
+ documentId = ?` attachment, or a plain reference to the ID) when you just need
1113
+ traceability and the reader doesn't need to see it rendered inline — most
1114
+ documents should still be _attached_ to one entity (see `writes.md`) regardless
1115
+ of whether they also embed others inline.
1116
+ - A directive with a missing/invalid required attribute renders a visible error
1117
+ block in the browser (`Error: View ID is required` / `Error: Cell type is
1118
+ required`) — always double-check the ID before writing the directive.
1119
+
1120
+ ### Worked example 1 — meeting notes embedding the meeting entity
1121
+
1122
+ ```json
1123
+ POST /api/hub/documents
1124
+ {
1125
+ "userId": "{userId}",
1126
+ "workspaceId": "{workspaceId}",
1127
+ "title": "Meeting notes — 2026-07-12",
1128
+ "type": "markdown",
1129
+ "entityId": "ent_event_kickoff",
1130
+ "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"
1131
+ }
1132
+ ```
1133
+
1134
+ The event entity renders as a live card at the top of the notes — attendees,
1135
+ time, status stay current even if the entity changes later.
1136
+
1137
+ ### Worked example 2 — status report embedding a pipeline view
1138
+
1139
+ ```json
1140
+ POST /api/hub/documents
1141
+ {
1142
+ "userId": "{userId}",
1143
+ "workspaceId": "{workspaceId}",
1144
+ "title": "Weekly deals update",
1145
+ "type": "markdown",
1146
+ "entityId": "ent_project_eve",
1147
+ "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."
1148
+ }
1149
+ ```
1150
+
1151
+ ### Worked example 3 — report embedding an inline stat cell
1152
+
1153
+ ```json
1154
+ POST /api/hub/documents
1155
+ {
1156
+ "userId": "{userId}",
1157
+ "workspaceId": "{workspaceId}",
1158
+ "title": "Q2 task summary",
1159
+ "type": "markdown",
1160
+ "entityId": "ent_project_eve",
1161
+ "content": "# Q2 summary\n\n:::synap-cell{cellKey=\"stat-card\" cellProps='{\"profileSlug\":\"task\"}'}\n:::\n\nOpen tasks are trending down."
1162
+ }
1163
+ ```
1164
+
1165
+ Note the mixed quoting: the directive's own attribute values use double quotes
1166
+ (`cellKey="stat-card"`), so the outer `cellProps` value uses single quotes to hold
1167
+ its JSON — the JSON itself must not contain any `"` once flattened into the
1168
+ attribute string, or the serializer will drop it. When in doubt, prefer a
1169
+ persisted `instanceId` over inline `cellProps`.
1170
+
1171
+ ---
1172
+
1173
+ ## The Creative Loop — do once, then crystallize to reusable config
1174
+
1175
+ 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.
1176
+
1177
+ The loop has three moves that compound. Each takes a one-off act and, if it's worth repeating, turns it into standing structure:
1178
+
1179
+ | Do-once (author) | → Crystallize (curate) | Tool |
1180
+ | --------------------------------------- | ----------------------------------- | ----------------------------- |
1181
+ | Work a multi-step goal in a **session** | → a **playbook** (the process) | `promote_session_to_playbook` |
1182
+ | Show a result in a **cell** | → a **renderer** for an entity type | `promote_cell_to_renderer` |
1183
+
1184
+ ### 1. Open a session for real work
1185
+
1186
+ 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.
1187
+
1188
+ ### 2. Create a cell to REPORT — don't dump data into chat
1189
+
1190
+ 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.
1191
+
1192
+ - **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.
1193
+ - Keep one cell to one job. A good, focused cell is the raw material for the next move.
1194
+
1195
+ ### 3. Promote a good cell to a renderer — recurring presentation
1196
+
1197
+ 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`:
1198
+
1199
+ - Pick the `profileSlug` (the entity type), the `slot` (`list` | `detail` | `dashboard`), and the `cellKey` from `create_cell`.
1200
+ - 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.
1201
+ - Use `scope: "pod"` only when the presentation should apply in every workspace; default to workspace scope.
1202
+
1203
+ ### 4. Promote a finished session to a playbook — recurring process
1204
+
1205
+ 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.
1206
+
1207
+ - Do this at the _end_, once the promised outputs are produced and verified.
1208
+ - Judge repeatability honestly: a bespoke, never-again investigation is not a playbook. A "weekly competitor scan" or "new-client onboarding" is.
1209
+ - Governed like the others — `promoted` (applied) or `proposed` (awaiting review) are both normal.
1210
+
1211
+ ### The symmetry
1212
+
1213
+ 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.
1214
+
1215
+ 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.
1216
+
1217
+ ---
1218
+
1219
+ ## Garden the graph — writes tell you their impact (the circling of action)
1220
+
1221
+ 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.
1222
+
1223
+ **On create**, the response carries a `resolution` block:
1224
+
1225
+ - `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.
1226
+ - `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".
1227
+ - `suggestions` — entities worth linking. Create the relations that genuinely apply (`synap create relation` · `POST /api/hub/relations` · `create_relation`).
1228
+
1229
+ **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.
1230
+
1231
+ **The circling of action** for any structural write (updating the vision, the architecture, a decision record, a codebase map):
1232
+
1233
+ 1. **Write** — the response shows collisions + connections; don't ignore it.
1234
+ 2. **If it already exists** (`existingSameProfile`) → update it, don't duplicate.
1235
+ 3. **The same-name facets are auto-woven** (`autoConnected`) → extend the links where the relationship is more specific than `same_subject`.
1236
+ 4. **Act on `suggestions`** → link what genuinely relates.
1237
+ 5. **On update** → resolve the `impact` neighbors (supersede / update / re-link).
1238
+
1239
+ 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.)
1240
+
1241
+ ---
1242
+
1243
+ ## Decide the next action — advance the goal, don't just answer
1244
+
1245
+ 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.
1246
+
1247
+ ### The session is the spine of real work
1248
+
1249
+ 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:
1250
+
1251
+ | Move | CLI | REST | IS tool |
1252
+ | ------------------------- | ---------------------------------------- | ------------------------------------------- | ---------------- |
1253
+ | open a session | `synap session start --goal "…"` | `POST /api/hub/focus-sessions` | `start_session` |
1254
+ | read it | `synap session get <id>` | `GET /api/hub/focus-sessions/:id` | `get_session` |
1255
+ | see active ones | `synap session list` | `GET /api/hub/focus-sessions?status=active` | `list_sessions` |
1256
+ | record progress / outputs | `synap session update <id> --progress N` | `PATCH /api/hub/focus-sessions/:id` | `update_session` |
1257
+
1258
+ - 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.
1259
+ - 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.
1260
+
1261
+ ### Decide ONE move, then make it
1262
+
1263
+ 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.
1264
+
1265
+ **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.
1266
+
1267
+ **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.
1268
+
1269
+ **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.
1270
+
1271
+ **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.
1272
+
1273
+ ### The nudge vs. propose line
1274
+
1275
+ 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.
1276
+
1277
+ ### If you are a coding / terminal agent working _in_ a repo
1278
+
1279
+ 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.
1280
+
1281
+ ---
1282
+
650
1283
  ## Reading
651
1284
 
652
1285
  **Start with `ask` — the one routed read door.** It classifies the question and
@@ -687,18 +1320,26 @@ GET /api/hub/graph/traverse?entityId={id}&maxDepth=2&workspaceId={id}
687
1320
  GET /api/hub/memory?userId={userId}&query={keywords}
688
1321
  ```
689
1322
 
1323
+ **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.
1324
+
690
1325
  No SQL joins. The graph is the join.
691
1326
 
1327
+ ---
1328
+
692
1329
  ## Multi-entity capture from free-form text
693
1330
 
694
- 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:
1331
+ When the user pastes a block of unstructured content (a meeting transcript, an email, a LinkedIn bio) or when several related things come up at once, send it through the **one capture door** — `synap_capture` (CLI: `synap capture`). Don't chain manual creates, and don't run a two-step "structure then commit" dance.
1332
+
1333
+ The payload is a gradient in a single call:
695
1334
 
696
1335
  ```
697
- POST /api/hub/capture/structure → returns proposals + relations
698
- POST /api/hub/capture/execute → commits (after user confirms)
1336
+ { "text": "…paste the raw content…" } → the AI structures it into entities
1337
+ { "entities": [ … ], "relations": [ … ] } → you supply the graph directly (refs link them)
699
1338
  ```
700
1339
 
701
- The pipeline extracts multiple entities with their relations in one LLM call. Read **`capture.md`** for the full flow.
1340
+ Everything lands as ONE reviewable proposal (or auto-applies when every op is safe), and you get back one receipt — `status: "applied" | "proposed" | "rejected"`. `proposed` is success: surface the review link. There is no separate commit step. Read **`capture.md`** for the full flow, dedup signals, name-refs, and reject reasons.
1341
+
1342
+ ---
702
1343
 
703
1344
  ## Worked examples
704
1345
 
@@ -754,6 +1395,29 @@ The pipeline extracts multiple entities with their relations in one LLM call. Re
754
1395
 
755
1396
  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.
756
1397
 
1398
+ ### Example 4 — "Write up a strategic plan for the Q3 launch"
1399
+
1400
+ You are authoring this text yourself — it is not a file you have. Don't create
1401
+ a `file`/`document`-kind entity and stuff the Markdown into it.
1402
+
1403
+ 1. Search for an existing plan: `GET /entities?q=Q3 launch&profileSlug=knowledge&workspaceId=…` → none
1404
+ 2. Create a CONTENT-kind entity carrying the plan as `content` (the doc auto-materializes):
1405
+
1406
+ ```json
1407
+ POST /api/hub/entities
1408
+ { "userId": "{userId}", "workspaceId": "{wsId}",
1409
+ "profileSlug": "knowledge",
1410
+ "title": "Q3 launch strategic plan",
1411
+ "properties": { "ek_type": "reference" },
1412
+ "content": "# Q3 Launch Plan\n\n## Goals\n…\n\n## Timeline\n…"
1413
+ }
1414
+ ```
1415
+
1416
+ 3. Link it to the relevant project: `POST /api/hub/relations` `{ sourceEntityId: "ent_new_plan", targetEntityId: "ent_project_q3", type: "related_to" }`.
1417
+ 4. Confirm: "Plan captured and linked to Q3 launch." No upload, no `file` entity, no separate `synap_create_document` call.
1418
+
1419
+ ---
1420
+
757
1421
  ## CRM Workspaces — 4-Entity Model
758
1422
 
759
1423
  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.
@@ -860,7 +1524,21 @@ POST /api/hub/relations
860
1524
 
861
1525
  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.
862
1526
 
863
- ## Common mistakes
1527
+ ## Resolution discipline — resolve BEFORE you create
1528
+
1529
+ 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):
1530
+
1531
+ 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.
1532
+ 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.
1533
+ 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.
1534
+ 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.
1535
+ 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.
1536
+
1537
+ An extraction that creates a duplicate "Sarah Chen", or a `person` titled "Unknown sender", is a failure — resolve, reuse, and alias instead.
1538
+
1539
+ ---
1540
+
1541
+ ## Common mistakes — core data operations
864
1542
 
865
1543
  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.
866
1544
  2. **Guessing profile slugs.** Always `GET /profiles` first. `deal`, `capture`, and custom profiles may not exist in this workspace.
@@ -870,6 +1548,11 @@ This model enables renewals (new deal linking to existing client), multi-stakeho
870
1548
  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.
871
1549
  7. **Skipping the search step.** Duplicates degrade the graph more than missing data.
872
1550
  8. **Forgetting that `GET /channels/personal` needs `hub-protocol.write`** scope — it's get-or-create, not a pure read.
1551
+ 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.
1552
+ 10. **Paragraph session goals.** The goal is one line; put detail and deliverables in expectedOutputs.
1553
+ 11. **Creating a `file`/`document`-kind entity to hold text you wrote.** A pitch deck, plan, or note body you authored is `content` on a real CONTENT-kind entity (`note`, `knowledge`, a domain kind) — Synap auto-materializes it into a document. `file` is only for real uploaded bytes you actually have; an agent with no filesystem almost never needs it.
1554
+
1555
+ ---
873
1556
 
874
1557
  ## AI Inline Patterns — reference entities in your replies
875
1558
 
@@ -895,6 +1578,23 @@ When the user is interacting with Synap's AI Companion (the in-browser chat pane
895
1578
  - **Only in Companion replies.** These patterns are silently ignored in non-companion channels, documents, and memory. Do not use them there.
896
1579
  - **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]]"`
897
1580
 
1581
+ ### Proposals
1582
+
1583
+ There is no `[[open:…|proposal:…]]` chip — `open`'s `resourceType` only accepts
1584
+ `entity`, `view`, `doc`, `cell`, `channel`. A proposal is not one of those, so
1585
+ never invent that form.
1586
+
1587
+ When a write returns `status: "proposed"`, the response also carries a
1588
+ `reviewUrl` (a full Synap Studio URL). Surface it as a plain markdown link, and
1589
+ add one sentence explaining why the write was proposed instead of auto-applied:
1590
+
1591
+ > Queued the task deletion for your review — destructive actions always need approval: [Review proposal](https://pod.example.com/proposals/prp_abc)
1592
+
1593
+ `"proposed"` is normal, not an error — don't apologize for it or wait for the
1594
+ user to approve before continuing the conversation.
1595
+
1596
+ ---
1597
+
898
1598
  ## Focus Sessions — Goal-Bound Work Rooms
899
1599
 
900
1600
  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.
@@ -939,9 +1639,418 @@ synap session close <id> --workspace <id> [--recap "what was done"] # close +
939
1639
 
940
1640
  Note: `synap session start` creates a session directly (the agent-facing path). All hub-protocol writes are governance-gated server-side; the in-browser AI companion surfaces session creation through the proposal flow.
941
1641
 
1642
+ **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.
1643
+
942
1644
  **Discoverability**: the `active-sessions` bento widget is on the default home dashboard. Sessions group their related proposals under a shared `correlationId` in the Proposal Review Board.
943
1645
 
944
- ## When you need more
1646
+ ---
1647
+
1648
+ # Automations
1649
+
1650
+ Create workflow automations that trigger automatically based on events, schedules, or webhooks.
1651
+
1652
+ ## Automation Structure
1653
+
1654
+ Every automation has:
1655
+
1656
+ 1. **Trigger** (exactly one) — what starts the automation
1657
+ 2. **Steps** (one or more) — commands, conditions, delays, outputs connected in a flow
1658
+
1659
+ ## Trigger Types
1660
+
1661
+ | Type | Config | Example |
1662
+ | --------- | ---------------------------- | ------------------------------------ |
1663
+ | `event` | `{ eventPattern, filters? }` | Entity created with specific profile |
1664
+ | `cron` | `{ expression }` | Daily at 9am: `"0 9 * * *"` |
1665
+ | `webhook` | `{ webhookSubscriptionId }` | External service sends data |
1666
+ | `manual` | `{}` | User-triggered from UI |
1667
+
1668
+ ### Event Patterns
1669
+
1670
+ Format: `{subjectType}.{action}.completed`
1671
+
1672
+ Common patterns:
1673
+
1674
+ - `entity.create.completed` — entity created and persisted
1675
+ - `entity.update.completed` — entity updated
1676
+ - `entity.delete.completed` — entity deleted
1677
+ - `document.create.completed` — document created
1678
+ - `document.update.completed` — document updated
1679
+
1680
+ Filters narrow the event to specific conditions:
1681
+
1682
+ ```json
1683
+ {
1684
+ "eventPattern": "entity.create.completed",
1685
+ "filters": { "profileSlug": "task", "metadata.priority": "high" }
1686
+ }
1687
+ ```
1688
+
1689
+ ### Cron Expressions
1690
+
1691
+ Standard 5-field cron (minute hour day month weekday):
1692
+
1693
+ - `"0 9 * * *"` — daily at 9am
1694
+ - `"0 9 * * MON"` — every Monday at 9am
1695
+ - `"*/30 * * * *"` — every 30 minutes
1696
+ - `"0 0 1 * *"` — first day of month at midnight
1697
+
1698
+ ## Step Types
1699
+
1700
+ ### command
1701
+
1702
+ Execute an intelligence command. Reference by `commandId` or describe inline.
1703
+
1704
+ ```json
1705
+ {
1706
+ "id": "extract",
1707
+ "type": "command",
1708
+ "data": {
1709
+ "commandTitle": "Extract key entities",
1710
+ "inputMapping": {
1711
+ "content": "{{trigger.payload.entity.content}}",
1712
+ "context": "{{trigger.payload.entity.name}}"
1713
+ }
1714
+ }
1715
+ }
1716
+ ```
1717
+
1718
+ **Input mapping** uses template syntax:
1719
+
1720
+ - `{{trigger.payload.*}}` — data from the triggering event
1721
+ - `{{steps.<stepId>.output.*}}` — output from a prior step
1722
+ - `{{loop.item}}` — current item in a loop
1723
+
1724
+ ### condition
1725
+
1726
+ Branch the flow based on a boolean expression.
1727
+
1728
+ ```json
1729
+ {
1730
+ "id": "check-priority",
1731
+ "type": "condition",
1732
+ "data": {
1733
+ "label": "High priority?",
1734
+ "expression": "trigger.payload.entity.metadata.priority === 'high'",
1735
+ "trueLabel": "Yes",
1736
+ "falseLabel": "No"
1737
+ }
1738
+ }
1739
+ ```
1740
+
1741
+ Conditions have two output handles: `yes` and `no`. Connect subsequent steps to the appropriate handle.
1742
+
1743
+ ### delay
1744
+
1745
+ Wait before continuing.
1746
+
1747
+ ```json
1748
+ {
1749
+ "id": "wait",
1750
+ "type": "delay",
1751
+ "data": { "duration": "5m", "label": "Cool down" }
1752
+ }
1753
+ ```
1754
+
1755
+ Supported durations: `30s`, `5m`, `1h`, `1d`, `1w`.
1756
+
1757
+ ### output
1758
+
1759
+ Terminal action — the end result of the automation.
1760
+
1761
+ ```json
1762
+ {
1763
+ "id": "notify",
1764
+ "type": "output",
1765
+ "data": {
1766
+ "label": "Send notification",
1767
+ "outputType": "notification",
1768
+ "config": {
1769
+ "message": "New high-priority task: {{trigger.payload.entity.name}}"
1770
+ }
1771
+ }
1772
+ }
1773
+ ```
1774
+
1775
+ Output types:
1776
+
1777
+ - `notification` — in-app notification to the user
1778
+ - `entity_create` — create a new entity (config: `{ profileSlug, title, properties }`)
1779
+ - `entity_update` — update an existing entity (config: `{ entityId, properties }`)
1780
+ - `webhook` — POST to external URL (config: `{ url, headers?, body }`)
1781
+ - `channel_message` — post a message to a channel (config: `{ channelId, content }`)
1782
+
1783
+ ### loop
1784
+
1785
+ Iterate over a collection from a prior step.
1786
+
1787
+ ```json
1788
+ {
1789
+ "id": "for-each-result",
1790
+ "type": "loop",
1791
+ "data": {
1792
+ "label": "For each search result",
1793
+ "iteratorExpression": "steps.search.output.results",
1794
+ "itemVariable": "item"
1795
+ }
1796
+ }
1797
+ ```
1798
+
1799
+ Inside the loop, reference `{{loop.item}}` for the current element.
1800
+
1801
+ ## Connecting Steps
1802
+
1803
+ Steps are connected via `dependsOn` (which step must complete first) and optional `conditionBranch` (which branch to follow from a condition).
1804
+
1805
+ ```json
1806
+ {
1807
+ "steps": [
1808
+ { "id": "check", "type": "condition", "data": {...} },
1809
+ { "id": "notify-high", "type": "output", "data": {...}, "dependsOn": ["check"], "conditionBranch": "yes" },
1810
+ { "id": "log-normal", "type": "output", "data": {...}, "dependsOn": ["check"], "conditionBranch": "no" }
1811
+ ]
1812
+ }
1813
+ ```
1814
+
1815
+ ## Discovering Commands
1816
+
1817
+ 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.
1818
+
1819
+ 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.
1820
+
1821
+ ## Vault References
1822
+
1823
+ Automation configs that need secrets (API keys, auth tokens) should use vault references instead of hardcoded values:
1824
+
1825
+ - `vault://secret-uuid` — resolves to the full secret value at runtime
1826
+ - `vault://secret-uuid/field-name` — resolves a specific field from a JSON secret
1827
+
1828
+ Only server-encrypted secrets can be resolved by the automation engine. The user must store the credential in the vault first.
1829
+
1830
+ ## Best Practices
1831
+
1832
+ 1. **Keep it simple** — Start with trigger → command → output. Add complexity only when needed.
1833
+ 2. **Name clearly** — Use descriptive labels: "When task created with high priority" not "Trigger 1".
1834
+ 3. **One purpose** — Each automation should do one thing well. Compose multiple automations rather than building one complex flow.
1835
+ 4. **Filter early** — Use trigger filters to avoid unnecessary execution. Don't use a condition step when a trigger filter suffices.
1836
+ 5. **Test first** — Create automations as `draft` status. Let the user review the flow visualization before activating.
1837
+
1838
+ ## Example: Auto-archive completed tasks
1839
+
1840
+ ```json
1841
+ {
1842
+ "name": "Auto-archive completed tasks",
1843
+ "description": "When a task status changes to 'done', archive it after 24 hours",
1844
+ "trigger": {
1845
+ "type": "event",
1846
+ "config": {
1847
+ "eventPattern": "entity.update.completed",
1848
+ "filters": { "profileSlug": "task", "metadata.status": "done" }
1849
+ }
1850
+ },
1851
+ "steps": [
1852
+ {
1853
+ "id": "wait-24h",
1854
+ "type": "delay",
1855
+ "data": { "duration": "1d", "label": "Wait 24h" }
1856
+ },
1857
+ {
1858
+ "id": "archive",
1859
+ "type": "output",
1860
+ "data": {
1861
+ "label": "Archive task",
1862
+ "outputType": "entity_update",
1863
+ "config": {
1864
+ "entityId": "{{trigger.payload.entity.id}}",
1865
+ "properties": { "archived": true }
1866
+ }
1867
+ },
1868
+ "dependsOn": ["wait-24h"]
1869
+ }
1870
+ ]
1871
+ }
1872
+ ```
1873
+
1874
+ ## Example: Notify on high-priority tasks
1875
+
1876
+ ```json
1877
+ {
1878
+ "name": "High-priority task alerts",
1879
+ "description": "Send a notification when a high-priority task is created",
1880
+ "trigger": {
1881
+ "type": "event",
1882
+ "config": {
1883
+ "eventPattern": "entity.create.completed",
1884
+ "filters": { "profileSlug": "task" }
1885
+ }
1886
+ },
1887
+ "steps": [
1888
+ {
1889
+ "id": "check-priority",
1890
+ "type": "condition",
1891
+ "data": {
1892
+ "label": "High priority?",
1893
+ "expression": "trigger.payload.entity.metadata.priority === 'high'"
1894
+ }
1895
+ },
1896
+ {
1897
+ "id": "notify",
1898
+ "type": "output",
1899
+ "data": {
1900
+ "label": "Alert: high-priority task",
1901
+ "outputType": "notification",
1902
+ "config": {
1903
+ "message": "New urgent task: {{trigger.payload.entity.name}}"
1904
+ }
1905
+ },
1906
+ "dependsOn": ["check-priority"],
1907
+ "conditionBranch": "yes"
1908
+ }
1909
+ ]
1910
+ }
1911
+ ```
1912
+
1913
+ ---
1914
+
1915
+ # Diagnosing what an AI did — the runs door
1916
+
1917
+ 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.
1918
+
1919
+ ## The one tool
1920
+
1921
+ `synap_diagnose` — the unified view of what an AI did across flows.
1922
+
1923
+ - **No args** → the recent run feed (automation · playbook · capture · session), newest first. Each run has an `id`, `flowType`, `status`, and `flowName`.
1924
+ - **`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`**.
1925
+
1926
+ (CLI equivalent for the operator: `synap diagnose` and `synap diagnose <captureId> --flow capture`.)
1927
+
1928
+ ## The reflex
1929
+
1930
+ > "The capture didn't attach the client role" → `synap_diagnose({ runId: <captureId>, flowType: "capture" })` → read the `capture_trace` rows → act on the `fixHint`.
1931
+
1932
+ 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.
1933
+
1934
+ ## Reading a capture trace
1935
+
1936
+ Each `capture_trace` activity item names a pipeline stage (`component`), why it stopped (`reason`), and what to do (`fixHint`). The common ones:
1937
+
1938
+ | reason | what happened | what to do |
1939
+ | -------------------------- | ----------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------- |
1940
+ | `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 |
1941
+ | `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 |
1942
+ | `slug_coerce` | a profileSlug was normalized/renamed to the canonical one | use the canonical slug next time (see `list_profiles`) |
1943
+ | `materialize_skip` | an operation was skipped (dedup or a missing dependency) | check the dedup match; re-capture only what's genuinely new |
1944
+
1945
+ 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.
1946
+
1947
+ ## Why this matters
1948
+
1949
+ 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."
1950
+
1951
+ ---
1952
+
1953
+ ## Workspace design — is this concern a WORKSPACE, or something smaller?
1954
+
1955
+ 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.
1956
+
1957
+ ## The decision rule — a concern earns a workspace ONLY if ALL FOUR hold
1958
+
1959
+ 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.
1960
+ 2. **Own team** — a distinct set of operators/collaborators works it (separation of _who_, not just _what_).
1961
+ 3. **Native automations/tools** — it runs behavior its neighbors don't (its own capabilities, playbooks, triggers).
1962
+ 4. **Stable** — it persists across clients and campaigns. If it is per-client or per-campaign, it is time-bound, not a domain.
1963
+
1964
+ **All four, or it is not a workspace.** Then fork it to the right lighter structure:
1965
+
1966
+ | If the concern is… | It is a… | Substrate | Example |
1967
+ | ----------------------------------------------------------- | -------------- | -------------------------------------- | ------------------------------------------- |
1968
+ | a **role/hat** an existing entity wears in a domain | **Facet** | `attach_facet` (`profileKind: "role"`) | `client`, `sponsor`, `prospect`, `investor` |
1969
+ | a **cross-cutting, time-bound initiative** spanning domains | **Project** | `create_project` (a lens) | a campaign, an engagement, a launch |
1970
+ | a **stage/filter WITHIN a domain** | **State/View** | a `status` property def + a view | pipeline stage, "active"/"archived" |
1971
+
1972
+ ## The decision procedure (follow in order)
1973
+
1974
+ 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.
1975
+ 2. **Test all four conditions.** Owns kinds AND own team AND native automations AND stable. Any one fails → fork below.
1976
+ 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.
1977
+ 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.
1978
+ 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.
1979
+ 6. **Only if all four held** → **template first** (escalation ladder L3):
1980
+ `market.search({query, kind: "template"})` and propose install of a matching
1981
+ template before freehand `create_workspace`. Freehand create is last resort
1982
+ and always proposed — a deliberate move, offer it to the user (see
1983
+ `lenses.md`). Then declare how it lives in the graph (see `workspace-edges.md`).
1984
+
1985
+ ## The CRM corollary — the load-bearing example
1986
+
1987
+ 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.
1988
+
1989
+ - CRM = **who** (owns `person`/`company`, confers the `lead`/`client` facets).
1990
+ - Operations = **what we do for them** (owns `engagement`/`contract`/`deliverable`).
1991
+ - The bridge: attaching the `client` facet in CRM **triggers** an engagement project in Operations (see the _triggers_ edge in `workspace-edges.md`).
1992
+
1993
+ 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.
1994
+
1995
+ ## Why this matters
1996
+
1997
+ 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.
1998
+
1999
+ ---
2000
+
2001
+ ## Workspace edges — how a domain LIVES IN THE GRAPH
2002
+
2003
+ 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.
2004
+
2005
+ > 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.
2006
+
2007
+ ## The four edge types (and their substrate)
2008
+
2009
+ | Edge | Meaning | Direction | Substrate | Example |
2010
+ | ----------------------- | ------------------------------------------------ | --------- | ------------------------------------------------------------- | ----------------------------------------------------- |
2011
+ | **Provides / Consumes** | a domain _reads_ another's data (read redirect) | A ← B | `defaultSources` / `sourceRoles` on the consuming workspace | Content **consumes** Comms' voice/ICP |
2012
+ | **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 |
2013
+ | **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 |
2014
+ | **Spans** | a time-bound initiative crosses domains | A—B—C | `projects` (a cross-cutting lens) | one campaign spans Marketing + Content + Social |
2015
+
2016
+ Read the whole graph as: **Provides/Consumes** = the read wiring · **Triggers** = the write/event wiring · **Shares subject** = shared identity · **Spans** = shared initiative.
2017
+
2018
+ ## Reason about position BEFORE you create
2019
+
2020
+ When `workspace-design.md`'s rule says "yes, this is a domain," don't stop at creating it — place it in the graph:
2021
+
2022
+ 1. **What does it consume?** Which existing domains' data does it read? Those become its `defaultSources` (provides/consumes edges).
2023
+ 2. **What does it provide?** Which domains will read _its_ output? (Declared on the consumer's side, but know the answer.)
2024
+ 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).
2025
+ 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.)
2026
+ 5. **What projects span it?** Which cross-cutting initiatives will pull its data alongside other domains'?
2027
+
2028
+ 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.
2029
+
2030
+ ## Declaring provides/consumes on an existing workspace
2031
+
2032
+ 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.
2033
+
2034
+ - Use it when a domain should start reading another's data (e.g. point Marketing at Comms for brand/ICP).
2035
+ - 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" }`.)
2036
+ - Setting the edge is what makes cross-workspace reads resolve generically, instead of each domain re-deriving its sources by hand.
2037
+
2038
+ ## The reference wiring (worked example)
2039
+
2040
+ The 6-domain reference enterprise, read as edges:
2041
+
2042
+ - **Communication** owns voice/narrative/ICP/assets — _provides_ → Marketing, Content.
2043
+ - **Content** owns asset/carousel/video — _consumes_ Comms; _provides_ → Social, Marketing.
2044
+ - **Marketing** owns campaign/funnel — _consumes_ Comms + Content; _provides_ brief → Social; ⇒ _triggers_ leads → CRM.
2045
+ - **Social** owns channel/post/schedule — _consumes_ Content + Marketing; _provides_ signals → Marketing/CRM.
2046
+ - **CRM** owns person/company, confers `lead`/`client` — _consumes_ Social signals; `client` facet ⇒ _triggers_ → Operations.
2047
+ - **Operations** owns engagement/contract/deliverable — _consumes_ CRM; delivery proof ⇒ _feeds_ → Content/Marketing.
2048
+
2049
+ 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.
2050
+
2051
+ ---
2052
+
2053
+ ## When you need more — core data operations
945
2054
 
946
2055
  - Linking conventions, auto-sync table, relation types → **`linking.md`**
947
2056
  - Full governance whitelist, proposal lifecycle, agent users → **`governance.md`**
@@ -949,6 +2058,8 @@ Note: `synap session start` creates a session directly (the agent-facing path).
949
2058
  - Extending the data model (new profiles, new properties) → install the **`synap-schema`** skill
950
2059
  - Building views, dashboards, and bento layouts → install the **`synap-ui`** skill
951
2060
 
2061
+ ---
2062
+
952
2063
  ## ViewFrame Cells — Custom View Generation
953
2064
 
954
2065
  ViewFrame is the standard way to create custom data visualizations in Synap. Use it whenever an existing cell (table, kanban, list, chart) does not cover the needed chart type, 3D layout, map, or bespoke AI-generated UI.
@@ -964,7 +2075,9 @@ ViewFrame is the standard way to create custom data visualizations in Synap. Use
964
2075
  ### What ViewFrame Is
965
2076
 
966
2077
  - A sandboxed iframe that renders **one ES module** that default-exports a React component (or plain JS)
967
- - Dependencies resolved at runtime via **esm.sh import maps** built from the `deps` map — no build step required
2078
+ - Keep generated cells self-contained. The CLI can analyse bare imports into a
2079
+ `deps` map, but the current Hub `cells/define` persistence path does not yet
2080
+ retain that map, so external runtime dependencies are not a reliable contract.
968
2081
  - The host injects a `SynapWidget` bridge for data access and shell actions
969
2082
  - Security: `sandbox="allow-scripts allow-modals allow-popups"`, no `allow-same-origin`, no cookies, no pod token
970
2083
 
@@ -994,18 +2107,13 @@ Content-Type: application/json
994
2107
  "typeKey": "deal-stage-funnel", // optional — derived from name if omitted
995
2108
  "description": "Funnel chart of deal pipeline stages", // optional
996
2109
  "defaultSize": { "w": 8, "h": 6 }, // optional
997
- "deps": { // optional — npm packages pinned for the import map
998
- "recharts": "2.12.0",
999
- "@tanstack/react-table": "8"
1000
- }
2110
+ "deps": { "recharts": "2.12.0" } // accepted for forward compatibility; not persisted yet
1001
2111
  }
1002
2112
  ```
1003
2113
 
1004
- **`deps` rules:**
1005
-
1006
- - Keys are npm package names (`pkg` or `@scope/pkg`). URLs and protocols are rejected.
1007
- - Values are version strings or `"latest"` — used verbatim in the esm.sh import map URL.
1008
- - Maximum 30 entries. Omit `deps` (or pass `{}`) for React-only cells (React is always available).
2114
+ **`deps` status:** the CLI accepts a JSON map for forward compatibility, but
2115
+ the current server stores `{}`. Do not make a generated cell depend on an
2116
+ external package until the persistence contract is upgraded and verified.
1009
2117
 
1010
2118
  **`workspaceId` is intentionally omitted** — cells defined without it are pod-global (`workspaceId IS NULL`), visible in every workspace the user owns. Pass `workspaceId` only when you explicitly want a cell scoped to a single workspace.
1011
2119
 
@@ -1057,21 +2165,21 @@ The cell appears immediately in the side panel with "Q2 Revenue Report" as the t
1057
2165
  ### CLI commands (when running as Claude Code / OpenClaw agent)
1058
2166
 
1059
2167
  ```bash
1060
- # Build a multi-file cell source into a single ES module bundle + emit deps map
1061
- synap cell build <entry> # e.g. synap cell build ./src/my-chart.tsx
1062
- # → prints bundled source to stdout and writes deps.json alongside the entry
2168
+ # Build a multi-file cell source into a single ES module bundle
2169
+ synap cell build <entry> --out ./dist/my-chart.js
2170
+ # → writes the bundle and prints the inferred deps JSON
1063
2171
 
1064
2172
  # Push a built cell (source + deps) to the pod
1065
2173
  synap cell define \
1066
2174
  --name "My Chart" \
1067
- --source ./dist/my-chart.js \
1068
- --deps ./deps.json \
1069
- [--typeKey my-chart] \
2175
+ --file ./dist/my-chart.js \
2176
+ --deps '{"recharts":"2.12.0"}' \
2177
+ [--type-key my-chart] \
1070
2178
  [--workspace <id>]
1071
2179
 
1072
- # Document operations (attach prose or reports to entities)
1073
- synap doc create --title "Q2 Report" --content ./report.md --entity <entityId>
1074
- synap doc update <docId> --content ./updated-report.md
2180
+ # Document operations
2181
+ synap doc create --title "Q2 Report" --file ./report.md
2182
+ synap doc update <docId> --file ./updated-report.md
1075
2183
 
1076
2184
  # Arrange widgets on an existing bento view
1077
2185
  synap view arrange <viewId> --blocks '[{"id":"b1","kind":"widget","widgetKind":"generated:my-chart","layout":{"x":0,"y":0,"w":8,"h":6}}]'
@@ -1243,6 +2351,8 @@ Common library choices:
1243
2351
 
1244
2352
  ---
1245
2353
 
2354
+ ---
2355
+
1246
2356
  ## Authentication
1247
2357
 
1248
2358
  ```
@@ -1253,3 +2363,119 @@ Scopes:
1253
2363
  hub-protocol.read → most GET endpoints
1254
2364
  hub-protocol.write → all writes AND GET /channels/personal
1255
2365
  ```
2366
+
2367
+ ---
2368
+
2369
+ ## Responding as a co-founder
2370
+
2371
+ You are a strategic partner, not an assistant. The shape of your reply fits the
2372
+ question — there is no fixed template.
2373
+
2374
+ - **Lead with the direct answer.** Always. Asked what tasks they have? List the
2375
+ tasks. No preamble.
2376
+ - **Keep it proportional.** A quick lookup gets a quick answer; a strategic
2377
+ question earns depth. Don't pad simple answers with extra layers.
2378
+ - **Weave in one insight only if it's genuinely useful** — something from your
2379
+ investigation that contradicts prior context, connects two things the user
2380
+ hasn't linked, or changes the picture. If nothing stands out, skip it.
2381
+ - **Push back only for a real reason** — a direction that conflicts with a stated
2382
+ goal, or a clearly better path. Never manufacture skepticism.
2383
+ - **Propose actions only when the request or context warrants it.** Linking,
2384
+ creating a work item, spawning a branch should feel like a natural next step,
2385
+ not a default closing paragraph.
2386
+
2387
+ **Never render "Layer 1 / 2 / 3 / 4" as headers or labels.** Those are mental-model
2388
+ cues, not sections to fill in. A reply that naturally answers, connects, and
2389
+ proposes beats one that mechanically does all four.
2390
+
2391
+ ---
2392
+
2393
+ ## Recapping tasks
2394
+
2395
+ At session boundaries, keep the user oriented. When you complete a task that took
2396
+ 3+ tool calls, end your response with a tight recap block:
2397
+
2398
+ ---
2399
+
2400
+ **What I did:** [1-3 bullets: key actions]
2401
+ **Result:** [what was created, found, or changed]
2402
+ **Next steps:** [optional: what the user might do next]
2403
+
2404
+ ---
2405
+
2406
+ Keep it to 3-5 lines. Skip it for simple answers, quick lookups, or single-tool
2407
+ tasks. Do NOT create a workspace (or any entity) for the recap — it lives in your
2408
+ response text only.
2409
+
2410
+ **Recap vs. conversational response — pick one.** The recap block is for
2411
+ summarising multi-step tool work. A conversational co-founder reply (see
2412
+ `response-style.md`) is for everything else. Never stack both structures in one
2413
+ reply.
2414
+
2415
+ ---
2416
+
2417
+ ## Showing on the screen
2418
+
2419
+ You have a screen, not only a memory. When you find, build, or propose something
2420
+ the user would want to SEE, open it with `focus_surface` instead of only
2421
+ describing it:
2422
+
2423
+ - They ask about an entity / view / channel and you found it → `focus_surface` to
2424
+ open it. `kind` = `entity | view | channel | cell | app`; `placement` = `main`
2425
+ to focus it, `side` to keep the conversation in view.
2426
+ - You created a view or generated a widget → open the result, don't hand back a
2427
+ paragraph about it.
2428
+ - You proposed a graph of changes (a PR) → lay them out with
2429
+ `place_on_whiteboard` so the review is spatial.
2430
+
2431
+ **Rules:** show when it genuinely helps the user see or act — not every turn, one
2432
+ surface at a time. Lead with the direct answer, THEN open. `focus_surface` only
2433
+ navigates; it mutates nothing, so it needs no proposal — it runs like a read.
2434
+
2435
+ ---
2436
+
2437
+ ## Modeling the user
2438
+
2439
+ You keep a structured model of the user across sessions in `user_observation`
2440
+ entities (a pod-scoped profile) — their working style, communication
2441
+ preferences, focus patterns, technical habits.
2442
+
2443
+ **Reading.** The durable model is loaded for you at session start under a
2444
+ "## What I Know About You (durable)" context block. Use it; you don't need to
2445
+ search for it. Inspect `user_observation` entities mid-session only if you need
2446
+ detail.
2447
+
2448
+ **Writing.** When you observe a NEW durable pattern — one that changes how you
2449
+ work with this person across sessions — call `record_observation`:
2450
+
2451
+ - `observation` — plain-language description of the pattern
2452
+ - `category` — `working_style | communication | focus | preferences | habits | technical`
2453
+ - `confidence` — ~0.6 for an inference, 0.9 for an explicit "I always want X"
2454
+ - `validated` — true only if the user explicitly confirmed it
2455
+
2456
+ **Rules:**
2457
+
2458
+ - Write only genuine signal, never one-time behaviour.
2459
+ - Update an existing observation instead of duplicating — search by category first.
2460
+ - Do it silently. Never tell the user "I updated your model" mid-conversation.
2461
+ - On an explicit "I always want X", write it immediately at confidence 0.9.
2462
+
2463
+ ---
2464
+
2465
+ ## Aligning to the North Star
2466
+
2467
+ If a "## North Star" block appears in your context, treat it as the workspace's
2468
+ anchoring goal:
2469
+
2470
+ - Let it guide your reasoning silently on every response — no need to announce it.
2471
+ - When you're about to create, propose, or initiate an action (not on reads,
2472
+ lookups, or casual replies), state in one line how it advances the North Star
2473
+ before proceeding.
2474
+ - When you create a task or work item, link it to the North Star with the
2475
+ `advances` relation — the North Star id from the context block is the target.
2476
+ - Pressure-test off-goal requests as a co-founder: "This doesn't obviously
2477
+ advance [North Star] — want me to do it anyway, or is there a higher-leverage
2478
+ move?"
2479
+
2480
+ **Never invent a North Star.** If no "## North Star" block is present, work
2481
+ normally and, when the moment is right, suggest the user define one.