@synap-core/workspace-templates 0.12.0 → 0.24.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +509 -0
- package/automations/bookmark-enrichment.automation.json +321 -0
- package/automations/github-pr-events.automation.json +505 -0
- package/automations/mail-feed.automation.json +86 -0
- package/automations/ship-a-change-checks-green.automation.json +106 -0
- package/automations/ship-a-change-checks-red.automation.json +106 -0
- package/automations/ship-a-change-deployed.automation.json +56 -0
- package/automations/ship-a-change-pr-merged.automation.json +110 -0
- package/automations/ship-a-change-pr-opened.automation.json +104 -0
- package/automations/url-bookmark-capture.automation.json +132 -0
- package/capabilities/agency-skills.capability.json +157 -0
- package/capabilities/apify.capability.json +265 -0
- package/capabilities/apollo.capability.json +120 -0
- package/capabilities/arch-backend.capability.json +97 -0
- package/capabilities/arch-client-intelligence.capability.json +401 -0
- package/capabilities/backblaze-b2.capability.json +205 -0
- package/capabilities/cal-com.capability.json +266 -0
- package/capabilities/canva.capability.json +274 -0
- package/capabilities/claude-code-routines.capability.json +172 -0
- package/capabilities/claude-managed-agents.capability.json +215 -0
- package/capabilities/client-comms-report.capability.json +246 -0
- package/capabilities/content-idea-to-production.capability.json +29 -0
- package/capabilities/datagouv.capability.json +315 -0
- package/capabilities/discord-bot.capability.json +157 -0
- package/capabilities/dropbox.capability.json +281 -0
- package/capabilities/exa.capability.json +109 -0
- package/capabilities/fal-ai.capability.json +83 -0
- package/capabilities/fal-media-lab.capability.json +158 -0
- package/capabilities/fireflies.capability.json +191 -0
- package/capabilities/freellmapi.capability.json +114 -0
- package/capabilities/generic-apikey.capability.json +92 -0
- package/capabilities/github-merge.capability.json +99 -0
- package/capabilities/github-read.capability.json +325 -0
- package/capabilities/governance-tighten-recommender.capability.json +81 -0
- package/capabilities/jev.capability.json +226 -0
- package/capabilities/lead-conversion-enrollment.capability.json +246 -0
- package/capabilities/mail-feed.capability.json +277 -0
- package/capabilities/mailgun.capability.json +52 -0
- package/capabilities/nango-google.capability.json +639 -0
- package/capabilities/notion.capability.json +417 -0
- package/capabilities/omniroute.capability.json +137 -0
- package/capabilities/posthog-analytics.capability.json +258 -0
- package/capabilities/postiz.capability.json +467 -0
- package/capabilities/proactive-digest.capability.json +190 -0
- package/capabilities/proton.capability.json +288 -0
- package/capabilities/replicate-rvm.capability.json +97 -0
- package/capabilities/research-methods.capability.json +57 -0
- package/capabilities/shopify.capability.json +395 -0
- package/capabilities/slack.capability.json +369 -0
- package/capabilities/stripe.capability.json +1496 -0
- package/capabilities/telegram-bridge.capability.json +56 -0
- package/capabilities/treg.capability.json +102 -0
- package/capabilities/unipile-linkedin.capability.json +494 -0
- package/capabilities/vercel.capability.json +424 -0
- package/dist/define-DqoItRiR.d.ts +1370 -0
- package/dist/index.d.ts +174 -900
- package/dist/index.js +26447 -24075
- package/dist/index.js.map +1 -1
- package/dist/wire.d.ts +222 -0
- package/dist/wire.js +77 -0
- package/dist/wire.js.map +1 -0
- package/package.json +27 -4
- package/retired.yaml +136 -0
- package/skills/app-factory-build-slice/SKILL.md +70 -0
- package/skills/app-factory-build-slice.catalog.json +17 -0
- package/skills/app-factory-design-canvas/SKILL.md +96 -0
- package/skills/app-factory-design-canvas.catalog.json +17 -0
- package/skills/app-factory-design-review/SKILL.md +105 -0
- package/skills/app-factory-design-review.catalog.json +18 -0
- package/skills/app-factory-research/SKILL.md +123 -0
- package/skills/app-factory-research.catalog.json +17 -0
- package/skills/bookmark-enrichment/SKILL.md +40 -0
- package/skills/carousel-format/carousel-format.skill.json +18 -0
- package/skills/collage-broll/collage-broll.skill.json +20 -0
- package/skills/content-taste/content-taste.skill.json +18 -0
- package/skills/creative-director/creative-director.skill.json +18 -0
- package/skills/design-critique/SKILL.md +138 -0
- package/skills/design-critique.catalog.json +12 -0
- package/skills/designing-with-real-components/SKILL.md +140 -0
- package/skills/designing-with-real-components.catalog.json +12 -0
- package/skills/edit-loop/edit-loop.skill.json +25 -0
- package/skills/facecam-edit/facecam-edit.skill.json +24 -0
- package/skills/mapping-the-server/SKILL.md +54 -0
- package/skills/math-explainer-manimgx/math-explainer-manimgx.skill.json +20 -0
- package/skills/motion-quality-gates/motion-quality-gates.skill.json +20 -0
- package/skills/open-design-edit/open-design-edit.skill.json +24 -0
- package/skills/reading-client-progress/SKILL.md +65 -0
- package/skills/routing-to-channels/SKILL.md +54 -0
- package/skills/stellar-scf-grants-advisor/ASSOCIATE-CHEATSHEET.md +133 -0
- package/skills/stellar-scf-grants-advisor/ASSOCIATE-ONBOARDING-GUIDE.md +761 -0
- package/skills/stellar-scf-grants-advisor/CLIENT-FAQ.md +181 -0
- package/skills/stellar-scf-grants-advisor/PARTNER-HANDOVER.md +130 -0
- package/skills/stellar-scf-grants-advisor/SKILL.md +102 -0
- package/skills/stellar-scf-grants-advisor/examples/README.md +43 -0
- package/skills/stellar-scf-grants-advisor/examples/chat-summary-templates.md +158 -0
- package/skills/stellar-scf-grants-advisor/examples/corpus-comparable-rows.md +136 -0
- package/skills/stellar-scf-grants-advisor/examples/q10-team-rewrites.md +109 -0
- package/skills/stellar-scf-grants-advisor/examples/q2-description-rewrites.md +88 -0
- package/skills/stellar-scf-grants-advisor/examples/q6-integration-rewrites.md +107 -0
- package/skills/stellar-scf-grants-advisor/examples/tranche-structures.md +176 -0
- package/skills/stellar-scf-grants-advisor/reference/01-methodology.md +96 -0
- package/skills/stellar-scf-grants-advisor/reference/02-scoring-framework.md +199 -0
- package/skills/stellar-scf-grants-advisor/reference/03-rejection-patterns.md +241 -0
- package/skills/stellar-scf-grants-advisor/reference/04-winner-patterns.md +120 -0
- package/skills/stellar-scf-grants-advisor/reference/05-comeback-patterns.md +92 -0
- package/skills/stellar-scf-grants-advisor/reference/06-budget-benchmarks.md +125 -0
- package/skills/stellar-scf-grants-advisor/reference/07-integration-list.md +147 -0
- package/skills/stellar-scf-grants-advisor/reference/08-abstract-fields.md +258 -0
- package/skills/stellar-scf-grants-advisor/reference/09-build-fields.md +259 -0
- package/skills/stellar-scf-grants-advisor/reference/10-corpus-winners.md +135 -0
- package/skills/stellar-scf-grants-advisor/reference/11-corpus-rejects.md +267 -0
- package/skills/stellar-scf-grants-advisor/reference/12-client-communication.md +131 -0
- package/skills/stellar-scf-grants-advisor/reference/13-excel-output-spec.md +199 -0
- package/skills/stellar-scf-grants-advisor/reference/14-worked-examples.md +160 -0
- package/skills/stellar-scf-grants-advisor/templates/abstract-review-builder.py +253 -0
- package/skills/stellar-scf-grants-advisor/templates/build-review-builder.py +213 -0
- package/skills/video-direction/video-direction.skill.json +20 -0
- package/skills/video-style-vocabulary/video-style-vocabulary.skill.json +20 -0
- package/skills/video-toolbox/video-toolbox.skill.json +20 -0
- package/src/__fixtures__/lifecycle-presets.flat-before.json +205 -0
- package/src/agency-os.yaml +122 -0
- package/src/agent-os-skill-slugs.test.ts +65 -0
- package/src/apify-social-reader.test.ts +317 -0
- package/src/base.yaml +85 -39
- package/src/blockchain-ecosystem.yaml +1 -0
- package/src/brand-library.yaml +525 -14
- package/src/builder-dev-fold.template.test.ts +113 -0
- package/src/builder.yaml +779 -91
- package/src/business-model-catalog-description.test.ts +47 -0
- package/src/business-model-grp.template.test.ts +214 -0
- package/src/business-model.yaml +296 -49
- package/src/catalog-safety.test.ts +198 -0
- package/src/catalog.template-items.test.ts +75 -0
- package/src/catalog.ts +66 -2
- package/src/concept-guard.test.ts +28 -8
- package/src/concept-guard.ts +2 -2
- package/src/content-creation.template.test.ts +52 -0
- package/src/content-creation.yaml +192 -0
- package/src/content-os.edit-loop.template.test.ts +210 -0
- package/src/content-os.publish-due-posts.template.test.ts +261 -0
- package/src/content-os.template.test.ts +349 -0
- package/src/content-os.video-takes.template.test.ts +338 -0
- package/src/content-os.work-a-take.template.test.ts +125 -0
- package/src/content-os.yaml +1873 -39
- package/src/content-studio-retired.template.test.ts +82 -0
- package/src/corpus-flow-validity.test.ts +4 -5
- package/src/crm-absorbs-outreach.template.test.ts +117 -0
- package/src/crm-build-plan.template.test.ts +128 -0
- package/src/crm-lists.template.test.ts +96 -0
- package/src/crm-messages-cadence.template.test.ts +246 -0
- package/src/crm-one-send-door.template.test.ts +65 -0
- package/src/crm.yaml +1563 -181
- package/src/decisions-wave.template.test.ts +186 -0
- package/src/define.ts +146 -17
- package/src/design-to-implementation.template.test.ts +94 -0
- package/src/domain-template-type.test.ts +75 -0
- package/src/ecosystem.yaml +27 -66
- package/src/enterprise-os.yaml +10 -134
- package/src/facecam-edit.audio-studio.test.ts +188 -0
- package/src/fal-generate-image.test.ts +200 -0
- package/src/fal-media-lab-pod-files.test.ts +180 -0
- package/src/finance.yaml +254 -1
- package/src/foundation.yaml +78 -38
- package/src/frame-host-modules.d.mts +6 -0
- package/src/frame-host-modules.mjs +58 -0
- package/src/generate.mjs +9 -3
- package/src/github-merge.test.ts +210 -0
- package/src/grants.yaml +46 -17
- package/src/hr.yaml +39 -6
- package/src/index.ts +36 -1
- package/src/intent.test.ts +161 -0
- package/src/internal-runbook.yaml +19 -1
- package/src/jev.test.ts +356 -0
- package/src/legal.yaml +33 -1
- package/src/life-os.yaml +51 -240
- package/src/lifecycle-coverage.test.ts +124 -0
- package/src/lifecycle-declarations.test.ts +252 -0
- package/src/lifecycle-presets.test.ts +468 -0
- package/src/lifecycle-presets.ts +494 -0
- package/src/lifecycle-stuck.test.ts +46 -0
- package/src/lifecycle-waits.parity.test.ts +319 -0
- package/src/marketing.yaml +108 -4
- package/src/meetings-knowledge.yaml +18 -0
- package/src/networking.yaml +23 -2
- package/src/operations.yaml +304 -4
- package/src/pipeline-views.template.test.ts +88 -0
- package/src/playbook-goal-placeholders.test.ts +57 -0
- package/src/postiz-contract.test.ts +134 -0
- package/src/presentation-declarations.test.ts +174 -0
- package/src/process-contract.validate.test.ts +352 -0
- package/src/project-management.yaml +39 -740
- package/src/publish-body.d.mts +29 -0
- package/src/publish-body.mjs +74 -0
- package/src/publish-body.test.ts +74 -0
- package/src/publish-official-lib.test.ts +176 -0
- package/src/replicate-rvm.test.ts +504 -0
- package/src/research-base.yaml +119 -20
- package/src/research-decision.yaml +60 -3
- package/src/reserved-profile-slugs.test.ts +81 -0
- package/src/retired-goal-kinds.test.ts +52 -0
- package/src/role-reference-and-step-domain.test.ts +192 -0
- package/src/seed-obligation.test.ts +1 -6
- package/src/send-message-maps.test.ts +227 -0
- package/src/shared-role-ssot.test.ts +40 -2
- package/src/ship-a-change.template.test.ts +177 -0
- package/src/skills.test.ts +170 -0
- package/src/social.yaml +85 -27
- package/src/space-packages.freshness.test.ts +97 -0
- package/src/space-packages.generated.ts +40 -0
- package/src/space-packages.test.ts +125 -0
- package/src/space-packages.ts +73 -0
- package/src/start.test.ts +188 -0
- package/src/stripe-capability.test.ts +192 -0
- package/src/task-intents.test.ts +293 -0
- package/src/template-documents.test.ts +158 -0
- package/src/templates.ts +20907 -21464
- package/src/types.ts +475 -61
- package/src/unipile-linkedin-find-profile.test.ts +183 -0
- package/src/unipile-publish-post.test.ts +162 -0
- package/src/validate-flow.drift.test.ts +2 -3
- package/src/validate-flow.ts +22 -2
- package/src/validate-onboarding.test.ts +59 -0
- package/src/validate.test.ts +71 -4
- package/src/validate.ts +1416 -7
- package/src/wire-core.ts +231 -0
- package/src/wire.test.ts +159 -0
- package/src/wire.ts +107 -0
- package/views/bookmark-views-pack.view.json +39 -0
- package/views/event-views-pack.view.json +45 -0
- package/views/task-views-pack.view.json +53 -0
- package/src/agent-fleet.yaml +0 -1645
- package/src/business-developer.template.test.ts +0 -351
- package/src/business-developer.yaml +0 -860
- package/src/content-studio.yaml +0 -361
- package/src/dev-dashboard.yaml +0 -990
- package/src/outreach-comms.yaml +0 -486
- 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
|
|
3
|
-
#
|
|
4
|
-
#
|
|
5
|
-
#
|
|
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
|
|
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
|
|
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
|
-
|
|
94
|
-
|
|
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:
|
|
171
|
-
label:
|
|
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
|
-
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
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:
|
|
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 —
|
|
362
|
+
# ECOSYSTEM ROLES — REFERENCES to foundation's pod-wide shared roles.
|
|
277
363
|
#
|
|
278
|
-
#
|
|
279
|
-
#
|
|
280
|
-
# (
|
|
281
|
-
#
|
|
282
|
-
#
|
|
283
|
-
#
|
|
284
|
-
#
|
|
285
|
-
#
|
|
286
|
-
#
|
|
287
|
-
#
|
|
288
|
-
#
|
|
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
|
-
#
|
|
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:
|
|
306
|
-
label:
|
|
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
|
-
|
|
311
|
-
|
|
312
|
-
|
|
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:
|
|
320
|
-
label:
|
|
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
|
-
|
|
336
|
-
|
|
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
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
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
|
-
|
|
349
|
-
|
|
350
|
-
|
|
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
|
-
|
|
356
|
-
|
|
357
|
-
|
|
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
|
-
# ──
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
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
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
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:
|
|
552
|
+
- slug: campaign-status
|
|
373
553
|
label: Status
|
|
374
554
|
valueType: string
|
|
375
555
|
inputType: select
|
|
376
|
-
enumValues:
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
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
|
-
|
|
387
|
-
|
|
388
|
-
|
|
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:
|
|
393
|
-
- slug:
|
|
394
|
-
label:
|
|
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:
|
|
398
|
-
label:
|
|
580
|
+
- slug: campaign-budget
|
|
581
|
+
label: Budget
|
|
399
582
|
valueType: number
|
|
400
583
|
inputType: number
|
|
401
|
-
- slug:
|
|
402
|
-
label:
|
|
584
|
+
- slug: campaign-lead-count
|
|
585
|
+
label: Lead Count
|
|
403
586
|
valueType: number
|
|
404
587
|
inputType: number
|
|
405
|
-
- slug:
|
|
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
|
-
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
-
|
|
414
|
-
|
|
415
|
-
|
|
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
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
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
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
-
|
|
450
|
-
|
|
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
|
-
# ──
|
|
455
|
-
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
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:
|
|
467
|
-
label:
|
|
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:
|
|
471
|
-
label:
|
|
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.
|
|
533
|
-
|
|
534
|
-
|
|
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: [
|
|
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 `
|
|
637
|
-
|
|
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,985 @@ 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.
|
|
1402
|
+
A LinkedIn message is drafted whatever their reach: the pod holds a
|
|
1403
|
+
draft to someone who is not a connection yet, shows it with the next
|
|
1404
|
+
step and lets it go out once they connect, so never hold a draft back
|
|
1405
|
+
for want of a connection. Draft it, then, in this order: (1) a person
|
|
1406
|
+
with no linkedinUrl: find their profile with linkedin_find_profile
|
|
1407
|
+
(their full name and company); on a match, propose its linkedinUrl and
|
|
1408
|
+
linkedinProviderId on the person for a person to approve; on none or
|
|
1409
|
+
several, ask the person to choose among its candidates (the process
|
|
1410
|
+
"Find their LinkedIn" does this for one lead). (2) Read their
|
|
1411
|
+
linkedinConnection, which only the pod writes (never set it or
|
|
1412
|
+
linkedinInvitedAt): connected, nothing more; none or not set, ask to
|
|
1413
|
+
connect with linkedin_send_invite (it refuses someone already connected
|
|
1414
|
+
or already invited: then nothing more); pending, the draft waits for
|
|
1415
|
+
them; ignored, draft by email instead when they have an email address.
|
|
1416
|
+
While the draft waits for them to connect, set the lead's nextAction to
|
|
1417
|
+
"Wait for <name> to accept the LinkedIn request" and its nextActionAt 7
|
|
1418
|
+
days later. An email message needs none of this. Its message
|
|
1419
|
+
is personal: it cites the buying signal and what it showed, or a past
|
|
1420
|
+
exchange, never a generic template; short; in the person's language,
|
|
1421
|
+
French by default, and in French it says vous. It offers one useful
|
|
1422
|
+
next step, never pressure. Draft one message only: a person reviews
|
|
1423
|
+
it in the channel, edits it if needed and approves it. NEXT ACTION:
|
|
1424
|
+
set the lead's nextAction to "Send the first message to <name> on
|
|
1425
|
+
LinkedIn" ("Send the first message to <name> by email" on email),
|
|
1426
|
+
naming the person written to, and its nextActionAt to today. Never
|
|
1427
|
+
move the lead's stage while drafting. SENT: once the message is sent,
|
|
1428
|
+
follow the cadence, so "Follow up on leads in outreach" drafts the
|
|
1429
|
+
next one. CADENCE: when a message is sent (a person approved its
|
|
1430
|
+
proposed message, so it is an outbound message in the channel), set
|
|
1431
|
+
its lead's lastContactedAt to the sent day, count the messages sent to
|
|
1432
|
+
the lead (the outbound messages in its channels, read with
|
|
1433
|
+
synap_get_channel mode person: for a company lead, the channels of the
|
|
1434
|
+
person written to there), and set the lead's next action: after the
|
|
1435
|
+
first message, nextAction "Follow up with <name> if no reply" and
|
|
1436
|
+
nextActionAt the sent day + 3 days; after the first follow-up,
|
|
1437
|
+
nextAction "Follow up with <name> a last time if no reply" and
|
|
1438
|
+
nextActionAt the sent day + 7 days; after the second follow-up,
|
|
1439
|
+
nextAction "No reply after two follow-ups: nurture or drop <name>" and
|
|
1440
|
+
nextActionAt the sent day + 7 days, and when that date passes with no
|
|
1441
|
+
reply, "Follow up on leads in outreach" proposes nurturing. Never a
|
|
1442
|
+
third follow-up. Propose moving a lead at new or researching to
|
|
1443
|
+
outreach through the status door, with its expected status. A reply
|
|
1444
|
+
stops the cadence: a reply is an inbound message after our last
|
|
1445
|
+
outbound one in the channel, and "Follow up on leads in outreach"
|
|
1446
|
+
reads it and proposes the next step.
|
|
1447
|
+
subjectProfile:
|
|
1448
|
+
profileSlug: lead
|
|
1449
|
+
activators:
|
|
1450
|
+
- { on: manual, mode: propose }
|
|
1451
|
+
params:
|
|
1452
|
+
- { name: lead, type: entity, label: Lead, required: true }
|
|
1453
|
+
- { name: campaign, type: entity, label: Outreach campaign (optional) }
|
|
1454
|
+
- { name: channel, type: choice, label: Channel, options: [linkedin, email], defaultValue: linkedin }
|
|
1455
|
+
executor: is-agent
|
|
1456
|
+
status: active
|
|
1457
|
+
grants: [entity.create]
|
|
1458
|
+
stages:
|
|
1459
|
+
- key: draft
|
|
1460
|
+
name: Draft the first message
|
|
1461
|
+
category: started
|
|
1462
|
+
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.
|
|
1463
|
+
expectedOutputs:
|
|
1464
|
+
# The proposed message: a `messaging.external.send` proposal on the
|
|
1465
|
+
# person's channel (target type `messaging`). The channel is already
|
|
1466
|
+
# bound to the person, so it writes no edge to the lead.
|
|
1467
|
+
- { kind: messaging, label: Proposed first message, relationToSubject: none }
|
|
1468
|
+
criteria:
|
|
1469
|
+
# A judge: no core or catalog verb reads a session's proposals and
|
|
1470
|
+
# answers {passed}, so the pod has no exact check for this yet.
|
|
1471
|
+
- key: first-message-one-draft
|
|
1472
|
+
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, whatever the person's LinkedIn reach; on LinkedIn to someone not yet a connection, the pod holds it, and their profile is found and the connection asked for or awaited, in the DRAFT RULES order.
|
|
1473
|
+
check: { kind: judge }
|
|
1474
|
+
- key: first-message-no-capture
|
|
1475
|
+
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.
|
|
1476
|
+
check: { kind: judge }
|
|
1477
|
+
- key: first-message-personal
|
|
1478
|
+
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.
|
|
1479
|
+
check: { kind: judge }
|
|
1480
|
+
- key: first-message-not-rescored
|
|
1481
|
+
statement: The lead's icpScore, buyingSignal and scoredAt are unchanged, and its stage was not moved.
|
|
1482
|
+
check: { kind: judge }
|
|
1483
|
+
- key: first-message-next-action
|
|
1484
|
+
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.
|
|
1485
|
+
check: { kind: judge }
|
|
1486
|
+
- key: send
|
|
1487
|
+
name: Person sends
|
|
1488
|
+
category: completed
|
|
1489
|
+
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.
|
|
1490
|
+
gate: { kind: human }
|
|
1491
|
+
onFail: { toStage: draft }
|
|
1492
|
+
|
|
1493
|
+
# ── Find their LinkedIn — one lead's LinkedIn profile ───────────────────
|
|
1494
|
+
# CRM Messages contract (decision b603d75e, build map ca310f27): the first of
|
|
1495
|
+
# the steps a LinkedIn lead walks (Find their LinkedIn, Ask to connect,
|
|
1496
|
+
# Waiting, Send). linkedin_find_profile is a read with no inputMap, so the
|
|
1497
|
+
# actions door cannot run it for the CRM app: the app starts this process
|
|
1498
|
+
# for one lead (`params.lead`, as "Draft a first message"). On a strict match
|
|
1499
|
+
# it PROPOSES linkedinUrl and linkedinProviderId on the person; on none or
|
|
1500
|
+
# several it hands the person up to 3 candidates. The reach fields stay the
|
|
1501
|
+
# pod's (backend#786). A lifecycle READER of leadStage: no stage moves.
|
|
1502
|
+
- name: Find their LinkedIn
|
|
1503
|
+
description: Find one lead's LinkedIn profile by their full name and company and propose its URL and member id on the person for a person to approve, or offer up to 3 candidates to choose from; nothing is sent.
|
|
1504
|
+
goalTemplate: >-
|
|
1505
|
+
Find the LinkedIn profile of the lead {lead}. WHO: a person lead is
|
|
1506
|
+
looked up themselves; for a company lead, the person who decides there,
|
|
1507
|
+
from the pod; when no decider is known, ask the person who it is. When
|
|
1508
|
+
that person already has a linkedinUrl, say so and stop. FIND: run
|
|
1509
|
+
linkedin_find_profile with their full name, their company and the
|
|
1510
|
+
LinkedIn account unipile_list_accounts lists (when several are
|
|
1511
|
+
connected, ask the person which one reads). On a match, propose its
|
|
1512
|
+
linkedinUrl and linkedinProviderId on the person through the entity
|
|
1513
|
+
door, for a person to approve; never write them directly, and never set
|
|
1514
|
+
linkedinConnection or linkedinInvitedAt, which only the pod writes. On
|
|
1515
|
+
none, several or more results than it read, ask the person to choose
|
|
1516
|
+
among its candidates (at most 3, each with its name, headline and
|
|
1517
|
+
profile URL) or to give the URL themselves, then propose the one chosen
|
|
1518
|
+
the same way. Never send a message or a connection request, never draft
|
|
1519
|
+
one, and never move the lead's stage.
|
|
1520
|
+
subjectProfile:
|
|
1521
|
+
profileSlug: lead
|
|
1522
|
+
activators:
|
|
1523
|
+
- { on: manual, mode: propose }
|
|
1524
|
+
params:
|
|
1525
|
+
- { name: lead, type: entity, label: Lead, required: true }
|
|
1526
|
+
executor: is-agent
|
|
1527
|
+
status: active
|
|
1528
|
+
requiredIntents: [enrich_entity]
|
|
1529
|
+
stages:
|
|
1530
|
+
- key: find
|
|
1531
|
+
name: Find the profile
|
|
1532
|
+
category: started
|
|
1533
|
+
goal: Run linkedin_find_profile for the lead (a company lead's decider) with their full name and company; on a match propose linkedinUrl and linkedinProviderId on the person, otherwise ask the person to choose among up to 3 candidates or give the URL.
|
|
1534
|
+
criteria:
|
|
1535
|
+
- key: find-linkedin-proposed
|
|
1536
|
+
statement: The person's linkedinUrl and linkedinProviderId are proposed on them for a person to approve (a strict match, or the candidate the person chose), or the person was asked to choose among at most 3 candidates; nothing was written directly.
|
|
1537
|
+
check: { kind: judge }
|
|
1538
|
+
- key: find-linkedin-reader-only
|
|
1539
|
+
statement: No message or connection request was sent or drafted, linkedinConnection and linkedinInvitedAt were not set, and the lead's stage was not moved.
|
|
1540
|
+
check: { kind: judge }
|
|
1541
|
+
- key: confirm
|
|
1542
|
+
name: Person confirms
|
|
1543
|
+
category: completed
|
|
1544
|
+
goal: The person approves the proposed profile, or picks a candidate or gives the URL, and the pod stores linkedinUrl and linkedinProviderId on the person; the lead's next step is then to ask to connect.
|
|
1545
|
+
gate: { kind: human }
|
|
1546
|
+
onFail: { toStage: find }
|
|
1547
|
+
|
|
1548
|
+
# ── Follow up on leads in outreach — THE one follow-up process ───────────
|
|
1549
|
+
# CRM build plan A6 (founder Q4). A batch worker: each weekday it reads the
|
|
1550
|
+
# channels of the leads in outreach. Due with no reply, it drafts one message
|
|
1551
|
+
# each, a proposed message in the person's channel through the pod's send
|
|
1552
|
+
# door `messaging.send`, in the angle of the lead's outreach campaign (A8).
|
|
1553
|
+
# The agent never sends (founder decision 2026-10-20): a person approves each
|
|
1554
|
+
# proposed message and the pod sends it. It replaces the pod-local process of
|
|
1555
|
+
# the same name (founder's pod, 2026-10-21), which wrote its drafts nowhere.
|
|
1556
|
+
# Replies (send door, 2026-11-02): a message sent from a channel is no
|
|
1557
|
+
# outreach touch, so "Read the reply to an outreach message" never watches
|
|
1558
|
+
# it; THIS process is the one reader of a reply in a channel (an inbound
|
|
1559
|
+
# message after our last outbound one): it summarises it and proposes the
|
|
1560
|
+
# next step, and a deal is never opened. "Read the reply" keeps only the
|
|
1561
|
+
# touches marked sent before the channels. A lifecycle READER of leadStage:
|
|
1562
|
+
# no stage moves the lead; nurturing after the second follow-up is proposed
|
|
1563
|
+
# through the door once the last date passes in silence. `statusProperty`
|
|
1564
|
+
# is named because the schedule worker reads `outreach`.
|
|
1565
|
+
# Next action (CRM review 2026-10-27): a drafted message becomes the lead's
|
|
1566
|
+
# `nextAction` ("Send the drafted message to <name>", `nextActionAt` today),
|
|
1567
|
+
# and a message marked sent sets `nextActionAt` by the CADENCE (founder
|
|
1568
|
+
# decision 2026-10-27, the same words as "Draft a first message" and
|
|
1569
|
+
# "Personalized Outreach"): 3 days after the first message, 7 days after
|
|
1570
|
+
# each follow-up, at most two follow-ups, never a third. The CRM app's lead
|
|
1571
|
+
# page shows the next action, and a process that leaves a lead open without
|
|
1572
|
+
# one leaves the person with nothing to do.
|
|
1573
|
+
- name: Follow up on leads in outreach
|
|
1574
|
+
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.
|
|
1575
|
+
goalTemplate: >-
|
|
1576
|
+
Follow up on the leads in outreach. For each, read its channels
|
|
1577
|
+
(synap_get_channel, mode person: for a company lead, the channels of
|
|
1578
|
+
the person who decides there) and its notes. A REPLY is an inbound
|
|
1579
|
+
message after our last outbound one in a channel. With a reply, draft
|
|
1580
|
+
nothing: summarise it for a person; when they show interest, propose
|
|
1581
|
+
the next step (prepare the free estimate, "Prepare a pre-sale
|
|
1582
|
+
estimate", or book a meeting) and propose moving the lead to engaged;
|
|
1583
|
+
when they decline, propose nurturing or disqualified with their reason
|
|
1584
|
+
(in disqualificationReason). Unless you propose dropping them, write
|
|
1585
|
+
the next step on the lead as nextAction, one imperative line, and
|
|
1586
|
+
nextActionAt, the date they named or today. Never open a deal and
|
|
1587
|
+
never answer the reply yourself; a person does. With no reply, and
|
|
1588
|
+
only when the lead's next action date is today or past and no
|
|
1589
|
+
proposed message to it is still waiting, draft the next message, to
|
|
1590
|
+
the lead (for a company, the person who decides there) and in the
|
|
1591
|
+
angle of the lead's outreach campaign: follow its value proposition
|
|
1592
|
+
and call to action, short and in the lead's language, offering the
|
|
1593
|
+
next useful step, never pressure. For a lead with no campaign, ask the
|
|
1594
|
+
person which one. Draft at most two follow-ups per lead: a lead
|
|
1595
|
+
already sent two follow-ups gets no draft, never a third follow-up;
|
|
1596
|
+
when its last date passes in silence, propose moving it to nurturing
|
|
1597
|
+
through the status door, with its expected status. DRAFT RULES: each
|
|
1598
|
+
draft is one proposed message in the channel of the person it is
|
|
1599
|
+
written to, filed through the pod's send door: run messaging.send with
|
|
1600
|
+
to = that person's entity id (for a company lead, its decider: when
|
|
1601
|
+
that person is not in the pod, create them through the entity door
|
|
1602
|
+
with only their own fields, such as name, role and LinkedIn URL, never
|
|
1603
|
+
the company's website, domain or email), text = the message, and
|
|
1604
|
+
provider linkedin or email. Read their channel first
|
|
1605
|
+
(synap_get_channel, mode person) for what was already said. The pod
|
|
1606
|
+
files it for a person to review in that channel; it goes out only once
|
|
1607
|
+
they approve it. Never call a tool's own send verb (Unipile, Gmail),
|
|
1608
|
+
never create an outreach touch, a note or a task holding the message,
|
|
1609
|
+
and never use synap_capture for it. Never send.
|
|
1610
|
+
A LinkedIn message is drafted whatever their reach: the pod holds a
|
|
1611
|
+
draft to someone who is not a connection yet, shows it with the next
|
|
1612
|
+
step and lets it go out once they connect, so never hold a draft back
|
|
1613
|
+
for want of a connection. Draft it, then, in this order: (1) a person
|
|
1614
|
+
with no linkedinUrl: find their profile with linkedin_find_profile
|
|
1615
|
+
(their full name and company); on a match, propose its linkedinUrl and
|
|
1616
|
+
linkedinProviderId on the person for a person to approve; on none or
|
|
1617
|
+
several, ask the person to choose among its candidates (the process
|
|
1618
|
+
"Find their LinkedIn" does this for one lead). (2) Read their
|
|
1619
|
+
linkedinConnection, which only the pod writes (never set it or
|
|
1620
|
+
linkedinInvitedAt): connected, nothing more; none or not set, ask to
|
|
1621
|
+
connect with linkedin_send_invite (it refuses someone already connected
|
|
1622
|
+
or already invited: then nothing more); pending, the draft waits for
|
|
1623
|
+
them; ignored, draft by email instead when they have an email address.
|
|
1624
|
+
While the draft waits for them to connect, set the lead's nextAction to
|
|
1625
|
+
"Wait for <name> to accept the LinkedIn request" and its nextActionAt 7
|
|
1626
|
+
days later. An email message needs none of this. For each draft, set
|
|
1627
|
+
the lead's `nextAction` to send it (e.g. "Send the drafted message to
|
|
1628
|
+
Ana on LinkedIn") and `nextActionAt` to today. The person reviews each
|
|
1629
|
+
proposed message in its channel, edits it if needed and approves it;
|
|
1630
|
+
the agent never sends. CADENCE: when a message is sent (a person
|
|
1631
|
+
approved its proposed message, so it is an outbound message in the
|
|
1632
|
+
channel), set its lead's lastContactedAt to the sent day, count the
|
|
1633
|
+
messages sent to the lead (the outbound messages in its channels, read
|
|
1634
|
+
with synap_get_channel mode person: for a company lead, the channels of
|
|
1635
|
+
the person written to there), and set the lead's next action: after
|
|
1636
|
+
the first message, nextAction "Follow up with <name> if no reply" and
|
|
1637
|
+
nextActionAt the sent day + 3 days; after the first follow-up,
|
|
1638
|
+
nextAction "Follow up with <name> a last time if no reply" and
|
|
1639
|
+
nextActionAt the sent day + 7 days; after the second follow-up,
|
|
1640
|
+
nextAction "No reply after two follow-ups: nurture or drop <name>" and
|
|
1641
|
+
nextActionAt the sent day + 7 days, and when that date passes with no
|
|
1642
|
+
reply, "Follow up on leads in outreach" proposes nurturing. Never a
|
|
1643
|
+
third follow-up. Propose moving a lead at new or researching to
|
|
1644
|
+
outreach through the status door, with its expected status. A reply
|
|
1645
|
+
stops the cadence: a reply is an inbound message after our last
|
|
1646
|
+
outbound one in the channel, and "Follow up on leads in outreach"
|
|
1647
|
+
reads it and proposes the next step.
|
|
1648
|
+
subjectProfile:
|
|
1649
|
+
profileSlug: lead
|
|
1650
|
+
statusProperty: leadStage
|
|
1651
|
+
activators:
|
|
1652
|
+
# Weekdays 07:00 UTC, one batch over the leads in outreach.
|
|
1653
|
+
- { on: schedule, cron: "0 7 * * 1-5", status: outreach, mode: propose }
|
|
1654
|
+
- { on: manual, mode: propose }
|
|
1655
|
+
executor: is-agent
|
|
1656
|
+
status: active
|
|
1657
|
+
expectedOutputs:
|
|
1658
|
+
# Proposed messages on the people's channels (see "Draft a first message").
|
|
1659
|
+
- { kind: messaging, label: Proposed follow-ups, relationToSubject: none }
|
|
1660
|
+
stages:
|
|
1661
|
+
- key: draft
|
|
1662
|
+
name: Draft due follow-ups
|
|
1663
|
+
category: started
|
|
1664
|
+
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.
|
|
1665
|
+
criteria:
|
|
1666
|
+
- key: followup-history-read
|
|
1667
|
+
statement: Each draft reflects what was already sent to that lead and any reply, read from its channels.
|
|
1668
|
+
check: { kind: judge }
|
|
1669
|
+
- key: followup-angle
|
|
1670
|
+
statement: Each draft follows the angle of the lead's outreach campaign and its call to action.
|
|
1671
|
+
check: { kind: judge }
|
|
1672
|
+
- key: followup-no-draft-after-reply
|
|
1673
|
+
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.
|
|
1674
|
+
check: { kind: judge }
|
|
1675
|
+
# A judge, not an exact check: see "first-message-one-draft" in
|
|
1676
|
+
# "Draft a first message" for what the pod lacks.
|
|
1677
|
+
- key: followup-draft-rules
|
|
1678
|
+
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.
|
|
1679
|
+
check: { kind: judge }
|
|
1680
|
+
- key: followup-next-action
|
|
1681
|
+
statement: Every lead with a draft has nextAction to send it and nextActionAt today.
|
|
1682
|
+
check: { kind: judge }
|
|
1683
|
+
- key: send
|
|
1684
|
+
name: Person sends
|
|
1685
|
+
category: completed
|
|
1686
|
+
goal: >-
|
|
1687
|
+
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.
|
|
1688
|
+
gate: { kind: human }
|
|
1689
|
+
onFail: { toStage: draft }
|
|
1690
|
+
|
|
1691
|
+
# ── Personalized Outreach — a campaign process (folded from Outreach & Comms) ─
|
|
1692
|
+
# Runs one OUTREACH campaign (a `campaign` with campaign-type outreach: the
|
|
1693
|
+
# angle) through campaign-status planning → active → completed, the same
|
|
1694
|
+
# lifecycle marketing's "Run a campaign" follows (workers are per space, so a
|
|
1695
|
+
# campaign created here starts this process, one created in Marketing starts
|
|
1696
|
+
# that one). It drafts the FIRST message for each person; the next ones are
|
|
1697
|
+
# "Follow up on leads in outreach"'s, which also reads the replies.
|
|
1698
|
+
# Each first message is a proposed message in the person's channel, filed
|
|
1699
|
+
# through the pod's send door `messaging.send` (2026-11-02, decisions d1/d6).
|
|
1700
|
+
# Changed in the fold (2026-10-23): the agent never sends (no send grant; the
|
|
1701
|
+
# person approves each proposed message), people are linked with
|
|
1702
|
+
# `member_of` (Outreach & Comms' `targets` twin is gone), and the campaign's
|
|
1703
|
+
# result is counted in booked meetings, never in deals the agent opened.
|
|
1704
|
+
- name: Personalized Outreach
|
|
1705
|
+
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.
|
|
1706
|
+
goalTemplate: >-
|
|
1707
|
+
Run this outreach campaign ({campaign}) on {channel}; each stage moves
|
|
1708
|
+
its status. If its type is not outreach, stop and say so. PREPARE: write
|
|
1709
|
+
the target audience, the value proposition and the call to action. Pick
|
|
1710
|
+
the people to reach from the pod (and public sources when the pod has
|
|
1711
|
+
too few), resolving each one before creating anything, link each as a
|
|
1712
|
+
member of the campaign with the lead role (lead source cold-outreach
|
|
1713
|
+
when it has none), and for each draft one first message to that person
|
|
1714
|
+
in this campaign's angle, specific to them — their company, role and
|
|
1715
|
+
any past message — never a generic template. DRAFT RULES: each draft is
|
|
1716
|
+
one proposed message in the channel of the person it is written to,
|
|
1717
|
+
filed through the pod's send door: run messaging.send with to = that
|
|
1718
|
+
person's entity id (for a company lead, its decider: when that person
|
|
1719
|
+
is not in the pod, create them through the entity door with only their
|
|
1720
|
+
own fields, such as name, role and LinkedIn URL, never the company's
|
|
1721
|
+
website, domain or email), text = the message, and provider linkedin
|
|
1722
|
+
or email. Read their channel first (synap_get_channel, mode person) for
|
|
1723
|
+
what was already said. The pod files it for a person to review in that
|
|
1724
|
+
channel; it goes out only once they approve it. Never call a tool's own
|
|
1725
|
+
send verb (Unipile, Gmail), never create an outreach touch, a note or a
|
|
1726
|
+
task holding the message, and never use synap_capture for it. Never
|
|
1727
|
+
send.
|
|
1728
|
+
A LinkedIn message is drafted whatever their reach: the pod holds a
|
|
1729
|
+
draft to someone who is not a connection yet, shows it with the next
|
|
1730
|
+
step and lets it go out once they connect, so never hold a draft back
|
|
1731
|
+
for want of a connection. Draft it, then, in this order: (1) a person
|
|
1732
|
+
with no linkedinUrl: find their profile with linkedin_find_profile
|
|
1733
|
+
(their full name and company); on a match, propose its linkedinUrl and
|
|
1734
|
+
linkedinProviderId on the person for a person to approve; on none or
|
|
1735
|
+
several, ask the person to choose among its candidates (the process
|
|
1736
|
+
"Find their LinkedIn" does this for one lead). (2) Read their
|
|
1737
|
+
linkedinConnection, which only the pod writes (never set it or
|
|
1738
|
+
linkedinInvitedAt): connected, nothing more; none or not set, ask to
|
|
1739
|
+
connect with linkedin_send_invite (it refuses someone already connected
|
|
1740
|
+
or already invited: then nothing more); pending, the draft waits for
|
|
1741
|
+
them; ignored, draft by email instead when they have an email address.
|
|
1742
|
+
While the draft waits for them to connect, set the lead's nextAction to
|
|
1743
|
+
"Wait for <name> to accept the LinkedIn request" and its nextActionAt 7
|
|
1744
|
+
days later. An email message needs none of this. START: a person approves the audience, the offer and
|
|
1745
|
+
the list,
|
|
1746
|
+
then reviews each proposed message in its channel and approves it;
|
|
1747
|
+
nothing goes out before they approve it, and the agent never sends.
|
|
1748
|
+
Once a message is sent, follow the cadence, so "Follow up on leads in
|
|
1749
|
+
outreach" drafts the next one. CADENCE: when a message is sent (a
|
|
1750
|
+
person approved its proposed message, so it is an outbound message in
|
|
1751
|
+
the channel), set its lead's lastContactedAt to the sent day, count
|
|
1752
|
+
the messages sent to the lead (the outbound messages in its channels,
|
|
1753
|
+
read with synap_get_channel mode person: for a company lead, the
|
|
1754
|
+
channels of the person written to there), and set the lead's next
|
|
1755
|
+
action: after the first message, nextAction "Follow up with <name> if
|
|
1756
|
+
no reply" and nextActionAt the sent day + 3 days; after the first
|
|
1757
|
+
follow-up, nextAction "Follow up with <name> a last time if no reply"
|
|
1758
|
+
and nextActionAt the sent day + 7 days; after the second follow-up,
|
|
1759
|
+
nextAction "No reply after two follow-ups: nurture or drop <name>" and
|
|
1760
|
+
nextActionAt the sent day + 7 days, and when that date passes with no
|
|
1761
|
+
reply, "Follow up on leads in outreach" proposes nurturing. Never a
|
|
1762
|
+
third follow-up. Propose moving a lead at new or researching to
|
|
1763
|
+
outreach through the status door, with its expected status. A reply
|
|
1764
|
+
stops the cadence: a reply is an inbound message after our last
|
|
1765
|
+
outbound one in the channel, and "Follow up on leads in outreach"
|
|
1766
|
+
reads it and proposes the next step. FINISH: when every person written
|
|
1767
|
+
to has replied or their follow-ups ended in silence, record in the
|
|
1768
|
+
campaign notes how many messages were sent (the outbound messages in
|
|
1769
|
+
their channels), how many got a reply and how many led to a booked
|
|
1770
|
+
meeting. Pausing or cancelling is a person's call.
|
|
1771
|
+
subjectProfile:
|
|
1772
|
+
profileSlug: campaign
|
|
1773
|
+
statusProperty: campaign-status
|
|
1774
|
+
humanOnlyStatuses: [paused, cancelled]
|
|
1775
|
+
activators:
|
|
1776
|
+
- { on: created, mode: propose }
|
|
1777
|
+
params:
|
|
1778
|
+
- { name: campaign, type: entity, label: Outreach campaign, required: true }
|
|
1779
|
+
- { name: channel, type: choice, label: Channel, options: [linkedin, email], defaultValue: linkedin }
|
|
1780
|
+
executor: is-agent
|
|
1781
|
+
status: active
|
|
1782
|
+
requiredIntents: [find_people, enrich_entity]
|
|
1783
|
+
grants: [entity.create]
|
|
1784
|
+
expectedOutputs:
|
|
1785
|
+
# Proposed messages on the members' channels (see "Draft a first message").
|
|
1786
|
+
- { kind: messaging, label: Proposed first messages, relationToSubject: none }
|
|
1787
|
+
stages:
|
|
1788
|
+
- key: prepare
|
|
1789
|
+
name: Prepare
|
|
1790
|
+
category: planned
|
|
1791
|
+
subjectStatus: planning
|
|
1792
|
+
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).
|
|
1793
|
+
criteria:
|
|
1794
|
+
- key: outreach-offer
|
|
1795
|
+
statement: The target audience, value proposition and call to action are written on the campaign.
|
|
1796
|
+
check: { kind: judge }
|
|
1797
|
+
- key: outreach-personal
|
|
1798
|
+
statement: Every drafted message names something specific to its person.
|
|
1799
|
+
check: { kind: judge }
|
|
1800
|
+
- key: start
|
|
1801
|
+
name: Person approves the list
|
|
1802
|
+
category: started
|
|
1803
|
+
subjectStatus: active
|
|
1804
|
+
goal: A person approves the audience, offer and list; then the person approves each proposed message in its channel, and the pod sends it.
|
|
1805
|
+
gate: { kind: human }
|
|
1806
|
+
onFail: { toStage: prepare }
|
|
1807
|
+
indefinite: true
|
|
1808
|
+
- key: finish
|
|
1809
|
+
name: Finish
|
|
1810
|
+
category: completed
|
|
1811
|
+
subjectStatus: completed
|
|
1812
|
+
goal: Record how many messages were sent, how many got a reply and how many led to a booked meeting.
|
|
1813
|
+
criteria:
|
|
1814
|
+
- key: outreach-reply-rate
|
|
1815
|
+
statement: The campaign notes record how many messages were sent, replied to, and led to a booked meeting.
|
|
1816
|
+
check: { kind: judge }
|
|
1817
|
+
|
|
1818
|
+
# ── Read the reply to an outreach message — one message's process ─────────
|
|
1819
|
+
# Was Outreach & Comms' "Send an outreach message" (folded 2026-10-23). The
|
|
1820
|
+
# agent no longer sends: the run starts once a PERSON marked the message
|
|
1821
|
+
# sent (`sent` is humanOnly on the lifecycle), and it only watches.
|
|
1822
|
+
# LEGACY SCOPE (send door, 2026-11-02): it watches only the outreach touches
|
|
1823
|
+
# a person marked sent; a message sent from a channel (messaging.send) is no
|
|
1824
|
+
# touch, and its reply is read by "Follow up on leads in outreach". It
|
|
1825
|
+
# retires with the outreach-touch kind once the touches are moved into their
|
|
1826
|
+
# channels (decision d5). It is the reader of a reply to a touch: a
|
|
1827
|
+
# positive reply proposes the next step (the
|
|
1828
|
+
# free estimate or a meeting) and never opens a deal (a deal exists only once
|
|
1829
|
+
# a person records a booked meeting). The next message after silence is
|
|
1830
|
+
# "Follow up on leads in outreach"'s, never this run's. `mode: run`: it
|
|
1831
|
+
# cannot send or commit anything, and its done/lost moves are proposed.
|
|
1832
|
+
# Next action (CRM review 2026-10-27): the next step it proposes after a
|
|
1833
|
+
# reply, and the new way to reach someone after a bounce, are written on the
|
|
1834
|
+
# lead facet as `nextAction` + `nextActionAt`, not only in the summary: the
|
|
1835
|
+
# CRM app's lead page shows the next action, and a process that leaves a
|
|
1836
|
+
# lead open without one leaves the person with nothing to do.
|
|
1837
|
+
# No response (founder decision 2026-10-27): the lead's next action date,
|
|
1838
|
+
# set by the CADENCE (3 days after the first message, 7 days after each
|
|
1839
|
+
# follow-up), passing in silence; after the second follow-up this run also
|
|
1840
|
+
# proposes nurturing, since no third follow-up is ever drafted.
|
|
1841
|
+
- name: Read the reply to an outreach message
|
|
1842
|
+
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.
|
|
1843
|
+
goalTemplate: >-
|
|
1844
|
+
Watch this sent outreach message ({touch}) for a reply; each stage
|
|
1845
|
+
moves its status. WAIT: watch its conversation. Set it bounced when it
|
|
1846
|
+
did not reach the person, or no response when its lead's next action
|
|
1847
|
+
date passes with no reply; that date is the cadence's (the sent day + 3
|
|
1848
|
+
days after the first message, the sent day + 7 days after each
|
|
1849
|
+
follow-up), so when the lead has none, count from this message's sent
|
|
1850
|
+
date. The next message is drafted by "Follow up on leads in outreach",
|
|
1851
|
+
not here. When this message is the second follow-up (the third message
|
|
1852
|
+
sent to the lead) and its date passes with no reply, mark it no
|
|
1853
|
+
response and propose moving the lead to nurturing through the status
|
|
1854
|
+
door, with its expected status; there is never a third follow-up.
|
|
1855
|
+
REPLIED: its
|
|
1856
|
+
check reads the reply. Summarise it for a person. When they show
|
|
1857
|
+
interest, propose the next step — prepare the free estimate ("Prepare a
|
|
1858
|
+
pre-sale estimate") or book a meeting — and propose moving the lead to
|
|
1859
|
+
engaged; when they decline, propose nurturing or disqualified with their
|
|
1860
|
+
reason (in `disqualificationReason`). Unless you propose dropping them,
|
|
1861
|
+
write the next step you propose on the lead as `nextAction`, one
|
|
1862
|
+
imperative line (e.g. "Book a meeting with Ana"; "Check in again" for
|
|
1863
|
+
nurturing), and `nextActionAt`, the date they named or today; never
|
|
1864
|
+
leave it only in the summary. BOUNCED: set
|
|
1865
|
+
the lead's `nextAction` to find another way to reach them and
|
|
1866
|
+
`nextActionAt` to today. Never open a deal: a deal is opened only when a
|
|
1867
|
+
person records a booked meeting. Never answer the reply yourself; a
|
|
1868
|
+
person does. Opened is recorded by a person.
|
|
1869
|
+
subjectProfile:
|
|
1870
|
+
profileSlug: outreach-touch
|
|
1871
|
+
statusProperty: status
|
|
1872
|
+
humanOnlyStatuses: [drafted, sent, opened]
|
|
1873
|
+
activators:
|
|
1874
|
+
- { on: enters_status, status: sent, mode: run }
|
|
1875
|
+
params:
|
|
1876
|
+
- { name: touch, type: entity, label: Outreach message, required: true }
|
|
1877
|
+
executor: is-agent
|
|
1878
|
+
status: active
|
|
1879
|
+
stages:
|
|
1880
|
+
- key: wait
|
|
1881
|
+
name: Waiting for a reply
|
|
1882
|
+
category: started
|
|
1883
|
+
goal: Watch the conversation for a reply, a bounce, or the follow-up date passing in silence.
|
|
1884
|
+
indefinite: true
|
|
1885
|
+
criteria:
|
|
1886
|
+
- key: touch-reply-recorded
|
|
1887
|
+
statement: A reply from the person is recorded in the conversation.
|
|
1888
|
+
check: { kind: judge }
|
|
1889
|
+
- key: replied
|
|
1890
|
+
name: Replied
|
|
1891
|
+
category: completed
|
|
1892
|
+
subjectStatus: replied
|
|
1893
|
+
goal: Summarise the reply for a person and propose the next step — the free estimate or a meeting; never open a deal.
|
|
1894
|
+
gate: { kind: check }
|
|
1895
|
+
onFail: { toStage: wait }
|
|
1896
|
+
criteria:
|
|
1897
|
+
- key: touch-next-step-proposed
|
|
1898
|
+
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.
|
|
1899
|
+
check: { kind: judge }
|
|
1900
|
+
- key: bounced
|
|
1901
|
+
name: Bounced
|
|
1902
|
+
category: canceled
|
|
1903
|
+
subjectStatus: bounced
|
|
1904
|
+
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.
|
|
1905
|
+
- key: no-response
|
|
1906
|
+
name: No response
|
|
1907
|
+
category: canceled
|
|
1908
|
+
subjectStatus: no_response
|
|
1909
|
+
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.
|
|
1910
|
+
|
|
1911
|
+
# ── Prepare a pre-sale estimate ──────────────────────────────────────────
|
|
1912
|
+
# CRM build plan A7 (founder Q8). A free one-page estimate for a lead,
|
|
1913
|
+
# before any deal. Generic: written in the lead's language and the company's
|
|
1914
|
+
# voice, from the offers in Foundation; amounts come from the pod's own won
|
|
1915
|
+
# deals for that offer or from the approver, never invented; the approver is
|
|
1916
|
+
# the pod's owner. After approval a person sends it, and the SENT step records
|
|
1917
|
+
# the date and the channel on the lead (`estimateSentAt`,
|
|
1918
|
+
# `estimateSentChannel`, above). A lifecycle READER: it never moves the lead.
|
|
1919
|
+
- name: Prepare a pre-sale estimate
|
|
1920
|
+
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.
|
|
1921
|
+
goalTemplate: >-
|
|
1922
|
+
Prepare a free pre-sale estimate for {lead}. Form answers, if any:
|
|
1923
|
+
{formAnswers}. RESEARCH: from the pod and public sources only (their
|
|
1924
|
+
website, company page, job posts, public posts), write what the company
|
|
1925
|
+
does, its size, the tools it uses and where its time goes; cite the
|
|
1926
|
+
source of every fact and leave out anything you cannot verify. ESTIMATE:
|
|
1927
|
+
one page in the lead's language and the company's voice from Foundation
|
|
1928
|
+
(outcomes for the buyer, no jargon), in six blocks: 1) what we
|
|
1929
|
+
understood, in 3 to 5 lines they can correct; 2) where their time and
|
|
1930
|
+
money go today; 3) what we would do, from the company's offers in
|
|
1931
|
+
Foundation, never an offer it does not have; 4) what it would gain them,
|
|
1932
|
+
as a RANGE with every assumption written next to it, never a single
|
|
1933
|
+
promised number; 5) scope, timeline and price: the offer's price model,
|
|
1934
|
+
with amounts from the company's past won deals for that offer, else left
|
|
1935
|
+
blank for the approver to set, never invented; 6) the next step.
|
|
1936
|
+
APPROVE: the pod's owner approves the estimate before it goes anywhere;
|
|
1937
|
+
nothing is sent without that yes. SENT: once a person has sent it,
|
|
1938
|
+
record on the lead the date and the channel it went out on, and its last
|
|
1939
|
+
contacted date.
|
|
1940
|
+
subjectProfile:
|
|
1941
|
+
profileSlug: lead
|
|
1942
|
+
activators:
|
|
1943
|
+
- { on: manual, mode: propose }
|
|
1944
|
+
params:
|
|
1945
|
+
- { name: lead, type: entity, label: Lead, required: true }
|
|
1946
|
+
- { name: formAnswers, type: text, label: What they wrote in the website form (empty for outbound), default: "" }
|
|
1947
|
+
executor: is-agent
|
|
1948
|
+
status: active
|
|
1949
|
+
requiredIntents: [search_external]
|
|
1950
|
+
stages:
|
|
1951
|
+
- key: research
|
|
1952
|
+
name: Research
|
|
1953
|
+
category: planned
|
|
1954
|
+
goal: Gather sourced facts about the company — what it does, its size, its tools, where its time goes.
|
|
1955
|
+
criteria:
|
|
1956
|
+
- key: estimate-facts-sourced
|
|
1957
|
+
statement: Every fact about the company names its source, and nothing unverified is presented as true.
|
|
1958
|
+
check: { kind: judge }
|
|
1959
|
+
- key: draft
|
|
1960
|
+
name: Draft the estimate
|
|
1961
|
+
category: started
|
|
1962
|
+
goal: Write the one-page estimate in its six blocks, with ranges and their assumptions, from the company's own offers.
|
|
1963
|
+
expectedOutputs:
|
|
1964
|
+
- { kind: document, label: Pre-sale estimate, relationToSubject: relates_to }
|
|
1965
|
+
criteria:
|
|
1966
|
+
- key: estimate-six-blocks
|
|
1967
|
+
statement: The estimate has all six blocks, in the lead's language.
|
|
1968
|
+
check: { kind: judge }
|
|
1969
|
+
- key: estimate-ranges
|
|
1970
|
+
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.
|
|
1971
|
+
check: { kind: judge }
|
|
1972
|
+
- key: approve
|
|
1973
|
+
name: Owner approves
|
|
1974
|
+
category: started
|
|
1975
|
+
goal: The pod's owner reads the estimate and approves it for sending, or sends it back with corrections.
|
|
1976
|
+
gate: { kind: human }
|
|
1977
|
+
onFail: { toStage: draft }
|
|
1978
|
+
- key: sent
|
|
1979
|
+
name: Sent
|
|
1980
|
+
category: completed
|
|
1981
|
+
goal: A person sends the approved estimate and says when and on which channel; record both on the lead with its last contacted date.
|
|
1982
|
+
gate: { kind: human }
|
|
1983
|
+
criteria:
|
|
1984
|
+
- key: estimate-sent-recorded
|
|
1985
|
+
statement: The lead records the date and the channel the estimate was sent on.
|
|
1986
|
+
check: { kind: judge }
|
|
1987
|
+
|
|
1988
|
+
# The lead requirement keeps "Qualify a lead" out of plain person/company
|
|
1989
|
+
# records (rendered by CRM and Browser through the shared entity-action runner).
|
|
1990
|
+
actionPlacements:
|
|
1991
|
+
- profileSlug: person
|
|
1992
|
+
surface: entity-detail
|
|
1993
|
+
kind: playbook
|
|
1994
|
+
ref: Qualify a lead
|
|
1995
|
+
label: Qualify lead
|
|
1996
|
+
when: { requiredFacetSlugs: [lead] }
|
|
1997
|
+
|
|
1998
|
+
- profileSlug: company
|
|
1999
|
+
surface: entity-detail
|
|
2000
|
+
kind: playbook
|
|
2001
|
+
ref: Qualify a lead
|
|
2002
|
+
label: Qualify lead
|
|
2003
|
+
when: { requiredFacetSlugs: [lead] }
|
|
2004
|
+
|
|
654
2005
|
# Apify powers the enrichment playbook (scrape public sources for a lead's
|
|
655
2006
|
# firmographics). Composed pod-wide at provisioning via the existing
|
|
656
2007
|
# integrations door; the user supplies the Apify API key once on install — a
|
|
657
2008
|
# missing key fails gracefully per-skill, not the whole workspace.
|
|
658
2009
|
integrations:
|
|
659
2010
|
- templateKey: apify
|
|
2011
|
+
# Folded from Outreach & Comms (2026-10-23): LinkedIn (Unipile) and email
|
|
2012
|
+
# (Google) are the accounts people are written to from. The CRM processes
|
|
2013
|
+
# never call their send verbs: every message goes through the pod's send
|
|
2014
|
+
# door (messaging.send, a proposed message the person approves), and the
|
|
2015
|
+
# pod's messaging webhook and history import record LinkedIn messages in the
|
|
2016
|
+
# people's channels (2026-11-02; they replace the CRM's LinkedIn ingest).
|
|
2017
|
+
- templateKey: unipile-linkedin
|
|
2018
|
+
- templateKey: nango-google
|
|
2019
|
+
|
|
2020
|
+
# ── taskIntents — WHAT THIS WORKSPACE NEEDS TO FUNCTION ───────────────────
|
|
2021
|
+
# Closed vocabulary: `@synap-core/types/capability-intents` (pod migrations
|
|
2022
|
+
# 0283/0284). DERIVED from the declared intents of this template's own
|
|
2023
|
+
# `integrations` capability packs — not hand-guessed. Checked at authoring
|
|
2024
|
+
# time by `validateTemplate` (`task-intent-unknown`) and by
|
|
2025
|
+
# `src/task-intents.test.ts`.
|
|
2026
|
+
taskIntents:
|
|
2027
|
+
- search_external
|
|
2028
|
+
- fetch_record
|
|
2029
|
+
- list_records
|
|
2030
|
+
- run_external_job
|
|
2031
|
+
- capture_into_pod
|
|
2032
|
+
- enrich_entity
|
|
2033
|
+
# From unipile-linkedin and nango-google (folded from Outreach & Comms).
|
|
2034
|
+
# send_message: the pod's own send door (Synap Core's messaging.send) and
|
|
2035
|
+
# Gmail; Unipile's send verbs no longer serve it (decision d6, 2026-11-02).
|
|
2036
|
+
- connect_account
|
|
2037
|
+
- send_message
|
|
2038
|
+
- request_connection
|
|
2039
|
+
- find_people
|
|
2040
|
+
- schedule_event
|
|
2041
|
+
- manage_file
|