@synap-core/workspace-templates 0.11.0 → 0.23.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 (249) hide show
  1. package/README.md +483 -0
  2. package/automations/bookmark-enrichment.automation.json +321 -0
  3. package/automations/github-pr-events.automation.json +505 -0
  4. package/automations/mail-feed.automation.json +86 -0
  5. package/automations/ship-a-change-checks-green.automation.json +106 -0
  6. package/automations/ship-a-change-checks-red.automation.json +106 -0
  7. package/automations/ship-a-change-deployed.automation.json +56 -0
  8. package/automations/ship-a-change-pr-merged.automation.json +110 -0
  9. package/automations/ship-a-change-pr-opened.automation.json +104 -0
  10. package/automations/url-bookmark-capture.automation.json +132 -0
  11. package/capabilities/agency-skills.capability.json +157 -0
  12. package/capabilities/apify.capability.json +265 -0
  13. package/capabilities/apollo.capability.json +120 -0
  14. package/capabilities/arch-backend.capability.json +97 -0
  15. package/capabilities/arch-client-intelligence.capability.json +401 -0
  16. package/capabilities/backblaze-b2.capability.json +205 -0
  17. package/capabilities/cal-com.capability.json +266 -0
  18. package/capabilities/canva.capability.json +274 -0
  19. package/capabilities/claude-code-routines.capability.json +172 -0
  20. package/capabilities/claude-managed-agents.capability.json +215 -0
  21. package/capabilities/client-comms-report.capability.json +246 -0
  22. package/capabilities/content-idea-to-production.capability.json +29 -0
  23. package/capabilities/datagouv.capability.json +315 -0
  24. package/capabilities/discord-bot.capability.json +157 -0
  25. package/capabilities/dropbox.capability.json +281 -0
  26. package/capabilities/exa.capability.json +109 -0
  27. package/capabilities/fal-ai.capability.json +83 -0
  28. package/capabilities/fal-media-lab.capability.json +158 -0
  29. package/capabilities/fireflies.capability.json +191 -0
  30. package/capabilities/freellmapi.capability.json +114 -0
  31. package/capabilities/generic-apikey.capability.json +92 -0
  32. package/capabilities/github-merge.capability.json +99 -0
  33. package/capabilities/github-read.capability.json +325 -0
  34. package/capabilities/governance-tighten-recommender.capability.json +81 -0
  35. package/capabilities/jev.capability.json +226 -0
  36. package/capabilities/lead-conversion-enrollment.capability.json +246 -0
  37. package/capabilities/mail-feed.capability.json +277 -0
  38. package/capabilities/mailgun.capability.json +52 -0
  39. package/capabilities/nango-google.capability.json +639 -0
  40. package/capabilities/notion.capability.json +417 -0
  41. package/capabilities/omniroute.capability.json +137 -0
  42. package/capabilities/posthog-analytics.capability.json +258 -0
  43. package/capabilities/postiz.capability.json +467 -0
  44. package/capabilities/proactive-digest.capability.json +190 -0
  45. package/capabilities/proton.capability.json +288 -0
  46. package/capabilities/replicate-rvm.capability.json +97 -0
  47. package/capabilities/research-methods.capability.json +57 -0
  48. package/capabilities/shopify.capability.json +395 -0
  49. package/capabilities/slack.capability.json +369 -0
  50. package/capabilities/stripe.capability.json +420 -0
  51. package/capabilities/telegram-bridge.capability.json +56 -0
  52. package/capabilities/treg.capability.json +102 -0
  53. package/capabilities/unipile-linkedin.capability.json +471 -0
  54. package/capabilities/vercel.capability.json +424 -0
  55. package/dist/define-CPS0n8BV.d.ts +1331 -0
  56. package/dist/index.d.ts +189 -900
  57. package/dist/index.js +24654 -21639
  58. package/dist/index.js.map +1 -1
  59. package/dist/wire.d.ts +222 -0
  60. package/dist/wire.js +77 -0
  61. package/dist/wire.js.map +1 -0
  62. package/package.json +41 -27
  63. package/packs/bookmark-views-pack.json +33 -0
  64. package/packs/crm-pack.json +76 -0
  65. package/packs/customer-profile.json +28 -0
  66. package/packs/deep-research.playbook.json +30 -0
  67. package/packs/event-views-pack.json +33 -0
  68. package/packs/habit-profile.json +27 -0
  69. package/packs/hr-pack.json +54 -0
  70. package/packs/invoice-profile.json +28 -0
  71. package/packs/knowledge-base-pack.json +56 -0
  72. package/packs/meeting-profile.json +29 -0
  73. package/packs/product-profile.json +30 -0
  74. package/packs/project-management-pack.json +49 -0
  75. package/packs/recipe-profile.json +29 -0
  76. package/packs/task-views-pack.json +39 -0
  77. package/retired.yaml +136 -0
  78. package/skills/app-factory-build-slice/SKILL.md +70 -0
  79. package/skills/app-factory-build-slice.catalog.json +17 -0
  80. package/skills/app-factory-design-canvas/SKILL.md +96 -0
  81. package/skills/app-factory-design-canvas.catalog.json +17 -0
  82. package/skills/app-factory-design-review/SKILL.md +99 -0
  83. package/skills/app-factory-design-review.catalog.json +18 -0
  84. package/skills/app-factory-research/SKILL.md +123 -0
  85. package/skills/app-factory-research.catalog.json +17 -0
  86. package/skills/bookmark-enrichment/SKILL.md +40 -0
  87. package/skills/carousel-format/carousel-format.skill.json +18 -0
  88. package/skills/collage-broll/collage-broll.skill.json +20 -0
  89. package/skills/content-taste/content-taste.skill.json +18 -0
  90. package/skills/creative-director/creative-director.skill.json +18 -0
  91. package/skills/design-critique/SKILL.md +135 -0
  92. package/skills/design-critique.catalog.json +12 -0
  93. package/skills/designing-with-real-components/SKILL.md +140 -0
  94. package/skills/designing-with-real-components.catalog.json +12 -0
  95. package/skills/edit-loop/edit-loop.skill.json +25 -0
  96. package/skills/facecam-edit/facecam-edit.skill.json +24 -0
  97. package/skills/mapping-the-server/SKILL.md +54 -0
  98. package/skills/math-explainer-manimgx/math-explainer-manimgx.skill.json +20 -0
  99. package/skills/motion-quality-gates/motion-quality-gates.skill.json +20 -0
  100. package/skills/open-design-edit/open-design-edit.skill.json +24 -0
  101. package/skills/reading-client-progress/SKILL.md +65 -0
  102. package/skills/routing-to-channels/SKILL.md +54 -0
  103. package/skills/stellar-scf-grants-advisor/ASSOCIATE-CHEATSHEET.md +133 -0
  104. package/skills/stellar-scf-grants-advisor/ASSOCIATE-ONBOARDING-GUIDE.md +761 -0
  105. package/skills/stellar-scf-grants-advisor/CLIENT-FAQ.md +181 -0
  106. package/skills/stellar-scf-grants-advisor/PARTNER-HANDOVER.md +130 -0
  107. package/skills/stellar-scf-grants-advisor/SKILL.md +102 -0
  108. package/skills/stellar-scf-grants-advisor/examples/README.md +43 -0
  109. package/skills/stellar-scf-grants-advisor/examples/chat-summary-templates.md +158 -0
  110. package/skills/stellar-scf-grants-advisor/examples/corpus-comparable-rows.md +136 -0
  111. package/skills/stellar-scf-grants-advisor/examples/q10-team-rewrites.md +109 -0
  112. package/skills/stellar-scf-grants-advisor/examples/q2-description-rewrites.md +88 -0
  113. package/skills/stellar-scf-grants-advisor/examples/q6-integration-rewrites.md +107 -0
  114. package/skills/stellar-scf-grants-advisor/examples/tranche-structures.md +176 -0
  115. package/skills/stellar-scf-grants-advisor/reference/01-methodology.md +96 -0
  116. package/skills/stellar-scf-grants-advisor/reference/02-scoring-framework.md +199 -0
  117. package/skills/stellar-scf-grants-advisor/reference/03-rejection-patterns.md +241 -0
  118. package/skills/stellar-scf-grants-advisor/reference/04-winner-patterns.md +120 -0
  119. package/skills/stellar-scf-grants-advisor/reference/05-comeback-patterns.md +92 -0
  120. package/skills/stellar-scf-grants-advisor/reference/06-budget-benchmarks.md +125 -0
  121. package/skills/stellar-scf-grants-advisor/reference/07-integration-list.md +147 -0
  122. package/skills/stellar-scf-grants-advisor/reference/08-abstract-fields.md +258 -0
  123. package/skills/stellar-scf-grants-advisor/reference/09-build-fields.md +259 -0
  124. package/skills/stellar-scf-grants-advisor/reference/10-corpus-winners.md +135 -0
  125. package/skills/stellar-scf-grants-advisor/reference/11-corpus-rejects.md +267 -0
  126. package/skills/stellar-scf-grants-advisor/reference/12-client-communication.md +131 -0
  127. package/skills/stellar-scf-grants-advisor/reference/13-excel-output-spec.md +199 -0
  128. package/skills/stellar-scf-grants-advisor/reference/14-worked-examples.md +160 -0
  129. package/skills/stellar-scf-grants-advisor/templates/abstract-review-builder.py +253 -0
  130. package/skills/stellar-scf-grants-advisor/templates/build-review-builder.py +213 -0
  131. package/skills/video-direction/video-direction.skill.json +20 -0
  132. package/skills/video-style-vocabulary/video-style-vocabulary.skill.json +20 -0
  133. package/skills/video-toolbox/video-toolbox.skill.json +20 -0
  134. package/src/__fixtures__/lifecycle-presets.flat-before.json +205 -0
  135. package/src/agency-os.yaml +122 -0
  136. package/src/agent-os-skill-slugs.test.ts +65 -0
  137. package/src/apify-social-reader.test.ts +317 -0
  138. package/src/base.template.test.ts +25 -19
  139. package/src/base.yaml +95 -39
  140. package/src/blockchain-ecosystem.yaml +1 -0
  141. package/src/brand-library.yaml +525 -14
  142. package/src/builder-dev-fold.template.test.ts +113 -0
  143. package/src/builder.yaml +798 -92
  144. package/src/business-model-catalog-description.test.ts +47 -0
  145. package/src/business-model-grp.template.test.ts +214 -0
  146. package/src/business-model.yaml +598 -0
  147. package/src/catalog-safety.test.ts +198 -0
  148. package/src/catalog.template-items.test.ts +75 -0
  149. package/src/catalog.ts +85 -2
  150. package/src/civic-france.yaml +102 -0
  151. package/src/concept-guard.test.ts +28 -8
  152. package/src/concept-guard.ts +2 -2
  153. package/src/content-creation.template.test.ts +52 -0
  154. package/src/content-creation.yaml +192 -0
  155. package/src/content-os.edit-loop.template.test.ts +210 -0
  156. package/src/content-os.publish-due-posts.template.test.ts +261 -0
  157. package/src/content-os.template.test.ts +322 -0
  158. package/src/content-os.video-takes.template.test.ts +338 -0
  159. package/src/content-os.work-a-take.template.test.ts +125 -0
  160. package/src/content-os.yaml +1830 -39
  161. package/src/content-studio-retired.template.test.ts +82 -0
  162. package/src/corpus-flow-validity.test.ts +4 -5
  163. package/src/crm-absorbs-outreach.template.test.ts +117 -0
  164. package/src/crm-build-plan.template.test.ts +128 -0
  165. package/src/crm-lists.template.test.ts +96 -0
  166. package/src/crm-messages-cadence.template.test.ts +164 -0
  167. package/src/crm-one-send-door.template.test.ts +65 -0
  168. package/src/crm.yaml +1456 -181
  169. package/src/decisions-wave.template.test.ts +186 -0
  170. package/src/define.ts +130 -17
  171. package/src/design-to-implementation.template.test.ts +94 -0
  172. package/src/dist-freshness.test.ts +97 -0
  173. package/src/domain-template-type.test.ts +75 -0
  174. package/src/ecosystem.yaml +27 -66
  175. package/src/enterprise-os.yaml +10 -134
  176. package/src/facecam-edit.audio-studio.test.ts +188 -0
  177. package/src/fal-generate-image.test.ts +200 -0
  178. package/src/fal-media-lab-pod-files.test.ts +180 -0
  179. package/src/finance.yaml +254 -1
  180. package/src/foundation.yaml +130 -28
  181. package/src/frame-host-modules.d.mts +6 -0
  182. package/src/frame-host-modules.mjs +58 -0
  183. package/src/generate.mjs +9 -3
  184. package/src/github-merge.test.ts +210 -0
  185. package/src/grants.yaml +57 -17
  186. package/src/hr.yaml +39 -6
  187. package/src/index.ts +36 -1
  188. package/src/intent.test.ts +161 -0
  189. package/src/internal-runbook.yaml +19 -1
  190. package/src/jev.test.ts +356 -0
  191. package/src/legal.yaml +33 -1
  192. package/src/life-os.yaml +65 -240
  193. package/src/lifecycle-coverage.test.ts +124 -0
  194. package/src/lifecycle-declarations.test.ts +252 -0
  195. package/src/lifecycle-presets.test.ts +468 -0
  196. package/src/lifecycle-presets.ts +494 -0
  197. package/src/lifecycle-stuck.test.ts +46 -0
  198. package/src/lifecycle-waits.parity.test.ts +319 -0
  199. package/src/marketing.yaml +108 -4
  200. package/src/meetings-knowledge.yaml +18 -0
  201. package/src/networking.yaml +23 -2
  202. package/src/operations.yaml +304 -4
  203. package/src/pipeline-views.template.test.ts +88 -0
  204. package/src/playbook-goal-placeholders.test.ts +57 -0
  205. package/src/postiz-contract.test.ts +134 -0
  206. package/src/presentation-declarations.test.ts +174 -0
  207. package/src/process-contract.validate.test.ts +352 -0
  208. package/src/project-management.yaml +39 -740
  209. package/src/publish-body.d.mts +29 -0
  210. package/src/publish-body.mjs +74 -0
  211. package/src/publish-body.test.ts +74 -0
  212. package/src/publish-official-lib.test.ts +176 -0
  213. package/src/replicate-rvm.test.ts +504 -0
  214. package/src/research-base.yaml +119 -20
  215. package/src/research-decision.yaml +60 -3
  216. package/src/reserved-profile-slugs.test.ts +83 -0
  217. package/src/retired-goal-kinds.test.ts +58 -0
  218. package/src/role-reference-and-step-domain.test.ts +192 -0
  219. package/src/seed-obligation.test.ts +176 -0
  220. package/src/send-message-maps.test.ts +227 -0
  221. package/src/shared-role-ssot.test.ts +42 -1
  222. package/src/ship-a-change.template.test.ts +177 -0
  223. package/src/skills.test.ts +170 -0
  224. package/src/social.yaml +85 -27
  225. package/src/space-packages.freshness.test.ts +97 -0
  226. package/src/space-packages.generated.ts +40 -0
  227. package/src/space-packages.test.ts +125 -0
  228. package/src/space-packages.ts +73 -0
  229. package/src/start.test.ts +188 -0
  230. package/src/task-intents.test.ts +293 -0
  231. package/src/templates.ts +21639 -21522
  232. package/src/types.ts +443 -60
  233. package/src/unipile-publish-post.test.ts +162 -0
  234. package/src/validate-flow.drift.test.ts +2 -3
  235. package/src/validate-flow.ts +32 -2
  236. package/src/validate-onboarding.test.ts +59 -0
  237. package/src/validate.test.ts +156 -3
  238. package/src/validate.ts +1384 -7
  239. package/src/wire-core.ts +231 -0
  240. package/src/wire.test.ts +159 -0
  241. package/src/wire.ts +107 -0
  242. package/src/__fixtures__/report-automation-flow.json +0 -393
  243. package/src/agent-fleet.yaml +0 -1645
  244. package/src/business-developer.template.test.ts +0 -351
  245. package/src/business-developer.yaml +0 -860
  246. package/src/content-studio.yaml +0 -361
  247. package/src/dev-dashboard.yaml +0 -990
  248. package/src/outreach-comms.yaml +0 -486
  249. package/src/the-arch.yaml +0 -201
package/src/crm.yaml CHANGED
@@ -1,8 +1,35 @@
1
1
  # CRM workspace template
2
- # The CRM owns pre-sale work only. A won deal is handed to the operating
3
- # workspace; this template deliberately does not provision client delivery,
4
- # campaigns, tasks, documents, automations, or sales-specific playbooks. A
5
- # use-case overlay (for example `business-developer`) may add those workflows.
2
+ # The CRM owns pre-sale work only: who is worth pursuing (the `lead` role's
3
+ # leadStage, "Qualify a lead"), the sale (the deal's commercialStage, "Win a
4
+ # deal") and the campaigns people come from. A won deal is handed to the
5
+ # operating workspace; client delivery belongs to Operations.
6
+ #
7
+ # Business Developer folded into this file (founder decision 2026-10-12): its
8
+ # lead/client fields, the campaign kind and "Qualify a lead" live here now; its
9
+ # "Advance a commercial proposal" was dropped for "Win a deal"; its Synap-fee
10
+ # and commission model moved to the private arch-sales, retired the same day with
11
+ # The Arch (retired.yaml; source kept in retired-sources/).
12
+ #
13
+ # Outreach & Comms folded into this file (founder decision 2026-10-23): for a
14
+ # founder-led company outreach is part of the commercial job, one space per
15
+ # domain. Its `outreach-touch` kind (same slug, same lifecycle keys), its
16
+ # relations `sent_to` / `part_of`, its views, its campaign process and its
17
+ # LinkedIn ingest live here now. Its `outreach-campaign` kind was a twin of
18
+ # this file's `campaign` (whose `campaign-type` already has `outreach`): an
19
+ # outreach campaign is a `campaign` with campaign-type outreach, and its
20
+ # `targets` edge to people and companies was a twin of `member_of`. The agent
21
+ # drafts, a person sends; a reply never opens a deal (a deal exists only once a
22
+ # meeting is booked). `outreach-comms` is retired (retired.yaml).
23
+ #
24
+ # One send door (founder decisions d1, d5, d6 of 2026-11-02, session d00cd2ac):
25
+ # the conversation with a person is their CHANNEL. A drafted message is a
26
+ # proposed message in it, filed through the pod's `messaging.send` ({to:
27
+ # <person or company entity id>, text}): a `messaging.external.send` proposal
28
+ # the person approves, and only then is it sent. No process creates an
29
+ # `outreach-touch` any more (d5 retires those drafts; the kind stays until the
30
+ # existing ones are moved into their channels), and none calls a tool's own
31
+ # send verb. The LinkedIn ingest automation is gone: the pod's messaging
32
+ # webhook and its history import record LinkedIn messages in the channels.
6
33
 
7
34
  meta:
8
35
  slug: crm
@@ -17,6 +44,37 @@ meta:
17
44
  - pipeline
18
45
  - contacts
19
46
  isPublic: true
47
+ # Onboarding start (meta.start): the "sales" card. Its views are the deal
48
+ # views below (deal-pipeline, deals-table, deals-list).
49
+ start:
50
+ order: 3
51
+ label: Sales
52
+ promise: Leads and deals, each with a dated next step.
53
+ suggestFor: [small-business, founder]
54
+ profile: deal
55
+ views: [deal-pipeline, deals-table, deals-list]
56
+ example:
57
+ - { title: "Acme renewal", values: { commercialStage: negotiating, value: 12000, nextStep: "Send revised quote Thu" } }
58
+ - { title: "Nordlys brand audit", values: { commercialStage: proposed, value: 6000, nextStep: "Follow up on proposal Mon" } }
59
+ - { title: "Atelier 9 workshop", values: { commercialStage: draft, value: 2800, nextStep: "Discovery call Wed" } }
60
+ - { title: "Maison Dupont redesign", values: { commercialStage: won, value: 18000, nextStep: "Kick-off next week" } }
61
+ - { title: "Lumen Studio retainer", values: { commercialStage: lost, value: 4500 } }
62
+ firstSession:
63
+ title: Your live deals
64
+ goal: "Add the 3 to 5 deals you are moving now, each with its company, a real person and a dated next step."
65
+ starters:
66
+ - "We sell services to mid-size agencies"
67
+ - "I am a freelancer with a few warm leads"
68
+ - "We sell a SaaS subscription to small businesses"
69
+ # What this space PRODUCES (homes): the purpose word, then its kinds/roles.
70
+ intent:
71
+ label: Sales
72
+ produces:
73
+ - profile: lead
74
+ - profile: client
75
+ - profile: deal
76
+ - profile: campaign
77
+ - profile: outreach-touch
20
78
 
21
79
  # CRM is a website, so Browser opens it in its normal first-party web session.
22
80
  # It deliberately does not use the SDK-hosted iframe/auth-broker path.
@@ -27,7 +85,8 @@ layout:
27
85
  title: CRM
28
86
 
29
87
  # SSOT ordering guarantee — this template REFERENCES foundation's pod-wide
30
- # shared `client`/`partner`/`lead`/`churned` roles below. The apply engine seeds
88
+ # shared `client`/`partner`/`lead` roles below (reference form: no
89
+ # body restated). The apply engine seeds
31
90
  # a shared profile's pod-wide BASE property body from whichever template
32
91
  # creates it FIRST, and adds a REUSING template's extra props as workspace
33
92
  # OVERLAYS. Requiring foundation makes the resolver install it first
@@ -38,7 +97,7 @@ dependencies:
38
97
  - slug: foundation
39
98
  kind: workspace
40
99
  relation: require
41
- reason: "Provides the pod-wide client/partner/lead/churned role vocabulary this workspace reuses."
100
+ reason: "Provides the pod-wide client/partner/lead role vocabulary this workspace reuses."
42
101
 
43
102
  workspace:
44
103
  name: CRM
@@ -53,9 +112,12 @@ workspace:
53
112
  - crm.pipeline
54
113
  - crm.contacts
55
114
  - crm.deals
115
+ - outreach.campaigns
116
+ - outreach.touches
56
117
  sourceRoles:
57
118
  social: consumer
58
119
  crm: provider
120
+ outreach: provider
59
121
 
60
122
  profiles:
61
123
  - slug: person
@@ -90,8 +152,11 @@ profiles:
90
152
  label: LinkedIn URL
91
153
  valueType: string
92
154
  inputType: url
93
- - slug: source
94
- label: Source
155
+ # Lead attribution — WHERE the lead came from commercially. Never
156
+ # `source`: that pod-wide field held transport and lead values in one
157
+ # enum (decision 2026-09-14); HOW a record arrived is `provenance`.
158
+ - slug: leadSource
159
+ label: Lead source
95
160
  valueType: string
96
161
  inputType: select
97
162
  enumValues:
@@ -167,8 +232,8 @@ profiles:
167
232
  label: Location
168
233
  valueType: string
169
234
  inputType: text
170
- - slug: source
171
- label: Source
235
+ - slug: leadSource
236
+ label: Lead source
172
237
  valueType: string
173
238
  inputType: select
174
239
  enumValues:
@@ -207,22 +272,27 @@ profiles:
207
272
  color: violet
208
273
  description: A pre-sale opportunity tracked until won, lost, or handed off.
209
274
  scope: WORKSPACE
275
+ # The ONE lifecycle (work model L1): every option's category. A deal is
276
+ # opened only when a person records a booked meeting (CRM build plan A2/A4,
277
+ # 2026-10-22), so its first stage reads "Meeting booked"; `proposed` reads
278
+ # "Proposal sent". A stage still open after 14 days is stuck (the stalled
279
+ # mark, and CRM Hygiene's threshold below).
280
+ lifecycle:
281
+ property: commercialStage
282
+ options:
283
+ - { key: draft, label: Meeting booked, category: open, stuckAfterDays: 14 }
284
+ - { key: proposed, label: Proposal sent, category: active, stuckAfterDays: 14 }
285
+ - { key: negotiating, category: active, stuckAfterDays: 14 }
286
+ - { key: won, category: done }
287
+ - { key: lost, category: lost, humanOnly: true }
210
288
  properties:
211
- - slug: dealStage
212
- label: Stage
213
- valueType: string
214
- inputType: select
215
- enumValues:
216
- - lead
217
- - contacted
218
- - qualifying
219
- - proposal
220
- - negotiating
221
- - won
222
- - lost
223
- - inactive
289
+ # The deal's ONE lifecycle (work model W15, 2026-10-11): a deal is the
290
+ # sale only. Qualification (who is worth pursuing) is the `lead` role's
291
+ # `leadStage`. The legacy `dealStage` (lead/contacted/qualifying/…) and
292
+ # the system `stage` are retired; live rows fold via conversions
293
+ # `crm.deal-stage.commercial-fold`.
224
294
  - slug: commercialStage
225
- label: Commercial stage
295
+ label: Stage
226
296
  valueType: string
227
297
  inputType: select
228
298
  enumValues:
@@ -255,6 +325,14 @@ profiles:
255
325
  label: Expected close date
256
326
  valueType: date
257
327
  inputType: date
328
+ # The booked meeting that opened the deal (CRM build plan A3). A date on
329
+ # the deal, not a link to a calendar event: the person records it in one
330
+ # click whether or not a calendar is connected, and the pipeline reads it
331
+ # without a join. A synced calendar event can still be linked to the deal.
332
+ - slug: meetingDate
333
+ label: Meeting date
334
+ valueType: date
335
+ inputType: date
258
336
  - slug: nextStep
259
337
  label: Next step
260
338
  valueType: string
@@ -271,208 +349,339 @@ profiles:
271
349
  - pending
272
350
  - ready
273
351
  - handed_off
352
+ - slug: wonAt
353
+ label: Won at
354
+ valueType: date
355
+ inputType: date
356
+ - slug: lostReason
357
+ label: Lost reason
358
+ valueType: string
359
+ inputType: textarea
274
360
 
275
361
  # ═══════════════════════════════════════════════════════════════════════════
276
- # ECOSYSTEM ROLES — shared pod-wide role vocabulary (SSOT is foundation.yaml).
362
+ # ECOSYSTEM ROLES — REFERENCES to foundation's pod-wide shared roles.
277
363
  #
278
- # These `scope: shared`, `profileKind: role` profiles are ONE pod-wide row each,
279
- # defined ONCE in foundation.yaml. The CRM re-declares them so the apply engine
280
- # (create-workspace-from-definition.ts) resolves each slug to the existing
281
- # pod-wide profile and GRANTS this workspace access to it (SHARED profiles are
282
- # grant-gated — a workspace only sees a shared role if it references the slug).
283
- # Without these references the CRM workspace could attach `client`/`lead`/…
284
- # facets that entities.list could never surface, so every row fell through to
285
- # the "Idle" relationship status (crmData.ts getRelationshipStatus).
286
- #
287
- # Bodies match foundation VERBATIM (row-level icon/color/displayName/description
288
- # + every property definition): two templates declaring the same shared slug
289
- # with DIVERGENT bodies fight over one pod-wide row (base drift). The
290
- # template-shared-role-ssot tripwire enforces this — do NOT diverge here.
364
+ # One role per name, pod-wide (concepts.md). The body lives ONCE in
365
+ # foundation.yaml. A reference is `slug + displayName + scope: shared +
366
+ # profileKind: role` (+ only the properties THIS workspace adds, which land as
367
+ # workspace overlays): the apply engine resolves the slug to the one pod-wide
368
+ # row and GRANTS this workspace access (shared profiles are grant-gated — the
369
+ # CRM only sees `client`/`lead`/… facets because it references them). Row
370
+ # fields (icon, colour, description, applicableKinds) are NOT restated: the
371
+ # reused row is never mutated, so restating them only invited drift.
372
+ # `displayName` stays because the tRPC install door requires it, and must
373
+ # equal foundation's (shared-role-ssot rule c). validate.ts rule
374
+ # `shared-role-reference-*` requires foundation in the dependency chain.
291
375
  # ═══════════════════════════════════════════════════════════════════════════
292
-
293
- # ── Client (active commercial relationship) ───────────────────────────────
376
+ # The handoff context a won deal writes ("Win a deal" attaches the role with
377
+ # handoffStatus handed_off) and Operations' "Pick up handed-off clients"
378
+ # reads. Moved verbatim from business-developer (2026-10-12).
294
379
  - slug: client
295
380
  displayName: Client
296
- icon: briefcase
297
- color: emerald
298
- description: An active commercial relationship — created when a deal is won.
299
381
  scope: shared
300
382
  profileKind: role
301
- applicableKinds:
302
- - company
303
- - person
304
383
  properties:
305
- - slug: clientStatus
306
- label: Status
384
+ - slug: becameClientAt
385
+ label: Became client at
386
+ valueType: date
387
+ inputType: date
388
+ - slug: handoffStatus
389
+ label: Handoff status
307
390
  valueType: string
308
391
  inputType: select
309
- enumValues:
310
- - prospect
311
- - active
312
- - paused
313
- - former
314
- - churned
315
- - slug: accountManager
316
- label: Account Manager
392
+ enumValues: [pending, ready, handed_off]
393
+ constraints: { defaultValue: pending }
394
+ - slug: handoffDestination
395
+ label: Handoff destination
317
396
  valueType: string
318
397
  inputType: text
319
- - slug: engagementStartDate
320
- label: Engagement Start
321
- valueType: date
322
- inputType: date
323
- - slug: satisfactionScore
324
- label: Satisfaction
325
- valueType: number
326
- inputType: number
327
- - slug: lifetimeValue
328
- label: Lifetime Value
329
- valueType: number
330
- inputType: number
331
- - slug: notes
332
- label: Notes
398
+ - slug: handoffNotes
399
+ label: Handoff notes
333
400
  valueType: string
334
401
  inputType: textarea
335
- - slug: contractValue
336
- label: Contract Value
402
+ - slug: partner
403
+ displayName: Partner
404
+ scope: shared
405
+ profileKind: role
406
+ properties: []
407
+ # The people you talk to at a company: the pod's SYSTEM `contact` role (no
408
+ # scope claim, like `person`). Declaring it grants it to this space, so the
409
+ # space and its apps see who wears it (founder decision 2, 2026-10-26;
410
+ # synap-backend resolveProfileForApply, PR #237).
411
+ - slug: contact
412
+ displayName: Contact
413
+ profileKind: role
414
+ # Qualification fields "Qualify a lead" fills. Extras only: foundation's own
415
+ # `leadStage` (the lead's ONE lifecycle) and `notes` are not restated.
416
+ # Moved verbatim from business-developer (2026-10-12).
417
+ - slug: lead
418
+ displayName: Lead
419
+ scope: shared
420
+ profileKind: role
421
+ properties:
422
+ - slug: assignedUserId
423
+ label: Assigned user ID
424
+ valueType: string
425
+ inputType: text
426
+ placeholder: Workspace member ID
427
+ - slug: leadSource
428
+ label: Lead source
429
+ valueType: string
430
+ inputType: select
431
+ # warm-network: someone the owner knows; research: found by "Build a
432
+ # prospect list from our foundation"; import: a contacts file;
433
+ # inbound-form: the website's form (CRM build plan A1).
434
+ enumValues:
435
+ [
436
+ cold-outreach,
437
+ linkedin,
438
+ twitter,
439
+ landing-page,
440
+ inbound-call,
441
+ inbound-form,
442
+ referral,
443
+ warm-network,
444
+ event,
445
+ research,
446
+ import,
447
+ other,
448
+ ]
449
+ # Always 1–5, signal first (founder decision 2026-10-27): set by "Score a
450
+ # list", whose goal is the ONE home of the rubric. No min/max constraint:
451
+ # pods may still hold scores on the older scale until they are rescored.
452
+ - slug: icpScore
453
+ label: ICP score (1–5)
337
454
  valueType: number
338
455
  inputType: number
339
- - slug: renewalDate
340
- label: Renewal Date
341
- valueType: date
342
- inputType: date
343
- - slug: healthScore
344
- label: Health
456
+ placeholder: 1 to 5
457
+ # The public sign that this lead needs the offer now (founder decision
458
+ # 2026-10-27; the rubric's "signal" part, "Score a list"): hiring = a job
459
+ # post or mission offer for automation, AI agents or no-code; asking = a
460
+ # public post or freelance request asking for that help; tooling = uses
461
+ # those tools publicly, no ask; none = nothing found.
462
+ - slug: buyingSignal
463
+ label: Buying signal
345
464
  valueType: string
346
465
  inputType: select
347
- enumValues:
348
- - healthy
349
- - at_risk
350
- - critical
351
- - slug: driveLink
352
- label: Drive folder
466
+ enumValues: [hiring, asking, tooling, none]
467
+ # The public URL proving the buying signal. Public sources only.
468
+ - slug: buyingSignalSource
469
+ label: Buying signal source
353
470
  valueType: string
354
471
  inputType: url
355
- - slug: driveFolderId
356
- label: Drive folder ID
357
- valueType: string
472
+ # When the ICP score was last set; "Score a list" rescores a lead only
473
+ # when this is older than its rescoreAfterDays.
474
+ - slug: scoredAt
475
+ label: Scored at
476
+ valueType: date
477
+ inputType: date
478
+ - slug: outreachChannels
479
+ label: Outreach channels
480
+ valueType: array
358
481
  inputType: text
482
+ placeholder: linkedin, email, phone
483
+ - slug: lastContactedAt
484
+ label: Last contacted at
485
+ valueType: date
486
+ inputType: date
487
+ - slug: nextActionAt
488
+ label: Next action at
489
+ valueType: date
490
+ inputType: date
491
+ - slug: nextAction
492
+ label: Next action
493
+ valueType: string
494
+ inputType: textarea
495
+ - slug: qualificationNotes
496
+ label: Qualification notes
497
+ valueType: string
498
+ inputType: textarea
499
+ - slug: disqualificationReason
500
+ label: Disqualification reason
501
+ valueType: string
502
+ inputType: textarea
503
+ - slug: convertedAt
504
+ label: Converted at
505
+ valueType: date
506
+ inputType: date
507
+ # Written by "Prepare a pre-sale estimate"'s SENT step (CRM build plan A7).
508
+ - slug: estimateSentAt
509
+ label: Estimate sent at
510
+ valueType: date
511
+ inputType: date
512
+ - slug: estimateSentChannel
513
+ label: Estimate sent on
514
+ valueType: string
515
+ inputType: select
516
+ enumValues: [email, linkedin, phone, in-person, other]
359
517
 
360
- # ── Partner (referral source + co-delivery collaborator) ──────────────────
361
- - slug: partner
362
- displayName: Partner
363
- icon: handshake
364
- color: teal
365
- description: A partner relationship — both a referral source and a co-delivery collaborator, distinguished by partner type.
518
+ # ── Audience (shared kind — SSOT is foundation.yaml) ─────────────────────
519
+ # A lead's SEGMENT is the audience it was found for: a `targets` edge from
520
+ # the person or company to the audience (CRM build plan A8, below). Declared
521
+ # here as a reference so the CRM space is granted the pod-wide kind (shared
522
+ # profiles are grant-gated) and the edge and "Build a prospect list from our
523
+ # foundation" (whose subject is an audience) resolve. No body restated.
524
+ - slug: audience
525
+ displayName: Audience
366
526
  scope: shared
367
- profileKind: role
368
- applicableKinds:
369
- - company
370
- - person
527
+ entityScope: pod
528
+ properties: []
529
+
530
+ # ── Campaign (reused kind — SSOT is marketing.yaml) ───────────────────────
531
+ # The SAME concept as marketing's campaign (`semanticSlug: marketing.campaign`),
532
+ # re-declared so the CRM can attach people and companies to the campaign that
533
+ # sourced them (`member_of`, below) and the CRM app's campaign pages have a
534
+ # kind to read. Body kept verbatim across marketing / content-os / here.
535
+ # Moved from business-developer (2026-10-12).
536
+ - slug: campaign
537
+ displayName: Campaign
538
+ icon: megaphone
539
+ color: pink
540
+ description: "A marketing campaign — email, social, event, content, or partnership"
541
+ semanticSlug: marketing.campaign
542
+ # The ONE lifecycle (work model L1): every option's category.
543
+ lifecycle:
544
+ property: campaign-status
545
+ options:
546
+ - { key: planning, category: open }
547
+ - { key: active, category: active }
548
+ - { key: paused, category: paused, humanOnly: true }
549
+ - { key: completed, category: done }
550
+ - { key: cancelled, category: lost, humanOnly: true }
371
551
  properties:
372
- - slug: partnerStatus
552
+ - slug: campaign-status
373
553
  label: Status
374
554
  valueType: string
375
555
  inputType: select
376
- enumValues:
377
- - prospect
378
- - active
379
- - paused
380
- - churned
381
- - slug: partnerType
382
- label: Partner Type
556
+ enumValues: [planning, active, paused, completed, cancelled]
557
+ constraints: { defaultValue: planning }
558
+ - slug: campaign-channel
559
+ label: Channel
383
560
  valueType: string
384
561
  inputType: select
385
562
  enumValues:
386
- - referral
387
- - co-delivery
388
- - both
389
- - slug: accountManager
390
- label: Account Manager
563
+ [email, social, event, content, partnership, paid, organic, other]
564
+ - slug: campaign-goal
565
+ label: Goal
391
566
  valueType: string
392
- inputType: text
393
- - slug: engagementStartDate
394
- label: Engagement Start
567
+ inputType: textarea
568
+ - slug: campaign-target-audience
569
+ label: Target Audience
570
+ valueType: string
571
+ inputType: textarea
572
+ - slug: campaign-start-date
573
+ label: Start Date
574
+ valueType: date
575
+ inputType: date
576
+ - slug: campaign-end-date
577
+ label: End Date
395
578
  valueType: date
396
579
  inputType: date
397
- - slug: satisfactionScore
398
- label: Satisfaction
580
+ - slug: campaign-budget
581
+ label: Budget
399
582
  valueType: number
400
583
  inputType: number
401
- - slug: lifetimeValue
402
- label: Lifetime Value
584
+ - slug: campaign-lead-count
585
+ label: Lead Count
403
586
  valueType: number
404
587
  inputType: number
405
- - slug: notes
588
+ - slug: campaign-conversion-rate
589
+ label: Conversion Rate (%)
590
+ valueType: number
591
+ inputType: number
592
+ - slug: campaign-notes
406
593
  label: Notes
407
594
  valueType: string
408
595
  inputType: textarea
409
- - slug: contractValue
410
- label: Contract Value
411
- valueType: number
412
- inputType: number
413
- - slug: renewalDate
414
- label: Renewal Date
415
- valueType: date
416
- inputType: date
417
- - slug: healthScore
418
- label: Health
596
+ # `campaign-type` is the campaign's NATURE/phase, orthogonal to
597
+ # `campaign-channel` (the delivery medium). scoping/outreach/live have no
598
+ # channel equivalent, so folding them into `campaign-channel` would lose
599
+ # them — they get their own def. Kept verbatim across the three campaign
600
+ # declarations (marketing / content-os / here) so there is no drift.
601
+ - slug: campaign-type
602
+ label: Type
419
603
  valueType: string
420
604
  inputType: select
421
605
  enumValues:
422
- - healthy
423
- - at_risk
424
- - critical
425
-
426
- # ── Lead (early-pipeline prospect, pre-client) ─────────────────────────────
427
- - slug: lead
428
- displayName: Lead
429
- icon: user-plus
430
- color: yellow
431
- description: An early-pipeline prospect — not yet a client, being qualified or nurtured.
432
- scope: shared
433
- profileKind: role
434
- applicableKinds:
435
- - company
436
- - person
437
- properties:
438
- - slug: leadStage
439
- label: Stage
606
+ [scoping, outreach, live, partnership, event, other]
607
+ constraints: { defaultValue: outreach }
608
+ # `campaign-color` backs the per-campaign colour the CRM surfaces render.
609
+ # Stored as a palette NAME (see apps/crm/lib/campaignColors.ts), not a hex.
610
+ - slug: campaign-color
611
+ label: Color
440
612
  valueType: string
441
613
  inputType: select
442
- enumValues:
443
- - prospect
444
- - qualified
445
- - nurturing
446
- - converted
447
- - lost
448
- constraints: { defaultValue: prospect }
449
- - slug: notes
450
- label: Notes
614
+ enumValues: [indigo, violet, amber, cyan, pink, teal]
615
+ # ── CRM-only extras (workspace overlay; NOT part of the verbatim body) ──
616
+ # An outreach campaign is the ANGLE a lead is approached with (CRM build
617
+ # plan A8): a `campaign` with campaign-type outreach. Folded from Outreach
618
+ # & Comms' `outreach-campaign` (2026-10-23), whose other fields already had
619
+ # a home here: targetSegment = campaign-target-audience (plus the lead's
620
+ # `targets` edge to an audience), startDate = campaign-start-date, notes =
621
+ # campaign-notes, status draft/active/paused/completed = campaign-status
622
+ # planning/active/paused/completed. These two had none; the drafts of
623
+ # "Personalized Outreach" and "Follow up on leads in outreach" follow them.
624
+ - slug: campaign-value-prop
625
+ label: Value proposition
451
626
  valueType: string
452
627
  inputType: textarea
628
+ - slug: campaign-call-to-action
629
+ label: Call to action
630
+ valueType: string
631
+ inputType: text
632
+ placeholder: e.g. Book a 20-minute call, or ask for the free estimate
453
633
 
454
- # ── Churned (formerly-active relationship that ended) ──────────────────────
455
- - slug: churned
456
- displayName: Churned
457
- icon: user-x
458
- color: gray
459
- description: A relationship (client, partner, or otherwise) that was active and has since ended.
460
- scope: shared
461
- profileKind: role
462
- applicableKinds:
463
- - company
464
- - person
634
+ # ── Outreach touch (owned here — folded from Outreach & Comms, 2026-10-23) ─
635
+ # RETIRED AS A DRAFT (founder decision d5, 2026-11-02): no process creates a
636
+ # touch any more; a new message is a proposed message in the person's
637
+ # channel (messaging.send). The kind stays only while the existing touches
638
+ # are moved into their channels; it is removed after that move.
639
+ # One message drafted for (and then sent to) one person, as part of an
640
+ # outreach campaign. Same slug and lifecycle keys as in Outreach & Comms.
641
+ # The agent drafts, a person sends (founder decision 2026-10-20): `sent` is
642
+ # humanOnly, and no CRM process carries a send grant. Who it goes to is the
643
+ # `sent_to` edge; whose message it is, the lead, is `relates_to` (required,
644
+ # entityLinks below); its campaign (the angle) is `part_of`.
645
+ - slug: outreach-touch
646
+ displayName: Outreach Touch
647
+ icon: message-circle
648
+ color: blue
649
+ description: A single message drafted for one person as part of an outreach campaign; a person sends it.
650
+ # The ONE lifecycle (work model L1): every option's category.
651
+ lifecycle:
652
+ property: status
653
+ options:
654
+ - { key: drafted, category: open }
655
+ - { key: sent, category: active, humanOnly: true }
656
+ - { key: opened, category: active }
657
+ - { key: replied, category: done }
658
+ - { key: bounced, category: lost }
659
+ - { key: no_response, category: lost }
465
660
  properties:
466
- - slug: churnedDate
467
- label: Churned Date
661
+ - slug: channel
662
+ label: Channel
663
+ valueType: string
664
+ inputType: select
665
+ enumValues: [linkedin, email]
666
+ # first_message: the first personal message to a lead, drafted by "Draft
667
+ # a first message" (2026-10-27).
668
+ - slug: touchType
669
+ label: Touch type
670
+ valueType: string
671
+ inputType: select
672
+ enumValues: [first_message, connection_request, follow_up, intro_email, reply]
673
+ - slug: status
674
+ label: Status
675
+ valueType: string
676
+ inputType: select
677
+ enumValues: [drafted, sent, opened, replied, bounced, no_response]
678
+ constraints: { defaultValue: drafted }
679
+ - slug: sentDate
680
+ label: Sent date
468
681
  valueType: date
469
682
  inputType: date
470
- - slug: churnReason
471
- label: Reason
472
- valueType: string
473
- inputType: textarea
474
- - slug: notes
475
- label: Notes
683
+ - slug: messageContent
684
+ label: Message content
476
685
  valueType: string
477
686
  inputType: textarea
478
687
 
@@ -514,6 +723,103 @@ entityLinks:
514
723
  targetProfileSlug: deal
515
724
  type: partner_on_deal
516
725
  label: Partner side
726
+ # Campaign membership — the sourcing edge between a counterparty and the
727
+ # campaign it came from (moved from business-developer, 2026-10-12).
728
+ - sourceProfileSlug: person
729
+ targetProfileSlug: campaign
730
+ type: member_of
731
+ label: Member of
732
+ - sourceProfileSlug: company
733
+ targetProfileSlug: campaign
734
+ type: member_of
735
+ label: Member of
736
+ # Outreach touches (folded from Outreach & Comms, 2026-10-23): who a message
737
+ # goes to, and the outreach campaign (the angle) it is part of. The people a
738
+ # campaign reaches are its members (`member_of` above); Outreach & Comms'
739
+ # `targets` edge from a campaign to people and companies was a twin of it, and
740
+ # `targets` here means a lead's segment (below).
741
+ - sourceProfileSlug: outreach-touch
742
+ targetProfileSlug: person
743
+ type: sent_to
744
+ label: Sent to
745
+ - sourceProfileSlug: outreach-touch
746
+ targetProfileSlug: campaign
747
+ type: part_of
748
+ label: Part of
749
+ # Whose message it is (founder D3.1, research R3 2469006b, plan 38fb38a7 §3):
750
+ # a message is about ONE lead, the person or company it `relates_to`. It is
751
+ # the one rule the CADENCE counts by and the CRM app reads ("whose message"),
752
+ # so it is REQUIRED: the pod's create doors warn when a new touch has none
753
+ # (D3.2: warn, never refuse yet), and template health lists the touches
754
+ # missing it. The two rows are ONE rule, met by an edge to either kind.
755
+ - sourceProfileSlug: outreach-touch
756
+ targetProfileSlug: person
757
+ type: relates_to
758
+ label: Relates to
759
+ required: true
760
+ - sourceProfileSlug: outreach-touch
761
+ targetProfileSlug: company
762
+ type: relates_to
763
+ label: Relates to
764
+ required: true
765
+ # Segment (CRM build plan A8): the audience a lead was found for. The same
766
+ # `targets` relation foundation uses for positioning → audience. The ANGLE a
767
+ # lead is approached with is its outreach campaign (a `campaign` with
768
+ # campaign-type outreach), which the follow-up drafts are `part_of`.
769
+ - sourceProfileSlug: person
770
+ targetProfileSlug: audience
771
+ type: targets
772
+ label: Targets
773
+ - sourceProfileSlug: company
774
+ targetProfileSlug: audience
775
+ type: targets
776
+ label: Targets
777
+
778
+ # Views folded from Outreach & Comms (2026-10-23). The CRM's own surface is the
779
+ # website (layout.primarySurface); these are the workbench lenses on the same
780
+ # rows. Same slugs as there.
781
+ views:
782
+ # The deal views: the sale, drawn three ways over the `deal` kind.
783
+ - name: Pipeline
784
+ slug: deal-pipeline
785
+ # The ONE board (work model L3, D3): columns ARE the deal's declared
786
+ # lifecycle (`commercialStage`), moves go through the one mover.
787
+ type: pipeline
788
+ scopeProfileSlug: deal
789
+ description: Every deal on a board by stage, from meeting booked to won or lost.
790
+ config:
791
+ profileSlug: deal
792
+ - name: Deals
793
+ slug: deals-table
794
+ type: table
795
+ scopeProfileSlug: deal
796
+ description: Every deal with its stage, value, expected close date and next step.
797
+ config:
798
+ columns: [title, commercialStage, value, closeDate, nextStep]
799
+ - name: Deals list
800
+ slug: deals-list
801
+ type: list
802
+ scopeProfileSlug: deal
803
+ description: Deals in the order they are expected to close.
804
+ config:
805
+ sortColumn: closeDate
806
+ sortDirection: asc
807
+ - name: Campaigns
808
+ slug: outreach-campaigns
809
+ type: table
810
+ scopeProfileSlug: campaign
811
+ description: Every campaign people come from — its type, its status, who it is for and its offer.
812
+ config:
813
+ columns: [title, campaign-type, campaign-status, campaign-target-audience, campaign-start-date]
814
+ - name: Messages
815
+ slug: outreach-touches
816
+ type: kanban
817
+ scopeProfileSlug: outreach-touch
818
+ groupBy: status
819
+ description: Every outreach message, grouped by where it stands — drafted for a person to send, through replied.
820
+ config:
821
+ groupBy: status
822
+ cardFields: [channel, touchType, sentDate]
517
823
 
518
824
  onboarding:
519
825
  goal: >
@@ -529,10 +835,10 @@ onboarding:
529
835
  expertise:
530
836
  starters:
531
837
  - >
532
- A useful first pipeline is deliberately small. Start with the few stages
533
- that change how the team acts — contacted, qualifying, proposal,
534
- negotiating, won, and lost — and keep the template's default stages
535
- unless the user's real process needs a different view.
838
+ A useful first pipeline is deliberately small. A deal's stages are the
839
+ sale only — draft, proposed, negotiating, won, lost; who is worth
840
+ pursuing at all is the lead's qualification, not a deal stage. Keep the
841
+ template's stages unless the user's real process needs a different view.
536
842
  - >
537
843
  Populate the workspace with live work, not a perfect historical archive:
538
844
  propose the three to five opportunities the user is actively trying to
@@ -561,17 +867,20 @@ onboarding:
561
867
  and no invented commercial data.
562
868
  collect:
563
869
  - profileSlug: company
870
+ min: 1
564
871
  what: The real prospect organisations behind current conversations.
565
872
  cardinality: few
566
873
  keyFields: [website, industry, companySize, source, nextStep]
567
874
  - profileSlug: person
875
+ min: 1
568
876
  what: The known stakeholders for those organisations and opportunities.
569
877
  cardinality: few
570
878
  keyFields: [email, jobTitle, company, source, nextFollowUpDate]
571
879
  - profileSlug: deal
880
+ min: 1
572
881
  what: The live opportunities worth actively moving through the pipeline.
573
882
  cardinality: few
574
- keyFields: [dealStage, value, closeDate, nextStep]
883
+ keyFields: [commercialStage, value, closeDate, nextStep]
575
884
  openingQuestions:
576
885
  - "Which three to five opportunities are you actively trying to move right now?"
577
886
  - "For the most important one, which company and real stakeholder are involved?"
@@ -605,6 +914,103 @@ playbooks:
605
914
  - entity.create
606
915
  status: active
607
916
 
917
+ # ── Qualify a lead — a ROLE-subject process ─────────────────────────────
918
+ # Runs on the `lead` ROLE (foundation's pod-wide role, profileKind: role),
919
+ # not on a kind: its status is the role's leadStage, written on the FACET of
920
+ # the person or company carrying it (the lifecycle door `moveStatus`); a
921
+ # person starts it (manual since 2026-10-27, see activators). Moved from
922
+ # business-developer's "Qualify a CRM lead" (founder decision 2026-10-12).
923
+ # Deciding is ONE `choose` (pursue / nurture / drop) with no gate asking
924
+ # again. Pursue sets the lead qualified with the next action "book a
925
+ # meeting"; it never opens a deal (CRM build plan A4, 2026-10-22): a deal
926
+ # exists only once a person records a booked meeting (founder decision
927
+ # 2026-10-20), and that same move converts the lead. Nurture and drop are the
928
+ # person's answer, recorded as nurturing / disqualified.
929
+ # Next action (CRM review 2026-10-27): every outcome that leaves the lead
930
+ # open (score, pursue, nurture) writes the facet's `nextAction` (one
931
+ # imperative line) and `nextActionAt` (a date) by name: the CRM app's lead
932
+ # page shows the next action, and a process that leaves a lead open without
933
+ # one leaves the person with nothing to do. Drop stays the person's move:
934
+ # `disqualified` is humanOnly on the lifecycle, so the agent's move after
935
+ # the `choose` is a proposal the person approves, never applied by it.
936
+ - name: Qualify a lead
937
+ description: Research one lead, decide with a person whether it is worth pursuing, and set a pursued lead qualified with "book a meeting" as its next action.
938
+ goalTemplate: >-
939
+ Qualify this lead ({lead}). RESEARCH: verify and enrich only facts you
940
+ can support — company, role, size, recent signal — and record the
941
+ source of each. SCORE: assess fit against the offers and the ideal
942
+ client, set the ICP score, qualification notes and outreach channels,
943
+ and write the step you recommend as the lead's next action: `nextAction`
944
+ one imperative line (e.g. "Send the first message on LinkedIn") and
945
+ `nextActionAt` its date. DECIDE: ask the person to `choose` pursue,
946
+ nurture or drop, your recommendation first; that answer is the
947
+ decision. NURTURE: set the lead to nurturing, with `nextAction` to check
948
+ in again and `nextActionAt` the date they gave (three months out when
949
+ they gave none). DROP: write their reason in `disqualificationReason`
950
+ and move the lead to disqualified; that move is the person's
951
+ (disqualified is human-only), so it comes back proposed for them to
952
+ approve, which is success; never write it another way. PURSUE: only
953
+ when they chose pursue, set the lead to qualified with `nextAction`
954
+ "Book a meeting" (one line naming the channel, e.g. "Ask for a
955
+ meeting on LinkedIn") and `nextActionAt` its date. Never create a deal:
956
+ a deal is opened when a person records a booked meeting, and that same
957
+ step converts the lead. Never mark the lead a client.
958
+ subjectProfile:
959
+ profileSlug: lead
960
+ statusProperty: leadStage
961
+ humanOnlyStatuses: [new, outreach, engaged, nurturing, converted, disqualified]
962
+ # Manual only (founder decision 2026-10-27): the `created` worker started
963
+ # one run per lead and burned the budget; a list is scored in ONE run by
964
+ # "Score a list" instead, and a person qualifies the leads worth it.
965
+ activators:
966
+ - { on: manual, mode: propose }
967
+ params:
968
+ - { name: lead, type: entity, label: Lead, required: true }
969
+ executor: is-agent
970
+ status: active
971
+ requiredIntents: [enrich_entity, search_external]
972
+ stages:
973
+ - key: research
974
+ name: Research
975
+ category: planned
976
+ subjectStatus: researching
977
+ goal: Verify and enrich the lead's facts from the pod and public sources, citing each source.
978
+ criteria:
979
+ - key: lead-facts-sourced
980
+ statement: Every fact added to the lead names where it came from.
981
+ check: { kind: judge }
982
+ - key: score
983
+ name: Score
984
+ category: started
985
+ goal: Set the ICP score, qualification notes and outreach channels, and the recommended step as the lead's nextAction and nextActionAt.
986
+ criteria:
987
+ - key: lead-score-set
988
+ statement: The lead has an ICP score and a written reason for it.
989
+ check: { kind: judge }
990
+ - key: lead-next-action
991
+ statement: The lead's nextAction is one imperative line and its nextActionAt is set.
992
+ check: { kind: judge }
993
+ - key: decide
994
+ name: Decide
995
+ category: started
996
+ goal: Ask the person to choose pursue, nurture or drop, your recommendation first, and record the answer on the lead; a nurtured lead gets a nextAction to check in and its nextActionAt, a dropped one its disqualificationReason and a proposed move to disqualified.
997
+ criteria:
998
+ - key: lead-decision-chosen
999
+ statement: The person chose pursue, nurture or drop, and the lead records the choice.
1000
+ check: { kind: human }
1001
+ - key: lead-open-has-next-action
1002
+ statement: A lead the answer leaves open (pursued or nurtured) has a nextAction and a nextActionAt for that answer.
1003
+ check: { kind: judge }
1004
+ - key: pursue
1005
+ name: Pursue
1006
+ category: completed
1007
+ subjectStatus: qualified
1008
+ goal: Only after the person chose pursue, set the lead qualified with nextAction "Book a meeting" (one line naming the channel) and its nextActionAt. Open no deal.
1009
+ criteria:
1010
+ - key: lead-book-meeting
1011
+ statement: The lead's nextAction is to book a meeting and its nextActionAt is set.
1012
+ check: { kind: judge }
1013
+
608
1014
  # ── CRM Hygiene — always-on maintenance agent ────────────────────────────
609
1015
  # A scheduled agent that keeps the pipeline healthy: (1) enriches stale
610
1016
  # contact/company records with missing or outdated fields, and (2) flags deals
@@ -633,8 +1039,8 @@ playbooks:
633
1039
  records that are already complete and current.
634
1040
 
635
1041
  Pass 2 — Flag stalled deals. Find `deal` records with no activity for a
636
- while or stuck in the same `dealStage` past a healthy threshold (roughly:
637
- contacted/qualified for 14+ days, proposal/negotiation for 30+ days).
1042
+ while or stuck in the same `commercialStage` past the stage's stuck
1043
+ threshold (14 days in Meeting booked, Proposal sent or negotiating).
638
1044
  For each stalled deal, propose a concise `nextStep` to unblock it and note
639
1045
  why it looks stalled. Do not advance a deal's stage on its behalf — surface
640
1046
  the risk and a recommended action for the human to decide.
@@ -651,9 +1057,878 @@ playbooks:
651
1057
  - apify_run_actor
652
1058
  status: active
653
1059
 
1060
+ - name: Win a deal
1061
+ description: Take a deal from first conversation to won or lost — qualify it, write and send the proposal, negotiate, and hand a won deal to delivery, with a person deciding every commitment.
1062
+ goalTemplate: >-
1063
+ Work this deal ({deal}) through its stages; each stage moves the deal's
1064
+ commercial stage. QUALIFY: from the pod (the company, its people, every
1065
+ past message and meeting) and public sources, establish the four facts a
1066
+ proposal needs — the problem the buyer wants solved and what it costs
1067
+ them, a budget range, who signs, and when they need it — and link the
1068
+ people involved to the deal. Do not guess a fact: when one is missing,
1069
+ ask the person who owns the relationship. WRITE: draft the proposal
1070
+ document (scope, deliverables, timeline, price, terms) from the agency's
1071
+ offers and past won proposals, and set the deal's value and close date.
1072
+ SEND: a person approves the proposal before it goes to the client;
1073
+ nothing is sent without that approval. AWAIT: wait for the client's
1074
+ answer and record it on the deal. NEGOTIATE: its check reads that
1075
+ answer; log every objection and counter-offer and propose the next step.
1076
+ WON: once a person confirms the client said yes, reuse the engagement
1077
+ Operations already has for this company; only when there is none, open
1078
+ one at proposed with its client set to the company. Then attach the
1079
+ client role to the company with handoff status handed_off. The daily
1080
+ "Pick up handed-off clients" automation skips any client that already
1081
+ has an engagement, so no second one is opened. A lost deal is a
1082
+ person's call — record why it was lost.
1083
+ subjectProfile:
1084
+ profileSlug: deal
1085
+ statusProperty: commercialStage
1086
+ humanOnlyStatuses: [lost]
1087
+ activators:
1088
+ - { on: created, mode: propose }
1089
+ params:
1090
+ - { name: deal, type: entity, label: Deal, required: true }
1091
+ executor: is-agent
1092
+ status: active
1093
+ requiredIntents: [enrich_entity, search_external, list_records]
1094
+ expectedOutputs:
1095
+ - { kind: person, label: Decision maker, relationToSubject: linked_to_deal }
1096
+ - { kind: document, label: Proposal }
1097
+ - { kind: engagement, label: Client engagement, relationToSubject: relates_to }
1098
+ metadata:
1099
+ lineage: { method: "BANT qualification, proposal-led B2B sale" }
1100
+ stages:
1101
+ - key: qualify
1102
+ name: Qualify
1103
+ category: planned
1104
+ subjectStatus: draft
1105
+ goal: Establish need, budget, decision maker and timing from evidence, and link the people involved.
1106
+ criteria:
1107
+ - key: qualify-need
1108
+ statement: The buyer's problem and what it costs them are written on the deal.
1109
+ check: { kind: judge }
1110
+ - key: qualify-budget
1111
+ statement: A budget range is recorded, or the deal says the buyer refused to give one.
1112
+ check: { kind: judge }
1113
+ - key: qualify-decision-maker
1114
+ statement: The person who signs is linked to the deal.
1115
+ check: { kind: judge }
1116
+ - key: qualify-timing
1117
+ statement: The date the buyer needs this by is recorded.
1118
+ check: { kind: judge }
1119
+ - key: write
1120
+ name: Write proposal
1121
+ category: started
1122
+ goal: Draft the proposal and set the deal's value and close date.
1123
+ gate: { kind: check }
1124
+ onFail: { toStage: qualify }
1125
+ criteria:
1126
+ - key: proposal-drafted
1127
+ statement: A proposal document with scope, timeline, price and terms is attached to the deal.
1128
+ check: { kind: judge }
1129
+ - key: proposal-value-set
1130
+ statement: The deal's value and expected close date are set.
1131
+ check: { kind: evidence, evidenceKey: deal-value }
1132
+ - key: send
1133
+ name: Send proposal
1134
+ category: started
1135
+ subjectStatus: proposed
1136
+ goal: A person approves the proposal; then send it to the decision maker and record when.
1137
+ gate: { kind: human }
1138
+ onFail: { toStage: write }
1139
+ - key: await-answer
1140
+ name: Awaiting the answer
1141
+ category: started
1142
+ goal: Wait for the client's answer to the proposal and record it on the deal.
1143
+ indefinite: true
1144
+ criteria:
1145
+ - key: proposal-answered
1146
+ statement: The client's answer to the proposal is recorded on the deal.
1147
+ check: { kind: judge }
1148
+ - key: negotiate
1149
+ name: Negotiate
1150
+ category: started
1151
+ subjectStatus: negotiating
1152
+ goal: Answer objections, log every counter-offer on the deal, and propose the next step.
1153
+ gate: { kind: check }
1154
+ onFail: { toStage: await-answer }
1155
+ indefinite: true
1156
+ criteria:
1157
+ - key: negotiate-terms-agreed
1158
+ statement: The final price and scope the client agreed to are written on the deal.
1159
+ check: { kind: judge }
1160
+ - key: won
1161
+ name: Won
1162
+ category: completed
1163
+ subjectStatus: won
1164
+ goal: A person confirms the client said yes; then reuse the company's engagement in Operations (open one only when none exists) and attach the client role to the company.
1165
+ gate: { kind: human }
1166
+ onFail: { toStage: negotiate }
1167
+
1168
+ # ── Build a prospect list from our foundation — a BATCH process ──────────
1169
+ # CRM build plan A5 (founder Q5). One run = one batch of candidate accounts
1170
+ # for ONE audience, its subject. The person decides the batch in ONE review:
1171
+ # the candidates are ONE composite proposal, and keep or drop is a per-item
1172
+ # disposition on it (stored in the proposal's `dispositions`; a partly kept
1173
+ # batch is never counted as a full approval). Approving creates only the kept
1174
+ # candidates. The dropped ones stay readable on that proposal, and the next
1175
+ # batch reads them back so it never suggests them again. Generic: the
1176
+ # audience, the offers and the market come from Foundation.
1177
+ # Pool (2026-10-27, CRM lists): `pool: network` searches nothing outside the
1178
+ # pod; it ranks the person's own `contact` role holders with the "Score a
1179
+ # list" rubric (its one home), and the kept ones become leads with lead
1180
+ # source warm-network, in the same single review.
1181
+ - name: Build a prospect list from our foundation
1182
+ description: Turn one audience and the company's offers into a short, sourced batch of companies and people worth contacting; a person keeps or drops each one in a single review, and only the kept ones become leads.
1183
+ goalTemplate: >-
1184
+ Build a prospect list for the audience {audience}: about {count}
1185
+ accounts in {market}, from the {pool} pool. READ: the audience
1186
+ (segment, persona, jobs, traits) and the company's offers and
1187
+ positioning in Foundation; never invent an offer. Then read this
1188
+ process's earlier batches for this audience: each batch's review
1189
+ records which candidates a person dropped, and none of them may be
1190
+ suggested again. POOL public — SEARCH: public sources only (company
1191
+ sites, company pages, job posts, public posts, directories); never
1192
+ collect contact data a person chose to hide, never use a scraped
1193
+ personal database. For each account record the company name, website,
1194
+ size, why it fits (one line naming the audience trait it matches), a
1195
+ recent trigger if any, the likely decider's role and public profile URL,
1196
+ and the source URL of every fact. DEDUPE: look for the same company
1197
+ (website or domain) or person (email or profile URL) in the pod first;
1198
+ an existing record is reused, never duplicated. POOL network — RANK:
1199
+ search nothing outside the pod. The candidates are the person's own
1200
+ contacts, the people and companies holding the contact role in the pod
1201
+ that are not a lead already. Score each against the audience with the
1202
+ rubric written in the "Score a list" process (read it there; never
1203
+ restate or change it), leave out the ones scoring 1, and keep the best
1204
+ ones. Each is an existing record, never a new one. PROPOSE: submit the
1205
+ whole batch as ONE proposal, best fits first. From the public pool,
1206
+ per account: the company (or the existing one), its decider when known, the lead role at stage
1207
+ new with lead source research and the sources, and a targets link to
1208
+ the audience. From the network pool, per contact: the existing record,
1209
+ the lead role at stage new with lead source warm-network, its
1210
+ icpScore, buyingSignal, buyingSignalSource, scoredAt and
1211
+ qualificationNotes from the rubric, and a targets link to the
1212
+ audience. The person keeps or drops each candidate in that one review,
1213
+ and approving creates only the kept ones. Never create a lead outside
1214
+ that proposal, and never ask the person to pick the accounts a second
1215
+ time.
1216
+ subjectProfile:
1217
+ profileSlug: audience
1218
+ activators:
1219
+ - { on: manual, mode: propose }
1220
+ params:
1221
+ - { name: audience, type: entity, label: Audience to prospect, required: true }
1222
+ - { name: count, type: number, label: How many accounts to propose, default: 25 }
1223
+ - { name: market, type: text, label: Country and language, default: the market Foundation names }
1224
+ # public = search public sources; network = rank the person's own
1225
+ # contacts (the `contact` role holders) instead (2026-10-27).
1226
+ - { name: pool, type: choice, label: Where to look, options: [public, network], defaultValue: public }
1227
+ executor: is-agent
1228
+ status: active
1229
+ requiredIntents: [search_external]
1230
+ stages:
1231
+ - key: read
1232
+ name: Read the foundation
1233
+ category: planned
1234
+ goal: Read the audience and the offers, restate the ideal account in three lines, and list the candidates a person dropped in earlier batches.
1235
+ criteria:
1236
+ - key: prospect-icp-restated
1237
+ statement: The ideal account is restated from the pod's audience and offers, with no invented offer.
1238
+ check: { kind: judge }
1239
+ - key: search
1240
+ name: Search the pool
1241
+ category: started
1242
+ goal: Find candidate accounts in public sources, or rank the person's own contacts with the "Score a list" rubric for the network pool, and record a source for every fact.
1243
+ criteria:
1244
+ - key: prospect-facts-sourced
1245
+ statement: Every fact on every candidate names its source URL.
1246
+ check: { kind: judge }
1247
+ - key: prospect-no-duplicates
1248
+ statement: No candidate duplicates a company or person already in the pod.
1249
+ check: { kind: judge }
1250
+ - key: prospect-not-dropped-before
1251
+ statement: No candidate is one a person dropped in an earlier batch.
1252
+ check: { kind: judge }
1253
+ - key: review
1254
+ name: Person keeps or drops each candidate
1255
+ category: completed
1256
+ goal: Submit the batch as one proposal; the person keeps or drops each candidate in that one review, and only the kept ones are created.
1257
+ expectedOutputs:
1258
+ - { kind: company, label: Prospect accounts, relationToSubject: targets }
1259
+ criteria:
1260
+ - key: prospect-one-proposal
1261
+ statement: The whole batch is one proposal, each candidate an item of it with its sources.
1262
+ check: { kind: judge }
1263
+
1264
+ # ── Score a list — THE one home of the lead rubric ──────────────────────
1265
+ # CRM lists (founder decision 2026-10-27). A list IS an audience; its leads
1266
+ # are the people and companies with the lead role whose `targets` edge
1267
+ # points at it. ONE run scores the whole list, signal first, 1 to 5 (it
1268
+ # replaces one "Qualify a lead" run per lead, which burned the budget). The
1269
+ # rubric is written ONCE, in this goal: "Build a prospect list from our
1270
+ # foundation" (network pool) reads it here, and "Draft a first message"
1271
+ # reads the fields it writes and never rescores. The subject is an audience,
1272
+ # which declares no lifecycle, so no stage moves anything (the shape of
1273
+ # "Build a prospect list from our foundation"); it never moves a lead's
1274
+ # leadStage either.
1275
+ - name: Score a list
1276
+ description: Score every lead of one list (an audience) from public signals, 1 to 5, signal first, and write the score, the signal, its source and why on each lead; one run for the whole list.
1277
+ goalTemplate: >-
1278
+ Score the leads of the list {audience}: the people and companies with
1279
+ the lead role and a targets link to this audience whose scoredAt is
1280
+ empty or older than {rescoreAfterDays} days, at most {limit} of them,
1281
+ the never-scored ones first. READ: the audience (segment, persona,
1282
+ jobs-to-be-done, traits, description), which are the list's criteria,
1283
+ and the company's offers in Foundation. RESEARCH each lead from public
1284
+ sources only (its website, company page, public profile, job posts,
1285
+ public posts, freelance platforms) and from the pod (past messages,
1286
+ conversations, notes); never collect contact data a person chose to
1287
+ hide, never use a scraped personal database. RUBRIC (its one home):
1288
+ score = signal + fit + warmth, at least 1 and at most 5. SIGNAL, 0 to
1289
+ 2: hiring or asking within the last 90 days = 2, where hiring is a job
1290
+ post or mission offer for automation, AI agents or no-code (n8n, Make,
1291
+ Zapier, Hermes, Claude Code, AI ops and the like) and asking is a
1292
+ public post or freelance request asking for that help; tooling = 1, it
1293
+ uses those tools publicly with no ask; none = 0, nothing found. FIT, 0
1294
+ to 2: a founder or director of a company of 2 to 30 people in France,
1295
+ or an independent expert or small agency, that is not a competitor =
1296
+ 2; partial (size unknown, outside France, or near the decider rather
1297
+ than the decider) = 1; excluded (an AI or no-code agency that
1298
+ competes, a regulated profession, more than 50 people, a student) = 0.
1299
+ WARMTH, 0 or 1: an existing two-way exchange with the person, or a
1300
+ first-degree connection with messages, = 1. WRITE on each lead's lead
1301
+ role: icpScore (the score), buyingSignal (hiring, asking, tooling or
1302
+ none), buyingSignalSource (the public URL proving the signal, empty for
1303
+ none), scoredAt (today) and qualificationNotes (the points for signal,
1304
+ fit and warmth with one line each, the doubt you could not resolve, and
1305
+ the source URL of every fact). Leave a fact out rather than guess.
1306
+ Write the scores in one batch. Never move a lead's stage, never create
1307
+ a lead, never draft a message. End with a summary: how many leads were
1308
+ scored, how many per score, and the best five with their signal.
1309
+ subjectProfile:
1310
+ profileSlug: audience
1311
+ activators:
1312
+ - { on: manual, mode: propose }
1313
+ params:
1314
+ - { name: audience, type: entity, label: List (audience) to score, required: true }
1315
+ - { name: limit, type: number, label: Most leads to score in this run, defaultValue: 50 }
1316
+ - { name: rescoreAfterDays, type: number, label: Rescore leads scored more than this many days ago, defaultValue: 30 }
1317
+ executor: is-agent
1318
+ status: active
1319
+ requiredIntents: [search_external]
1320
+ stages:
1321
+ - key: read
1322
+ name: Read the list
1323
+ category: planned
1324
+ goal: Read the audience's criteria and the offers, and list the leads to score — those targeting the audience never scored or scored more than rescoreAfterDays days ago, at most limit.
1325
+ criteria:
1326
+ - key: score-leads-listed
1327
+ statement: The leads to score are the ones targeting the audience that were never scored or were scored more than rescoreAfterDays days ago, at most limit.
1328
+ check: { kind: judge }
1329
+ - key: score
1330
+ name: Score each lead
1331
+ category: started
1332
+ goal: Research each lead from public sources and the pod, score it with the rubric, and write icpScore, buyingSignal, buyingSignalSource, scoredAt and qualificationNotes on its lead role.
1333
+ criteria:
1334
+ - key: score-fields-set
1335
+ statement: Every scored lead has an icpScore from 1 to 5, a buyingSignal and a scoredAt.
1336
+ check: { kind: judge }
1337
+ - key: score-notes-sourced
1338
+ statement: Every scored lead's qualificationNotes give the signal, fit and warmth points, the doubt, and the source URL of each fact.
1339
+ check: { kind: judge }
1340
+ - key: score-signal-proved
1341
+ statement: Every lead whose buyingSignal is not none has a buyingSignalSource URL proving it.
1342
+ check: { kind: judge }
1343
+ - key: score-public-only
1344
+ statement: Every fact comes from a public source or the pod, and no contact data a person hid was collected.
1345
+ check: { kind: judge }
1346
+ - key: score-reader-only
1347
+ statement: No lead's stage was moved, no lead was created and no message was drafted.
1348
+ check: { kind: judge }
1349
+ - key: summary
1350
+ name: Sum up the list
1351
+ category: completed
1352
+ goal: Write how many leads were scored, how many per score, and the best five with their signal.
1353
+ criteria:
1354
+ - key: score-summary
1355
+ statement: The summary gives the number scored, the count per score and the best five with their signal.
1356
+ check: { kind: judge }
1357
+
1358
+ # ── Draft a first message — one lead's first message ────────────────────
1359
+ # CRM lists (founder decision 2026-10-27). One run drafts ONE personal first
1360
+ # message for one lead: a proposed message in the person's channel, filed
1361
+ # through the pod's send door `messaging.send` (founder decisions d1/d6,
1362
+ # 2026-11-02), for a person to approve. The agent never sends: the pod sends
1363
+ # only what a person approved, and no CRM process carries a send grant. It
1364
+ # reads the score fields "Score a list" wrote and never rescores.
1365
+ # A lifecycle READER of leadStage, like "Follow up on leads in outreach": no
1366
+ # stage moves the lead; once the message is sent, the move to outreach is
1367
+ # PROPOSED through the door, so "Follow up on leads in outreach" drafts the
1368
+ # next one. Next action: the draft becomes the lead's `nextAction` with
1369
+ # `nextActionAt` today, like every CRM draft.
1370
+ # Messages and cadence (founder decisions 2026-10-27, send door 2026-11-02):
1371
+ # every drafter writes a draft the same way (DRAFT RULES: messaging.send to
1372
+ # the person's entity id, never a tool's own send verb, never an outreach
1373
+ # touch, never synap_capture), and every send sets the next follow-up by the same
1374
+ # CADENCE: 3 days after the first message, 7 days after each follow-up, at
1375
+ # most two follow-ups. The DRAFT RULES and CADENCE paragraphs are each word
1376
+ # for word the same in this process, "Follow up on leads in outreach" and
1377
+ # "Personalized Outreach" (crm-messages-cadence.template.test.ts pins them).
1378
+ - name: Draft a first message
1379
+ description: Draft one personal first message for a lead, citing its buying signal or a past exchange, in the person's language, as a proposed message in their channel for a person to approve; the agent never sends.
1380
+ goalTemplate: >-
1381
+ Draft the first message to the lead {lead} on {channel}. READ: the
1382
+ lead's score fields (icpScore, buyingSignal, buyingSignalSource,
1383
+ qualificationNotes) as they are, never rescoring them; its channels,
1384
+ past messages and notes in the pod; and the outreach campaign given
1385
+ ({campaign}), if any: its value proposition and call to action are the
1386
+ angle. WHO: a person lead is written to directly; for a company lead,
1387
+ write to the person who decides there (from the pod, else its public
1388
+ decider), and when no decider is known ask the person who to write to.
1389
+ DRAFT: one first message on channel {channel}, in the campaign's angle
1390
+ when one is given. DRAFT RULES: each draft is one proposed message in
1391
+ the channel of the person it is written to, filed through the pod's
1392
+ send door: run messaging.send with to = that person's entity id (for a
1393
+ company lead, its decider: when that person is not in the pod, create
1394
+ them through the entity door with only their own fields, such as name,
1395
+ role and LinkedIn URL, never the company's website, domain or email),
1396
+ text = the message, and provider linkedin or email. Read their channel
1397
+ first (synap_get_channel, mode person) for what was already said. The
1398
+ pod files it for a person to review in that channel; it goes out only
1399
+ once they approve it. Never call a tool's own send verb (Unipile,
1400
+ Gmail), never create an outreach touch, a note or a task holding the
1401
+ message, and never use synap_capture for it. Never send. Its message
1402
+ is personal: it cites the buying signal and what it showed, or a past
1403
+ exchange, never a generic template; short; in the person's language,
1404
+ French by default, and in French it says vous. It offers one useful
1405
+ next step, never pressure. Draft one message only: a person reviews
1406
+ it in the channel, edits it if needed and approves it. NEXT ACTION:
1407
+ set the lead's nextAction to "Send the first message to <name> on
1408
+ LinkedIn" ("Send the first message to <name> by email" on email),
1409
+ naming the person written to, and its nextActionAt to today. Never
1410
+ move the lead's stage while drafting. SENT: once the message is sent,
1411
+ follow the cadence, so "Follow up on leads in outreach" drafts the
1412
+ next one. CADENCE: when a message is sent (a person approved its
1413
+ proposed message, so it is an outbound message in the channel), set
1414
+ its lead's lastContactedAt to the sent day, count the messages sent to
1415
+ the lead (the outbound messages in its channels, read with
1416
+ synap_get_channel mode person: for a company lead, the channels of the
1417
+ person written to there), and set the lead's next action: after the
1418
+ first message, nextAction "Follow up with <name> if no reply" and
1419
+ nextActionAt the sent day + 3 days; after the first follow-up,
1420
+ nextAction "Follow up with <name> a last time if no reply" and
1421
+ nextActionAt the sent day + 7 days; after the second follow-up,
1422
+ nextAction "No reply after two follow-ups: nurture or drop <name>" and
1423
+ nextActionAt the sent day + 7 days, and when that date passes with no
1424
+ reply, "Follow up on leads in outreach" proposes nurturing. Never a
1425
+ third follow-up. Propose moving a lead at new or researching to
1426
+ outreach through the status door, with its expected status. A reply
1427
+ stops the cadence: a reply is an inbound message after our last
1428
+ outbound one in the channel, and "Follow up on leads in outreach"
1429
+ reads it and proposes the next step.
1430
+ subjectProfile:
1431
+ profileSlug: lead
1432
+ activators:
1433
+ - { on: manual, mode: propose }
1434
+ params:
1435
+ - { name: lead, type: entity, label: Lead, required: true }
1436
+ - { name: campaign, type: entity, label: Outreach campaign (optional) }
1437
+ - { name: channel, type: choice, label: Channel, options: [linkedin, email], defaultValue: linkedin }
1438
+ executor: is-agent
1439
+ status: active
1440
+ grants: [entity.create]
1441
+ stages:
1442
+ - key: draft
1443
+ name: Draft the first message
1444
+ category: started
1445
+ goal: Read the lead's score fields, channels and history, draft one personal first message for the person (a company's decider) as a proposed message in their channel through messaging.send (to = their entity id, never a tool's own send verb, never an outreach touch or synap_capture), and set the lead's nextAction "Send the first message to <name> on LinkedIn" with nextActionAt today.
1446
+ expectedOutputs:
1447
+ # The proposed message: a `messaging.external.send` proposal on the
1448
+ # person's channel (target type `messaging`). The channel is already
1449
+ # bound to the person, so it writes no edge to the lead.
1450
+ - { kind: messaging, label: Proposed first message, relationToSubject: none }
1451
+ criteria:
1452
+ # A judge: no core or catalog verb reads a session's proposals and
1453
+ # answers {passed}, so the pod has no exact check for this yet.
1454
+ - key: first-message-one-draft
1455
+ statement: One proposed message, filed through messaging.send to the person written to (for a company, its decider), waits for review in that person's channel, holds the message text, and follows the campaign's angle when one was given.
1456
+ check: { kind: judge }
1457
+ - key: first-message-no-capture
1458
+ statement: No tool's own send verb was called, no outreach touch, note or task holds the message, no record was made with synap_capture, and a decider created for a company lead carries only the person's own fields, never the company's website, domain or email.
1459
+ check: { kind: judge }
1460
+ - key: first-message-personal
1461
+ statement: The message cites the lead's buying signal or a past exchange, is in the person's language (French by default, with vous), and is not a generic template.
1462
+ check: { kind: judge }
1463
+ - key: first-message-not-rescored
1464
+ statement: The lead's icpScore, buyingSignal and scoredAt are unchanged, and its stage was not moved.
1465
+ check: { kind: judge }
1466
+ - key: first-message-next-action
1467
+ statement: The lead's nextAction is "Send the first message to <name>" on the channel, naming the person written to, and its nextActionAt is today.
1468
+ check: { kind: judge }
1469
+ - key: send
1470
+ name: Person sends
1471
+ category: completed
1472
+ goal: The person reviews the proposed message in the channel, edits it if needed and approves it, and the pod sends it; then set the lead's lastContactedAt to the sent day, nextAction "Follow up with <name> if no reply" and nextActionAt the sent day + 3 days, and propose moving a new or researching lead to outreach.
1473
+ gate: { kind: human }
1474
+ onFail: { toStage: draft }
1475
+
1476
+ # ── Follow up on leads in outreach — THE one follow-up process ───────────
1477
+ # CRM build plan A6 (founder Q4). A batch worker: each weekday it reads the
1478
+ # channels of the leads in outreach. Due with no reply, it drafts one message
1479
+ # each, a proposed message in the person's channel through the pod's send
1480
+ # door `messaging.send`, in the angle of the lead's outreach campaign (A8).
1481
+ # The agent never sends (founder decision 2026-10-20): a person approves each
1482
+ # proposed message and the pod sends it. It replaces the pod-local process of
1483
+ # the same name (founder's pod, 2026-10-21), which wrote its drafts nowhere.
1484
+ # Replies (send door, 2026-11-02): a message sent from a channel is no
1485
+ # outreach touch, so "Read the reply to an outreach message" never watches
1486
+ # it; THIS process is the one reader of a reply in a channel (an inbound
1487
+ # message after our last outbound one): it summarises it and proposes the
1488
+ # next step, and a deal is never opened. "Read the reply" keeps only the
1489
+ # touches marked sent before the channels. A lifecycle READER of leadStage:
1490
+ # no stage moves the lead; nurturing after the second follow-up is proposed
1491
+ # through the door once the last date passes in silence. `statusProperty`
1492
+ # is named because the schedule worker reads `outreach`.
1493
+ # Next action (CRM review 2026-10-27): a drafted message becomes the lead's
1494
+ # `nextAction` ("Send the drafted message to <name>", `nextActionAt` today),
1495
+ # and a message marked sent sets `nextActionAt` by the CADENCE (founder
1496
+ # decision 2026-10-27, the same words as "Draft a first message" and
1497
+ # "Personalized Outreach"): 3 days after the first message, 7 days after
1498
+ # each follow-up, at most two follow-ups, never a third. The CRM app's lead
1499
+ # page shows the next action, and a process that leaves a lead open without
1500
+ # one leaves the person with nothing to do.
1501
+ - name: Follow up on leads in outreach
1502
+ description: Each weekday, read the channels of the leads in outreach; summarise a reply and propose the next step, or, when the follow-up is due with no reply, draft the next message in the angle of its outreach campaign as a proposed message for a person to approve; the agent never sends.
1503
+ goalTemplate: >-
1504
+ Follow up on the leads in outreach. For each, read its channels
1505
+ (synap_get_channel, mode person: for a company lead, the channels of
1506
+ the person who decides there) and its notes. A REPLY is an inbound
1507
+ message after our last outbound one in a channel. With a reply, draft
1508
+ nothing: summarise it for a person; when they show interest, propose
1509
+ the next step (prepare the free estimate, "Prepare a pre-sale
1510
+ estimate", or book a meeting) and propose moving the lead to engaged;
1511
+ when they decline, propose nurturing or disqualified with their reason
1512
+ (in disqualificationReason). Unless you propose dropping them, write
1513
+ the next step on the lead as nextAction, one imperative line, and
1514
+ nextActionAt, the date they named or today. Never open a deal and
1515
+ never answer the reply yourself; a person does. With no reply, and
1516
+ only when the lead's next action date is today or past and no
1517
+ proposed message to it is still waiting, draft the next message, to
1518
+ the lead (for a company, the person who decides there) and in the
1519
+ angle of the lead's outreach campaign: follow its value proposition
1520
+ and call to action, short and in the lead's language, offering the
1521
+ next useful step, never pressure. For a lead with no campaign, ask the
1522
+ person which one. Draft at most two follow-ups per lead: a lead
1523
+ already sent two follow-ups gets no draft, never a third follow-up;
1524
+ when its last date passes in silence, propose moving it to nurturing
1525
+ through the status door, with its expected status. DRAFT RULES: each
1526
+ draft is one proposed message in the channel of the person it is
1527
+ written to, filed through the pod's send door: run messaging.send with
1528
+ to = that person's entity id (for a company lead, its decider: when
1529
+ that person is not in the pod, create them through the entity door
1530
+ with only their own fields, such as name, role and LinkedIn URL, never
1531
+ the company's website, domain or email), text = the message, and
1532
+ provider linkedin or email. Read their channel first
1533
+ (synap_get_channel, mode person) for what was already said. The pod
1534
+ files it for a person to review in that channel; it goes out only once
1535
+ they approve it. Never call a tool's own send verb (Unipile, Gmail),
1536
+ never create an outreach touch, a note or a task holding the message,
1537
+ and never use synap_capture for it. Never send. For each draft, set
1538
+ the lead's `nextAction` to send it (e.g. "Send the drafted message to
1539
+ Ana on LinkedIn") and `nextActionAt` to today. The person reviews each
1540
+ proposed message in its channel, edits it if needed and approves it;
1541
+ the agent never sends. CADENCE: when a message is sent (a person
1542
+ approved its proposed message, so it is an outbound message in the
1543
+ channel), set its lead's lastContactedAt to the sent day, count the
1544
+ messages sent to the lead (the outbound messages in its channels, read
1545
+ with synap_get_channel mode person: for a company lead, the channels of
1546
+ the person written to there), and set the lead's next action: after
1547
+ the first message, nextAction "Follow up with <name> if no reply" and
1548
+ nextActionAt the sent day + 3 days; after the first follow-up,
1549
+ nextAction "Follow up with <name> a last time if no reply" and
1550
+ nextActionAt the sent day + 7 days; after the second follow-up,
1551
+ nextAction "No reply after two follow-ups: nurture or drop <name>" and
1552
+ nextActionAt the sent day + 7 days, and when that date passes with no
1553
+ reply, "Follow up on leads in outreach" proposes nurturing. Never a
1554
+ third follow-up. Propose moving a lead at new or researching to
1555
+ outreach through the status door, with its expected status. A reply
1556
+ stops the cadence: a reply is an inbound message after our last
1557
+ outbound one in the channel, and "Follow up on leads in outreach"
1558
+ reads it and proposes the next step.
1559
+ subjectProfile:
1560
+ profileSlug: lead
1561
+ statusProperty: leadStage
1562
+ activators:
1563
+ # Weekdays 07:00 UTC, one batch over the leads in outreach.
1564
+ - { on: schedule, cron: "0 7 * * 1-5", status: outreach, mode: propose }
1565
+ - { on: manual, mode: propose }
1566
+ executor: is-agent
1567
+ status: active
1568
+ expectedOutputs:
1569
+ # Proposed messages on the people's channels (see "Draft a first message").
1570
+ - { kind: messaging, label: Proposed follow-ups, relationToSubject: none }
1571
+ stages:
1572
+ - key: draft
1573
+ name: Draft due follow-ups
1574
+ category: started
1575
+ goal: Read the channels of the leads in outreach; summarise each reply and propose its next step; for each due lead with no reply, draft one follow-up as a proposed message through messaging.send to the person (never a tool's own send verb, never an outreach touch or synap_capture), setting the lead's nextAction to send it and nextActionAt to today; a lead who replied, or was already sent two follow-ups, gets none.
1576
+ criteria:
1577
+ - key: followup-history-read
1578
+ statement: Each draft reflects what was already sent to that lead and any reply, read from its channels.
1579
+ check: { kind: judge }
1580
+ - key: followup-angle
1581
+ statement: Each draft follows the angle of the lead's outreach campaign and its call to action.
1582
+ check: { kind: judge }
1583
+ - key: followup-no-draft-after-reply
1584
+ statement: No message is drafted for a lead who replied (its reply is summarised and a next step proposed, no deal opened), nor a third follow-up for a lead already sent two.
1585
+ check: { kind: judge }
1586
+ # A judge, not an exact check: see "first-message-one-draft" in
1587
+ # "Draft a first message" for what the pod lacks.
1588
+ - key: followup-draft-rules
1589
+ statement: Each draft is a proposed message filed through messaging.send to the person written to and waits in their channel; no tool's own send verb was called, no outreach touch, note or task holds a message, and no record was made with synap_capture.
1590
+ check: { kind: judge }
1591
+ - key: followup-next-action
1592
+ statement: Every lead with a draft has nextAction to send it and nextActionAt today.
1593
+ check: { kind: judge }
1594
+ - key: send
1595
+ name: Person sends
1596
+ category: completed
1597
+ goal: >-
1598
+ The person reviews each proposed message in its channel, edits it if needed and approves it, and the pod sends it; then set each lead's lastContactedAt to the sent day and, after the first follow-up, nextAction "Follow up with <name> a last time if no reply" and nextActionAt the sent day + 7 days; after the second follow-up, nextAction "No reply after two follow-ups: nurture or drop <name>" and nextActionAt the sent day + 7 days.
1599
+ gate: { kind: human }
1600
+ onFail: { toStage: draft }
1601
+
1602
+ # ── Personalized Outreach — a campaign process (folded from Outreach & Comms) ─
1603
+ # Runs one OUTREACH campaign (a `campaign` with campaign-type outreach: the
1604
+ # angle) through campaign-status planning → active → completed, the same
1605
+ # lifecycle marketing's "Run a campaign" follows (workers are per space, so a
1606
+ # campaign created here starts this process, one created in Marketing starts
1607
+ # that one). It drafts the FIRST message for each person; the next ones are
1608
+ # "Follow up on leads in outreach"'s, which also reads the replies.
1609
+ # Each first message is a proposed message in the person's channel, filed
1610
+ # through the pod's send door `messaging.send` (2026-11-02, decisions d1/d6).
1611
+ # Changed in the fold (2026-10-23): the agent never sends (no send grant; the
1612
+ # person approves each proposed message), people are linked with
1613
+ # `member_of` (Outreach & Comms' `targets` twin is gone), and the campaign's
1614
+ # result is counted in booked meetings, never in deals the agent opened.
1615
+ - name: Personalized Outreach
1616
+ description: Run one outreach campaign — write the angle and its offer, pick the people, draft one personal first message each as a proposed message in their channel; a person approves the list, then approves each message, which the pod sends.
1617
+ goalTemplate: >-
1618
+ Run this outreach campaign ({campaign}) on {channel}; each stage moves
1619
+ its status. If its type is not outreach, stop and say so. PREPARE: write
1620
+ the target audience, the value proposition and the call to action. Pick
1621
+ the people to reach from the pod (and public sources when the pod has
1622
+ too few), resolving each one before creating anything, link each as a
1623
+ member of the campaign with the lead role (lead source cold-outreach
1624
+ when it has none), and for each draft one first message to that person
1625
+ in this campaign's angle, specific to them — their company, role and
1626
+ any past message — never a generic template. DRAFT RULES: each draft is
1627
+ one proposed message in the channel of the person it is written to,
1628
+ filed through the pod's send door: run messaging.send with to = that
1629
+ person's entity id (for a company lead, its decider: when that person
1630
+ is not in the pod, create them through the entity door with only their
1631
+ own fields, such as name, role and LinkedIn URL, never the company's
1632
+ website, domain or email), text = the message, and provider linkedin
1633
+ or email. Read their channel first (synap_get_channel, mode person) for
1634
+ what was already said. The pod files it for a person to review in that
1635
+ channel; it goes out only once they approve it. Never call a tool's own
1636
+ send verb (Unipile, Gmail), never create an outreach touch, a note or a
1637
+ task holding the message, and never use synap_capture for it. Never
1638
+ send. START: a person approves the audience, the offer and the list,
1639
+ then reviews each proposed message in its channel and approves it;
1640
+ nothing goes out before they approve it, and the agent never sends.
1641
+ Once a message is sent, follow the cadence, so "Follow up on leads in
1642
+ outreach" drafts the next one. CADENCE: when a message is sent (a
1643
+ person approved its proposed message, so it is an outbound message in
1644
+ the channel), set its lead's lastContactedAt to the sent day, count
1645
+ the messages sent to the lead (the outbound messages in its channels,
1646
+ read with synap_get_channel mode person: for a company lead, the
1647
+ channels of the person written to there), and set the lead's next
1648
+ action: after the first message, nextAction "Follow up with <name> if
1649
+ no reply" and nextActionAt the sent day + 3 days; after the first
1650
+ follow-up, nextAction "Follow up with <name> a last time if no reply"
1651
+ and nextActionAt the sent day + 7 days; after the second follow-up,
1652
+ nextAction "No reply after two follow-ups: nurture or drop <name>" and
1653
+ nextActionAt the sent day + 7 days, and when that date passes with no
1654
+ reply, "Follow up on leads in outreach" proposes nurturing. Never a
1655
+ third follow-up. Propose moving a lead at new or researching to
1656
+ outreach through the status door, with its expected status. A reply
1657
+ stops the cadence: a reply is an inbound message after our last
1658
+ outbound one in the channel, and "Follow up on leads in outreach"
1659
+ reads it and proposes the next step. FINISH: when every person written
1660
+ to has replied or their follow-ups ended in silence, record in the
1661
+ campaign notes how many messages were sent (the outbound messages in
1662
+ their channels), how many got a reply and how many led to a booked
1663
+ meeting. Pausing or cancelling is a person's call.
1664
+ subjectProfile:
1665
+ profileSlug: campaign
1666
+ statusProperty: campaign-status
1667
+ humanOnlyStatuses: [paused, cancelled]
1668
+ activators:
1669
+ - { on: created, mode: propose }
1670
+ params:
1671
+ - { name: campaign, type: entity, label: Outreach campaign, required: true }
1672
+ - { name: channel, type: choice, label: Channel, options: [linkedin, email], defaultValue: linkedin }
1673
+ executor: is-agent
1674
+ status: active
1675
+ requiredIntents: [find_people, enrich_entity]
1676
+ grants: [entity.create]
1677
+ expectedOutputs:
1678
+ # Proposed messages on the members' channels (see "Draft a first message").
1679
+ - { kind: messaging, label: Proposed first messages, relationToSubject: none }
1680
+ stages:
1681
+ - key: prepare
1682
+ name: Prepare
1683
+ category: planned
1684
+ subjectStatus: planning
1685
+ goal: Write the audience, value proposition and call to action; pick the people; draft one first message each as a proposed message in their channel (messaging.send).
1686
+ criteria:
1687
+ - key: outreach-offer
1688
+ statement: The target audience, value proposition and call to action are written on the campaign.
1689
+ check: { kind: judge }
1690
+ - key: outreach-personal
1691
+ statement: Every drafted message names something specific to its person.
1692
+ check: { kind: judge }
1693
+ - key: start
1694
+ name: Person approves the list
1695
+ category: started
1696
+ subjectStatus: active
1697
+ goal: A person approves the audience, offer and list; then the person approves each proposed message in its channel, and the pod sends it.
1698
+ gate: { kind: human }
1699
+ onFail: { toStage: prepare }
1700
+ indefinite: true
1701
+ - key: finish
1702
+ name: Finish
1703
+ category: completed
1704
+ subjectStatus: completed
1705
+ goal: Record how many messages were sent, how many got a reply and how many led to a booked meeting.
1706
+ criteria:
1707
+ - key: outreach-reply-rate
1708
+ statement: The campaign notes record how many messages were sent, replied to, and led to a booked meeting.
1709
+ check: { kind: judge }
1710
+
1711
+ # ── Read the reply to an outreach message — one message's process ─────────
1712
+ # Was Outreach & Comms' "Send an outreach message" (folded 2026-10-23). The
1713
+ # agent no longer sends: the run starts once a PERSON marked the message
1714
+ # sent (`sent` is humanOnly on the lifecycle), and it only watches.
1715
+ # LEGACY SCOPE (send door, 2026-11-02): it watches only the outreach touches
1716
+ # a person marked sent; a message sent from a channel (messaging.send) is no
1717
+ # touch, and its reply is read by "Follow up on leads in outreach". It
1718
+ # retires with the outreach-touch kind once the touches are moved into their
1719
+ # channels (decision d5). It is the reader of a reply to a touch: a
1720
+ # positive reply proposes the next step (the
1721
+ # free estimate or a meeting) and never opens a deal (a deal exists only once
1722
+ # a person records a booked meeting). The next message after silence is
1723
+ # "Follow up on leads in outreach"'s, never this run's. `mode: run`: it
1724
+ # cannot send or commit anything, and its done/lost moves are proposed.
1725
+ # Next action (CRM review 2026-10-27): the next step it proposes after a
1726
+ # reply, and the new way to reach someone after a bounce, are written on the
1727
+ # lead facet as `nextAction` + `nextActionAt`, not only in the summary: the
1728
+ # CRM app's lead page shows the next action, and a process that leaves a
1729
+ # lead open without one leaves the person with nothing to do.
1730
+ # No response (founder decision 2026-10-27): the lead's next action date,
1731
+ # set by the CADENCE (3 days after the first message, 7 days after each
1732
+ # follow-up), passing in silence; after the second follow-up this run also
1733
+ # proposes nurturing, since no third follow-up is ever drafted.
1734
+ - name: Read the reply to an outreach message
1735
+ description: Once a person has marked an outreach touch sent (messages recorded before the channels), watch for the reply; summarise it and propose the next step — the free estimate or a meeting — and never open a deal.
1736
+ goalTemplate: >-
1737
+ Watch this sent outreach message ({touch}) for a reply; each stage
1738
+ moves its status. WAIT: watch its conversation. Set it bounced when it
1739
+ did not reach the person, or no response when its lead's next action
1740
+ date passes with no reply; that date is the cadence's (the sent day + 3
1741
+ days after the first message, the sent day + 7 days after each
1742
+ follow-up), so when the lead has none, count from this message's sent
1743
+ date. The next message is drafted by "Follow up on leads in outreach",
1744
+ not here. When this message is the second follow-up (the third message
1745
+ sent to the lead) and its date passes with no reply, mark it no
1746
+ response and propose moving the lead to nurturing through the status
1747
+ door, with its expected status; there is never a third follow-up.
1748
+ REPLIED: its
1749
+ check reads the reply. Summarise it for a person. When they show
1750
+ interest, propose the next step — prepare the free estimate ("Prepare a
1751
+ pre-sale estimate") or book a meeting — and propose moving the lead to
1752
+ engaged; when they decline, propose nurturing or disqualified with their
1753
+ reason (in `disqualificationReason`). Unless you propose dropping them,
1754
+ write the next step you propose on the lead as `nextAction`, one
1755
+ imperative line (e.g. "Book a meeting with Ana"; "Check in again" for
1756
+ nurturing), and `nextActionAt`, the date they named or today; never
1757
+ leave it only in the summary. BOUNCED: set
1758
+ the lead's `nextAction` to find another way to reach them and
1759
+ `nextActionAt` to today. Never open a deal: a deal is opened only when a
1760
+ person records a booked meeting. Never answer the reply yourself; a
1761
+ person does. Opened is recorded by a person.
1762
+ subjectProfile:
1763
+ profileSlug: outreach-touch
1764
+ statusProperty: status
1765
+ humanOnlyStatuses: [drafted, sent, opened]
1766
+ activators:
1767
+ - { on: enters_status, status: sent, mode: run }
1768
+ params:
1769
+ - { name: touch, type: entity, label: Outreach message, required: true }
1770
+ executor: is-agent
1771
+ status: active
1772
+ stages:
1773
+ - key: wait
1774
+ name: Waiting for a reply
1775
+ category: started
1776
+ goal: Watch the conversation for a reply, a bounce, or the follow-up date passing in silence.
1777
+ indefinite: true
1778
+ criteria:
1779
+ - key: touch-reply-recorded
1780
+ statement: A reply from the person is recorded in the conversation.
1781
+ check: { kind: judge }
1782
+ - key: replied
1783
+ name: Replied
1784
+ category: completed
1785
+ subjectStatus: replied
1786
+ goal: Summarise the reply for a person and propose the next step — the free estimate or a meeting; never open a deal.
1787
+ gate: { kind: check }
1788
+ onFail: { toStage: wait }
1789
+ criteria:
1790
+ - key: touch-next-step-proposed
1791
+ statement: The reply is summarised, a proposed next step (other than a drop) is the lead's nextAction with a nextActionAt, and no deal was opened.
1792
+ check: { kind: judge }
1793
+ - key: bounced
1794
+ name: Bounced
1795
+ category: canceled
1796
+ subjectStatus: bounced
1797
+ goal: The message did not reach the person; note why on the message, and set the lead's nextAction to find another way to reach them, with nextActionAt today.
1798
+ - key: no-response
1799
+ name: No response
1800
+ category: canceled
1801
+ subjectStatus: no_response
1802
+ goal: The lead's next action date passed with no reply; note it on the message, and after the second follow-up propose moving the lead to nurturing.
1803
+
1804
+ # ── Prepare a pre-sale estimate ──────────────────────────────────────────
1805
+ # CRM build plan A7 (founder Q8). A free one-page estimate for a lead,
1806
+ # before any deal. Generic: written in the lead's language and the company's
1807
+ # voice, from the offers in Foundation; amounts come from the pod's own won
1808
+ # deals for that offer or from the approver, never invented; the approver is
1809
+ # the pod's owner. After approval a person sends it, and the SENT step records
1810
+ # the date and the channel on the lead (`estimateSentAt`,
1811
+ # `estimateSentChannel`, above). A lifecycle READER: it never moves the lead.
1812
+ - name: Prepare a pre-sale estimate
1813
+ description: Write a free one-page estimate for a lead from sourced facts and the company's own offers; the pod's owner approves it, a person sends it, and the date and channel it went out on are recorded on the lead.
1814
+ goalTemplate: >-
1815
+ Prepare a free pre-sale estimate for {lead}. Form answers, if any:
1816
+ {formAnswers}. RESEARCH: from the pod and public sources only (their
1817
+ website, company page, job posts, public posts), write what the company
1818
+ does, its size, the tools it uses and where its time goes; cite the
1819
+ source of every fact and leave out anything you cannot verify. ESTIMATE:
1820
+ one page in the lead's language and the company's voice from Foundation
1821
+ (outcomes for the buyer, no jargon), in six blocks: 1) what we
1822
+ understood, in 3 to 5 lines they can correct; 2) where their time and
1823
+ money go today; 3) what we would do, from the company's offers in
1824
+ Foundation, never an offer it does not have; 4) what it would gain them,
1825
+ as a RANGE with every assumption written next to it, never a single
1826
+ promised number; 5) scope, timeline and price: the offer's price model,
1827
+ with amounts from the company's past won deals for that offer, else left
1828
+ blank for the approver to set, never invented; 6) the next step.
1829
+ APPROVE: the pod's owner approves the estimate before it goes anywhere;
1830
+ nothing is sent without that yes. SENT: once a person has sent it,
1831
+ record on the lead the date and the channel it went out on, and its last
1832
+ contacted date.
1833
+ subjectProfile:
1834
+ profileSlug: lead
1835
+ activators:
1836
+ - { on: manual, mode: propose }
1837
+ params:
1838
+ - { name: lead, type: entity, label: Lead, required: true }
1839
+ - { name: formAnswers, type: text, label: What they wrote in the website form (empty for outbound), default: "" }
1840
+ executor: is-agent
1841
+ status: active
1842
+ requiredIntents: [search_external]
1843
+ stages:
1844
+ - key: research
1845
+ name: Research
1846
+ category: planned
1847
+ goal: Gather sourced facts about the company — what it does, its size, its tools, where its time goes.
1848
+ criteria:
1849
+ - key: estimate-facts-sourced
1850
+ statement: Every fact about the company names its source, and nothing unverified is presented as true.
1851
+ check: { kind: judge }
1852
+ - key: draft
1853
+ name: Draft the estimate
1854
+ category: started
1855
+ goal: Write the one-page estimate in its six blocks, with ranges and their assumptions, from the company's own offers.
1856
+ expectedOutputs:
1857
+ - { kind: document, label: Pre-sale estimate, relationToSubject: relates_to }
1858
+ criteria:
1859
+ - key: estimate-six-blocks
1860
+ statement: The estimate has all six blocks, in the lead's language.
1861
+ check: { kind: judge }
1862
+ - key: estimate-ranges
1863
+ statement: The gain is a range with its assumptions written next to it, and every price comes from an offer and a past won deal or is left blank.
1864
+ check: { kind: judge }
1865
+ - key: approve
1866
+ name: Owner approves
1867
+ category: started
1868
+ goal: The pod's owner reads the estimate and approves it for sending, or sends it back with corrections.
1869
+ gate: { kind: human }
1870
+ onFail: { toStage: draft }
1871
+ - key: sent
1872
+ name: Sent
1873
+ category: completed
1874
+ goal: A person sends the approved estimate and says when and on which channel; record both on the lead with its last contacted date.
1875
+ gate: { kind: human }
1876
+ criteria:
1877
+ - key: estimate-sent-recorded
1878
+ statement: The lead records the date and the channel the estimate was sent on.
1879
+ check: { kind: judge }
1880
+
1881
+ # The lead requirement keeps "Qualify a lead" out of plain person/company
1882
+ # records (rendered by CRM and Browser through the shared entity-action runner).
1883
+ actionPlacements:
1884
+ - profileSlug: person
1885
+ surface: entity-detail
1886
+ kind: playbook
1887
+ ref: Qualify a lead
1888
+ label: Qualify lead
1889
+ when: { requiredFacetSlugs: [lead] }
1890
+
1891
+ - profileSlug: company
1892
+ surface: entity-detail
1893
+ kind: playbook
1894
+ ref: Qualify a lead
1895
+ label: Qualify lead
1896
+ when: { requiredFacetSlugs: [lead] }
1897
+
654
1898
  # Apify powers the enrichment playbook (scrape public sources for a lead's
655
1899
  # firmographics). Composed pod-wide at provisioning via the existing
656
1900
  # integrations door; the user supplies the Apify API key once on install — a
657
1901
  # missing key fails gracefully per-skill, not the whole workspace.
658
1902
  integrations:
659
1903
  - templateKey: apify
1904
+ # Folded from Outreach & Comms (2026-10-23): LinkedIn (Unipile) and email
1905
+ # (Google) are the accounts people are written to from. The CRM processes
1906
+ # never call their send verbs: every message goes through the pod's send
1907
+ # door (messaging.send, a proposed message the person approves), and the
1908
+ # pod's messaging webhook and history import record LinkedIn messages in the
1909
+ # people's channels (2026-11-02; they replace the CRM's LinkedIn ingest).
1910
+ - templateKey: unipile-linkedin
1911
+ - templateKey: nango-google
1912
+
1913
+ # ── taskIntents — WHAT THIS WORKSPACE NEEDS TO FUNCTION ───────────────────
1914
+ # Closed vocabulary: `@synap-core/types/capability-intents` (pod migrations
1915
+ # 0283/0284). DERIVED from the declared intents of this template's own
1916
+ # `integrations` capability packs — not hand-guessed. Checked at authoring
1917
+ # time by `validateTemplate` (`task-intent-unknown`) and by
1918
+ # `src/task-intents.test.ts`.
1919
+ taskIntents:
1920
+ - search_external
1921
+ - fetch_record
1922
+ - list_records
1923
+ - run_external_job
1924
+ - capture_into_pod
1925
+ - enrich_entity
1926
+ # From unipile-linkedin and nango-google (folded from Outreach & Comms).
1927
+ # send_message: the pod's own send door (Synap Core's messaging.send) and
1928
+ # Gmail; Unipile's send verbs no longer serve it (decision d6, 2026-11-02).
1929
+ - connect_account
1930
+ - send_message
1931
+ - request_connection
1932
+ - find_people
1933
+ - schedule_event
1934
+ - manage_file