@elevasis/ui 2.25.6 → 2.26.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (105) hide show
  1. package/dist/api/index.js +2 -2
  2. package/dist/app/index.css +15 -5
  3. package/dist/app/index.d.ts +61 -14
  4. package/dist/app/index.js +6 -6
  5. package/dist/charts/index.js +6 -5
  6. package/dist/chunk-3MEXPLWT.js +265 -0
  7. package/dist/{chunk-BDKM56TP.js → chunk-4KTLOK7K.js} +1 -1
  8. package/dist/{chunk-KMAXFJPH.js → chunk-CW3UNAF2.js} +5 -409
  9. package/dist/{chunk-HKBEURCV.js → chunk-G26INIF3.js} +1 -1
  10. package/dist/{chunk-7F3IQMLI.js → chunk-G66QFZXD.js} +11 -214
  11. package/dist/{chunk-QIW6OCEI.js → chunk-HLFFKKT3.js} +27 -373
  12. package/dist/chunk-JDNEWB5F.js +10 -0
  13. package/dist/{chunk-L7D6KNHV.js → chunk-JKBGDFX2.js} +890 -749
  14. package/dist/{chunk-YRKQNPK2.js → chunk-JPGX3533.js} +4 -3
  15. package/dist/chunk-KCGGA36K.js +73 -0
  16. package/dist/chunk-KEFWANZY.js +155 -0
  17. package/dist/chunk-LH4GPYDX.js +448 -0
  18. package/dist/{chunk-JXSBOG2R.js → chunk-LWKZ3BCC.js} +5 -4
  19. package/dist/chunk-OGXKOMUT.js +412 -0
  20. package/dist/chunk-OHXU5WWK.js +3731 -0
  21. package/dist/chunk-ONFKASZI.js +2004 -0
  22. package/dist/{chunk-U36X6NZM.js → chunk-RIFTUOPE.js} +2 -14
  23. package/dist/{chunk-T6INEVX6.js → chunk-SGS4CQ2B.js} +1 -1
  24. package/dist/{chunk-C7IBFI5B.js → chunk-UPMX5GJI.js} +5 -5
  25. package/dist/{chunk-ARJPZ66V.js → chunk-UY5I2KOZ.js} +208 -3124
  26. package/dist/chunk-W2ZTLH7Y.js +662 -0
  27. package/dist/{chunk-KNISO652.js → chunk-WUVR4QY6.js} +9 -9
  28. package/dist/{chunk-Q5BEODAT.js → chunk-X2SUMO3P.js} +2 -1
  29. package/dist/{chunk-SNHGSCKH.js → chunk-XBMCDGHA.js} +1 -1
  30. package/dist/{chunk-N55DVMAG.js → chunk-XQQEKWTL.js} +2 -6
  31. package/dist/{chunk-SBQ4MYQV.js → chunk-XZSEPJZQ.js} +5 -6
  32. package/dist/{chunk-CPAJXBTL.js → chunk-YHBPR67D.js} +490 -676
  33. package/dist/{chunk-QARSVM7Q.js → chunk-YO2YORW4.js} +4 -4
  34. package/dist/{chunk-TAIX4NO3.js → chunk-ZFLM2YVW.js} +2 -2
  35. package/dist/components/index.css +15 -5
  36. package/dist/components/index.d.ts +202 -383
  37. package/dist/components/index.js +43 -429
  38. package/dist/components/navigation/index.css +25 -15
  39. package/dist/execution/index.d.ts +0 -73
  40. package/dist/features/auth/index.css +25 -15
  41. package/dist/features/crm/index.css +25 -15
  42. package/dist/features/crm/index.d.ts +49 -49
  43. package/dist/features/crm/index.js +14 -15
  44. package/dist/features/dashboard/index.css +25 -15
  45. package/dist/features/dashboard/index.js +18 -16
  46. package/dist/features/delivery/index.css +15 -5
  47. package/dist/features/delivery/index.js +14 -15
  48. package/dist/features/knowledge/index.css +611 -0
  49. package/dist/features/knowledge/index.js +375 -72
  50. package/dist/features/lead-gen/index.css +25 -15
  51. package/dist/features/lead-gen/index.d.ts +60 -21
  52. package/dist/features/lead-gen/index.js +16 -16
  53. package/dist/features/monitoring/index.css +15 -5
  54. package/dist/features/monitoring/index.js +17 -17
  55. package/dist/features/monitoring/requests/index.css +25 -15
  56. package/dist/features/monitoring/requests/index.js +13 -14
  57. package/dist/features/operations/index.css +25 -15
  58. package/dist/features/operations/index.d.ts +16 -98
  59. package/dist/features/operations/index.js +26 -22
  60. package/dist/features/settings/index.css +25 -15
  61. package/dist/features/settings/index.d.ts +1 -0
  62. package/dist/features/settings/index.js +15 -16
  63. package/dist/hooks/delivery/index.css +25 -15
  64. package/dist/hooks/delivery/index.js +2 -2
  65. package/dist/hooks/index.css +15 -5
  66. package/dist/hooks/index.d.ts +172 -380
  67. package/dist/hooks/index.js +13 -14
  68. package/dist/hooks/published.css +15 -5
  69. package/dist/hooks/published.d.ts +172 -380
  70. package/dist/hooks/published.js +13 -14
  71. package/dist/index.css +15 -5
  72. package/dist/index.d.ts +988 -403
  73. package/dist/index.js +15 -15
  74. package/dist/initialization/index.d.ts +1 -0
  75. package/dist/knowledge/index.d.ts +981 -41
  76. package/dist/knowledge/index.js +5449 -294
  77. package/dist/layout/index.d.ts +2 -0
  78. package/dist/layout/index.js +3 -2
  79. package/dist/organization/index.css +25 -15
  80. package/dist/organization/index.d.ts +1 -0
  81. package/dist/provider/index.css +25 -15
  82. package/dist/provider/index.d.ts +818 -26
  83. package/dist/provider/index.js +11 -11
  84. package/dist/provider/published.css +25 -15
  85. package/dist/provider/published.d.ts +817 -25
  86. package/dist/provider/published.js +8 -9
  87. package/dist/test-utils/index.js +2 -2
  88. package/dist/test-utils/setup.js +1 -1
  89. package/dist/theme/index.js +3 -2
  90. package/dist/theme/presets/index.d.ts +97 -0
  91. package/dist/theme/presets/index.js +3 -0
  92. package/dist/types/index.d.ts +71 -126
  93. package/dist/utils/index.js +1 -1
  94. package/dist/vite/index.d.ts +7 -0
  95. package/dist/vite/index.js +10 -0
  96. package/dist/vite-plugin-knowledge/index.d.ts +1 -33
  97. package/dist/vite-plugin-knowledge/index.js +1 -66
  98. package/package.json +16 -3
  99. package/src/knowledge/README.md +8 -8
  100. package/src/theme/presets/README.md +19 -0
  101. package/dist/chunk-5RLYII6P.js +0 -314
  102. package/dist/chunk-6U7AIIHF.js +0 -880
  103. package/dist/chunk-HAEJ4M54.js +0 -94
  104. package/dist/chunk-LPM7O6XM.js +0 -293
  105. /package/dist/{chunk-SGXXJE52.js → chunk-QD4X4H5A.js} +0 -0
@@ -0,0 +1,2004 @@
1
+ import { SemanticIcon } from './chunk-KEFWANZY.js';
2
+ import { Stack, Group, Title, Text, Box, Divider, useTree, Tree, UnstyledButton, TextInput } from '@mantine/core';
3
+ import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
4
+ import { useMemo, useState, useRef, useEffect } from 'react';
5
+ import { useClipboard } from '@mantine/hooks';
6
+ import { IconChevronDown, IconChevronRight, IconX, IconSearch, IconCheck, IconCopy } from '@tabler/icons-react';
7
+
8
+ // src/knowledge/iconTokens.ts
9
+ var KNOWLEDGE_ICON_TOKEN_BY_KIND = {
10
+ playbook: "knowledge.playbook",
11
+ strategy: "knowledge.strategy",
12
+ reference: "knowledge.reference"
13
+ };
14
+ function getKnowledgeIconToken(node) {
15
+ return node.icon ?? KNOWLEDGE_ICON_TOKEN_BY_KIND[node.kind];
16
+ }
17
+
18
+ // ../core/src/knowledge/queries.ts
19
+ function buildKnowledgeSourceIdMap(graph) {
20
+ const map = /* @__PURE__ */ new Map();
21
+ for (const node of graph.nodes) {
22
+ if (node.kind === "knowledge" && node.sourceId) {
23
+ map.set(node.id, node.sourceId);
24
+ }
25
+ }
26
+ return map;
27
+ }
28
+ function byFeature(graph, featureId, knowledgeNodes) {
29
+ const targetGraphNodeId = `feature:${featureId}`;
30
+ const governingKnowledgeNodeIds = /* @__PURE__ */ new Set();
31
+ for (const edge of graph.edges) {
32
+ if (edge.kind === "governs" && edge.targetId === targetGraphNodeId && edge.sourceId.startsWith("knowledge:")) {
33
+ governingKnowledgeNodeIds.add(edge.sourceId);
34
+ }
35
+ }
36
+ const sourceIdMap = buildKnowledgeSourceIdMap(graph);
37
+ const matchingOmIds = /* @__PURE__ */ new Set();
38
+ for (const graphNodeId of governingKnowledgeNodeIds) {
39
+ const omId = sourceIdMap.get(graphNodeId);
40
+ if (omId) matchingOmIds.add(omId);
41
+ }
42
+ return knowledgeNodes.filter((n) => matchingOmIds.has(n.id));
43
+ }
44
+ function byKind(_graph, kind, knowledgeNodes) {
45
+ return knowledgeNodes.filter((n) => n.kind === kind);
46
+ }
47
+ var KIND_CHIP_TONE_STYLES = {
48
+ subtle: {
49
+ backgroundColor: "var(--color-surface-hover)",
50
+ borderColor: "var(--color-border)",
51
+ color: "var(--color-text-dimmed)"
52
+ },
53
+ primary: {
54
+ backgroundColor: "var(--surface-primary-subtle)",
55
+ borderColor: "color-mix(in srgb, var(--color-primary) 28%, var(--color-border))",
56
+ color: "var(--color-primary)"
57
+ },
58
+ muted: {
59
+ backgroundColor: "var(--color-surface)",
60
+ borderColor: "var(--color-border)",
61
+ color: "var(--color-text-subtle)"
62
+ }
63
+ };
64
+ function KindChip({ kind, tone = "subtle", style, ...spanProps }) {
65
+ return /* @__PURE__ */ jsx(
66
+ "span",
67
+ {
68
+ ...spanProps,
69
+ "data-kind-chip": true,
70
+ "data-tone": tone,
71
+ style: {
72
+ display: "inline-flex",
73
+ alignItems: "center",
74
+ width: "fit-content",
75
+ maxWidth: "100%",
76
+ padding: "2px 8px",
77
+ border: "1px solid",
78
+ borderRadius: "var(--mantine-radius-sm)",
79
+ fontFamily: "var(--mantine-font-family)",
80
+ fontSize: "var(--mantine-font-size-xs)",
81
+ fontWeight: 600,
82
+ lineHeight: 1.2,
83
+ letterSpacing: 0,
84
+ whiteSpace: "nowrap",
85
+ transition: "background-color var(--duration-fast) var(--easing), border-color var(--duration-fast) var(--easing)",
86
+ ...KIND_CHIP_TONE_STYLES[tone],
87
+ ...style
88
+ },
89
+ children: kind
90
+ }
91
+ );
92
+ }
93
+ function NodeHeader({
94
+ title,
95
+ kind,
96
+ tone = "primary",
97
+ iconToken,
98
+ description,
99
+ updatedAt,
100
+ rightSection
101
+ }) {
102
+ return /* @__PURE__ */ jsxs(Stack, { gap: "xs", children: [
103
+ /* @__PURE__ */ jsxs(Group, { gap: "xs", align: "flex-start", wrap: "nowrap", children: [
104
+ iconToken && /* @__PURE__ */ jsx(
105
+ SemanticIcon,
106
+ {
107
+ token: iconToken,
108
+ fallbackKind: "knowledge",
109
+ size: 22,
110
+ style: { color: "var(--color-primary)", marginTop: 5 }
111
+ }
112
+ ),
113
+ /* @__PURE__ */ jsx(Title, { order: 3, style: { flex: 1, minWidth: 0, color: "var(--color-text)" }, children: title }),
114
+ /* @__PURE__ */ jsx(KindChip, { kind, tone, style: { flexShrink: 0, marginTop: 4 } }),
115
+ rightSection
116
+ ] }),
117
+ description && /* @__PURE__ */ jsx(Text, { size: "sm", c: "dimmed", style: { lineHeight: 1.5 }, children: description }),
118
+ updatedAt && /* @__PURE__ */ jsxs(Text, { size: "xs", c: "dimmed", style: { opacity: 0.7 }, children: [
119
+ "Updated ",
120
+ updatedAt
121
+ ] })
122
+ ] });
123
+ }
124
+ function EdgeChip({ id, onClick }) {
125
+ return /* @__PURE__ */ jsx(
126
+ "div",
127
+ {
128
+ role: onClick ? "button" : void 0,
129
+ tabIndex: onClick ? 0 : void 0,
130
+ onClick,
131
+ onKeyDown: onClick ? (e) => (e.key === "Enter" || e.key === " ") && onClick() : void 0,
132
+ style: {
133
+ display: "inline-flex",
134
+ alignItems: "center",
135
+ padding: "2px 8px",
136
+ borderRadius: "var(--mantine-radius-sm)",
137
+ backgroundColor: "var(--color-surface)",
138
+ border: "1px solid var(--color-border)",
139
+ fontSize: "var(--mantine-font-size-xs)",
140
+ color: onClick ? "var(--color-primary)" : "var(--color-text-subtle)",
141
+ cursor: onClick ? "pointer" : "default",
142
+ fontFamily: "var(--mantine-font-family-monospace)",
143
+ width: "fit-content",
144
+ background: "none"
145
+ },
146
+ children: id
147
+ }
148
+ );
149
+ }
150
+ function EdgeGroup({ label, ids, onNavigateToNode }) {
151
+ return /* @__PURE__ */ jsxs(Stack, { gap: 4, children: [
152
+ /* @__PURE__ */ jsx(Text, { size: "xs", c: "dimmed", fw: 500, children: label }),
153
+ ids.map((id) => /* @__PURE__ */ jsx(EdgeChip, { id, onClick: onNavigateToNode ? () => onNavigateToNode(id) : void 0 }, id))
154
+ ] });
155
+ }
156
+ function knowledgeNodeIdFromGraphNode(graphNode, graphNodeId) {
157
+ if (graphNode?.sourceId) return graphNode.sourceId;
158
+ return graphNodeId.startsWith("knowledge:") ? graphNodeId.slice("knowledge:".length) : graphNodeId;
159
+ }
160
+ function RelatedKnowledgeSection({
161
+ nodeId,
162
+ graph,
163
+ knowledgeNodes,
164
+ onNavigateToNode
165
+ }) {
166
+ const knowledgeById = new Map(knowledgeNodes.map((node) => [node.id, node]));
167
+ const cards = graph.edges.filter((edge) => edge.kind === "governs" && edge.targetId === nodeId).map((edge) => {
168
+ const graphNode = graph.nodes.find((node) => node.id === edge.sourceId && node.kind === "knowledge");
169
+ const knowledgeNode = knowledgeById.get(knowledgeNodeIdFromGraphNode(graphNode, edge.sourceId));
170
+ return knowledgeNode ? { graphNodeId: edge.sourceId, node: knowledgeNode } : null;
171
+ }).filter((card) => card !== null);
172
+ if (cards.length === 0) return null;
173
+ return /* @__PURE__ */ jsxs(Stack, { gap: "xs", "data-related-knowledge-section": true, children: [
174
+ /* @__PURE__ */ jsx(Text, { size: "xs", fw: 600, tt: "uppercase", c: "dimmed", style: { letterSpacing: "0.05em" }, children: "Governing Knowledge" }),
175
+ /* @__PURE__ */ jsx(Stack, { gap: "xs", children: cards.map(({ graphNodeId, node }) => /* @__PURE__ */ jsx(
176
+ Box,
177
+ {
178
+ component: onNavigateToNode ? "button" : "article",
179
+ type: onNavigateToNode ? "button" : void 0,
180
+ onClick: onNavigateToNode ? () => onNavigateToNode(graphNodeId) : void 0,
181
+ style: {
182
+ display: "block",
183
+ width: "100%",
184
+ padding: "var(--mantine-spacing-sm)",
185
+ border: "1px solid var(--color-border)",
186
+ borderRadius: "var(--mantine-radius-sm)",
187
+ background: "var(--color-surface)",
188
+ color: "var(--color-text)",
189
+ textAlign: "left",
190
+ cursor: onNavigateToNode ? "pointer" : "default",
191
+ font: "inherit"
192
+ },
193
+ children: /* @__PURE__ */ jsxs(Stack, { gap: 4, children: [
194
+ /* @__PURE__ */ jsxs(Group, { gap: "xs", align: "flex-start", wrap: "nowrap", children: [
195
+ /* @__PURE__ */ jsx(
196
+ SemanticIcon,
197
+ {
198
+ token: getKnowledgeIconToken(node),
199
+ fallbackKind: node.kind,
200
+ size: 16,
201
+ style: { color: "var(--color-text-subtle)", marginTop: 1 }
202
+ }
203
+ ),
204
+ /* @__PURE__ */ jsx(Text, { size: "sm", fw: 600, style: { flex: 1, minWidth: 0, lineHeight: 1.35 }, children: node.title }),
205
+ /* @__PURE__ */ jsx(KindChip, { kind: node.kind, tone: "muted", style: { flexShrink: 0 } })
206
+ ] }),
207
+ /* @__PURE__ */ jsx(Text, { size: "xs", c: "dimmed", style: { lineHeight: 1.45 }, children: node.summary })
208
+ ] })
209
+ },
210
+ node.id
211
+ )) })
212
+ ] });
213
+ }
214
+ function relatedIds(graph, nodeId, edgeKind, direction, targetKind) {
215
+ return graph.edges.filter(
216
+ (edge) => direction === "outgoing" ? edge.kind === edgeKind && edge.sourceId === nodeId : edge.kind === edgeKind && edge.targetId === nodeId
217
+ ).map((edge) => direction === "outgoing" ? edge.targetId : edge.sourceId).filter((id) => {
218
+ if (!targetKind) return true;
219
+ return graph.nodes.find((node) => node.id === id)?.kind === targetKind;
220
+ });
221
+ }
222
+ function EdgeRelationshipGroup({ nodeId, graph, knowledgeNodes, onNavigateToNode }) {
223
+ const subFeatures = relatedIds(graph, nodeId, "contains", "outgoing", "feature");
224
+ const resourcesUsed = relatedIds(graph, nodeId, "uses", "outgoing", "resource");
225
+ const entities = relatedIds(graph, nodeId, "operates-on", "outgoing", "entity");
226
+ const governedTargets = relatedIds(graph, nodeId, "governs", "outgoing");
227
+ const governingKnowledge = relatedIds(graph, nodeId, "governs", "incoming", "knowledge");
228
+ const hasRelationships = subFeatures.length > 0 || resourcesUsed.length > 0 || entities.length > 0 || governedTargets.length > 0 || governingKnowledge.length > 0;
229
+ if (!hasRelationships) return null;
230
+ return /* @__PURE__ */ jsxs(Stack, { gap: "xs", "data-edge-relationship-group": true, children: [
231
+ /* @__PURE__ */ jsx(Divider, {}),
232
+ /* @__PURE__ */ jsx(Text, { size: "xs", fw: 600, tt: "uppercase", c: "dimmed", style: { letterSpacing: "0.05em" }, children: "Relationships" }),
233
+ subFeatures.length > 0 && /* @__PURE__ */ jsx(
234
+ EdgeGroup,
235
+ {
236
+ label: `Sub-features (${subFeatures.length})`,
237
+ ids: subFeatures,
238
+ onNavigateToNode
239
+ }
240
+ ),
241
+ resourcesUsed.length > 0 && /* @__PURE__ */ jsx(
242
+ EdgeGroup,
243
+ {
244
+ label: `Resources used (${resourcesUsed.length})`,
245
+ ids: resourcesUsed,
246
+ onNavigateToNode
247
+ }
248
+ ),
249
+ entities.length > 0 && /* @__PURE__ */ jsx(EdgeGroup, { label: `Entities (${entities.length})`, ids: entities, onNavigateToNode }),
250
+ governedTargets.length > 0 && /* @__PURE__ */ jsx(
251
+ EdgeGroup,
252
+ {
253
+ label: `Governs (${governedTargets.length})`,
254
+ ids: governedTargets,
255
+ onNavigateToNode
256
+ }
257
+ ),
258
+ governingKnowledge.length > 0 && (knowledgeNodes ? /* @__PURE__ */ jsx(
259
+ RelatedKnowledgeSection,
260
+ {
261
+ nodeId,
262
+ graph,
263
+ knowledgeNodes,
264
+ onNavigateToNode
265
+ }
266
+ ) : /* @__PURE__ */ jsx(
267
+ EdgeGroup,
268
+ {
269
+ label: `Governing knowledge (${governingKnowledge.length})`,
270
+ ids: governingKnowledge,
271
+ onNavigateToNode
272
+ }
273
+ ))
274
+ ] });
275
+ }
276
+ var PRESENTATION_METADATA_KEYS = /* @__PURE__ */ new Set(["label", "description"]);
277
+ function formatMetadataLabel(key) {
278
+ return key.replace(/([a-z0-9])([A-Z])/g, "$1 $2").replace(/^./, (char) => char.toUpperCase());
279
+ }
280
+ function NodeMetadataFooter({ node, title = "Technical metadata" }) {
281
+ const entries = Object.entries(node).filter(
282
+ ([key, value]) => !PRESENTATION_METADATA_KEYS.has(key) && value !== void 0 && value !== null && typeof value !== "object"
283
+ );
284
+ if (entries.length === 0) return null;
285
+ return /* @__PURE__ */ jsxs(
286
+ Box,
287
+ {
288
+ component: "details",
289
+ style: {
290
+ borderTop: "1px solid var(--color-border)",
291
+ paddingTop: "var(--mantine-spacing-sm)"
292
+ },
293
+ children: [
294
+ /* @__PURE__ */ jsx(
295
+ Box,
296
+ {
297
+ component: "summary",
298
+ style: {
299
+ cursor: "pointer",
300
+ color: "var(--color-text-dimmed)",
301
+ fontSize: "var(--mantine-font-size-xs)",
302
+ fontWeight: 600,
303
+ letterSpacing: "0.05em",
304
+ textTransform: "uppercase"
305
+ },
306
+ children: title
307
+ }
308
+ ),
309
+ /* @__PURE__ */ jsx(Stack, { gap: 4, style: { paddingTop: "var(--mantine-spacing-xs)" }, children: entries.map(([key, value]) => /* @__PURE__ */ jsx(KeyField, { label: formatMetadataLabel(key), value: String(value) }, key)) })
310
+ ]
311
+ }
312
+ );
313
+ }
314
+ function NodeDescribeShell({ header, content, relationships, footer }) {
315
+ return /* @__PURE__ */ jsxs(Stack, { gap: "md", style: { padding: "var(--mantine-spacing-md)", width: "100%" }, children: [
316
+ header,
317
+ content && /* @__PURE__ */ jsxs(Fragment, { children: [
318
+ header && /* @__PURE__ */ jsx(Divider, {}),
319
+ content
320
+ ] }),
321
+ relationships,
322
+ footer
323
+ ] });
324
+ }
325
+ function KeyField({ label, value }) {
326
+ return /* @__PURE__ */ jsxs(Group, { gap: "xs", align: "baseline", children: [
327
+ /* @__PURE__ */ jsx(Text, { size: "xs", fw: 500, c: "dimmed", style: { minWidth: 96 }, children: label }),
328
+ /* @__PURE__ */ jsx(Text, { size: "sm", style: { fontFamily: "var(--mantine-font-family-monospace)" }, children: value })
329
+ ] });
330
+ }
331
+ var FEATURE_PREFIX = "feat:";
332
+ var LEAF_PREFIX = "leaf:";
333
+ var FOLDER_PREFIX = "folder:";
334
+ var GROUP_THRESHOLD = 8;
335
+ var FOLDER_ORDER = [
336
+ "campaigns",
337
+ "pipeline",
338
+ "targeting",
339
+ "channels",
340
+ "proof",
341
+ "references",
342
+ "playbooks",
343
+ "strategies"
344
+ ];
345
+ var FOLDER_LABELS = {
346
+ campaigns: "Campaigns",
347
+ pipeline: "Pipeline",
348
+ targeting: "Targeting and Strategy",
349
+ channels: "Channels",
350
+ proof: "Proof",
351
+ references: "References",
352
+ playbooks: "Playbooks",
353
+ strategies: "Strategies"
354
+ };
355
+ function toBareFeatureId(graphNodeId) {
356
+ return graphNodeId.startsWith("feature:") ? graphNodeId.slice("feature:".length) : graphNodeId;
357
+ }
358
+ function topLevelFeatureIds(allBareIds) {
359
+ return allBareIds.filter((id) => !id.includes("."));
360
+ }
361
+ function childFeatureIds(allBareIds, parentId) {
362
+ const prefix = `${parentId}.`;
363
+ return allBareIds.filter((id) => id.startsWith(prefix) && !id.slice(prefix.length).includes("."));
364
+ }
365
+ function featureHasKnowledgeDescendant(bareId, allBareIds, graph, knowledgeNodes) {
366
+ if (byFeature(graph, bareId, knowledgeNodes).length > 0) return true;
367
+ return childFeatureIds(allBareIds, bareId).some(
368
+ (childId) => featureHasKnowledgeDescendant(childId, allBareIds, graph, knowledgeNodes)
369
+ );
370
+ }
371
+ function buildFeatureMetaMap(graph) {
372
+ const map = {};
373
+ for (const node of graph.nodes) {
374
+ if (node.kind === "feature") {
375
+ map[toBareFeatureId(node.id)] = { label: node.label, icon: node.icon, node };
376
+ }
377
+ }
378
+ return map;
379
+ }
380
+ function getKnowledgeReadCommand(nodeId) {
381
+ return `/knowledge read ${nodeId}`;
382
+ }
383
+ function getKnowledgeReadCommands(nodeIds) {
384
+ return [...new Set(nodeIds)].map(getKnowledgeReadCommand).join("\n");
385
+ }
386
+ function getKnowledgeReadFeatureFolderCommand(bareFeatureId) {
387
+ return `/knowledge read-folder feature:${bareFeatureId}`;
388
+ }
389
+ function getKnowledgeFolderKey(node) {
390
+ const key = `${node.id} ${node.title}`.toLowerCase();
391
+ if (/\b(stage|booking|discovery|pipeline|communications?)\b/.test(key)) return "pipeline";
392
+ if (/\b(upwork scanning|youtube|obs|recording|reddit|social)\b/.test(key)) return "channels";
393
+ if (/\b(testimonials?|case stud|proof)\b/.test(key)) return "proof";
394
+ if (/\b(target|personalization|vertical|calibration|query|research|strategy)\b/.test(key)) return "targeting";
395
+ if (/\b(outreach|campaign|copy|handoff|bounce|reply|lead-gen playbook)\b/.test(key)) return "campaigns";
396
+ if (node.kind === "reference") return "references";
397
+ if (node.kind === "strategy") return "strategies";
398
+ return "playbooks";
399
+ }
400
+ function sortFolderKeys(left, right) {
401
+ const leftIndex = FOLDER_ORDER.indexOf(left);
402
+ const rightIndex = FOLDER_ORDER.indexOf(right);
403
+ if (leftIndex === -1 && rightIndex === -1) return left.localeCompare(right);
404
+ if (leftIndex === -1) return 1;
405
+ if (rightIndex === -1) return -1;
406
+ return leftIndex - rightIndex;
407
+ }
408
+ function createKnowledgeLeaf(bareId, node) {
409
+ return {
410
+ value: `${LEAF_PREFIX}${bareId}::${node.id}`,
411
+ label: node.title,
412
+ nodeType: "leaf",
413
+ knowledgeNodeId: node.id,
414
+ knowledgeNodeIds: [node.id]
415
+ };
416
+ }
417
+ function createKnowledgeLeaves(bareId, nodes) {
418
+ if (nodes.length < GROUP_THRESHOLD) {
419
+ return nodes.map((node) => createKnowledgeLeaf(bareId, node));
420
+ }
421
+ const grouped = /* @__PURE__ */ new Map();
422
+ for (const node of nodes) {
423
+ const folderKey = getKnowledgeFolderKey(node);
424
+ const folderNodes = grouped.get(folderKey) ?? [];
425
+ folderNodes.push(node);
426
+ grouped.set(folderKey, folderNodes);
427
+ }
428
+ return [...grouped.entries()].sort(([left], [right]) => sortFolderKeys(left, right)).map(([folderKey, folderNodes]) => ({
429
+ value: `${FOLDER_PREFIX}${bareId}::${folderKey}`,
430
+ label: FOLDER_LABELS[folderKey] ?? folderKey,
431
+ nodeType: "folder",
432
+ icon: "knowledge.playbook",
433
+ knowledgeNodeIds: folderNodes.map((node) => node.id),
434
+ children: folderNodes.map((node) => createKnowledgeLeaf(bareId, node))
435
+ }));
436
+ }
437
+ function collectKnowledgeNodeIds(node) {
438
+ if (node.knowledgeNodeIds) return node.knowledgeNodeIds;
439
+ return (node.children ?? []).flatMap(collectKnowledgeNodeIds);
440
+ }
441
+ function buildFeatureTreeNode(bareId, allBareIds, featureMetaMap, graph, knowledgeNodes) {
442
+ const governing = byFeature(graph, bareId, knowledgeNodes);
443
+ const childIds = childFeatureIds(allBareIds, bareId);
444
+ const childFeatureNodes = childIds.filter((childId) => featureHasKnowledgeDescendant(childId, allBareIds, graph, knowledgeNodes)).map((childId) => buildFeatureTreeNode(childId, allBareIds, featureMetaMap, graph, knowledgeNodes));
445
+ const knowledgeLeaves = createKnowledgeLeaves(bareId, governing);
446
+ const children = [...childFeatureNodes, ...knowledgeLeaves];
447
+ const meta = featureMetaMap[bareId];
448
+ return {
449
+ value: `${FEATURE_PREFIX}${bareId}`,
450
+ label: meta?.label ?? bareId,
451
+ nodeType: "feature",
452
+ icon: meta?.icon,
453
+ knowledgeNodeIds: children.flatMap(collectKnowledgeNodeIds),
454
+ children: children.length > 0 ? children : void 0
455
+ };
456
+ }
457
+ function collectExpandableValues(nodes, acc = {}) {
458
+ for (const node of nodes) {
459
+ if (typeof node.value === "string" && (node.value.startsWith(FEATURE_PREFIX) || node.value.startsWith(FOLDER_PREFIX))) {
460
+ acc[node.value] = true;
461
+ }
462
+ if (node.children) {
463
+ collectExpandableValues(node.children, acc);
464
+ }
465
+ }
466
+ return acc;
467
+ }
468
+ function KnowledgeTree({
469
+ graph,
470
+ knowledgeNodes,
471
+ onSelectNode,
472
+ onSelectGraphNode,
473
+ selectedNodeId
474
+ }) {
475
+ const treeData = useMemo(() => {
476
+ const featureMetaMap = buildFeatureMetaMap(graph);
477
+ const allBareIds = Object.keys(featureMetaMap);
478
+ const topIds = topLevelFeatureIds(allBareIds);
479
+ return topIds.filter((bareId) => featureHasKnowledgeDescendant(bareId, allBareIds, graph, knowledgeNodes)).map((bareId) => buildFeatureTreeNode(bareId, allBareIds, featureMetaMap, graph, knowledgeNodes));
480
+ }, [graph, knowledgeNodes]);
481
+ const initialExpandedState = useMemo(() => collectExpandableValues(treeData), [treeData]);
482
+ const treeController = useTree({ initialExpandedState });
483
+ const leafNodeMap = useMemo(() => {
484
+ const map = /* @__PURE__ */ new Map();
485
+ for (const node of knowledgeNodes) {
486
+ for (const graphNode of graph.nodes) {
487
+ if (graphNode.kind === "feature") {
488
+ const bareId = toBareFeatureId(graphNode.id);
489
+ map.set(`${LEAF_PREFIX}${bareId}::${node.id}`, node);
490
+ }
491
+ }
492
+ }
493
+ return map;
494
+ }, [graph, knowledgeNodes]);
495
+ const featureKnowledgeCountMap = useMemo(() => {
496
+ const counts = /* @__PURE__ */ new Map();
497
+ for (const graphNode of graph.nodes) {
498
+ if (graphNode.kind === "feature") {
499
+ const bareId = toBareFeatureId(graphNode.id);
500
+ counts.set(bareId, byFeature(graph, bareId, knowledgeNodes).length);
501
+ }
502
+ }
503
+ return counts;
504
+ }, [graph, knowledgeNodes]);
505
+ const featureGraphNodeMap = useMemo(() => {
506
+ const map = /* @__PURE__ */ new Map();
507
+ for (const graphNode of graph.nodes) {
508
+ if (graphNode.kind === "feature") {
509
+ map.set(toBareFeatureId(graphNode.id), graphNode);
510
+ }
511
+ }
512
+ return map;
513
+ }, [graph]);
514
+ if (treeData.length === 0) {
515
+ return /* @__PURE__ */ jsx(Text, { size: "sm", c: "dimmed", style: { padding: "var(--mantine-spacing-md)" }, children: "No features in graph." });
516
+ }
517
+ return /* @__PURE__ */ jsx(
518
+ Tree,
519
+ {
520
+ data: treeData,
521
+ tree: treeController,
522
+ style: { padding: "var(--mantine-spacing-xs)" },
523
+ renderNode: ({ node, expanded, hasChildren, elementProps }) => {
524
+ const value = node.value;
525
+ const typedNode = node;
526
+ if (typeof value === "string" && value.startsWith(LEAF_PREFIX)) {
527
+ const knowledgeNode = leafNodeMap.get(value);
528
+ if (!knowledgeNode) return null;
529
+ return /* @__PURE__ */ jsx(
530
+ KnowledgeLeafRow,
531
+ {
532
+ elementProps,
533
+ knowledgeNode,
534
+ isActive: knowledgeNode.id === selectedNodeId,
535
+ onSelectNode
536
+ }
537
+ );
538
+ }
539
+ if (typeof value === "string" && value.startsWith(FOLDER_PREFIX)) {
540
+ return /* @__PURE__ */ jsx(
541
+ DirectoryRow,
542
+ {
543
+ elementProps,
544
+ expanded,
545
+ hasChildren,
546
+ label: String(node.label),
547
+ iconToken: typedNode.icon,
548
+ count: typedNode.knowledgeNodeIds?.length ?? 0,
549
+ command: getKnowledgeReadCommands(typedNode.knowledgeNodeIds ?? []),
550
+ isActive: false
551
+ }
552
+ );
553
+ }
554
+ const bareId = typeof value === "string" && value.startsWith(FEATURE_PREFIX) ? value.slice(FEATURE_PREFIX.length) : String(value);
555
+ const count = featureKnowledgeCountMap.get(bareId) ?? 0;
556
+ const graphNode = featureGraphNodeMap.get(bareId);
557
+ const isActive = graphNode !== void 0 && (selectedNodeId === graphNode.id || selectedNodeId === graphNode.sourceId || selectedNodeId === bareId);
558
+ return /* @__PURE__ */ jsx(
559
+ DirectoryRow,
560
+ {
561
+ elementProps,
562
+ expanded,
563
+ hasChildren,
564
+ label: String(node.label),
565
+ iconToken: typedNode.icon,
566
+ count,
567
+ command: getKnowledgeReadFeatureFolderCommand(bareId),
568
+ isActive,
569
+ uppercase: true,
570
+ onSelectGraphNode: graphNode ? () => onSelectGraphNode?.(graphNode) : void 0
571
+ }
572
+ );
573
+ }
574
+ }
575
+ );
576
+ }
577
+ function CopyCommandControl({ command, label, visible }) {
578
+ const clipboard = useClipboard({ timeout: 1500 });
579
+ if (!command) return null;
580
+ return /* @__PURE__ */ jsx(
581
+ "span",
582
+ {
583
+ role: "button",
584
+ tabIndex: 0,
585
+ "aria-label": label,
586
+ onClick: (event) => {
587
+ event.preventDefault();
588
+ event.stopPropagation();
589
+ clipboard.copy(command);
590
+ },
591
+ onKeyDown: (event) => {
592
+ if (event.key !== "Enter" && event.key !== " ") return;
593
+ event.preventDefault();
594
+ event.stopPropagation();
595
+ clipboard.copy(command);
596
+ },
597
+ style: {
598
+ display: "inline-flex",
599
+ alignItems: "center",
600
+ justifyContent: "center",
601
+ width: 22,
602
+ height: 22,
603
+ flexShrink: 0,
604
+ opacity: visible ? 1 : 0,
605
+ pointerEvents: visible ? "auto" : "none",
606
+ color: clipboard.copied ? "var(--color-primary)" : "var(--color-text-subtle)",
607
+ transition: "opacity 120ms ease, color 120ms ease"
608
+ },
609
+ children: clipboard.copied ? /* @__PURE__ */ jsx(IconCheck, { size: 14 }) : /* @__PURE__ */ jsx(IconCopy, { size: 14 })
610
+ }
611
+ );
612
+ }
613
+ function KnowledgeLeafRow({ elementProps, knowledgeNode, isActive, onSelectNode }) {
614
+ const [hovered, setHovered] = useState(false);
615
+ return /* @__PURE__ */ jsx(
616
+ UnstyledButton,
617
+ {
618
+ ...elementProps,
619
+ onMouseEnter: (event) => {
620
+ elementProps.onMouseEnter?.(event);
621
+ setHovered(true);
622
+ },
623
+ onMouseLeave: (event) => {
624
+ elementProps.onMouseLeave?.(event);
625
+ setHovered(false);
626
+ },
627
+ onClick: () => onSelectNode(knowledgeNode),
628
+ style: {
629
+ ...elementProps.style ?? {},
630
+ padding: "5px 8px 5px 24px",
631
+ borderRadius: "var(--mantine-radius-sm)",
632
+ backgroundColor: isActive ? "color-mix(in srgb, var(--color-primary) 10%, transparent)" : hovered ? "var(--color-surface-hover)" : "transparent",
633
+ width: "100%",
634
+ textAlign: "left",
635
+ display: "block"
636
+ },
637
+ children: /* @__PURE__ */ jsxs(Group, { gap: "xs", wrap: "nowrap", children: [
638
+ /* @__PURE__ */ jsx(
639
+ SemanticIcon,
640
+ {
641
+ token: getKnowledgeIconToken(knowledgeNode),
642
+ fallbackKind: knowledgeNode.kind,
643
+ size: 15,
644
+ style: {
645
+ color: isActive ? "var(--color-primary)" : hovered ? "var(--color-text)" : "var(--color-text-subtle)"
646
+ }
647
+ }
648
+ ),
649
+ /* @__PURE__ */ jsx(
650
+ Text,
651
+ {
652
+ size: "sm",
653
+ c: isActive ? "var(--color-primary)" : hovered ? "var(--color-text)" : void 0,
654
+ fw: isActive ? 600 : 400,
655
+ style: { flex: 1, minWidth: 0, overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" },
656
+ children: knowledgeNode.title
657
+ }
658
+ ),
659
+ /* @__PURE__ */ jsx(
660
+ CopyCommandControl,
661
+ {
662
+ command: getKnowledgeReadCommand(knowledgeNode.id),
663
+ label: "Copy knowledge command",
664
+ visible: hovered
665
+ }
666
+ )
667
+ ] })
668
+ }
669
+ );
670
+ }
671
+ function DirectoryRow({
672
+ elementProps,
673
+ expanded,
674
+ hasChildren,
675
+ label,
676
+ iconToken,
677
+ count,
678
+ command,
679
+ isActive,
680
+ uppercase = false,
681
+ onSelectGraphNode
682
+ }) {
683
+ const [hovered, setHovered] = useState(false);
684
+ return /* @__PURE__ */ jsxs(
685
+ Group,
686
+ {
687
+ ...elementProps,
688
+ gap: "xs",
689
+ onMouseEnter: (event) => {
690
+ elementProps.onMouseEnter?.(event);
691
+ setHovered(true);
692
+ },
693
+ onMouseLeave: (event) => {
694
+ elementProps.onMouseLeave?.(event);
695
+ setHovered(false);
696
+ },
697
+ onClick: (event) => {
698
+ elementProps.onClick?.(event);
699
+ onSelectGraphNode?.();
700
+ },
701
+ style: {
702
+ ...elementProps.style ?? {},
703
+ padding: "4px 8px",
704
+ borderRadius: "var(--mantine-radius-sm)",
705
+ backgroundColor: isActive ? "color-mix(in srgb, var(--color-primary) 10%, transparent)" : hovered ? "var(--color-surface-hover)" : "transparent",
706
+ cursor: onSelectGraphNode || hasChildren ? "pointer" : "default",
707
+ userSelect: "none"
708
+ },
709
+ children: [
710
+ hasChildren ? /* @__PURE__ */ jsx(
711
+ "span",
712
+ {
713
+ "aria-hidden": "true",
714
+ style: {
715
+ display: "inline-flex",
716
+ alignItems: "center",
717
+ justifyContent: "center",
718
+ width: 12,
719
+ height: 12,
720
+ color: "var(--color-text-subtle)",
721
+ flexShrink: 0
722
+ },
723
+ children: expanded ? /* @__PURE__ */ jsx(IconChevronDown, { size: 12, stroke: 2 }) : /* @__PURE__ */ jsx(IconChevronRight, { size: 12, stroke: 2 })
724
+ }
725
+ ) : /* @__PURE__ */ jsx(Text, { size: "xs", style: { width: 12, flexShrink: 0 } }),
726
+ /* @__PURE__ */ jsx(
727
+ SemanticIcon,
728
+ {
729
+ token: iconToken,
730
+ fallbackKind: "feature",
731
+ size: 14,
732
+ style: {
733
+ color: isActive ? "var(--color-primary)" : hovered ? "var(--color-text)" : "var(--color-text-subtle)"
734
+ }
735
+ }
736
+ ),
737
+ /* @__PURE__ */ jsx(
738
+ Text,
739
+ {
740
+ size: "xs",
741
+ fw: isActive ? 700 : 600,
742
+ tt: uppercase ? "uppercase" : void 0,
743
+ c: isActive ? "var(--color-primary)" : hovered ? "var(--color-text)" : void 0,
744
+ style: {
745
+ letterSpacing: uppercase ? "0.05em" : 0,
746
+ flex: 1,
747
+ minWidth: 0,
748
+ overflow: "hidden",
749
+ textOverflow: "ellipsis",
750
+ whiteSpace: "nowrap"
751
+ },
752
+ children: label
753
+ }
754
+ ),
755
+ /* @__PURE__ */ jsx(TrailingCopySlot, { count, command, label: "Copy folder knowledge commands", showCopy: hovered })
756
+ ]
757
+ }
758
+ );
759
+ }
760
+ function TrailingCopySlot({ count, command, label, showCopy }) {
761
+ return /* @__PURE__ */ jsx(
762
+ "span",
763
+ {
764
+ style: {
765
+ display: "inline-flex",
766
+ alignItems: "center",
767
+ justifyContent: "center",
768
+ width: 32,
769
+ height: 22,
770
+ flexShrink: 0
771
+ },
772
+ children: showCopy && command ? /* @__PURE__ */ jsx(CopyCommandControl, { command, label, visible: true }) : count > 0 ? /* @__PURE__ */ jsx(KindChip, { kind: String(count), tone: "muted" }) : null
773
+ }
774
+ );
775
+ }
776
+
777
+ // src/knowledge/_generated/knowledge-search-index.json
778
+ var knowledge_search_index_default = [
779
+ {
780
+ id: "knowledge.outreach-playbook",
781
+ title: "Outreach Sequence Playbook",
782
+ summary: "Step-by-step runbook for launching a cold outreach campaign: prospect sourcing, copy review, sending schedule, and reply handling.",
783
+ bodyText: "Overview\n\nThis playbook covers the end-to-end process for launching a cold outreach campaign using the Elevasis lead-gen pipeline.\n\nSteps\n\n1. Source prospects via the Lead Gen feature.\n2. Review and approve copy in the CRM campaign editor.\n3. Schedule sends using the Task Scheduler.\n4. Monitor replies in the CRM inbox and route to the appropriate deal stage."
784
+ },
785
+ {
786
+ id: "knowledge.lead-gen-strategy",
787
+ title: "Lead Gen Targeting Strategy",
788
+ summary: "Defines ICP signal prioritization, firmographic filters, and scoring thresholds used by the lead-gen pipeline.",
789
+ bodyText: "Strategy\n\nThe lead-gen pipeline targets SMBs with 10-200 employees in recession-resistant verticals (manufacturing, logistics, professional services). Firmographic filters: revenue \\>$1M, HQ in US/CA/AU, tech stack includes at least one SaaS CRM.\n\nScoring Thresholds\n\n- High priority: ICP score \\>= 80\n- Medium priority: ICP score 60-79\n- Low priority: \\< 60 (excluded from active outreach)"
790
+ },
791
+ {
792
+ id: "knowledge.org-model-reference",
793
+ title: "Organization Model Schema Reference",
794
+ summary: "Technical reference for the OrganizationModel Zod schema: all domains, field contracts, and versioning rules.",
795
+ bodyText: "Schema\n\nThe OrganizationModel schema is defined in packages/core/src/organization-model/schema.ts. It is versioned at version: 1 and composed from domain sub-schemas.\n\nDomains\n\n- features \u2014 flat array of FeatureSchema nodes (nav tree)\n- knowledge \u2014 flat array of KnowledgeNodeSchema nodes\n- sales, prospecting, projects \u2014 sales and GTM domains\n- operations, statuses \u2014 runtime entity domains\n- customers, offerings, roles, goals \u2014 business context domains"
796
+ },
797
+ {
798
+ id: "knowledge.seo-lead-gen-playbook",
799
+ title: "SEO-to-Lead-Gen Handoff Playbook",
800
+ summary: "Runbook for promoting SEO-qualified prospects into the active lead-gen pipeline: signal capture, scoring override, and campaign assignment.",
801
+ bodyText: "Overview\n\nThis playbook governs the handoff from SEO-sourced traffic to the lead-gen pipeline.\n\nSteps\n\n1. SEO feature captures visitor signal (form fill or intent data).\n2. Score the lead using the standard ICP scoring thresholds.\n3. If score \\>= 60, inject into the lead-gen prospect list.\n4. Assign to the appropriate outreach campaign in the CRM."
802
+ },
803
+ {
804
+ id: "knowledge.outreach-campaign-playbook",
805
+ title: "Campaign Playbook",
806
+ summary: "Codified rules, benchmarks, and standard operating procedures for Instantly cold email campaigns -- testing methodology, personalization tiers, sequence structure, optimization loop, and batch readiness queries.",
807
+ bodyText: 'Benchmarks\n\nB2B cold email benchmarks (2025-2026). Use these as evaluation thresholds when running ist-analytics-workflow.\n\n| Metric | Bad | Needs Work | Good | Excellent |\n| --- | --- | --- | --- | --- |\n| Open Rate | \\<20% | 20-30% | 30-45% | 45%+ |\n| Reply Rate | \\<1% | 1-3% | 3-8% | 8%+ |\n| Positive Reply Rate | \\<0.5% | 0.5-1% | 1-3% | 3%+ |\n| Bounce Rate | \\>5% | 2-5% | 1-2% | \\<1% |\n\nCampaign Sizing Rules\n\n- 100-200 contacts per campaign segment -- small enough for meaningful personalization, large enough for statistical learning\n- 1-2 contacts per company -- reply rates drop from 8% to 4% when blasting 10+ people at the same company\n- 30-50 emails/day max per sending account\n- 50-100 emails/day total during testing (weeks 1-4), scaling to 500+/day once a winning formula is found\n\nTesting Priority\n\n1. Subject lines -- highest leverage, easiest to isolate\n2. Opening line / hook -- determines if they keep reading\n3. CTA type -- meeting request vs. soft question vs. value offer\n4. Body copy angle -- pain-point framing vs. benefit lead vs. social proof\n5. Send timing -- Tuesday-Thursday outperform Monday/Friday for B2B\n\nSequence Structure\n\n3 emails total (1 opener + 2 follow-ups). 87-95% of replies come within the first 3 emails.\n\n| Step | Timing | Strategy |\n| --- | --- | --- |\n| Email 1 | Day 0 | Cold read opener + EMRG social proof + interest-based CTA |\n| Email 2 | Day 3 | Short bump in same thread |\n| Email 3 | Day 5 | Breakup email. Soft close, loss aversion |\n\nAgent Optimization Loop\n\nPhase 1 (Launch): Verify batch readiness \u2192 Personalize \u2192 Create campaign \u2192 Create tracking list \u2192 Upload \u2192 Activate\n\nPhase 2 (Analyze Day 7-14): Run ist-campaign-review-workflow \u2192 Run ist-analytics-workflow \u2192 Interpret results\n\nPhase 3 (Optimize): Fix based on Phase 2 analysis (subject lines, personalization, bounce cleanup)\n\nPhase 4 (Scale): Document winning combination \u2192 Replicate to new segments \u2192 Maintain hygiene\n\nOffer Framing\n\n"We find one bottleneck, build an AI solution to fix it, and you only pay if it actually saves you time or makes you money."\n\n- Never use "free" in subject lines -- triggers spam filters\n- Frame as conditional, not charitable\n- Low-commitment CTAs get 2x more replies than direct meeting requests'
808
+ },
809
+ {
810
+ id: "knowledge.outreach-copy-strategy",
811
+ title: "Copy Strategy",
812
+ summary: "Reusable email copy framework, tone rules, personalization input guidance, subject line patterns, and offer framing for cold outreach campaigns.",
813
+ bodyText: `Base reference for writing and evaluating campaign copy. Batch-specific email text lives in each batch tracker's Campaign Strategy section.
814
+
815
+ Offer
816
+
817
+ Core message: "We find one bottleneck in your business, build an AI solution to fix it, and you only pay if it actually saves you time or makes you money."
818
+
819
+ - Never use "free" in subject lines or body copy
820
+ - Frame as conditional, not charitable
821
+ - Don't mention pricing in outreach
822
+
823
+ Tone
824
+
825
+ - Voice: First person singular ("I" not "we")
826
+ - Register: Conversational, direct, no jargon
827
+ - Formatting: Plain text only. No bold, no links in body, no HTML
828
+
829
+ 3-Email Sequence Framework
830
+
831
+ Email 1 \u2014 Hook + Offer (Day 0, 80 words)
832
+
833
+ Hi {{firstName | there}},
834
+
835
+ {{openingline}}
836
+
837
+ {{categorypain}}
838
+
839
+ I built 4 automations for a company that worked with Google and Sony Music. The first one alone saved them over $1,500 a month. I do the same thing for businesses like yours \u2014 you only pay if it actually works.
840
+
841
+ Worth a quick look?
842
+
843
+ {{sendingAccountFirstName}}
844
+
845
+ Email 2 \u2014 Bump (Day 3, 40 words)
846
+
847
+ Hi {{firstName | there}},
848
+
849
+ Wanted to circle back on this \u2014 I've been helping a few companies like {{companyName}} free up their team by automating the repetitive stuff that eats up the day.
850
+
851
+ Happy to take a 10-minute look \u2014 no pitch, just observations.
852
+
853
+ {{sendingAccountFirstName}}
854
+
855
+ Email 3 \u2014 Breakup (Day 5, 25 words)
856
+
857
+ Totally understand if this isn't the right time. If you ever want a second pair of eyes on what could be automated at {{companyName}}, the offer stands.
858
+
859
+ {{sendingAccountFirstName}}
860
+
861
+ Subject Lines
862
+
863
+ | Variant | Subject | Pattern |
864
+ | --- | --- | --- |
865
+ | A | quick thought | Colleague-camo |
866
+ | B | about {{companyName}} | Observation-trigger |
867
+
868
+ - Target 21-40 characters
869
+ - Personalized subjects: 46% open rate vs 35% without
870
+ - No "free," "guaranteed," "urgent," "risk-free"`
871
+ },
872
+ {
873
+ id: "knowledge.personalization-strategy",
874
+ title: "Personalization Strategy",
875
+ summary: "4-tier personalization waterfall, workflow inputs, quality rules, diagnostic process, and vertical adaptation for cold outreach opening line generation.",
876
+ bodyText: `How we generate personalized opening lines for Email 1. The ist-personalization-workflow produces ONLY the opening line.
877
+
878
+ 4-Tier Waterfall
879
+
880
+ | Tier | Name | Trigger | Expected Reply Rate |
881
+ | --- | --- | --- | --- |
882
+ | 1 | Individual Signal | contact.enrichmentData.linkedin.summary exists | 10-15% |
883
+ | 2 | Company Signal | Company has businessDescription OR services[] | 5-10% |
884
+ | 3 | Basic Context | Company has categoryName OR category | 3-5% |
885
+ | 4 | Minimal | Only company name and/or contact title | 1-3% |
886
+
887
+ Workflow Inputs
888
+
889
+ | Input | Purpose |
890
+ | --- | --- |
891
+ | batchId | Which contacts to personalize |
892
+ | emailBody | Rest of Email 1 after {openingline} |
893
+ | industryContext | Audience framing for LLM |
894
+ | creativeDirection | Override default creative guidance |
895
+ | performanceContext | Feedback from campaign results |
896
+ | overwrite | Re-personalize existing opening lines |
897
+
898
+ Quality Rules
899
+
900
+ Core principle: Wrong personalization is worse than none. Accuracy > depth.
901
+
902
+ Opening Line Style: Cold Read
903
+
904
+ The opening line must earn attention through recognition, not create tension through a pitch setup. Take one real fact from enrichment data and make an observation that shows you actually looked at their business.
905
+
906
+ Good examples:
907
+ - "4,400 reviews since 2018 \u2014 that kind of growth in Orange County is honestly wild."
908
+ - "60 years family-owned with a proprietary Clean Green method \u2014 that's rare in pest control."
909
+
910
+ Anti-patterns:
911
+ - "You do termite inspections \u2014 how many follow-up calls get missed?" (Implication \u2014 signals selling)
912
+ - "Great company you've built!" (Generic flattery)`
913
+ },
914
+ {
915
+ id: "knowledge.vertical-messaging-playbook",
916
+ title: "Vertical Messaging Playbook",
917
+ summary: "Research-backed messaging strategy per vertical \u2014 verified claims, owner voice, language guides, pain point framing, and copy guidelines for outreach messaging.",
918
+ bodyText: `Purpose
919
+
920
+ Reference doc for writing and evaluating vertical-specific outreach copy. Captures verified research, owner voice and language, and per-vertical messaging strategy.
921
+
922
+ Grounded Relatability Strategy
923
+
924
+ Layer 1: Claim Verification
925
+ Web-research every statistical claim. Grade each as Tier 1 (Verified), Tier 2 (Plausible), or Tier 3 (Unverified). Drop or soften anything Tier 3.
926
+
927
+ Layer 2: Voice-of-Customer Research
928
+ Search Reddit, trade forums, and industry communities for how owners actually talk about their frustrations.
929
+
930
+ Layer 3: Scenario-First Copy
931
+ Combine verified claims with owner language to write copy that sounds like a peer, not a vendor:
932
+ 1. Lead with the scenario
933
+ 2. Use their vocabulary
934
+ 3. Reinforce with stats only if they pass the "would I trust this from a stranger?" test
935
+ 4. Name concrete capabilities
936
+
937
+ Universal Owner Patterns
938
+
939
+ - The Identity Gap: They started the business to do the craft. Acknowledging this resonates deeply.
940
+ - Phone = Business: The owner's phone IS the business.
941
+ - Software Cynicism: Every vertical has been sold to aggressively by SaaS vendors.
942
+
943
+ Words That Work Everywhere: "Stop losing...", "Without lifting a finger", "Less software, not more"
944
+
945
+ Words That Fail Everywhere: "AI-powered", "Scale your business", "Digital transformation"
946
+
947
+ Copy Principles
948
+
949
+ Lead with Scenarios, Not Statistics
950
+
951
+ | Weak (stat-first) | Strong (scenario-first) |
952
+ | --- | --- |
953
+ | "78% of homeowners hire the first contractor who responds." | "When you're on a job, you can't answer the phone \u2014 but the homeowner already called two other plumbers." |
954
+
955
+ Claim Strength Tiers:
956
+ - Tier 1 (Verified): Named source, specific methodology. Use confidently.
957
+ - Tier 2 (Plausible): Widely cited, directionally correct. Use the insight, soften the specific number.
958
+ - Tier 3 (Unverified): No credible source found. Do not use specific numbers.`
959
+ },
960
+ {
961
+ id: "knowledge.cold-email-research-2026",
962
+ title: "Copy Research (March 2026)",
963
+ summary: "Cold email research findings from 2025-2026 benchmark reports, A/B test data, and industry studies \u2014 reply rates, subject lines, CTAs, sequence structure, social proof, and vertical-specific angles.",
964
+ bodyText: `Research findings that inform our copy strategy and campaign decisions. Sourced from Instantly's 2026 Benchmark Report (3M+ emails), industry studies, and platform data.
965
+
966
+ Last researched: 2026-03-18
967
+
968
+ Reply Rate Benchmarks (2026)
969
+
970
+ | Tier | Reply Rate | What It Takes |
971
+ | --- | --- | --- |
972
+ | Average B2B | 3.4% | Basic targeting, template emails |
973
+ | Top quartile | 5.5% | Segmentation + follow-ups |
974
+ | Top performers | 10%+ | Signal-based personalization + tight ICP |
975
+ | Best-in-class | 15-25% | Hyper-personalization + intent signals |
976
+
977
+ Advanced personalization doubles reply rates: 18% vs 9% for generic.
978
+
979
+ Email Length
980
+
981
+ - Under 80 words = 2.4x higher reply rate than emails over 200 words
982
+ - Sweet spot: 50-125 words
983
+
984
+ Subject Lines
985
+
986
+ 21-40 characters = 49.1% open rate -- long enough to communicate value, short enough for mobile.
987
+
988
+ - Personalized subject lines: 46% open rate vs 35% without
989
+ - Numbers in subject lines: 113% improvement in open rates
990
+ - Questions in subject lines: 21% increase in open rates
991
+ - Lowercase always -- title case reads as marketing email
992
+
993
+ CTA Research
994
+
995
+ - Interest-based CTAs: 30% success rate -- 2x any other type
996
+ - Timeline-based CTAs ("Worth 15 minutes this week?"): 3.4x meeting rate vs problem-based
997
+ - Keep Email 1 CTA short -- one sentence max
998
+
999
+ Sequence Length
1000
+
1001
+ 87-95% of replies come within the first 3 emails. Additional emails triple spam complaints without meaningful reply lift (Belkins, 16.5M emails).`
1002
+ },
1003
+ {
1004
+ id: "knowledge.bounce-recovery-protocol",
1005
+ title: "Bounce Recovery Protocol",
1006
+ summary: `Step-by-step protocol for handling campaigns that enter Instantly's "Bounces" status, including cleanup mechanics, root cause investigation, and decision framework for continuing vs. restarting.`,
1007
+ bodyText: `When to Use
1008
+
1009
+ A campaign enters Instantly's "Bounces" status (typically \\>5% bounce rate).
1010
+
1011
+ Key Mechanics
1012
+
1013
+ Bounce Rate Is Historical: Instantly's bounce rate = bounced / sent. Removing bounced leads does not retroactively reduce the rate.
1014
+
1015
+ What ist-cleanup-workflow does:
1016
+ - Stop sequence: marks lead as "not interested" in Instantly
1017
+ - Mark invalid in Lead DB: excludes from all future uploads
1018
+ - Delete from Instantly (only with removeFromInstantly: true)
1019
+
1020
+ Protocol
1021
+
1022
+ Step 1: Pause the Campaign
1023
+ bash
1024
+ pnpm exec elevasis --prod exec Elevasis/ist-campaign-pause-workflow --input '{"campaignId":"<campaign-id>"}'
1025
+
1026
+ Step 2: Dry-Run Cleanup
1027
+ bash
1028
+ pnpm exec elevasis --prod exec Elevasis/ist-cleanup-workflow --input '{"campaignIds":["<campaign-id>"],"dryRun":true}' --async
1029
+
1030
+ Step 3: Run Cleanup
1031
+ bash
1032
+ pnpm exec elevasis --prod exec Elevasis/ist-cleanup-workflow --input '{"campaignIds":["<id>"],"removeFromInstantly":true}' --async
1033
+
1034
+ Step 4: Decide -- Continue or Restart
1035
+
1036
+ | Scenario | Action |
1037
+ | --- | --- |
1038
+ | Most leads unsent (campaign was early) | Continue -- reactivate |
1039
+ | Most leads already received emails | Let it finish |
1040
+ | Bounce rate \\>10% or account health degraded | Restart -- fresh campaign |
1041
+
1042
+ Step 5: Investigate Root Cause
1043
+
1044
+ Common root causes: catch-all domains, stale data, verification false positives, generic/role addresses (info@, service@).
1045
+
1046
+ Step 6: Update Batch Tracker
1047
+
1048
+ Log bounce count, leads cleaned, root cause, and resolution in the batch tracker doc.`
1049
+ },
1050
+ {
1051
+ id: "knowledge.lead-gen-pipeline-playbook",
1052
+ title: "Lead-Gen Playbook",
1053
+ summary: "Empirical benchmarks, threshold guidance, provider economics, and codified learnings from 3 completed batches of the lead-gen pipeline.",
1054
+ bodyText: "Performance Benchmarks\n\nPer-stage success rates across the 3 completed Orange County batches (vet-1, auto-1, home-1).\n\nStage 1: Scrape (Raw to Filtered)\n\n| Metric | vet-1 | auto-1 | home-1 | Benchmark |\n| --- | --- | --- | --- | --- |\n| Raw results | 480 | 800 | 1000 | -- |\n| Companies created | 393 | 566 | 701 | -- |\n| Active in DB | 322 | 428 | 640 | -- |\n\nStage 3: Company Qualification Rate\n\n| Metric | vet-1 | auto-1 | home-1 | Benchmark |\n| --- | --- | --- | --- | --- |\n| Qualified | 213 (76%) | 284 (79%) | 326 (60%) | 60-80% |\n| Disqualified | 66 (24%) | 74 (21%) | 222 (40%) | 20-40% |\n\nStage 5: Email Verification\n\nVALID rate: 33-41% of discovered emails across batches. Target bounce rate \\<2%.\n\nModel Selection\n\nUse Gemini Flash models for high-volume qualification steps (cost/quality balance). Use GPT for personalization where quality matters most.\n\nProvider Economics\n\nTomba domain search provides the best cost-per-verified-contact for local SMBs. Dual-verify (Tomba + Mails.so) catches most false positives.\n\nPipeline Stages\n\n1. Scrape (Google Maps via Apify)\n2. LLM Extract (website crawl \u2192 structured data)\n3. Company Qualification (LLM ICP scoring)\n4. Email Discovery (Tomba domain search)\n5. Email Verification (Mails.so)\n6. Opening Line Generation (ist-personalization-workflow)\n7. Campaign Upload (ist-upload-workflow)"
1055
+ },
1056
+ {
1057
+ id: "knowledge.upwork-proposal-playbook",
1058
+ title: "Proposal Playbook",
1059
+ summary: "Data-backed strategies for writing winning Upwork proposals \u2014 structure, pricing, timing, client qualification, and benchmarks.",
1060
+ bodyText: `The Numbers That Matter
1061
+
1062
+ | Metric | Benchmark |
1063
+ | --- | --- |
1064
+ | Personalized proposals | 30% higher reply rate vs generic |
1065
+ | Proposals within first 2 hours | 3x response rate vs 24h+ |
1066
+ | First-hour proposals | 3-5x interview rate boost |
1067
+
1068
+ Proposal Structure
1069
+
1070
+ Keep proposals 150-200 words, mobile-scannable.
1071
+
1072
+ The Five-Part Formula
1073
+
1074
+ 1. Hook (1-2 sentences) \u2014 Lead with what you do and why it's relevant. Never use "caught my attention" \u2014 overused clich\xE9.
1075
+ 2. Proof (1-2 sentences) \u2014 One concrete metric from past work.
1076
+ 3. Plan (2-3 lines) \u2014 Maximum 3 steps.
1077
+ 4. Discovery Questions (exactly 2) \u2014 Smart, role-specific questions. End with a binary CTA.
1078
+ 5. Sample (optional) \u2014 Loom video (2-3 min).
1079
+
1080
+ Opening Lines
1081
+
1082
+ The first line decides 80% of reply outcomes. Always use the client's name or company name.
1083
+
1084
+ Good openers: "Hi \u2014 I build {whattheyneed} for a living and run my own business on them daily."
1085
+
1086
+ Never open with: "The part that caught my attention..." / "What stood out..."
1087
+
1088
+ Elevasis-Specific Positioning
1089
+
1090
+ Dogfooding (Strongest Differentiator): We run our own 7-stage client acquisition pipeline on the platform: Scrape \u2192 Extract \u2192 Qualify \u2192 Email Discovery \u2192 Verify \u2192 Personalize \u2192 Outreach.
1091
+
1092
+ The 4 Principles (Use in Discovery Questions):
1093
+ 1. Integrated \u2014 Works with existing tools
1094
+ 2. Improving \u2014 Learns from every human decision
1095
+ 3. Observable \u2014 Real-time visibility
1096
+ 4. Governed \u2014 Humans approve what matters`
1097
+ },
1098
+ {
1099
+ id: "knowledge.upwork-response-templates",
1100
+ title: "Response Templates",
1101
+ summary: "Templates and guidelines for responding to Upwork messages, offers, and client communications.",
1102
+ bodyText: `Offer Acceptance Response
1103
+
1104
+ When a client sends an offer and invites a call:
1105
+ 1. Thank them for the offer (brief, no flattery)
1106
+ 2. Confirm the call with a specific day/time and timezone
1107
+ 3. Ask 2-3 qualifying questions to show engagement
1108
+ 4. Sign off with first name (Alex, not Alexander)
1109
+
1110
+ Template
1111
+
1112
+ Hi {name},
1113
+
1114
+ Thanks for the offer and for reaching out, I'll book a time for {day} {time} ({timezone}). I look forward to learning more about your setup!
1115
+
1116
+ Just a few questions:
1117
+ 1. {qualifying question about current tools/stack}
1118
+ 2. {qualifying question about current process/pain}
1119
+ 3. {qualifying question about scope/direction}
1120
+
1121
+ Thanks!
1122
+ Alex
1123
+
1124
+ Qualifying Question Bank
1125
+
1126
+ - Current stack: "What do you currently use for workflows / automation?"
1127
+ - Pain points: "What's the most time-consuming repetitive task right now?"
1128
+ - Approval flow: "What do you currently use for approval control?"
1129
+ - Consolidation: "Are you using multiple tools or interested in custom software?"
1130
+ - Team size: "How many people will be using these workflows?"
1131
+ - Priority: "What's the first workflow you'd want built?"
1132
+
1133
+ Style Rules
1134
+
1135
+ - Tone: Warm but concise. No filler, no over-enthusiasm.
1136
+ - Length: 3-6 sentences max.
1137
+ - Timezone: Always specify timezone when confirming times (PDT/PST).
1138
+ - No selling: The call is for learning, not pitching.`
1139
+ },
1140
+ {
1141
+ id: "knowledge.seo-playbook",
1142
+ title: "SEO Playbook",
1143
+ summary: "Operational playbook for the SEO/AEO system \u2014 vertical launch workflow, content quality gates, AEO formatting rules, stale content management, metrics interpretation, and rollout phases.",
1144
+ bodyText: "Vertical Launch Workflow\n\nEnd-to-end process for launching a new vertical (e.g., hvac-contractors):\n\nStep 1: Research vertical data\n\nFind real, citable industry statistics: pain points, automation ROI, market context. Sources: trade associations, industry surveys, government data (BLS, Census).\n\nStep 2: Generate pages\n\nbash\nPillar page\npnpm tsx scripts/seo/generate-pages.ts --vertical hvac-contractors --type pillar --dry-run\npnpm tsx scripts/seo/generate-pages.ts --vertical hvac-contractors --type pillar\n\nCluster pages\npnpm tsx scripts/seo/generate-pages.ts --vertical hvac-contractors --type cluster\n\nStep 3: Backfill chart data\n\nCharts require post-generation patching. Write chart patch files using Step 1 research, then apply via content.ts patch.\n\nStep 4: AEO optimization\n\nEvery section must be self-contained. AI engines extract individual passages, not full pages.\n- Lead with a 30-50 word standalone answer in the first sentence of each section\n- 78% of AI Overview answers use list format\n- Frame headings as questions when possible\n\nStep 5: Post-publish distribution\n\nUpdate llms.txt, ping IndexNow, submit sitemap segments to Search Console.\n\nData Sourcing Rules\n\n- Tier 1 (Verified): Named source, specific methodology. Use confidently.\n- Tier 2 (Plausible): Widely cited, directionally correct. Soften the specific number.\n- Tier 3 (Unverified): No credible source. Do not use specific numbers."
1145
+ },
1146
+ {
1147
+ id: "knowledge.seo-content-guide",
1148
+ title: "SEO Content Writing Guide",
1149
+ summary: "Tactical guide for writing SEO and AEO-optimized content \u2014 page structure, keyword strategy, AI engine citation optimization, E-E-A-T signals, and CTA patterns.",
1150
+ bodyText: 'Overview\n\nThis guide covers how to write SEO content that ranks in traditional search AND gets cited by AI answer engines \u2014 ChatGPT, Perplexity, Google AI Overviews, and Gemini.\n\nThe distribution channel is split: 69% of Google searches result in zero clicks, while AI platforms generated 1.13 billion referral visits in June 2025 alone.\n\nPage Structure\n\nSection Architecture\n\nEvery section must be self-contained. AI engines extract individual passages, not full pages.\n\n- Lead with a 30-50 word standalone answer in the first sentence of each section\n- Paragraphs: 2-4 sentences max\n- 78% of AI Overview answers use list format -- use bullets and numbered lists extensively\n- Heading hierarchy: one H1 (page title), H2 for major sections, H3 for subsections\n- Frame headings as questions when possible\n\nSection Length\n\n- Pillar pages: 2,000-3,000 words total\n- Cluster pages: 1,000-2,000 words total\n- Each major section: 150-300 words\n\nConciseness Rules\n\n1. Two-paragraph intro max\n2. No stat duplication between intro and problem section\n3. Section character budget: Intro 450-600 chars, Problem 250-400 chars, Solution 300-550 chars\n\nE-E-A-T Signals\n\n- Brand name "Elevasis" must appear explicitly minimum 3x per page\n- Vertical names used as proper noun concepts\n- Organization + Service + SoftwareApplication schema markup reinforces entity recognition'
1151
+ },
1152
+ {
1153
+ id: "knowledge.seo-distribution-playbook",
1154
+ title: "Distribution & Citation Building",
1155
+ summary: "Post-publish distribution playbook \u2014 AI crawler discovery, directory citations, Google Business Profile, community seeding, monitoring cadence, and AEO optimization tactics.",
1156
+ bodyText: 'Overview\n\nEverything that happens after pages are published and sitemap/IndexNow pings are sent. Evergreen reference for maximizing visibility across traditional search, AI answer engines, and buyer-intent directories.\n\nAI Crawler Discovery\n\nllms.txt\n\nA plain-text Markdown file at the domain root that gives LLMs a structured map of key content. Location: apps/website/public/llms.txt.\n\nUpdate whenever pillar or cluster pages are published.\n\nSitemap Segmentation\n\n| Segment ID | Contents | Priority |\n| --- | --- | --- |\n| 0 | Static pages | Default |\n| 1 | Pillar pages (9) | 0.9 |\n| 2 | Cluster pages (52) | 0.8 |\n\nEntity Density Rules\n\nPages with 15+ recognized entities show 4.8x higher selection probability for AI citations.\n- Brand name "Elevasis" must appear explicitly (minimum 3x per page)\n- Stat density target: one statistic every 150-200 words\n\nDirectory Citations\n\nTier 1 (Buyer Intent): G2, Capterra, Product Hunt, AppSumo (direct buyer intent, high domain authority)\n\nTier 2 (Authority): Crunchbase, AngelList, LinkedIn company page, industry association directories\n\nGoogle Business Profile\n\nClaim and verify GBP listing. Category: "Software Company". Keep NAP (Name, Address, Phone) consistent across all citations.'
1157
+ },
1158
+ {
1159
+ id: "knowledge.content-playbook",
1160
+ title: "Content Playbook",
1161
+ summary: "Content creation guide \u2014 pillars, platform rules, format specs, repurposing chain, and metrics benchmarks for multi-platform distribution",
1162
+ bodyText: 'Content Pillars\n\nEvery content piece maps to one of five pillars.\n\n| Pillar | What It Is | Target Frequency |\n| --- | --- | --- |\n| dogfooding | How we run Elevasis using our own platform | 1-2x/week |\n| education | 4 Pattern Framework and AI orchestration concepts | 1x/week |\n| demo | "Watch AI Work" \u2014 showing the platform in action | 1x/2 weeks |\n| pain-point | SMB pain points backed by data | 1x/week |\n| founder-journey | Solo founder building with AI | 1x/week |\n\nThe 90-10 Rule\n\n90% educational value, 10% promotional. Keeping promotional content to \\<15% ensures your audience stays receptive when you do make an offer.\n\nContent Creation Workflow\n\n1. Capture the Idea \u2014 Insert into acqcontent with status: idea\n2. Draft the Source Content \u2014 Write core idea in acqcontent.body. LinkedIn first.\n3. Adapt for Platforms \u2014 Repurpose from LinkedIn to YouTube, Instagram, X\n4. Schedule and Publish \u2014 Use platform-native scheduling\n5. Track Performance \u2014 Monitor in acqcontent via Command Center\n\nPlatform Priority\n\n1. LinkedIn \u2014 #1 priority, 3x/week\n2. YouTube \u2014 #2 priority, 1x/week + Shorts\n3. Instagram \u2014 #3 priority, 2-3x/week, repurpose from LinkedIn/YouTube\n4. X \u2014 #4 priority, 2-3x/week, threads from LinkedIn'
1163
+ },
1164
+ {
1165
+ id: "knowledge.inbound-reply-handling-playbook",
1166
+ title: "Stage 01: Reply Handling",
1167
+ summary: "Classify email replies, create CRM contact, and send booking link to interested leads",
1168
+ bodyText: `Overview
1169
+
1170
+ Classify incoming email replies, create CRM contact for interested leads, and send booking link. Uses value-first approach: offer a free one-time AI service instead of passive content.
1171
+
1172
+ Classification
1173
+
1174
+ Four-way classification with confidence-based HITL routing. Auto-replies detected deterministically before any LLM call.
1175
+
1176
+ | Category | Examples | Action |
1177
+ | --- | --- | --- |
1178
+ | Interested | "Yes I'm interested", "Tell me more" | CRM \u2192 HITL task with LLM-drafted follow-up |
1179
+ | Not Interested | "Unsubscribe", "Not interested" | Stop outreach sequence |
1180
+ | Bounced | mailer-daemon@, "Undeliverable" | Stop outreach \u2192 Mark Lead DB INVALID \u2192 Skip CRM |
1181
+ | Auto-Reply | Out-of-office, ticketing acknowledgments | No action \u2014 campaign continues |
1182
+
1183
+ Auto-Reply Detection (Pre-LLM)
1184
+
1185
+ Caught deterministically via regex:
1186
+ - Sender address prefix: noreply, no-reply, donotreply, auto, mailer
1187
+ - Subject patterns: "auto-reply", "out of office", "OOO"
1188
+ - Body patterns: "this is an automated message", vacation auto-responders
1189
+
1190
+ Routing Notes
1191
+
1192
+ - NOTINTERESTED and BOUNCED: sequence stopped via Instantly update-interest-status
1193
+ - INTERESTED: does NOT stop sequence \u2014 proceeds to CRM creation and HITL queue
1194
+ - AUTO-REPLY: no CRM, no sequence stop, no reply
1195
+
1196
+ HITL Strategy
1197
+
1198
+ For INTERESTED replies, an LLM drafts a follow-up response. Admin reviews and approves via Command Queue before anything sends.`
1199
+ },
1200
+ {
1201
+ id: "knowledge.inbound-booking-playbook",
1202
+ title: "Stage 02: Booking",
1203
+ summary: "Cal.com booking handling -- create, reschedule, and cancel discovery call bookings",
1204
+ bodyText: 'Overview\n\nWhen a prospect books a discovery call via Cal.com, the booking handler manages CRM updates, notifications, and reminder scheduling across all booking lifecycle events.\n\nBooking Handler Workflow\n\nTriggers: Cal.com webhooks (BOOKINGCREATED, BOOKINGRESCHEDULED, BOOKINGCANCELLED)\n\nEvent Flows\n\nBOOKINGCREATED:\n1. create-contact \u2014 Create or update Attio Person record, resolve Deal via metadata or email, update Deal stage to booked, create Discovery Call Prep note, cancel brochure follow-ups\n2. notify-created \u2014 Send platform notification to admin\n3. schedule-reminders \u2014 Create absolute schedule with pre-computed -24h and -1h reminder times\n\nBOOKINGRESCHEDULED:\n1. update-schedule \u2014 Cancel existing reminders, schedule new ones at updated times\n2. finish-reschedule \u2014 Update Deal stage, notify admin\n\nBOOKINGCANCELLED:\n1. cancel-schedule \u2014 Cancel all scheduled reminders\n2. finish-cancel \u2014 Update Deal stage to cancelled, notify admin\n\nReminder Schedule\n\nTwo automated reminders per booking:\n- -24h reminder: "Looking forward to our call tomorrow"\n- -1h reminder: "See you in 1 hour" with discovery form link\n\nReminders are scheduled as absolute times (not relative), so rescheduling correctly cancels and re-schedules them.'
1205
+ },
1206
+ {
1207
+ id: "knowledge.discovery-call-playbook",
1208
+ title: "Stage 03: Discovery",
1209
+ summary: "Discovery form submission, qualification routing, and data storage -- the pipeline endpoint",
1210
+ bodyText: "Overview\n\nThe discovery form is filled during the call by the salesperson. On submission, the workflow stores structured data, updates the CRM, and routes based on qualification.\n\nThis is the current pipeline endpoint \u2014 qualified leads receive a HITL task for manual follow-up; no automated stages exist beyond this point.\n\nWorkflow Steps\n\n1. route-by-action \u2014 Route to submission or no-show flow\n2. compute-cost \u2014 Calculate totalAnnualCost from bottlenecks\n3. store-discovery \u2014 Save discoverydata JSONB to acqdeals\n4. find-deal \u2014 Find Attio Deal (acqdeals \u2192 URL context \u2192 Attio API)\n5. update-attio-stage \u2014 Update Deal.stage based on qualification\n6. create-summary-note \u2014 Add markdown Note to Deal\n7. create-hitl-task \u2014 Create HITL approval item for qualified leads\n\nQualification Routing\n\n- Qualified: Deal moves to qualified stage; HITL task created for admin follow-up\n- Not Qualified: Deal moves to closedlost stage; no further automation\n- No-Show: Deal moves to noshow stage; timeout checker handles re-engagement\n\nDiscovery Form Structure\n\nThe form captures: company size, current tools, 3 biggest bottlenecks, estimated time wasted per week, budget comfort range, urgency, decision-making authority."
1211
+ },
1212
+ {
1213
+ id: "knowledge.pipeline-management-playbook",
1214
+ title: "Pipeline Management",
1215
+ summary: "Automated pipeline maintenance workflows for the inbound pipeline, covering timeout monitoring, stale deal handling, unsubscribe cleanup, and full contact reset.",
1216
+ bodyText: "Overview\n\nPipeline management keeps the inbound pipeline clean without manual intervention.\n\n- Timeout monitoring \u2014 Detecting deals that have gone stale in a stage\n- Unsubscribe cleanup \u2014 Closing deals and cancelling automation when a contact opts out\n- Full cleanup \u2014 Complete removal of all pipeline data for a contact\n\nNo timeout action transitions a deal automatically. All stage changes from the timeout system require admin approval via a HITL Command Queue item.\n\nTimeout System\n\nTimeout Checker (inb-pipeline-timeout-checker-workflow)\n\nScans all active pipeline stages. For each stale deal, creates a HITL approval item in the Command Queue. Never modifies deal records directly.\n\nTimeout rules by stage:\n\n| Stage | Condition | Timeout |\n| --- | --- | --- |\n| interested | No activity | 30 days |\n| booked | Meeting time has passed | 3 hours after meeting |\n| discovery | No form submission | 24 hours after call |\n\nAction Workflows\n\n4 action workflows triggered by admin approval:\n- Nurture: Move to nurture sequence\n- Close-Lost: Mark deal closed, stop all automation\n- No-Show: Re-send booking link with personal note\n- Follow-Up: Send soft re-engagement email\n\nUnsubscribe Cleanup\n\nWhen a contact opts out:\n1. Find all active automation for the contact\n2. Cancel scheduled reminders\n3. Update Deal stage to unsubscribed\n4. Mark contact as unsubscribed in Attio"
1217
+ },
1218
+ {
1219
+ id: "knowledge.communications-map",
1220
+ title: "Communications Map",
1221
+ summary: "All lead-facing messages across the active client acquisition pipeline (stages 01-03). Stages 04-08 communications are archived.",
1222
+ bodyText: 'Overview\n\nEvery message sent to a lead across the active acquisition pipeline \u2014 outreach, follow-ups, reminders, and event-triggered emails. 10 total active messages (3 outreach + 1 reply + 2 subsequent reply + 2 discovery reminders + 1 timeout follow-up + 1 timeout re-engagement).\n\nStages 04-08 communications (proposal follow-ups, payment reminders, nurture sequences) are archived.\n\nActive Pipeline Phases\n\n1. Outreach (Instantly) 2. Reply Handling 3. Discovery Reminders\n 3 emails 1 email + 2 HITL 2 reminders\n Day 0/3/7 On reply -24h/-1h\n\nPhase 1: Outreach (Instantly)\n\n| # | Day | Subject | Angle | Purpose |\n| --- | --- | --- | --- | --- |\n| 1 | 0 | idea for {{companyName}} | Earn attention | Personal, specific |\n| 2 | 3 | where do business hours actually go? | New angle | Peer-level, intriguing |\n| 3 | 7 | the offer (whenever useful) | Permission to close | Warm, zero pressure |\n\nPhase 2: Reply Handling (Resend)\n\nSent only to INTERESTED replies. LLM drafts; admin approves via HITL before sending.\n\nPhase 3: Discovery Reminders (Resend)\n\nTwo automated reminders per booking:\n- -24h: "Looking forward to our call tomorrow"\n- -1h: "See you in 1 hour" with discovery form link\n\nAll scheduled/event emails signed "Alex". Plain text.'
1223
+ },
1224
+ {
1225
+ id: "knowledge.reddit-monitoring-playbook",
1226
+ title: "Social Monitoring",
1227
+ summary: "Automated Reddit monitoring pipeline \u2014 4-step workflow (search, LLM score, LLM draft, store) with tiered subreddit scraping via Apify, Gemini-powered scoring and response drafting, and lead-gen optimized responses via the AlexElevasis account.",
1228
+ bodyText: "Overview\n\nSocial monitoring surfaces Reddit posts where business owners discuss operational challenges, manual processes, and automation needs.\n\nCron Trigger (3x/day: 16:00, 22:00, 04:00 UTC)\n \u2192 mnt-reddit-monitor-workflow (4-step pipeline)\n \u2192 Step 1: Search \u2014 4 sequential Apify actors (one per tier)\n \u2192 Step 2: Score \u2014 Gemini Flash Lite (concurrency 5)\n \u2192 Step 3: Draft \u2014 Gemini Flash (high-score only)\n \u2192 Step 4: Report \u2014 Store to acqsocialposts, log breakdown\n \u2192 Command Center: /acquisition/monitoring\n\nStatus: Paused until further notice (2026-03-31). Pipeline technically validated but ROI is low without deep vertical expertise.\n\nVelocity Batches\n\nSubreddits grouped by posting velocity, each with its own maxPostsPerSource.\n\n| Batch | Subs | Posts/Source | Example Subreddits |\n| --- | --- | --- | --- |\n| Medium Velocity | 2 | 5 | Accounting, Plumbing |\n| Buyer Low-Volume | 10 | 3 | CRM, EntrepreneurRideAlong, sweatystartup |\n| Trade Niche | 10 | 2 | electricians, Dentists, InsuranceAgent |\n| Decision Niche | 3 | 2 | logistics, FreightBrokers, healthIT |\n\nScoring\n\nPosts scored 0-100 by Gemini Flash Lite for business automation opportunity relevance. Posts scoring \\>= 60 get response drafts via Gemini Flash.\n\nAlexElevasis Account\n\nAll responses posted via the AlexElevasis Reddit account. See Reddit Account strategy doc for warmup playbook and karma thresholds."
1229
+ },
1230
+ {
1231
+ id: "knowledge.reddit-account-strategy",
1232
+ title: "Reddit Account",
1233
+ summary: "AlexElevasis Reddit account strategy \u2014 warm-up playbook, karma thresholds, posting rules, and lead generation approach for the social monitoring pipeline.",
1234
+ bodyText: 'Account Details\n\n| Field | Value |\n| --- | --- |\n| Username | AlexElevasis |\n| Created | 2026-03-26 |\n| Purpose | Lead generation via Reddit monitoring pipeline |\n| Persona | Alexander, founder of an AI automation company |\n\nThe username serves double duty: every helpful comment puts "Elevasis" in front of the reader.\n\nWarm-Up Playbook\n\nDays 1-3 \u2014 Cold Start:\n- Browse, upvote, follow target subs\n- 1 comment per day max\n- No links, no self-promotion, no CTAs\n\nDays 4-14 \u2014 Gradual Ramp:\n- Increase to 2-3 comments/day\n- Genuine, helpful answers \u2014 no copy-paste across threads\n- Still no links or CTAs\n\nAfter 30 Days + 100 Karma \u2014 Activation:\n- Most automod filters cleared\n- Start using monitoring pipeline drafts\n- 300+ karma unlocks more restrictive subs (r/Entrepreneur, r/startups)\n\nKarma Thresholds\n\n| Karma | Access Level |\n| --- | --- |\n| 100 | Bypasses most automod filters |\n| 300+ | Access to restrictive subs |\n| 1,000+ | Considered established community member |\n\nPosting Rules\n\n- Genuine value first \u2014 no pitching without established reputation\n- 30-day account age required before using pipeline drafts\n- Open questions in responses (not CTAs) per pipeline draft conventions'
1235
+ },
1236
+ {
1237
+ id: "knowledge.linkedin-strategy",
1238
+ title: "LinkedIn Strategy",
1239
+ summary: "LinkedIn posting strategy \u2014 algorithm deep dive, format rankings, hook techniques, carousel optimization, newsletter strategy, engagement tactics, profile optimization, and growth playbook for B2B SaaS (2025-2026)",
1240
+ bodyText: 'Overview\n\nLinkedIn is the #1 priority platform. The algorithm heavily favors personal profiles over company pages. In 2025-2026, LinkedIn shifted from viral reach to depth and authority \u2014 topical consistency is now the single biggest strategic lever.\n\nCadence: 3-5x/week (Wed/Thu/Fri strongest, Monday weakest)\n\nAlgorithm Deep Dive\n\nRanking Signals (by weight)\n\n| Signal | Weight | Notes |\n| --- | --- | --- |\n| Comments | Highest | 15x more valuable than likes |\n| Saves and sends | Very high | Added to analytics in late 2025 |\n| Dwell time | High | 15+ seconds = meaningful |\n| "See more" clicks | High | Whether people expand the full post |\n| Reactions/likes | Lowest | Significantly de-emphasized |\n\nTopical Authority\n\nThe algorithm rewards consistent publishing within a defined area of expertise. Pick a lane (AI automation) and stay in it.\n\nPost Formats\n\n1. Carousels \u2014 Highest avg engagement; 10-slide with text + data\n2. Text posts \u2014 Quick takes, contrarian observations, founder stories\n3. Documents/PDFs \u2014 In-depth guides, playbooks, frameworks\n4. Videos \u2014 Native upload outperforms external links by 5x\n\nHook Techniques\n\n- Lead with the most surprising/counterintuitive insight\n- Use "I" not "we" \u2014 personal profile posts outperform company pages\n- 3 lines max before "...see more" break\n- Questions outperform statements for comments engagement'
1241
+ },
1242
+ {
1243
+ id: "knowledge.instagram-strategy",
1244
+ title: "Instagram Strategy",
1245
+ summary: "Comprehensive Instagram posting strategy for B2B SaaS founder content \u2014 algorithm deep dive, Reels, carousels, Stories, SEO, hashtags, captions, profile optimization, growth tactics, and analytics benchmarks (2025-2026)",
1246
+ bodyText: 'Overview\n\nInstagram is the #3 priority platform. Carousels repurpose easily from LinkedIn, and Reels come directly from YouTube Shorts.\n\nCadence: 3-5x/week feed posts + daily Stories\n\nB2B Reality Check: Instagram is a complementary channel for B2B SaaS, not a primary lead gen tool. Decision-makers are on Instagram (76% of B2B companies use it), but they are in consumer mode.\n\nAlgorithm Deep Dive (2025-2026)\n\nReels Algorithm (Most Important for Reach)\n\nReels are designed for discovery: 55% of Reels views come from non-followers.\n\nThree priority signals:\n| Signal | Weight | How to Optimize |\n| --- | --- | --- |\n| Watch time | #1 | Hook in first 1.7s; optimize for completion |\n| Sends per reach | Highest for new audiences | "Send this to someone" content |\n| Likes per reach | Highest for followers | Valuable content followers engage with |\n\nContent Strategy\n\n- Repurpose from LinkedIn: Carousel slides \u2192 Instagram carousel\n- Repurpose from YouTube: Long-form \u2192 Reels (cut 30-60s clips)\n- Stories: Daily touchpoints, polls, Q&A, behind-the-scenes\n\nCarousel Optimization\n\n- Slide 1: Hook (bold claim or question)\n- Slides 2-9: Value delivery (one point per slide)\n- Slide 10: CTA (follow, DM, link in bio)'
1247
+ },
1248
+ {
1249
+ id: "knowledge.x-strategy",
1250
+ title: "X (Twitter) Strategy",
1251
+ summary: "X/Twitter posting strategy \u2014 algorithm signals, thread tactics, engagement weights, Premium ROI, video specs, growth playbook, and B2B SaaS niche tactics",
1252
+ bodyText: "Overview\n\nX is the #4 priority platform. Most content is repurposed from LinkedIn text posts as threads.\n\nCadence: 3-5 posts/day (mix of original posts + replies + quote tweets), 2-3 threads/week\n\nPrerequisite: Get X Premium ($8/mo) \u2014 verified accounts get 10x more impressions. Non-Premium accounts posting links receive near-zero engagement as of 2026.\n\nAlgorithm Deep Dive\n\nEngagement Weight Scoring\n\n| Signal | Weight | vs. Like |\n| --- | --- | --- |\n| Reply engaged by author | +75 | 150x |\n| Reply | +13.5 | 27x |\n| Profile click + engagement | +12.0 | 24x |\n| Bookmark | +10.0 | 20x |\n| Retweet | +1.0 | 2x |\n| Like | +0.5 | 1x |\n\nCritical takeaway: Replying to your own replies is worth 150x a like.\n\nPenalties\n\n- External links: 30-50% reach reduction\n- Multiple hashtags: 40% penalty\n\nThread Strategy\n\n- First tweet = hook (bold claim or surprising stat)\n- 5-10 tweets per thread\n- Last tweet = CTA (follow for more, link to extended content)\n- Reply to your own threads within the first hour to boost algorithmic velocity"
1253
+ },
1254
+ {
1255
+ id: "knowledge.youtube-channel-strategy",
1256
+ title: "Channel Strategy",
1257
+ summary: 'Unified YouTube channel strategy \u2014 channel setup, content pipeline, differentiation positioning, launch queue, and production workflow for "Alexander Le | Elevasis" channel',
1258
+ bodyText: `Objective
1259
+
1260
+ Build the "Alexander Le | Elevasis" YouTube channel as an evergreen discovery engine for Elevasis. YouTube is the #1 content priority \u2014 evergreen discovery, SEO compounding, and 53% of B2B buyers watch video before requesting demos.
1261
+
1262
+ Channel Identity
1263
+
1264
+ - Name: "Alexander Le | Elevasis" \u2014 personal-brand hybrid with clear business association
1265
+ - Handle: @AlexanderElevasis
1266
+ - Why personal-brand hybrid: People subscribe to people. Algorithm favors creator-branded channels over faceless brands.
1267
+
1268
+ Content Format: Face Bubble Intro \u2192 Screen Recording
1269
+
1270
+ Primary format: Quick face-cam intro (circle overlay, 15-30s) to build trust, then full-screen recording.
1271
+
1272
+ Why this works:
1273
+ - 40%+ of YouTube's top 1,000 channels never show a presenter \u2014 faceless works
1274
+ - Screen recording tutorials rank extremely well on YouTube + Google
1275
+ - Face in thumbnails gets 921K more views on average
1276
+
1277
+ Differentiation
1278
+
1279
+ Compete on substance, not production quality:
1280
+ - Real execution data, real costs, real results (not simulations)
1281
+ - "Document the build" format: record while building, editing, deploying
1282
+ - Show Command Center in action \u2014 no equivalent content exists
1283
+
1284
+ Launch Queue
1285
+
1286
+ 3 priority video types for channel launch:
1287
+ 1. Platform demo walkthroughs (Command Center, workflow execution)
1288
+ 2. Founder journey episodes (building in public)
1289
+ 3. Educational explainers (AI orchestration concepts)`
1290
+ },
1291
+ {
1292
+ id: "knowledge.youtube-tactics",
1293
+ title: "YouTube Strategy",
1294
+ summary: "YouTube posting strategy \u2014 upload optimization, Shorts tactics, algorithm signals, SEO, first-48-hour playbook, faceless content tips, and small channel growth for B2B SaaS",
1295
+ bodyText: `Overview
1296
+
1297
+ YouTube is the #2 priority platform and the only one with true evergreen discovery. Videos keep gaining views months/years after posting.
1298
+
1299
+ Cadence: 1 long-form/week + 3-5 Shorts/week
1300
+
1301
+ Upload Checklist
1302
+
1303
+ Title
1304
+ - Keep meaningful part in first 60 chars (mobile truncation)
1305
+ - Front-load primary keyword: "AI Automation for Veterinary Clinics (Full Walkthrough)"
1306
+ - Long-tail titles outperform generic for small channels
1307
+
1308
+ Thumbnail
1309
+ - 1280x720px, 16:9, max 2MB
1310
+ - 4 words max of bold, high-contrast text
1311
+ - Use YouTube's Test & Compare \u2014 upload 3 thumbnails, YouTube A/B tests and reports CTR
1312
+
1313
+ Description
1314
+ - First 125 chars = what shows in search results
1315
+ - Add timestamps/chapters \u2014 improve retention + create extra search ranking hooks
1316
+
1317
+ Shorts Strategy
1318
+
1319
+ - Cut 30-60s clips from long-form: best moments, surprising reveals, "before/after"
1320
+ - Repurpose from YouTube to Instagram Reels and TikTok
1321
+ - Hook in first 1-2 seconds: start with the payoff, not the setup
1322
+
1323
+ First-48-Hour Playbook
1324
+
1325
+ 1. Publish during peak hours (12pm-3pm ET Tuesday-Thursday)
1326
+ 2. Post about the video on LinkedIn immediately after upload
1327
+ 3. Reply to every comment within the first 24 hours
1328
+ 4. Share in relevant communities (not spam \u2014 genuine value)`
1329
+ },
1330
+ {
1331
+ id: "knowledge.youtube-growth-playbook",
1332
+ title: "YouTube Growth Playbook",
1333
+ summary: "Practical YouTube optimization guide for Elevasis screen-recorded demos, founder-led walkthroughs, Shorts, SEO, and launch review loops",
1334
+ bodyText: 'Overview\n\nThis playbook maps YouTube growth tactics to the current Elevasis content format: screen recordings, Command Center walkthroughs, founder commentary, proof assets, and short clips cut from real demos.\n\nThe goal is to make real AI orchestration visible enough that buyers and technical peers understand what Elevasis does.\n\nCore Format\n\n| Format | Use For | Notes |\n| --- | --- | --- |\n| Face intro \u2192 screen recording | Flagship demos, founder-led walkthroughs | 15-30 second intro, then full-screen product work |\n| Screen recording only | Tutorials, Shorts source material | Fastest to produce |\n| Data walkthrough | Campaign metrics, cost tracking, workflow results | Use only real data |\n| Concept explainer | AI agents, orchestration, human oversight | Keep tied to concrete business workflows |\n\nCTR Optimization\n\nThumbnails should show the result or system, not abstract concepts:\n1. Command Center, workflow graph, campaign analytics, or visible output\n2. Optional face overlay when it improves trust\n3. Three to five words of context. High contrast, readable at mobile size.\n\nTitle rules:\n- Front-load the topic\n- Use real numbers only when current and verifiable\n- Prefer "Watch", "Real", "Live", "Actual", "Walkthrough" over hype language\n- Make the title and thumbnail say different things\n\nRetention\n\nOpen with the payoff:\n1. Show the final dashboard, result, or workflow output\n2. Explain what the viewer is about to see\n3. Switch into the walkthrough quickly\n\nFirst-24-Hour Workflow\n\nAlgorithm watches first 30-60 minutes closely. A video getting 10 replies in 15 minutes dramatically outperforms one getting 10 replies over 24 hours.'
1335
+ },
1336
+ {
1337
+ id: "knowledge.youtube-mental-prep",
1338
+ title: "Mental Preparation \u2014 Creator Anxiety Playbook",
1339
+ summary: "Research-backed strategies for overcoming publishing anxiety, the spotlight effect, graduated exposure, and building comfort on camera \u2014 practical guide for a technical founder starting YouTube",
1340
+ bodyText: `Why This Exists
1341
+
1342
+ Recording yourself and putting your ideas out publicly is genuinely uncomfortable. This doc captures the research and practical strategies so you can reference them when the anxiety spikes \u2014 not as motivation, but as evidence that the discomfort is normal, finite, and navigable.
1343
+
1344
+ The Core Insight
1345
+
1346
+ Readiness is a product of exposure, not a prerequisite for it. You don't stop feeling afraid and then start. You start, and the fear gradually recedes.
1347
+
1348
+ What You're Actually Afraid Of
1349
+
1350
+ The Spotlight Effect (Gilovich et al., 2000)
1351
+
1352
+ People overestimate how much others notice and judge them by roughly 2x. Early videos get almost zero views \u2014 you're practicing in an empty room.
1353
+
1354
+ The Five Common Fears (in order of prevalence)
1355
+
1356
+ 1. Judgment from people you know \u2014 not strangers. The #1 blocker.
1357
+ 2. Perfectionism as procrastination \u2014 "I'll start when I have better equipment." This is avoidance.
1358
+ 3. Impostor syndrome \u2014 "Who am I to teach this?"
1359
+ 4. Permanence anxiety \u2014 "This will be on the internet forever"
1360
+ 5. Voice/face aversion \u2014 Most people dislike their recorded voice (bone conduction gap)
1361
+
1362
+ The Real Fear: Cringe
1363
+
1364
+ People tolerate a video that flops quietly. What they can't tolerate is creating something they later find embarrassing.
1365
+
1366
+ Graduated Exposure Protocol
1367
+
1368
+ 1. Screen recording only (no face, no voice) \u2014 2 videos
1369
+ 2. Screen recording + voiceover (no face) \u2014 5 videos
1370
+ 3. Face bubble intro (15-30s) + screen recording \u2014 ongoing
1371
+
1372
+ Each stage desensitizes one fear at a time. Never skip ahead. The goal is consistency, not perfection.`
1373
+ },
1374
+ {
1375
+ id: "knowledge.new-vertical-launch-playbook",
1376
+ title: "New Vertical Launch Playbook",
1377
+ summary: "Zero-to-first-campaign workflow for launching a new acquisition vertical: batch definition, tracker setup, lead generation stages, campaign launch, and monitoring.",
1378
+ bodyText: `Overview
1379
+
1380
+ Use this playbook when launching a new acquisition vertical from zero to first campaign. A vertical launch turns an audience hypothesis such as independent dental practices, auto repair shops, or bookkeeping firms into a tracked lead generation batch, qualified contacts, a draft Instantly campaign, and an active monitoring loop.
1381
+
1382
+ The workflow has five phases:
1383
+
1384
+ 1. Define the batch and qualification rules.
1385
+ 2. Create the batch tracker.
1386
+ 3. Run the lead generation pipeline.
1387
+ 4. Launch the outreach campaign.
1388
+ 5. Monitor replies and campaign quality.
1389
+
1390
+ Define the Batch
1391
+
1392
+ Choose a batch ID using the acquisition naming convention: {vertical}-{number}, for example dental-1, auto-1, or home-1.
1393
+
1394
+ Record the batch configuration in the tracker before running pipeline stages. At minimum, capture:
1395
+
1396
+ - Target description, such as "independent dental practices in Orange County, California".
1397
+ - Search queries for the initial source pull.
1398
+ - Region: county, state, country, or other geography accepted by the scraper workflow.
1399
+ - Minimum review count and minimum rating, when Google Maps quality thresholds matter.
1400
+ - Custom disqualification rules, such as excluding chains, franchises, pediatric-only practices, or irrelevant subcategories.
1401
+ - Website crawl keywords, such as about, team, staff, contact, services, or vertical-specific service pages.
1402
+
1403
+ Use packages/elevasis-operations/src/sales/prospecting/constants.ts as the batch registry. Current launch work should keep the tracker as the human-readable source and pass qualification criteria through the workflow input, list qualification metadata, or the registered batch config for the stage being run.
1404
+
1405
+ Create the Batch Tracker
1406
+
1407
+ Create a tracker from the acquisition batch template:
1408
+
1409
+ text
1410
+ apps/docs/content/docs/operations/client-acquisition/outreach/batches/template.mdx
1411
+
1412
+ Place the new tracker in the pending batch directory:
1413
+
1414
+ text
1415
+ apps/docs/content/docs/operations/client-acquisition/outreach/batches/pending/{batch-id}.mdx
1416
+
1417
+ Fill in the frontmatter with status: in-progress, then complete the batch configuration table before running pipeline work. The tracker should make it possible to reconstruct the vertical, region, search inputs, disqualification rules, and campaign state without reading execution logs.
1418
+
1419
+ Run Lead Generation
1420
+
1421
+ Run the lead generation stages with the platform CLI from the monorepo root so .env.development and .env.production resolve correctly.
1422
+
1423
+ Stage 01: Google Maps Scrape
1424
+
1425
+ Use the Google Maps scrape workflow to acquire initial companies:
1426
+
1427
+ bash
1428
+ pnpm exec elevasis exec Elevasis/lgn-01a-google-maps-scrape-workflow --input '{"searchQueries":["dentist","dental clinic"],"county":"Orange County","state":"California"}' --async
1429
+
1430
+ After the execution starts, record the execution ID and source counts in the batch tracker.
1431
+
1432
+ Local Website Crawl
1433
+
1434
+ Run the local website crawler against the batch:
1435
+
1436
+ bash
1437
+ pnpm -C scripts/web-scraper run crawl -- {batch-id}
1438
+
1439
+ The crawl should capture relevant sub-pages for LLM extraction. If vertical-specific keywords are not available in the active code path, use the default crawl coverage and note any manual crawl gaps in the tracker before extraction.
1440
+
1441
+ Stage 02: Website Extract
1442
+
1443
+ Extract structured company profile data from crawl output:
1444
+
1445
+ bash
1446
+ pnpm exec elevasis exec Elevasis/lgn-02-website-extract-workflow --input '{"batchId":"{batch-id}"}' --async
1447
+
1448
+ Stage 03: Company Qualification
1449
+
1450
+ Qualify companies using the target description, review thresholds, rating thresholds, and custom rules captured in the tracker. If the workflow does not resolve criteria automatically for the batch, pass the criteria explicitly in the input or attach them through the list qualification surface before running the stage.
1451
+
1452
+ bash
1453
+ pnpm exec elevasis exec Elevasis/lgn-03-company-qualification-workflow --input '{"batchId":"{batch-id}","criteria":{"targetDescription":"Independent dental practices in Orange County, California","minimumReviewCount":5,"minimumRating":3,"customRules":"Disqualify franchises and chains. Disqualify orthodontics-only and pediatric-only practices."}}' --async
1454
+
1455
+ For list-oriented runs, use listId instead of batchId; list configuration takes priority over the batch registry unless an explicit criteria override is provided.
1456
+
1457
+ Stage 04: Email Discovery
1458
+
1459
+ Discover contacts for qualified companies:
1460
+
1461
+ bash
1462
+ pnpm exec elevasis exec Elevasis/lgn-04-email-discovery-workflow --input '{"batchId":"{batch-id}"}' --async
1463
+
1464
+ Stage 05: Email Verification
1465
+
1466
+ Verify discovered emails before campaign upload:
1467
+
1468
+ bash
1469
+ pnpm exec elevasis exec Elevasis/lgn-05-email-verification-workflow --input '{"batchId":"{batch-id}"}' --async
1470
+
1471
+ When verification completes, update the tracker with company counts, contact counts, usable email counts, and set the batch status to ready if campaign launch prerequisites are satisfied.
1472
+
1473
+ Launch the Campaign
1474
+
1475
+ Use the acquisition outreach workflow to move a ready batch into Instantly:
1476
+
1477
+ 1. Check account inventory with ist-account-inventory-workflow.
1478
+ 2. Personalize contacts with ist-personalization-workflow.
1479
+ 3. Create a draft campaign with ist-campaign-create-workflow and activate: false.
1480
+ 4. Create the tracking list with ist-campaign-list-workflow.
1481
+ 5. Upload contacts with ist-upload-workflow, dry run first and then real.
1482
+ 6. Activate with ist-campaign-activate-workflow.
1483
+ 7. Update the tracker to status: active and fill in campaign metadata.
1484
+
1485
+ Keep the first campaign small enough to evaluate copy and deliverability. Prefer 100-200 contacts per segment, 1-2 contacts per company, and conservative sending volume until benchmarks are visible.
1486
+
1487
+ Monitor and Optimize
1488
+
1489
+ After launch, monitor both campaign metrics and inbound replies:
1490
+
1491
+ - Use /acquisition --outreach for campaign review and analytics.
1492
+ - Use /acquisition --inbound status for reply handling and active deal state.
1493
+ - Watch open rate, reply rate, positive reply rate, and bounce rate.
1494
+ - Pause or repair the campaign if bounce rate rises above the accepted threshold.
1495
+ - Rework subject lines, personalization, or offer framing when reply rate is below target.
1496
+
1497
+ Every optimization pass should write back to the tracker: what changed, why it changed, and what result would justify scaling the vertical.
1498
+
1499
+ Launch Checklist
1500
+
1501
+ - Batch ID selected with {vertical}-{number} naming.
1502
+ - Batch tracker created from the template.
1503
+ - Target description, geography, search queries, thresholds, and custom rules recorded.
1504
+ - Stage 01 scrape execution complete.
1505
+ - Website crawl complete or crawl gaps documented.
1506
+ - Stage 02 extraction complete.
1507
+ - Stage 03 qualification complete with explicit criteria source.
1508
+ - Stage 04 email discovery complete.
1509
+ - Stage 05 email verification complete.
1510
+ - Tracker status set to ready.
1511
+ - Draft Instantly campaign created.
1512
+ - Tracking list created and contacts uploaded.
1513
+ - Campaign activated.
1514
+ - Tracker status set to active with campaign metadata.`
1515
+ },
1516
+ {
1517
+ id: "knowledge.upwork-calibration-strategy",
1518
+ title: "Upwork Calibration Strategy",
1519
+ summary: "Deep calibration process for Upwork queries, including scan parameters, scoring, duplicate handling, output format, verdict criteria, and current calibration results.",
1520
+ bodyText: "Overview\n\nUse this playbook when running a deep calibration scan for one or more Upwork queries. Calibration is separate from daily scanning: it evaluates query quality, competition, uniqueness, and noise patterns so the active saved-query set stays healthy.\n\nUse calibration when:\n\n- Testing a new query candidate before adding it to active scans.\n- Re-evaluating an active query whose health score has declined.\n- Running periodic re-calibration of the full active query set. Quarterly is the default cadence.\n\nUse knowledge.upwork-query-strategy for the active query table and knowledge.upwork-scanning-playbook for the daily scan workflow.\n\nScan Parameters\n\n| Parameter | Value | Rationale |\n| --------- | ----- | --------- |\n| Max posts | 20 per query | Enough for confidence without turning calibration into an archive. |\n| Max age | 2 weeks | Captures 2 full hiring cycles; older posts are stale. |\n| Filters | U.S. only, Most Recent | Matches the standard scan surface while preserving full competition visibility. |\n| Proposal filter | None | Calibration needs to see competition levels, not hide them. |\n| Extraction | Search page script | Capture title, snippet, budget, proposals, and client info without click-in enrichment. |\n\nCalibration Process\n\n1. Check the Upwork query registry for the exact candidate query and close variants.\n2. Re-test a dropped query only if the search terms or market conditions have changed.\n3. Navigate to https://www.upwork.com/nx/search/jobs/.\n4. Enter the query and apply U.S. only plus Most Recent sort.\n5. Run the search-card extraction script from the operations docs.\n6. If more than 20 results are visible, analyze only the first 20.\n7. If fewer than 20 results are visible, analyze all visible posts.\n8. Record the exact result count shown by Upwork in the search header.\n9. Score each extracted post with the calibration relevance rubric.\n10. For each post scoring 50+, check whether it appears in other query calibration docs.\n11. Write the per-query calibration doc and update the summary results.\n\nRelevance Rubric\n\nFor each extracted post, assign a relevance score from 0 to 100. The core question is whether the client needs a system, workflow, integration, automation, or operational build that Elevasis can deliver.\n\n| Score Range | Tier | Meaning |\n| ----------- | ---- | ------- |\n| 75-100 | Strong fit | Clear build intent, named platforms or tools, business context, and specific deliverable. |\n| 50-74 | Possible fit | Some build signals, but scope or domain is ambiguous. |\n| 25-49 | Weak fit | Marginally related and mostly noise. |\n| 0-24 | Irrelevant | Hiring posts, marketing or design work, personal projects, manual work, or VA work. |\n\nAuto-score disqualifiers in the 0-10 range even if they contain automation language:\n\n- Hiring for a role.\n- Marketing, design, or funnel work.\n- Manual or VA tasks.\n- Personal, non-business projects.\n\nDuplicate Handling\n\nFor each post scoring 50+, check whether the same job appeared in another query's calibration output. Mark it with DUPE: Q{N} when found.\n\nTrack total relevance and unique relevance separately. A query with 60% relevance but 0% unique relevance is usually not worth keeping because it adds scan time without adding opportunity coverage.\n\nOutput Format\n\nPer-query calibration docs use this naming pattern:\n\ntext\nq{NN}-{slug}.mdx\ncandidate-{slug}.mdx\nsummary.mdx\n\nUse active-query filenames such as q15-system-nouns.mdx and candidate filenames such as candidate-pipedrive.mdx.\n\nEach per-query doc should include:\n\n- Frontmatter with title, exact query, scan date, total Upwork result count, analyzed post count, relevant count, and verdict.\n- A ## Posts section with post-level scoring.\n- A ## Summary section with relevance rate, high-relevance count, perfect fits, low-competition gems, unique posts, noise patterns, why the query works or fails, and final verdict.\n\nKeep irrelevant posts short. Relevant posts should include budget, proposals, posted age, client signal, description snippet, relevance rationale, and uniqueness or duplicate marker.\n\nVerdict Criteria\n\n| Verdict | Criteria |\n| ------- | -------- |\n| KEEP (strong) | More than 40% relevance, more than 20% unique relevance, more than 2 low-competition gems, and fresh posts within 1 week. |\n| KEEP (borderline) | 25-40% relevance, or low unique relevance but useful low-competition gems. Trial for 3 scans. |\n| MONITOR | Relevant posts exist but are flooded with 20-50 proposals. Cherry-pick only; do not treat as a core saved search. |\n| DROP | Less than 20% relevance, 0 low-competition gems, dead volume under 5 results, or all stale posts older than 2 weeks. |\n\nQuick vs Deep Calibration\n\n| Aspect | Quick Screening | Deep Calibration |\n| ------ | --------------- | ---------------- |\n| Purpose | Fast keep/drop triage for new candidates. | Full analysis for active-query management. |\n| Posts | All visible, no hard max. | Max 20, max 2 weeks old. |\n| Scoring | Inline summary table. | Per-post entries with rationale. |\n| Output | Task notes or conversation. | Dedicated calibration MDX document. |\n| Duplicate check | Informal overlap note. | Formal cross-reference with post numbers. |\n| Use case | Bulk screening 5+ candidates. | Promotion, quarterly review, or declined health score. |\n\nCurrent Calibration Results\n\nLast full calibration: 2026-03-29.\n\nDeep scan results exist as original scan session notes. Formal per-query calibration files have not yet been written.\n\n| Current Q# | Query | Total | Rel | Unique Rel | Calibration Status |\n| ---------- | ----- | ----- | --- | ---------- | ------------------ |\n| Q1 | System nouns | 47 | 60% | 30% | Not yet written: q15-system-nouns.mdx. |\n| Q2 | GHL | 111 | 50% | 45% | Not yet written: q16-ghl.mdx. |\n| Q3 | AR/AP/Collections | 66 | 40% | 30% | Not yet written: q01-ar-ap-collections.mdx. |\n| Q4 | Inventory | 69 | 40% | 35% | Not yet written: q06-inventory.mdx. |\n| Q5 | Invoice/billing | 67 | 55% | 30% | Not yet written: q04-invoice-billing.mdx. |\n| Q6 | CRM | 370 | 40% | 20% | Not yet written: q08-crm.mdx. |\n| Q7 | API integration | 298 | 40% | 15% | Not yet written: q14-api-integrate.mdx. |\n| Q8 | Pipedrive | 10 | 40% | 30% | Quick scan only; no deep calibration file yet. |\n\nThe current query numbers match knowledge.upwork-query-strategy. The older calibration filenames reflect original 16-query scan numbering.\n\nNoise Patterns\n\nCommon noise patterns across calibrated queries:\n\n- Hiring posts: the top noise source across all queries, especially CRM and API searches.\n- Marketing and funnel work: especially common in GHL-adjacent searches.\n- Bookkeeper and accountant roles: common in AR/AP and invoice searches.\n- BI and analytics specialist work: common in invoice/billing and API searches.\n- High competition: broad CRM and API queries often attract 20-50+ proposals.\n\nCross-Query Overlap\n\nThe highest-overlap pairs from calibration:\n\n- Q3 AR/AP and Q5 Invoice/billing overlap heavily on financial automation posts.\n- Q6 CRM and Q7 API catch many of each other's broad integration posts.\n- Q1 System nouns produces the most unique gems and remains the strongest standalone query."
1521
+ },
1522
+ {
1523
+ id: "knowledge.upwork-query-strategy",
1524
+ title: "Upwork Query Strategy",
1525
+ summary: "Active saved-query set, tiering, calibration outcomes, query-design principles, and failure modes for the Upwork acquisition channel.",
1526
+ bodyText: 'Overview\n\nUse this reference to choose which Upwork saved searches to scan, understand why each query is active, and avoid repeating failed discovery work. The active query set targets niche operations and business-process automation jobs rather than broad "AI automation" searches, which are usually saturated.\n\nUse knowledge.upwork-scanning-playbook for the scan workflow and knowledge.upwork-calibration-strategy for deep query calibration.\n\nActive Saved Queries\n\nThe active saved-query set has 14 queries as of 2026-03-31. Q1-Q7 were kept after the full calibration scan of the original 16 queries. Q8 Pipedrive was added after SaaS platform calibration. Q9-Q14 are formerly monitor-tier queries moved into active scanning with stricter freshness and competition discipline.\n\n| Tier | # | Query | Relevance | Unique Rel | Rationale |\n| ---- | -- | ----- | --------- | ---------- | --------- |\n| T1 | 1 | ("booking system" OR "intake form" OR "scheduling system" OR "ticketing system" OR "tracking system" OR "reservation system" OR "billing system" OR "payment system") AND (build OR automate OR custom) | 60% | 30% | Best query. SMB owners describe systems they need built. Lowest competition and highest ROI per connect. |\n| T1 | 2 | ("GoHighLevel" OR "GHL") AND (build OR setup OR integration OR workflow OR automate) | 50% | 45% | GHL niche with high unique relevance. "Funnel" was removed because it pulled marketing noise; "automate" replaced it. |\n| T2 | 3 | ("accounts receivable" OR "accounts payable" OR "collections") AND automate | 40% | 30% | "Automate" catches real builds and maps to the Xero testimonial. Some overlap with Q1 and Q7. |\n| T2 | 4 | "inventory" AND ("automation" OR "integration" OR "sync") | 40% | 35% | Inventory system builds are unique to this query. Low cross-query overlap. |\n| T3 | 5 | ("invoice" OR "billing") AND ("automate" OR "integration") | 55% | 30% | Finds healthcare EDI, Stripe Connect, and Power Automate AP work. Heavy Q3 overlap. |\n| T3 | 6 | "CRM" AND ("integration" OR "automate" OR "migrate") | 40% | 20% | Finds Twenty CRM, HubSpot Service Hub, and Zoho automation. Noisy and high-volume. |\n| T3 | 7 | "integrate" AND "API" AND (build OR develop OR custom) | 40% | 15% | Finds strong integration work such as Authorize.Net webhooks and Email-to-ERP pipelines. Competition is often high. |\n| T3 | 8 | "Pipedrive" AND (build OR setup OR integration OR workflow OR automate) | 40% | 30% | CRM setup and automation builds. Only platform query besides GHL to pass calibration. Trial query; evaluate after 3 scans. |\n| T4 | 9 | ("Zapier" OR "Make.com" OR "n8n") AND (automate OR build OR workflow) | Very high | -- | High relevance but competition risk. Apply only to posts with fewer than 10 proposals. |\n| T4 | 10 | "SaaS" AND ("MVP" OR "prototype") AND (build OR develop) | High | -- | Bimodal competition: some low-proposal gems, some flooded posts. Skip flooded posts. |\n| T4 | 11 | "Google Sheets" AND (automate OR app OR dashboard OR replace) | 40% | -- | Fresh and even competition spread. Good posts get flooded quickly, so prioritize speed. |\n| T4 | 12 | "chatbot" AND (build OR custom OR develop) | 25% | -- | Low relevance baseline. Pulls AI engineering hiring and chat UI work, but occasional SMB gems exist. |\n| T4 | 13 | ("voice agent" OR "AI receptionist") AND (build OR develop OR custom) | High | -- | Very low volume, often 1-2 results. High quality when fresh posts appear. |\n| T4 | 14 | "sales funnel" AND (automate OR build OR system OR custom) | 40% | -- | Fresh GHL integrations and CRM builds mixed with affiliate-funnel noise. Filter hard. |\n\nAll active queries use the Upwork proposal-count filters "Less than 5" and "5 to 10". Tier 1 queries are the primary scan focus. Tier 2 queries are solid contributors. Tier 3 queries are borderline and should be dropped if health scores decline across 3 or more scans. Tier 4 queries have higher competition risk and should only be applied to fresh, low-proposal posts.\n\nQuery Design Principles\n\nStrong Upwork queries describe the system the buyer needs, not the freelancer category they think they are hiring.\n\nUse:\n\n- Specific operational system nouns such as booking system, intake form, scheduling system, ticketing system, tracking system, reservation system, billing system, and payment system.\n- Action verbs such as build, automate, custom, develop, setup, integration, and workflow.\n- Platform-specific searches when system-build intent overlaps with implementation work, such as GoHighLevel, Pipedrive, CRM, API integration, and inventory sync.\n- The Q1 pattern: specific system noun plus action verb. This catches clients describing the thing they need built.\n- The proposal-count filter from 0-10 proposals. Competition control is non-negotiable.\n\nAvoid:\n\n- Vertical keywords such as clinic, contractor, salon, or real estate. On Upwork these usually pull marketing or hiring posts for that industry, not system buyers from that industry.\n- Pain-signal phrases such as "hours per week", "manual", "repetitive", or "bottleneck". On Upwork these usually mean the client is hiring humans to perform the work.\n- Migration terms such as migrate, replace, or switch unless tied to a concrete platform or system. Generic migration queries pull website and email platform changes.\n- Broad tool or category nouns such as portal, dashboard, tool, or custom without a specific system type.\n- Solution-first keywords such as AI automation, AI agent, and workflow automation. They attract the saturated AI freelancer crowd.\n- Revenue-proximity service terms such as cold email, outbound, nurture, appointment setting, and qualify leads. These are proposal-positioning angles, not good Upwork query filters.\n- Professional-service terms such as contract review, due diligence, or reputation management. They pull human professionals rather than system builds.\n\nQuery Evolution\n\nDiscovery is complete for the current strategy: 127 queries were tested across 18 rounds and calibrated to 14 active queries. The current ROI lever is execution quality: faster scans, stronger proposal targeting, and query-health monitoring.\n\nQ1 has 8 proven system nouns:\n\n- Booking system.\n- Intake form.\n- Scheduling system.\n- Ticketing system.\n- Tracking system.\n- Reservation system.\n- Billing system.\n- Payment system.\n\nTwenty-three other system nouns were tested and failed. Before testing any new query, check the Upwork query registry in the operations docs for prior verdicts and failure modes.\n\nKnown Limitations\n\n- The "U.S. only" checkbox does not persist with saved searches. It is a session-only filter and must be manually checked each time a saved search loads.\n- Upwork search is not strict phrase matching. A quoted phrase such as "client portal" can still match posts containing the words separately.\n- Relevance percentages are from the expanded 2026-03-29 evaluation: 147 jobs across 15 queries plus 32-query discovery analysis. Re-evaluate monthly as job mix shifts.\n\nLive Reference Docs\n\nPreserve these operations docs because they remain live working references rather than migrated Knowledge Map content:\n\n- apps/docs/content/docs/operations/client-acquisition/upwork/query-registry.mdx tracks all 127 tested queries, verdicts, and failure modes.\n- apps/docs/content/docs/operations/client-acquisition/upwork/scripts.mdx stores runnable browser extraction scripts and current Upwork DOM notes.'
1527
+ },
1528
+ {
1529
+ id: "knowledge.upwork-scanning-playbook",
1530
+ title: "Upwork Scanning Playbook",
1531
+ summary: "Stable scanning, scoring, freshness, and query-health workflow for the Upwork acquisition channel.",
1532
+ bodyText: 'Overview\n\nUse this playbook to run the Upwork acquisition scanning loop. The goal is to find fresh, low-competition jobs where Elevasis can deliver a real business system, score them consistently, and turn the best opportunities into proposals through the Upwork proposal playbook.\n\nThe scanning loop has six phases:\n\n1. Confirm browser and login readiness.\n2. Run saved search queries with the right filters.\n3. Extract fresh job cards.\n4. Deduplicate, score, and enrich strong candidates.\n5. Write the scan output.\n6. Review query health before the next scan.\n\nProposal copy and response handling are intentionally separate. Use knowledge.upwork-proposal-playbook for proposal strategy and knowledge.upwork-response-templates for client replies.\n\nPreconditions\n\nBefore scanning, verify the browser automation surface is available and Upwork is logged in.\n\n- Chrome tooling must be available through the active browser automation tools.\n- The Upwork search page must load at https://www.upwork.com/nx/search/jobs/.\n- The page must show the job search interface, not a login prompt.\n- If login is required, stop and log in manually before scanning again.\n\nDo not try to work around a missing browser session by inventing scan results. The scan depends on live Upwork search pages, current saved-search filters, and the visible result cards.\n\nScan Surface\n\nRun scans from the Upwork search page:\n\ntext\nhttps://www.upwork.com/nx/search/jobs/\n\nEach saved query is run individually. Apply the filters every time, because Upwork search state is session-dependent and not always persisted across queries.\n\n| Filter | Value | Reason |\n| --------- | ----------- | ---------------------------------------------------------------------- |\n| U.S. only | Checked | Higher-quality clients, stronger timezone fit, more verified payments. |\n| Sort by | Most Recent | Freshness is the strongest conversion lever. |\n| Proposals | \\<5, 5-10 | Low competition is required before spending connects. |\n\nUse saved queries as the primary scan source. The strategy targets operational system-build terms, not generic "AI automation" searches that attract high competition.\n\nFreshness Rule\n\nOnly collect posts from the last 12 hours. Results should be sorted by Most Recent, so stop extracting for a query once the visible posts are older than 12 hours. Do not do catch-up windows.\n\nFreshness priority:\n\n| Post age | Action |\n| ---------- | ---------------------------------------------------------------- |\n| 0-1 hour | Highest priority. Client is most likely active and shortlisting. |\n| 1-12 hours | Valid scan window. Still worth scoring if competition is low. |\n| 12+ hours | Skip. The client has usually already formed the shortlist. |\n\nRecord the scan timestamp as lastscanned in the daily scan doc frontmatter. This timestamp helps avoid reprocessing posts from prior scans, but the hard 12-hour cap still applies.\n\nRun Each Query\n\nFor each active saved query:\n\n1. Clear the search box.\n2. Enter the query string and submit.\n3. Apply U.S. only, Most Recent, and proposal-count filters.\n4. Wait for results to render.\n5. Extract visible job cards from the search page.\n6. Stop once a post exceeds the freshness cutoff.\n7. Tag each job with the source query.\n\nThe first page of most-recent, low-proposal results is the high-value slice. Do not paginate unless a specific investigation requires it.\n\nWhile scanning the API integration query, flag custom dashboard opportunities where a custom build is likely better than Power BI, Looker, Tableau, or a spreadsheet. Strong signs include 3+ data sources, live dashboard requirements, AI-generated summaries, anomaly detection, or workflow triggers.\n\nExtract Job Card Data\n\nThe search-card extraction should capture the fields needed for initial triage:\n\n- Title.\n- Upwork job URL path.\n- Posted age.\n- Proposal count.\n- Budget or hourly range.\n- Payment verification.\n- Client rating.\n- Total client spend.\n- Description snippet.\n- Source query.\n\nDeduplicate by Upwork job URL path after all queries finish. If a job appears under multiple queries, keep one row and note the overlap.\n\nScore and Enrich\n\nScoring has two stages.\n\nStage 1: Relevance\n\nScore relevance from 0 to 100 by asking whether the client needs a system, workflow, integration, automation, or operational build that Elevasis can deliver.\n\n| Range | Label | Criteria |\n| ------ | ------------ | ------------------------------------------------------------------------- |\n| 75-100 | Strong fit | Clear build intent, specific business workflow, named tools or outputs. |\n| 50-74 | Possible fit | Some build signals, but scope or buyer intent is ambiguous. |\n| 25-49 | Weak fit | Marginally related, mostly noise, low business-system signal. |\n| 0-24 | Irrelevant | Hiring, marketing, design, manual VA work, personal projects, or errands. |\n\nDisqualify posts that are clearly hiring a role, outsourcing manual work, asking for generic marketing/design help, or describing a personal/non-business project.\n\nStage 2: Application Priority\n\nApply tactical modifiers after relevance scoring. The final score determines whether to draft a proposal.\n\n| Signal | Positive indicators | Negative indicators |\n| -------------- | ---------------------------------------------------- | ------------------------------------------- |\n| Freshness | \\<1h or same-session post. | 8-12h old, or outside the scan window. |\n| Competition | \\<5 proposals, or 5-10 with strong fit. | 10+ proposals, especially 20+. |\n| Client quality | Payment verified, spend history, good rating, hires. | Unverified, no history, 0% hire rate. |\n| Budget | Fixed \\>$5K or hourly \\>$75/hr. | Fixed \\<$1K or hourly \\<$40/hr. |\n| Fit | Workflow, integration, dashboard, CRM, operations. | Copywriting, funnels, ads, hiring, support. |\n\nOnly enrich posts with a strong enough relevance score to matter, usually 65+. For those posts, open the job detail view and capture:\n\n- Hire rate.\n- Jobs posted.\n- Member since.\n- Company size.\n- Last viewed by client.\n- Interviewing count.\n\nUse this data to avoid over-ranking stale or low-quality clients that have a good-sounding job description but weak application ROI.\n\nWrite the Scan Output\n\nWrite scan docs under:\n\ntext\napps/docs/content/docs/operations/client-acquisition/upwork/scans/{YYYY-MM-DD}.mdx\n\nIf a scan doc already exists for the day, append the new scan as a later section instead of creating a second daily file.\n\nEach scan output should include:\n\n- Date and timestamp.\n- Freshness cutoff.\n- Queries scanned.\n- Total posts collected.\n- Deduplicated count.\n- Top opportunities only.\n- Comparison table.\n- Query health table.\n- Draft proposals section, when proposals are generated.\n\nKeep scan docs as decision tools, not archives. Include the top 10 ranked opportunities rather than every collected post.\n\nQuery Health\n\nAfter scoring, compute a query-health score so weak searches can be removed over time.\n\n| Metric | Measurement | Weight |\n| --------------- | ----------------------------------------------------------------- | ------ |\n| Fresh posts | Count within cutoff, normalized against 5 posts. | 25% |\n| Low competition | Percent with fewer than 10 proposals. | 25% |\n| Relevance | Percent actionable after deduplication and disqualification. | 30% |\n| Client quality | Percent with verified payment and useful spend or rating signals. | 20% |\n\nQueries that score 0 across 3+ consecutive scans are drop candidates. Compare the latest query-health table with the previous scan before changing the active query set.\n\nActive Query Principles\n\nStrong Upwork queries describe the system the buyer needs, not the freelancer category they think they are hiring.\n\nUse:\n\n- Specific operational system nouns such as booking system, intake form, scheduling system, ticketing system, tracking system, reservation system, billing system, and payment system.\n- Action verbs such as build, automate, custom, develop, setup, integration, and workflow.\n- Platform-specific searches where system-build intent overlaps with implementation work, such as GoHighLevel, Pipedrive, CRM, API integration, or inventory sync.\n\nAvoid:\n\n- Broad solution-first terms such as AI automation, AI agent, workflow automation, portal, dashboard, tool, or custom without a specific system noun.\n- Vertical keywords such as clinic, contractor, salon, or real estate unless paired with concrete system intent.\n- Pain-signal phrases that usually mean the client is hiring a human to perform manual work.\n- Revenue-proximity service terms such as cold email, appointment setting, outbound specialist, or funnel work.\n\nBefore testing a new query, read knowledge.upwork-query-strategy and check the Upwork query registry in the operations docs so old failure modes are not repeated. Use knowledge.upwork-calibration-strategy when promoting, dropping, or re-evaluating saved searches.\n\nProposal Handoff\n\nAfter the scan, draft proposals only for opportunities that remain strong after relevance, tactical modifiers, and client-quality checks.\n\nFor proposal drafting:\n\n1. Load knowledge.upwork-proposal-playbook.\n2. Read the top opportunities from the latest scan doc.\n3. Select the right proposal template by scope, budget, and relevance.\n4. Tailor the opening, proof point, plan, and two discovery questions.\n5. Append draft proposals to the scan doc under ## Draft Proposals.\n\nUse knowledge.upwork-response-templates after a client replies or sends an offer.\n\nScan Checklist\n\n- Browser tooling available.\n- Upwork logged in.\n- Search page loaded.\n- U.S. only filter applied.\n- Sort set to Most Recent.\n- Proposal-count filters applied.\n- All active saved queries scanned.\n- Posts older than 12 hours skipped.\n- Jobs deduplicated by URL path.\n- Relevance scores assigned.\n- Strong candidates enriched from detail view.\n- Final scores sorted descending.\n- Daily scan doc created or appended.\n- Query health table written.\n- Top proposal candidates handed to knowledge.upwork-proposal-playbook.'
1533
+ },
1534
+ {
1535
+ id: "knowledge.youtube-obs-recording-setup",
1536
+ title: "YouTube OBS Recording Setup",
1537
+ summary: "Canonical OBS Studio recording setup for Elevasis YouTube production, covering 1080p60 screen capture, safe recording format, NVENC quality settings, audio routing, face-camera scenes, and pre-recording checks.",
1538
+ bodyText: `Overview
1539
+
1540
+ Use this setup for Elevasis YouTube recordings that combine a short face-camera intro with Command Center or workflow screen capture. The target output is a clean 1080p60 source file that YouTube can re-encode without avoidable motion, audio, or color artifacts.
1541
+
1542
+ Baseline configuration:
1543
+
1544
+ text
1545
+ Video: 1920x1080, 60fps, NV12, Rec. 709 Partial
1546
+ Encode: NVENC H.264, CQP 18, P5 preset, High profile
1547
+ Format: MKV with automatic remux to MP4
1548
+ Audio: 48 kHz stereo, 320 kbps AAC, mixed plus mic-only tracks
1549
+ Camera: Insta360 Link 2C at 1080p60 with circle mask
1550
+ Scenes: Face + Screen on F5, Screen Only on F6
1551
+
1552
+ Prefer 1080p60 over 4K30 for screen recordings. Smooth cursor motion, scrolling, typing, and app transitions matter more than pixel density, and most viewers watch at 1080p or below. 4K30 produces larger files, slower editing, and visibly choppier screen motion.
1553
+
1554
+ Recording Output
1555
+
1556
+ Record to MKV, not MP4. MKV writes continuously, so a crash usually leaves the file usable. MP4 writes its index at the end, so a crash can corrupt the recording.
1557
+
1558
+ Enable automatic remux:
1559
+
1560
+ text
1561
+ Settings > Advanced > Recording > Automatically remux to mp4
1562
+
1563
+ After each recording, upload the remuxed MP4 and keep the MKV as the backup.
1564
+
1565
+ Use the advanced recording output mode:
1566
+
1567
+ text
1568
+ Settings > Output > Output Mode: Advanced
1569
+ Recording Tab:
1570
+ Type: Standard
1571
+ Recording Format: mkv
1572
+ Encoder: NVIDIA NVENC H.264
1573
+ Rate Control: CQP
1574
+ CQ Level: 18
1575
+ Keyframe Interval: 2
1576
+ Preset: P5 (Slow)
1577
+ Profile: high
1578
+ Look-ahead: checked
1579
+ Psycho Visual Tuning: checked
1580
+ Max B-frames: 2
1581
+
1582
+ CQP 18 is the stable quality target for screen content. It is visually lossless for this use case and gives YouTube a high-quality source before its VP9 or AV1 re-encode.
1583
+
1584
+ Fallback encoders:
1585
+
1586
+ - AMD GPU: use AMD HW H.264 (AMF) with equivalent CQP settings.
1587
+ - No dedicated GPU: use x264, CRF 18, CPU preset slow, or medium if the CPU struggles.
1588
+
1589
+ Set audio bitrates:
1590
+
1591
+ text
1592
+ Output > Advanced > Audio:
1593
+ Track 1: 320 kbps
1594
+ Track 2: 320 kbps
1595
+
1596
+ Video Settings
1597
+
1598
+ Use a 1080p canvas and output:
1599
+
1600
+ text
1601
+ Settings > Video:
1602
+ Base (Canvas) Resolution: 1920x1080
1603
+ Output (Scaled) Resolution: 1920x1080
1604
+ Downscale Filter: Lanczos (36 samples)
1605
+ Common FPS Values: 60
1606
+
1607
+ Use Rec. 709 limited-range color:
1608
+
1609
+ text
1610
+ Settings > Advanced:
1611
+ Color Format: NV12
1612
+ Color Space: 709
1613
+ Color Range: Partial
1614
+
1615
+ YouTube expects Rec. 709 limited range. Full range can produce washed-out or overly contrasty results after processing.
1616
+
1617
+ For a 3440x1440 ultrawide monitor, crop the display capture to the center 16:9 region before it is downscaled:
1618
+
1619
+ 1. Add Display Capture for the primary monitor.
1620
+ 2. Right-click the source and open Transform > Edit Transform.
1621
+ 3. Set Crop Left to 440 and Crop Right to 440.
1622
+ 4. Right-click the source again and select Transform > Fit to Screen.
1623
+
1624
+ This crops the capture to the center 2560x1440 region. Keep recorded windows inside that center area. A Windows 11 FancyZones center 16:9 zone is useful for keeping Command Center, browser, and terminal windows inside the captured area.
1625
+
1626
+ Use Window Capture only when the recording is limited to one browser window. Display Capture is better for tutorials that switch windows, show the taskbar, or tile terminal and browser views.
1627
+
1628
+ Audio Settings
1629
+
1630
+ Global audio settings:
1631
+
1632
+ text
1633
+ Settings > Audio:
1634
+ Sample Rate: 48 kHz
1635
+ Channels: Stereo
1636
+ Desktop Audio: Default
1637
+ Mic/Auxiliary Audio: Focusrite USB Audio (Scarlett 2i2)
1638
+
1639
+ Set the Focusrite input in Windows before recording:
1640
+
1641
+ 1. Open Windows Sound Settings.
1642
+ 2. Select the Focusrite USB Audio input.
1643
+ 3. Set format to 48000 Hz, 24-bit.
1644
+
1645
+ Physical Focusrite setup:
1646
+
1647
+ - Turn 48V phantom power on for the Lewitt LCT 240 PRO condenser.
1648
+ - Start input gain at 12 o'clock and adjust so peaks land around -12 dB.
1649
+ - Keep Direct Monitor off to avoid double-monitoring.
1650
+
1651
+ Apply OBS mic filters to the Mic/Aux source in this exact order:
1652
+
1653
+ | Order | Filter | Settings |
1654
+ | ----- | ----------------- | ------------------------------------------------------------------------ |
1655
+ | 1 | Noise Suppression | RNNoise |
1656
+ | 2 | Noise Gate | Close -40 dB, Open -35 dB, Attack 10 ms, Hold 200 ms, Release 100 ms |
1657
+ | 3 | Compressor | Ratio 3:1, Threshold -18 dB, Attack 6 ms, Release 60 ms, Output Gain 6 dB |
1658
+ | 4 | Limiter | Threshold -3 dB, Release 60 ms |
1659
+
1660
+ This order removes steady noise first, gates silence-period noise second, evens speech dynamics third, and catches peaks last. Do not add a Gain filter unless the signal is still too quiet after setting the Scarlett gain.
1661
+
1662
+ Multi-Track Audio
1663
+
1664
+ Record mixed audio and isolated mic audio:
1665
+
1666
+ text
1667
+ Settings > Output > Advanced > Recording Tab:
1668
+ Audio Track: check 1 and 2
1669
+
1670
+ Route tracks in Audio Mixer > Advanced Audio Properties:
1671
+
1672
+ | Source | Track 1 | Track 2 |
1673
+ | ------------- | ------- | ------- |
1674
+ | Desktop Audio | checked | empty |
1675
+ | Mic/Aux | checked | checked |
1676
+
1677
+ Track 1 is YouTube-ready mixed audio. Track 2 is mic-only audio for cleanup or editing.
1678
+
1679
+ Scene Setup
1680
+
1681
+ Create two scenes.
1682
+
1683
+ Face + Screen
1684
+
1685
+ Use this scene for the intro and occasional commentary call-ins.
1686
+
1687
+ Sources from bottom to top:
1688
+
1689
+ 1. Display Capture named Screen.
1690
+ 2. Video Capture Device named Facecam.
1691
+ 3. Optional image source for a circle border or glow.
1692
+
1693
+ Display Capture settings:
1694
+
1695
+ text
1696
+ Source: Display Capture
1697
+ Display: Primary monitor
1698
+ Capture Method: Windows 10 (1903 and later)
1699
+ Capture Cursor: checked
1700
+
1701
+ Apply the ultrawide crop to this source when recording from the 3440x1440 monitor.
1702
+
1703
+ Facecam settings:
1704
+
1705
+ text
1706
+ Device: Insta360 Link 2C
1707
+ Resolution: 1920x1080
1708
+ FPS: 60
1709
+ Video Format: MJPEG or default
1710
+
1711
+ Create a 512x512 PNG with a white circle on a black background and store it permanently, for example:
1712
+
1713
+ text
1714
+ E:\\OBS\\circle-mask.png
1715
+
1716
+ Apply it to the Facecam source:
1717
+
1718
+ text
1719
+ Filters > Image Mask/Blend:
1720
+ Type: Alpha Mask (Colour Channel)
1721
+ Path: E:\\OBS\\circle-mask.png
1722
+
1723
+ Resize the facecam source to roughly 300-400 px diameter and place it in a lower corner.
1724
+
1725
+ Screen Only
1726
+
1727
+ Use this scene for the main tutorial content.
1728
+
1729
+ Add the existing Screen source from the Face + Screen scene. Do not duplicate the display capture source.
1730
+
1731
+ Hotkeys:
1732
+
1733
+ text
1734
+ Settings > Hotkeys:
1735
+ Switch to Scene "Face + Screen": F5
1736
+ Switch to Scene "Screen Only": F6
1737
+ Start Recording: Ctrl+F9
1738
+ Stop Recording: Ctrl+F10
1739
+
1740
+ Avoid hotkeys that collide with the app being recorded. If using F5 inside a browser-heavy recording, choose a different key because F5 refreshes the page.
1741
+
1742
+ Recording flow:
1743
+
1744
+ 1. Start on Face + Screen.
1745
+ 2. Press Ctrl+F9.
1746
+ 3. Record a 15-30 second face-camera intro.
1747
+ 4. Press F6 for Screen Only.
1748
+ 5. Record the screen walkthrough.
1749
+ 6. Optionally press F5 to bring face-camera back for commentary.
1750
+ 7. Press Ctrl+F10 to stop.
1751
+
1752
+ For a fade, set Scene Transitions to Fade with a 300 ms duration.
1753
+
1754
+ Windows 11 Checks
1755
+
1756
+ Before a recording session:
1757
+
1758
+ - Disable Xbox Game Bar.
1759
+ - Leave GPU scheduling enabled.
1760
+ - Run OBS as Administrator if frame drops or black-screen capture occur.
1761
+ - Set OBS process priority to Above Normal if the recording drops frames.
1762
+ - Use the High Performance power plan during recording.
1763
+
1764
+ Upload Quality
1765
+
1766
+ YouTube re-encodes every upload. The goal is to provide clean source material.
1767
+
1768
+ - Do not upload at YouTube's minimum recommended bitrate.
1769
+ - Do not run the recording through HandBrake or another extra encode just to save space.
1770
+ - Upload the OBS-remuxed MP4 directly unless the video was edited.
1771
+ - If editing in DaVinci Resolve or Premiere, render H.264 at 50 Mbps CBR for 1080p60 or use the YouTube preset.
1772
+
1773
+ The OBS output should match YouTube's expected upload shape: MP4 container, H.264 video, AAC-LC audio, 48 kHz stereo, Rec. 709 limited range.
1774
+
1775
+ Pre-Recording Checklist
1776
+
1777
+ - Scarlett 2i2 phantom power is on and voice peaks around -12 dB.
1778
+ - Correct OBS scene is selected, usually Face + Screen.
1779
+ - Insta360 Link Controller has AI tracking and HDR enabled when needed.
1780
+ - Door is closed, fan or AC is off, and the room is controlled.
1781
+ - OBS mic meter peaks in green or yellow, never red.
1782
+ - A 10-second test recording has been played back for audio, video quality, and facecam position.
1783
+ - The 12-minute warm-up from knowledge.youtube-mental-prep is complete when recording on camera.`
1784
+ },
1785
+ {
1786
+ id: "knowledge.finance-operations-playbook",
1787
+ title: "Finance Operations Playbook",
1788
+ summary: "Operating playbook for Elevasis finance: Xero as the system of record, Stripe payment collection and payout reconciliation, invoicing and AR cadence, tax estimates, deductions, 1099s, and annual filing prep.",
1789
+ bodyText: "Overview\n\nElevasis finance operations run through Xero, with Stripe handling payment collection. Xero is the single source of truth for financial records: business checking bank feeds, Stripe payouts, contractor payments, expenses, invoices, receivables, and year-end exports.\n\nThe finance loop has three connected parts:\n\n1. Invoicing captures client revenue through monthly retainers and Stripe Checkout.\n2. Accounting records and reconciles bank transactions, Stripe payouts, contractor payments, and operating expenses.\n3. Taxes use accurate Xero records for quarterly estimates, deductions, 1099s, and annual filing.\n\nAccounting and Reconciliation\n\nReconcile bank transactions in Xero weekly. Match each bank feed entry to its real-world source before month end.\n\n- Stripe payouts should match against Stripe bank feed activity.\n- Contractor payments should match checking account transfers.\n- Software subscriptions such as Railway, Vercel, Supabase, WorkOS, and OpenAI should be categorized as operating expenses.\n- Unmatched or ambiguous transactions should be flagged for manual review before monthly close.\n\nMaintain the core chart of accounts around revenue, cost of sales, operating expenses, and owner draws. Revenue includes client retainers and one-time project fees. Cost of sales covers contractor labor directly tied to client work. Operating expenses cover SaaS tools, hosting, banking fees, and professional services. Owner draws track business distributions.\n\nConfigure Xero with the connected business checking account, the default tax rate for the business jurisdiction, and the correct financial year end in organization settings.\n\nInvoicing and Accounts Receivable\n\nClient billing runs on a monthly retainer model. Create invoices in Xero at the start of each billing period, send them by Xero email or Stripe Checkout link, record payment after Stripe confirms checkout completion, and reconcile the Stripe payout in Xero.\n\nUse Xero repeating invoices for monthly retainers:\n\n- Frequency: monthly.\n- Start date: billing cycle start date.\n- Approval: automatic approval where the billing terms are stable.\n\nConfigure invoice reminders around the due date: a courtesy reminder 3 days before due date, an overdue notice 1 day after due date, and an escalation notice 7 days after due date.\n\nFor invoices more than 14 days overdue, contact the client directly through the active communication channel, pause active work pending payment confirmation, and resolve the balance before the next billing cycle.\n\nTaxes\n\nTax work depends on accurate Xero records throughout the year. As a pass-through entity, Elevasis business income flows to the owner personal return, so quarterly estimates reduce underpayment risk and year-end exports should be clean enough for a CPA or tax preparer.\n\nQuarterly estimated payment targets:\n\n- Q1: April 15.\n- Q2: June 15.\n- Q3: September 15.\n- Q4: January 15 of the following year.\n\nEstimate payments as roughly 25-30% of net profit for the quarter and pay via IRS Direct Pay or EFTPS.\n\nTrack deductible expenses in Xero during the year: SaaS subscriptions, contractor payments, home office expenses where applicable, professional development, courses, and business banking fees. Keep digital receipts organized by year in the business records folder.\n\nIssue 1099-NEC forms to US-based contractors paid more than $600 in a calendar year. Collect a W-9 before first payment, track annual contractor totals in Xero, file 1099-NEC forms by January 31 of the following year, and file the 1096 summary with the IRS when required.\n\nAt year end, export the Xero profit and loss statement and balance sheet. Provide them to the CPA or tax preparer with issued 1099s, bank statements for the business year, mileage logs if applicable, and home office documentation if applicable."
1790
+ },
1791
+ {
1792
+ id: "knowledge.platform-command-view",
1793
+ title: "Platform Command View",
1794
+ summary: "Reference for Command View, the read-only graph surface for organization automation resources, relationships, manifest validation, graph modes, filters, and troubleshooting.",
1795
+ bodyText: "Overview\n\nCommand View visualizes an organization's automation landscape: agents, workflows, triggers, integrations, external resources, and human checkpoints, plus the relationships between them.\n\nIt answers operational questions such as:\n\n- What does this agent talk to?\n- If a provider goes down, what breaks?\n- What is connected to the CRM?\n- Which resources are hidden, diagnostic-only, or currently relevant to this trace?\n\nAccess Command View through the Knowledge area at /knowledge/command-view.\n\nHow Command View Works\n\nCommand View is a visualization layer over the organization deployment manifest.\n\ntypescript\nconst yourOrgRegistry: DeploymentSpec = {\n agents: [],\n workflows: [],\n triggers: [],\n integrations: [],\n humanCheckpoints: [],\n relationships: {},\n externalResources: []\n}\n\nArchitecture:\n\n1. Manifest declares resources and relationships.\n2. SDK deploy and API registration validate declared references.\n3. Pre-serialization computes graph data once.\n4. Frontend visualizes cached graph data.\n\nCommand View does not create routing. It displays declarations and runtime topology data.\n\nNode Types\n\n| Type | Purpose | Badge info |\n| --- | --- | --- |\n| Triggers | Entry points such as webhook, schedule, manual, or event triggers | Trigger type |\n| Agents | Autonomous AI resources | Tool count, Knowledge Map, memory |\n| Workflows | Multi-step orchestrations | Step count |\n| Integrations | External service connections | Connection status |\n| External resources | Third-party platforms such as n8n, Make, or Zapier | Platform name |\n| Human checkpoints | Human decision points | Approval required |\n\nNode badges:\n\n- Agents show tool count, Knowledge Map, and memory.\n- Workflows show step count.\n- Integrations show connection status.\n- Triggers show webhook, schedule, manual, or event type.\n- External resources show platform.\n\nEdge Types\n\n| Type | Meaning |\n| --- | --- |\n| Triggers | A trigger initiates a resource |\n| Invokes | A resource calls another resource |\n| Uses | A resource uses an integration |\n| Approval | A resource requests human approval |\n\nDeclaring Relationships\n\nInternal Resources\n\nUse relationships to declare what agents and workflows invoke or use.\n\ntypescript\nconst relationships: ResourceRelationships = {\n 'executive-agent': {\n triggers: {\n workflows: ['report-workflow'],\n agents: ['research-agent']\n },\n uses: {\n integrations: ['integration-gmail', 'integration-attio']\n }\n }\n}\n\nTriggers\n\nTriggers declare their own metadata, while routing is declared through relationships.\n\ntypescript\nconst triggers: TriggerDefinition[] = [\n {\n resourceId: 'webhook-customer-created',\n type: 'trigger',\n triggerType: 'webhook',\n name: 'Customer Created Webhook',\n description: 'Triggers when a customer is created in Shopify',\n status: 'prod',\n webhookPath: '/webhooks/customer-created'\n }\n]\n\nTrigger routing is not configured on the trigger object itself.\n\nExternal Resources\n\nExternal resources can declare what they trigger and which integrations they use.\n\ntypescript\nconst externalResources: ExternalResourceDefinition[] = [\n {\n resourceId: 'external-n8n-sync',\n type: 'external',\n platform: 'n8n',\n name: 'N8N Product Sync',\n description: 'Syncs product data from Shopify to internal database',\n status: 'prod',\n triggers: { workflows: ['internal-sync-workflow'] },\n uses: { integrations: ['integration-shopify'] }\n }\n]\n\nThese declarations document the relationship. They do not programmatically connect the external platform.\n\nHuman Checkpoints\n\nHuman checkpoints declare which resources request approval and where approval can route.\n\ntypescript\nconst humanCheckpoints: HumanCheckpointDefinition[] = [\n {\n resourceId: 'approval-sales',\n name: 'Sales Approval Queue',\n description: 'Human approval for sales proposals',\n status: 'prod',\n requestedBy: { agents: ['sales-agent'] },\n routesTo: { workflows: ['send-proposal-workflow'] }\n }\n]\n\nActual human approval decisions are runtime choices, not automatic routing from the declaration alone.\n\nValidation\n\nCommand View shows relationships validated during deploy and API registration.\n\nValidation checks:\n\n- Referenced resource IDs exist.\n- Agent and workflow IDs are unique.\n- External resource references are valid.\n- Human checkpoint routes exist.\n- Model configs have valid providers, models, and API key references.\n\nThese checks cover declared relationships and registry consistency. They do not infer every runtime execution.trigger(...) target, especially when the target is computed dynamically.\n\nWhen validation errors occur, API startup fails with a descriptive error such as:\n\ntext\nRegistryValidationError: [YourOrg] Resource 'my-agent' uses non-existent integration: integration-attio\n\nFix validation errors by updating the manifest to reference the correct IDs or by adding the missing resource definition.\n\nUsing Command View\n\nVisualization Features\n\nCommand View uses Cytoscape graph modes:\n\n- Map: keeps organization structure visible and honors hidden-resource visibility.\n- Trace: resolves directed paths and reveals resources for the active trace.\n- Impact: pivots around the selected node and reveals related resources for impact review.\n\nFilter panel capabilities:\n\n- Search across nodes, relationships, and IDs.\n- Filter by node kind, resource type, environment, topology presence, integrations, and resource facets.\n- Toggle resource visibility and diagnostic/testing resource visibility.\n- Show visible and hidden resource counts in the same control surface.\n\nHidden resource behavior:\n\n- Resource nodes are hidden by default so the organization structure remains readable.\n- Structural nodes remain visible as the navigation skeleton.\n- Selecting a visible node can reveal directly connected hidden resources.\n- Diagnostic and testing resources are hidden by default and can be revealed from the filter panel.\n\nExpand Around behavior:\n\n- Select a node, open Details, and use Expand Around to preview nearby graph context before revealing it.\n- Semantic presets cover coverage, operational dependencies, organization context, and impact paths.\n- Feature, surface, entity, and capability nodes default to Coverage.\n- Resource nodes default to Operational Dependencies.\n- Preview counts show hidden resources before Apply reveals the graph patch.\n\nRead-only constraints:\n\n- No drag and drop.\n- No connection editing.\n- No graph mutation from the visualization layer.\n\nManifest Debugging\n\nUse Command View to verify:\n\n1. Resources appear as nodes.\n2. Relationships show as edges.\n3. Integration connections exist.\n4. Trigger invocations are declared correctly.\n5. Orphaned resources are intentional.\n\nCommon issues:\n\n- Typo in a resource ID reference.\n- Missing relationship declaration.\n- Incorrect integration credential name.\n- External resource missing a bidirectional declaration.\n\nSystem Understanding\n\nUseful exploration questions:\n\n- What triggers this workflow?\n- What integrations does this agent use?\n- What happens after this approval?\n- If this integration fails, what breaks?\n\nVisual patterns:\n\n- Hub nodes with many connections indicate critical resources.\n- Linear chains show sequential workflows.\n- Branching shows conditional logic.\n- Isolated nodes may indicate unused resources.\n\nTroubleshooting\n\nEmpty Graph\n\nLikely causes:\n\n- Organization has no resources defined.\n- Search, environment, node kind, topology, or resource facet filters hide all graph elements.\n- Command View resources are hidden and no structural nodes match current filters.\n- Validation errors prevented serialization.\n\nCheck:\n\n1. API logs for validation errors.\n2. Network tab for the /api/command-view response.\n3. Manifest resources arrays.\n4. Filter panel visibility and filter state.\n\nFix:\n\n- Add resources to the manifest.\n- Fix validation errors.\n- Reveal resources or reset graph filters.\n\nMissing Edges\n\nLikely causes:\n\n- Relationship is not declared in the manifest.\n- Resource ID reference has a typo.\n- Validation passed but the relationship declaration is incomplete.\n\nCheck:\n\n1. Manifest relationships field.\n2. Resource ID spelling and case.\n3. API response includes both nodes.\n\nFix the relationship declaration in the manifest.\n\nNode Not Appearing\n\nLikely causes:\n\n- Resource is not in the correct manifest array.\n- Resource is hidden by visibility controls.\n- Status, resource type, topology, or facet filters exclude the node.\n- Duplicate ID caused validation failure.\n\nCheck:\n\n1. Manifest includes the resource in the correct array.\n2. Filter panel visibility and filter state.\n3. Resource status field.\n4. API logs for duplicate ID errors.\n\nFix by adding the resource, revealing resources, or resetting filters.\n\nValidation Errors on Startup\n\nCommon error shape:\n\ntext\nRegistryValidationError: [OrgName] Resource 'resource-id' uses non-existent integration: integration-name\n\nCommon causes:\n\n- Typo in integration credential name.\n- Resource referenced before definition.\n- Missing import.\n\nFix:\n\n- Verify credential names match integration definitions.\n- Ensure all resources exist in manifest arrays.\n- Check module dependency statements in the manifest module.\n\nBest Practices\n\nKeep relationships current when resources change:\n\n1. Add new tools, then update uses.integrations.\n2. Add workflow invocation, then update triggers.workflows.\n3. Add agent delegation, then update triggers.agents.\n4. Add approval gate, then update the human checkpoint requestedBy.\n\nUse descriptive IDs:\n\n- integration-shopify-store-alpha instead of integration-1.\n- trigger-customer-created-webhook instead of trigger-webhook.\n- approval-sales-proposals instead of approval-1.\n\nValidate early and often:\n\nbash\npnpm dev:api\n\nAPI startup runs registry validation. Fix validation errors immediately because they prevent graph visualization.\n\nReview Command View regularly to onboard team members, plan new features, trace execution paths, and audit integration dependencies.\n\nRelationship Enforcement\n\nRelationships are declarations, not routing.\n\n| Layer | What it does | Enforcement |\n| --- | --- | --- |\n| DeploymentSpec.relationships | Declares relationships such as workflow A triggers workflow B | Validated at startup so IDs must exist |\n| buildEdges() in serialization.ts | Serializes declarations into CommandViewEdge[] | Pure transformation, no runtime check |\n| Runtime workflow and agent code | Calls resources through execution.trigger(...) or equivalent wiring | Independent of declarations |\n\nThe Resource Registry validates that declared targets reference real resource IDs during deploy and registration. However, there is no guarantee that declared graph relationships match runtime reality. Code can call a resource that is not declared in relationships, and that runtime call will not fail solely because the declaration is missing.\n\nEnforcement status:\n\n| Category | Validated at startup | Matches runtime | Status |\n| --- | --- | --- | --- |\n| Resource IDs unique | Yes | Yes, because lookup works | Enforced |\n| Input schema matches execution interface | Yes | Yes, because validation uses it | Enforced |\n| Relationship targets exist | Yes | No, invocations can be dynamic strings | Partially enforced |\n| Trigger routing | No | No, webhook handlers can hardcode routes | Decorative |\n| Tool availability per agent | No | No, tools are built dynamically by factories | Decorative |\n| Model config | Yes | No, runtime selects from environment variables | Decorative |\n| Integration usage | Partially, IDs checked | No, credentials resolve at runtime | Decorative |\n\nNon-Executable Node Types\n\nTriggers, integrations, external resources, and human checkpoints are non-executable node types. They exist for Command View.\n\n- TriggerDefinition does not set up actual routing. Webhook routing is independent.\n- IntegrationDefinition is a credentials reference with provider and credentialName; it does not connect to a provider by itself.\n- ExternalResourceDefinition documents a third-party automation but has no programmatic link to that platform.\n- HumanCheckpointDefinition documents approval queues and routes. Actual routing is determined by human choice and workflow code.\n\nRuntime Relationship Guard\n\nThere is no general runtime guard that discovers every nested execution target at call time. Deployed SDK workers can invoke nested resources through execution.trigger(...) and the API dispatcher, so dynamic targets can drift from DeploymentSpec.relationships even when deploy-time validation passes.\n\nDeploy-Time Validation\n\nResources are deployed through elevasis-sdk deploy. The SDK includes relationships in DeployMetadata, validates the resulting DeploymentSpec locally, and the API validates the merged deployment before registerOrganization().\n\nKey files:\n\n- packages/sdk/src/cli/commands/deploy.ts\n- apps/api/src/deployments/service.ts\n- packages/core/src/platform/registry/validation.ts\n\nAccepted Decorative Gaps\n\nThese gaps are inherent to the architecture and cannot be fully validated at deploy time:\n\n- Runtime invocation drift, especially with computed target IDs.\n- Tool declarations, because agents build tools dynamically by factory.\n- Model config, because runtime LLM calls use environment variables.\n- External resource triggers, because routing lives in external platforms.\n\nRelated References\n\n- /knowledge read knowledge.platform-composition-patterns\n- /knowledge read knowledge.platform-integration-patterns\n- /knowledge read knowledge.platform-capabilities"
1796
+ },
1797
+ {
1798
+ id: "knowledge.platform-composition-patterns",
1799
+ title: "Platform Composition Patterns",
1800
+ summary: "Reference for composing agents, workflows, integrations, tools, observability, scaling, error handling, and security into complete Elevasis automation systems.",
1801
+ bodyText: "Overview\n\nUse this reference to choose how Elevasis resources compose into complete systems. The core decision is whether reasoning, deterministic orchestration, or a hybrid of both should own the business flow.\n\nThree primary patterns exist:\n\n- Agent-centric: an agent is the primary orchestrator, and workflows or integrations are tools.\n- Workflow-centric: a workflow is the deterministic pipeline, and agents appear only as processing steps.\n- Hybrid: agents decide what should happen, and workflows execute reliable sequences.\n\nSystem Architecture Patterns\n\nAgent-Centric Systems\n\nPattern: Agent is the primary orchestrator; workflows and integrations are tools.\n\ntext\nTrigger to Agent\n |- Tool: Integration A\n |- Tool: Integration B\n |- Tool: Workflow 1 via resource invocation\n - Tool: Workflow 2 via resource invocation\n\nUse this pattern for ambiguous goals, dynamic decision-making, multi-turn conversations, and business logic that requires judgment.\n\nStrengths:\n\n- Flexible and adaptive.\n- Handles ambiguity well.\n- Can delegate to specialist resources.\n- Supports natural language interaction.\n\nWeaknesses:\n\n- Token cost per execution.\n- Non-deterministic LLM variance.\n- Requires strong observability for debugging.\n- Usually slower than deterministic workflows.\n\nWorkflow-Centric Systems\n\nPattern: Workflow is the pipeline; agents are processing steps.\n\ntext\nTrigger to Workflow\n |- Step 1: Integration A reads data\n |- Step 2: Agent processes or reasons\n |- Step 3: Integration B writes result\n - Step 4: Conditional routing\n\nUse this pattern for predictable sequences, data transformation pipelines, integration orchestration, and fixed business logic.\n\nStrengths:\n\n- Deterministic and reliable.\n- Fast execution.\n- Easy to debug step-by-step.\n- No token cost unless an agent step is included.\n\nWeaknesses:\n\n- Rigid, because steps are predefined.\n- Does not handle ambiguity well.\n- Requires code changes for logic updates.\n\nHybrid Systems\n\nPattern: Agents decide, workflows execute. This is the recommended default for complex automation.\n\ntext\nTrigger to Agent decides what to do\n |- Invokes: Workflow A\n |- Invokes: Workflow B\n - Uses: Integration C directly\n\nWorkflow A:\n |- Integration D reads\n |- Agent processes a specific subtask\n - Integration E writes\n\nUse this pattern when the system needs variable paths, reasoning, reliability, and a mix of predictable and unpredictable steps.\n\nStrengths:\n\n- Flexible where needed and reliable where possible.\n- Cost-efficient because workflows do not use tokens by default.\n- Good observability across agent reasoning and workflow steps.\n\nResource Composition Patterns\n\nAgent + Tools\n\nUse direct integration access for simple or frequent operations.\n\nTool categories:\n\n- Base tools: always available, high-frequency operations such as read queries or cached data access. Example: attiogetrecord.\n- Knowledge Map tools: lazy-loaded, low-frequency operations such as write operations or domain-specific capabilities. Examples: attiocreatecontact, dropboxuploadfile.\n- Resource invocation tools: delegate to workflows for deterministic sequences or call specialist agents.\n\nTool creation example:\n\ntypescript\nconst tool = createAttioCreateRecordTool('elevasis-attio')\n\nCredential names resolve to organization-scoped storage. RLS policies enforce organization isolation, credentials are encrypted at rest, and OAuth tokens can refresh automatically.\n\nAgent + Workflow\n\nUse an agent for reasoning and a workflow for deterministic execution.\n\ntext\nTrigger to Agent reasoning\n |- Direct tool: read integration\n |- Invoke tool: Workflow A\n - Invoke tool: Workflow B\n\nExecution flow:\n\n1. Agent analyzes the request.\n2. Agent invokes a workflow through a tool call.\n3. Workflow executes deterministic steps without token cost.\n4. Agent explains the result to the user.\n\nUse this pattern when the system needs both reasoning and reliability, has multiple execution paths, or should minimize token use by moving predictable work into workflows.\n\nWorkflow + Integration\n\nUse a workflow to orchestrate deterministic integration sequences.\n\ntext\nTrigger to Workflow\n |- Step 1: Integration A reads\n |- Step 2: Transform pure function\n |- Step 3: Integration B writes\n - Step 4: Notify\n\nUse this pattern for predictable step sequences, data transformation pipelines, integration orchestration, and cost-sensitive work that does not need LLM reasoning.\n\nPattern Selection Guide\n\n| Requirement | Pattern | Example |\n| --- | --- | --- |\n| Ambiguous goals | Agent-centric | Business orchestration with variable outcomes |\n| Predictable sequence | Workflow-centric | Shopify to CRM sync |\n| Hybrid needs | Agent + workflow | Support router where agent decides and workflow executes |\n| \\>10 tools | Knowledge Map | Agent with many domain capabilities |\n| High-frequency reads | Base tools | Attio read operations |\n| Low-frequency writes | Lazy-loaded tools | CRM updates or page creation |\n| External services | Integration tools | Attio, Gmail, Google Sheets |\n| Deterministic pipelines | Workflow + integration | Data transformation |\n\nComplexity guidelines:\n\n- Simple, 1-2 resources: agent with base tools, or workflow with 1-2 integrations.\n- Medium, 3-5 resources: agent plus Knowledge Map with 2-3 nodes, or workflow plus an agent step.\n- Complex, 6+ resources: agent plus Knowledge Map plus memory preload, or multi-integration workflows.\n\nCross-Cutting Concerns\n\nObservability\n\nTrack these surfaces:\n\n- Agent iterations, including reasoning and actions.\n- Workflow steps, including inputs and outputs.\n- Tool calls, including integration requests and responses.\n- Errors and retries.\n- Token usage and cost.\n\nPrimary tools:\n\n- Execution Logs for SSE-based debugging.\n- Activity Log for the real-time stream.\n- Execution Health for cost tracking and ROI review.\n- Command View for system architecture and dependency review.\n\nCost Tracking\n\nAgent cost comes from per-iteration token usage, model pricing, and tool execution cost. Workflow cost is usually zero token cost plus any external integration API fees.\n\nOptimization rules:\n\n- Use workflows for predictable tasks.\n- Lazy-load tools for 80-95% token savings.\n- Cache frequently accessed stable data.\n- Choose the appropriate model per task.\n\nScaling Strategies\n\nHorizontal scaling:\n\n- Keep agent execution stateless.\n- Store sessions in the database.\n- Let a load balancer distribute requests.\n\nVertical optimization:\n\n- Preload memory to reduce iteration count.\n- Lazy-load tools to reduce token usage.\n- Batch tool operations where possible.\n- Use parallel integration calls when the steps are independent.\n\nError Handling\n\nAgent error handling:\n\n- Tool errors are returned to the agent so it can reason about recovery.\n- Iteration limits prevent infinite loops.\n- Timeout protection constrains long-running tools and executions.\n- Graceful degradation explains what failed.\n\nWorkflow error handling:\n\n- Step retry logic uses exponential backoff, commonly 1, 4, and 9 minutes.\n- Conditional routing can send failures to an alternative step.\n- Manual intervention can route work to a human checkpoint.\n- Idempotent steps should be safe to retry.\n\nExample:\n\ntypescript\n{\n id: 'sync-to-crm',\n handler: async ({ input, context }) => {\n try {\n return await crmClient.createContact(input)\n } catch (error) {\n if (error.code === 'DUPLICATE') {\n return crmClient.updateContact(input)\n }\n\n throw error\n }\n },\n next: { type: 'linear', target: 'send-notification' }\n}\n\nSecurity\n\nCredential management:\n\n- Store credentials in the credentials table.\n- Encrypt values at rest.\n- Scope records by organization with RLS policies.\n- Refresh OAuth tokens automatically where supported.\n- Never store API keys in code.\n\nMulti-tenancy isolation layers:\n\n1. Database RLS on tenant-scoped tables.\n2. API middleware that requires organization context.\n3. Resource registry lookups scoped to the organization.\n4. Command View filtered by organization.\n\nBest Practices\n\n1. Start with a single agent or workflow, then add complexity only when needed.\n2. Build the working version before optimizing for caching or lazy loading.\n3. Put domain logic in agents and execution flow in workflows.\n4. Lazy-load large or low-frequency tool groups.\n5. Cache stable data, not fast-changing operational state.\n\nCommon mistakes:\n\n- Creating a Knowledge Map for fewer than five tools.\n- Using a workflow for one integration call when an agent tool would be simpler.\n- Loading more than ten tools as base tools.\n- Putting domain logic into workflows where it becomes hard to test and adapt.\n- Mixing unrelated concerns in one knowledge node.\n\nRelated References\n\n- /knowledge read knowledge.platform-integration-patterns\n- /knowledge read knowledge.platform-command-view"
1802
+ },
1803
+ {
1804
+ id: "knowledge.platform-integration-patterns",
1805
+ title: "Platform Integration Patterns",
1806
+ summary: "Reference for connecting Elevasis agents and workflows to external services through adapters, OAuth, API keys, tenant-isolated credentials, retries, and tests.",
1807
+ bodyText: "Overview\n\nIntegration patterns define how Elevasis agents and workflows connect to external services such as Gmail, Attio, Google Sheets, and custom APIs. The platform uses a standardized adapter pattern with OAuth 2.0 and API key authentication backed by tenant-isolated credentials.\n\nCore patterns:\n\n- Direct integration, where a resource uses integration tools directly.\n- Integration workflow, where a workflow coordinates multiple integrations.\n- Shared integration, where multiple resources use the same credential set.\n- Multi-account integration, where the same provider has separate credentials for different contexts.\n\nDirect Integration Pattern\n\nPattern: Resource uses an integration tool.\n\nUse direct integration tools when an agent or workflow performs frequent operations against a provider.\n\nCharacteristics:\n\n- Tools are always available to the resource.\n- There is no additional navigation overhead.\n- The pattern is optimized for frequent read-heavy operations.\n\nTool factory location:\n\ntext\npackages/core/src/execution/engine/tools/integration/server/adapters/{provider}/{provider}-tools.ts\n\nTool factories use createIntegrationTool() with a credentialName parameter. Example: createAttioCreateRecordTool(credentialName).\n\nIntegration Workflow Pattern\n\nPattern: Agent invokes a workflow, and the workflow uses integrations.\n\nUse an integration workflow for complex multi-step operations that need deterministic orchestration.\n\nExample lead capture flow:\n\n1. Webhook trigger receives the event.\n2. Workflow creates an Attio contact.\n3. Workflow sends a Gmail notification.\n4. Workflow returns confirmation.\n\nCharacteristics:\n\n- Deterministic execution order.\n- Step-level error handling and retry.\n- Multiple integrations coordinated in one place.\n- Built-in audit trail through workflow execution.\n\nShared Integration Pattern\n\nPattern: Multiple resources share the same integration.\n\nUse shared integrations for organization-wide providers that should use one credential set.\n\nExample:\n\ntext\npackages/elevasis-operations/src/index.ts\n\nCharacteristics:\n\n- One credential per integration.\n- Centralized credential management.\n- Shared across agents and workflows.\n\nMulti-Account Pattern\n\nPattern: Same provider, different credentials.\n\nUse this pattern for department-specific provider instances or separate dev/prod environments.\n\nExample:\n\ntypescript\nconst salesTool = createAttioCreateRecordTool('attio-sales')\nconst supportTool = createAttioCreateRecordTool('attio-support')\n\nDisambiguate multi-account tools with explicit tool names:\n\ntypescript\nconst tool = createAttioToolNamed(\n 'attiosalescreaterecord',\n 'attio-sales',\n 'createRecord'\n)\n\nAuthentication Patterns\n\nOAuth 2.0 vs API Key\n\n| Aspect | OAuth 2.0 | API key |\n| --- | --- | --- |\n| Auth flow | Browser redirect plus token exchange | Direct API key |\n| Setup complexity | High: client ID, secret, redirect URL | Low: single API key |\n| Token refresh | Automatic refresh token rotation | Manual key rotation |\n| Scope control | Granular permission scopes | Full access or none |\n| User context | Per-user authorization | Service-level access |\n| Revocation | User can revoke anytime | Manual key deletion |\n\nOAuth 2.0 Pattern\n\nProviders include Gmail and Google Sheets.\n\nCredential format:\n\ntypescript\ninterface OAuthToken {\n provider: string\n accessToken: string\n refreshToken: string\n expiresAt: string\n tokenType: 'Bearer'\n scope?: string\n}\n\nexpiresAt is an ISO 8601 timestamp. The platform handles token refresh when supported by the provider and credential implementation.\n\nAPI Key Pattern\n\nProviders include Attio and custom APIs.\n\nCredential format:\n\ntypescript\ninterface APIKeyCredentials {\n apiKey: string\n}\n\nProvider-specific validation belongs in the adapter. For Attio, see:\n\ntext\npackages/core/src/execution/engine/tools/integration/server/adapters/attio/attio-adapter.ts\n\nCredential Management\n\nCredentials are stored in the credentials table.\n\nKey columns:\n\n- organizationid for tenant isolation.\n- name, such as elevasis-attio.\n- provider, such as gmail or attio.\n- encryptedvalue containing encrypted JSON.\n\nSecurity rules:\n\n- RLS policies enforce tenant isolation.\n- Values are encrypted at rest.\n- Credentials do not cross organization boundaries.\n\nBest practices:\n\n- Store credentials encrypted in the database.\n- Use tenant-isolated credential names.\n- Validate credentials before API calls.\n- Rotate API keys regularly.\n- Use least-privilege OAuth scopes.\n\nDo not:\n\n- Hardcode credentials in code.\n- Share credentials across organizations.\n- Log credential values.\n- Store provider API keys in environment variables for tenant integrations.\n- Reuse the same credential name for dev and prod.\n\nCredential resolution flow:\n\n1. Tool requests a credential by name.\n2. Platform queries credentials with the current organizationid.\n3. Platform decrypts the credential.\n4. Adapter validates the credential against the provider schema.\n5. Tool passes the credential to the provider adapter.\n6. Usage is logged without credential values.\n\nImplementation reference:\n\ntext\npackages/core/src/execution/engine/tools/integration/tool.ts\n\nError Handling Patterns\n\nAdapters should convert provider errors into platform tooling errors.\n\nCommon error categories:\n\n- credentialsinvalid for invalid or missing credentials.\n- apierror for provider API errors.\n- networkerror for network or timeout failures.\n- validationerror for invalid parameters.\n- methodnotfound for unknown adapter methods.\n\nRetry strategy:\n\n- Network errors: retry with exponential backoff, commonly 1, 2, and 4 seconds.\n- Auth errors: fail immediately because the credential needs attention.\n- Validation errors: fail immediately because the input needs correction.\n- Rate limits: retry after reset when the provider exposes a reset time.\n- Workflow-level retries: commonly 3 attempts with 1, 4, and 9 minute delays.\n\nAdapter Development\n\nAdapter class location:\n\ntext\npackages/core/src/execution/engine/tools/integration/server/adapters/{provider}/{provider}-adapter.ts\n\nAdapter classes implement BaseIntegrationAdapter and contain call() and validateCredentials() methods.\n\nTool factory location:\n\ntext\npackages/core/src/execution/engine/tools/integration/server/adapters/{provider}/{provider}-tools.ts\n\nTool factories export create{Provider}{Operation}Tool(credentialName) functions.\n\nTests belong under:\n\ntext\npackages/core/src/execution/engine/tools/integration/server/adapters/{provider}/tests/\n\nRegistration happens in:\n\ntext\npackages/core/src/execution/engine/tools/integration/server/index.ts\n\nTesting Strategies\n\nUnit testing:\n\n- Mock external APIs.\n- Cover credential validation.\n- Cover API call construction.\n- Cover response parsing.\n- Cover error handling.\n\nIntegration testing:\n\n- Use real API calls only with test accounts or test workspaces.\n- Clean up test data after tests.\n- Run in CI only when secret credentials are available.\n- Keep provider behavior mocked in unit tests.\n\nAgent testing:\n\n- Test agents using integration tools at the resource boundary.\n- Tenant project agent tests normally live under external/{org}/src/agents/{agent}/tests/.\n\nRelated References\n\n- /knowledge read knowledge.platform-composition-patterns\n- /knowledge read knowledge.platform-command-view\n- /knowledge read knowledge.platform-capabilities"
1808
+ },
1809
+ {
1810
+ id: "knowledge.what-is-elevasis",
1811
+ title: "What is Elevasis",
1812
+ summary: "Company overview, positioning, value proposition, and pricing for the AI orchestration platform",
1813
+ bodyText: 'Executive Summary\n\nCompany: Bootstrapped AI orchestration platform for done-for-you business automation\n\nFounder: Solo (Alex, 34), bootstrapped\n\nMarket: Service-based SMBs that need practical AI automation without building internal AI teams\n\nGTM: Content-led marketing, proof assets, consultative sales, and land-and-expand pricing\n\nCompany Overview\n\nCurrent Status\n\nThe platform core is production-ready enough to support client-facing demonstrations and implementation work. Current business focus is client acquisition, proof-building, and turning the platform into a repeatable service delivery system.\n\nFounder Profile\n\nAlex, 34 \u2014 Solo founder with software engineering background (BSCS degree). Previous: Ecommerce platforms, Streaming technology. No formal AI background \u2014 practitioner, not academic.\n\nWhat We Are\n\nOperating layer for AI automation. We help SMBs operate with closed-loop feedback, decision capture, and continuous learning \u2014 at SMB pricing with done-for-you implementation (NOT self-service).\n\nTarget Market\n\nICP: Service-based SMBs (2-50 employees, USA, $200K-$5M revenue)\n\nCore Pain: Capacity crisis, pipeline problems, operational chaos, growth blockers \u2014 too busy serving clients to grow their own business.\n\nPricing (Land-and-Expand)\n\n- Tier 1 \u2014 Foundation (1-2 workflows): $500-2k/mo\n- Tier 2 \u2014 Scaled Operations (3-5 workflows): $2k-5k/mo\n- Tier 3 \u2014 AI-Powered Systems (6+ workflows): $5k-15k+/mo\n\nAdditional workflows cost less due to shared infrastructure.\n\nValue Proposition\n\n"We find one bottleneck in your business, build an AI solution to fix it, and you only pay if it actually saves you time or makes you money."'
1814
+ },
1815
+ {
1816
+ id: "knowledge.platform-capabilities",
1817
+ title: "Platform Capabilities",
1818
+ summary: "Authoritative overview of the Elevasis AI Orchestration Platform \u2014 what's built, what it does, and how the pieces fit together",
1819
+ bodyText: "Overview\n\nElevasis is a production AI orchestration platform that coordinates workflows, autonomous agents, and human approvals into a unified operating layer for SMBs. Everything described below is implemented and running in production.\n\nCore Architecture:\n\n| Layer | What It Does | Key Components |\n| --- | --- | --- |\n| Execution | Runs workflows and agents with schema validation | Workflow Engine, Agent Framework, Execution Runner |\n| Control | Human oversight, approvals, and decision capture | Command Queue (HITL), Dynamic Forms, Action System |\n| Intelligence | Autonomous reasoning, tool use, and memory management | ReAct Agents, Knowledge Map, Session Memory |\n| Observability | Real-time visibility into cost, performance, and health | Cost Tracking, Metrics, Activity Log, SSE Streaming |\n| Platform | Multi-tenancy, security, scheduling, integrations | Registry, RLS, Scheduler, Credential Vault, SDK |\n\nExecution Engine\n\nAI Workflows\n\nGraph-based workflow execution with schema-validated steps and conditional routing. Steps define explicit routing: linear, conditional (rule-based branching), or terminal. Context flows through the entire workflow.\n\nAutonomous Agents\n\nProduction-grade ReAct-style agents with tool use, memory management, and security hardening.\n\nLLM Provider Support: OpenAI (GPT-5), Google (Gemini 3 Flash), Anthropic (Claude Sonnet 4.5), OpenRouter (GLM-5)\n\nHuman-in-the-Loop (HITL)\n\nThe Command Queue surfaces pending approvals as structured tasks. Admin reviews, approves or edits, and the workflow continues. Every critical decision \u2014 sending emails to prospects, updating customer records, publishing content \u2014 requires human approval.\n\nKnowledge Map\n\nOrganizational knowledge loaded lazily into agent context. 80-95% token savings vs. always-loading full context. Nodes link to features, teams, and other nodes.\n\nIntegrations (13 active)\n\nAttio CRM, Cal.com, Instantly (cold email), Resend, Apify, Google Maps, Tomba, Mails.so, Supabase, Stripe, OpenAI, Google Gemini, Anthropic Claude.\n\nSDK\n\nTypeScript-based resource development with local testing, validation, and deployment pipeline. External consumers define workflows and agents in their own repos and deploy via elevasis-sdk deploy."
1820
+ },
1821
+ {
1822
+ id: "knowledge.client-testimonials",
1823
+ title: "Testimonials",
1824
+ summary: "Customer testimonials and case study permissions from previous client work",
1825
+ bodyText: `Status: 4 testimonials from 2 clients | Source: Upwork client work (2024)
1826
+
1827
+ Testimonials
1828
+
1829
+ Xero Automation \u2014 The Invoice Chase That Disappeared
1830
+
1831
+ Client: Word of Mouth Agency
1832
+
1833
+ Result: 10+ hours/week saved. Zero manual follow-up emails.
1834
+
1835
+ > "Working with Alex at Elevasis to automate our Xero follow-ups has been a game-changer. We're set to save over 5 hours a week, but more importantly, it has completely removed the stress of chasing invoices. The process was smooth and professional."
1836
+
1837
+ Permission: Case study approved with company name.
1838
+
1839
+ Influencer Discovery \u2014 From Spreadsheet Hell to Strategic Decisions
1840
+
1841
+ Client: Word of Mouth Agency (Perth, Australia)
1842
+
1843
+ Result: 10+ hours/week saved. Research scope: 50 to 200+ influencers. Team focus shifted from data to strategy.
1844
+
1845
+ > "Working with Alex at Elevasis to automate our Influencer Discovery has been a game-changer. We're set to save over 5 hours a week..."
1846
+
1847
+ Permission: Case study approved with company name.
1848
+
1849
+ EMRG Media \u2014 4 Automations for NYC's Premier Events Firm
1850
+
1851
+ Client: EMRG Media (NYC, est. 2001). Clients include Google, YouTube, Sony Music, Fiverr, Equinox.
1852
+
1853
+ What We Built:
1854
+ 1. Case Study Generator \u2014 7-10 hours \u2192 2 hours. Publication rate: 1/quarter \u2192 2/month.
1855
+ 2. EMRG Follow-Up Generator \u2014 Automated post-event follow-ups with signature management.
1856
+ 3. Event Sponsor Tracker \u2014 Automated sponsor pipeline tracking and communications.
1857
+ 4. Lead Scraper \u2014 Automated discovery of event planning prospects.
1858
+
1859
+ Fame Stats:
1860
+ - 3,500+ attendees at The Event Planner Expo
1861
+ - 25+ year track record
1862
+ - Fortune 500 client roster
1863
+
1864
+ Permission: Case study approved with company name.`
1865
+ },
1866
+ {
1867
+ id: "knowledge.understanding-elevasis",
1868
+ title: "Understanding Elevasis Overview",
1869
+ summary: "Company overview, product positioning, and platform capability context for Elevasis AI orchestration platform",
1870
+ bodyText: "Lean documentation explaining what Elevasis is and what the platform can do. This section provides the foundational context for business conversations and technical evaluation.\n\nElevasis is a done-for-you AI automation service backed by a production-grade platform. The company targets service-based SMBs (2-50 employees, $200K-$5M revenue) who are too busy serving clients to grow their own business.\n\nKey Facts\n\n- Stage: Pre-revenue, platform 100% complete, client acquisition active\n- Offer: Done-for-you AI automation -- we build and maintain the workflows, no coding required\n- ICP: Service-based SMBs (2-50 employees, $200K-$5M revenue, USA)\n- Pricing: $500-$2k/mo (Foundation) \u2192 $2k-$5k/mo (Scaled) \u2192 $5k-$15k+/mo (AI-Powered)\n- Market: Early Adopters stage, $23.77B TAM growing to $87.7B by 2032\n- Competitive position: No dominant done-for-you SMB AI provider exists; blue ocean\n\nWhen to Use Each Document\n\n| Situation | Document |\n| --- | --- |\n| First conversation with any audience | What is Elevasis |\n| Technical evaluation or demo prep | Platform Capabilities |\n\nDocumentation\n\n- What is Elevasis \u2014 AI orchestration platform overview, company stage, value proposition, and pricing tiers\n- Platform Capabilities \u2014 Authoritative overview of what is built, what it does, and how the pieces fit together"
1871
+ },
1872
+ {
1873
+ id: "knowledge.marketing-overview",
1874
+ title: "Marketing Overview",
1875
+ summary: "Marketing documentation for Elevasis - strategy, website infrastructure, and content systems",
1876
+ bodyText: "Welcome to the Elevasis marketing documentation. This section covers marketing strategy, website implementation, and content systems.\n\nDocumentation\n\nStrategy\n\n- Overview \u2014 Channel strategy, inbound content system, and proof-led demand generation\n\nWebsite\n\nThe marketing website's technical infrastructure, setup, and SEO docs now live under Architecture \u2192 Website."
1877
+ },
1878
+ {
1879
+ id: "knowledge.ai-orchestration-principles",
1880
+ title: "AI Orchestration Principles",
1881
+ summary: "The 4 principles that separate production AI from toy automation",
1882
+ bodyText: `Most AI automation fails. Not because the technology is bad, but because it's implemented without the principles that make AI systems work in production.
1883
+
1884
+ These four principles separate toy automation from AI systems that actually run businesses.
1885
+
1886
+ The 4 Principles
1887
+
1888
+ 1. Integrated: Works With Your Existing Systems
1889
+
1890
+ Principle: AI must work with your existing tools, not replace them.
1891
+
1892
+ AI that doesn't connect to your CRM, your email, your calendar, your existing workflows is a toy. Real AI automation works WITH what you already have \u2014 not instead of it.
1893
+
1894
+ - Your 50 Zapier workflows keep running
1895
+ - Your CRM stays your CRM
1896
+ - AI adds intelligence on top, not a rip-and-replace
1897
+
1898
+ The anti-pattern: "Use our AI instead of everything else." Creates vendor lock-in and throws away your existing investment.
1899
+
1900
+ 2. Improving: Gets Smarter Over Time
1901
+
1902
+ Principle: AI must learn from every decision and improve continuously.
1903
+
1904
+ Static automation is just fancy if-then logic. Real AI captures every approval, rejection, and edit \u2014 then uses that data to get smarter.
1905
+
1906
+ The anti-pattern: "Set it and forget it." Systems that don't learn stay dumb forever.
1907
+
1908
+ 3. Observable: You See What AI Is Doing
1909
+
1910
+ Principle: You must be able to see what AI systems are doing in real-time.
1911
+
1912
+ Black-box automation is unacceptable for business operations. You need to see every action, every decision, every cost \u2014 as it happens.
1913
+
1914
+ - Real-time activity feeds showing what's executing
1915
+ - Cost tracking per workflow, per execution
1916
+ - Token usage visibility
1917
+ - Execution logs for debugging
1918
+
1919
+ The anti-pattern: "It just works, trust us." Systems without observability create anxiety and prevent adoption.
1920
+
1921
+ 4. Governed: Humans Control What Matters
1922
+
1923
+ Principle: Humans must approve important actions before execution.
1924
+
1925
+ AI should handle the grunt work. But critical decisions \u2014 sending emails to prospects, updating customer records, publishing content \u2014 require human approval.
1926
+
1927
+ - AI researches 50 prospects and drafts personalized emails
1928
+ - Before anything sends, it lands in your approval queue
1929
+ - You spend 10 minutes reviewing, approve or tweak
1930
+ - Only then does it execute
1931
+
1932
+ The anti-pattern: "Fully autonomous AI." Systems without governance create risk and erode trust.`
1933
+ }
1934
+ ];
1935
+ function buildSearchIndex(entries) {
1936
+ return {
1937
+ search(query) {
1938
+ if (!query.trim()) return [];
1939
+ const q = query.trim().toLowerCase();
1940
+ const scored = [];
1941
+ for (const entry of entries) {
1942
+ let score = 0;
1943
+ if (entry.title.toLowerCase().includes(q)) score += 3;
1944
+ if (entry.summary.toLowerCase().includes(q)) score += 2;
1945
+ if (entry.bodyText.toLowerCase().includes(q)) score += 1;
1946
+ if (score > 0) scored.push({ id: entry.id, score });
1947
+ }
1948
+ return scored.sort((a, b) => b.score - a.score).map((s) => s.id);
1949
+ }
1950
+ };
1951
+ }
1952
+ function KnowledgeSearchBar({
1953
+ knowledgeNodes,
1954
+ onResults,
1955
+ placeholder = "Search knowledge\u2026"
1956
+ }) {
1957
+ const [query, setQuery] = useState("");
1958
+ const indexRef = useRef(null);
1959
+ useEffect(() => {
1960
+ indexRef.current = buildSearchIndex(knowledge_search_index_default);
1961
+ }, []);
1962
+ const nodeMapRef = useRef(/* @__PURE__ */ new Map());
1963
+ useEffect(() => {
1964
+ const map = /* @__PURE__ */ new Map();
1965
+ for (const node of knowledgeNodes) map.set(node.id, node);
1966
+ nodeMapRef.current = map;
1967
+ }, [knowledgeNodes]);
1968
+ const handleChange = (value) => {
1969
+ setQuery(value);
1970
+ if (!value.trim()) {
1971
+ onResults(null);
1972
+ return;
1973
+ }
1974
+ const ids = indexRef.current?.search(value) ?? [];
1975
+ const hits = ids.flatMap((id) => {
1976
+ const node = nodeMapRef.current.get(id);
1977
+ return node ? [node] : [];
1978
+ });
1979
+ onResults(hits);
1980
+ };
1981
+ const handleClear = () => {
1982
+ setQuery("");
1983
+ onResults(null);
1984
+ };
1985
+ return /* @__PURE__ */ jsx(
1986
+ TextInput,
1987
+ {
1988
+ value: query,
1989
+ onChange: (e) => handleChange(e.currentTarget.value),
1990
+ placeholder,
1991
+ leftSection: /* @__PURE__ */ jsx(IconSearch, { size: 16 }),
1992
+ rightSection: query ? /* @__PURE__ */ jsx(IconX, { size: 14, style: { cursor: "pointer", color: "var(--color-text-subtle)" }, onClick: handleClear }) : null,
1993
+ styles: {
1994
+ input: {
1995
+ backgroundColor: "var(--color-surface)",
1996
+ borderColor: "var(--color-border)",
1997
+ color: "var(--color-text)"
1998
+ }
1999
+ }
2000
+ }
2001
+ );
2002
+ }
2003
+
2004
+ export { EdgeRelationshipGroup, KNOWLEDGE_ICON_TOKEN_BY_KIND, KeyField, KindChip, KnowledgeSearchBar, KnowledgeTree, NodeDescribeShell, NodeHeader, NodeMetadataFooter, RelatedKnowledgeSection, byKind, getKnowledgeIconToken };