@rubytech/taskmaster 1.13.4 → 1.16.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 (68) hide show
  1. package/dist/agents/apply-patch.js +3 -1
  2. package/dist/agents/bash-tools.exec.js +3 -1
  3. package/dist/agents/bash-tools.process.js +3 -1
  4. package/dist/agents/skills/frontmatter.js +1 -0
  5. package/dist/agents/skills/workspace.js +64 -22
  6. package/dist/agents/system-prompt.js +1 -1
  7. package/dist/agents/taskmaster-tools.js +6 -4
  8. package/dist/agents/tool-policy.js +2 -1
  9. package/dist/agents/tools/contact-create-tool.js +4 -3
  10. package/dist/agents/tools/contact-delete-tool.js +3 -2
  11. package/dist/agents/tools/contact-lookup-tool.js +5 -4
  12. package/dist/agents/tools/contact-update-tool.js +6 -3
  13. package/dist/agents/tools/memory-tool.js +3 -1
  14. package/dist/agents/tools/qr-generate-tool.js +45 -0
  15. package/dist/agents/workspace-migrations.js +479 -0
  16. package/dist/build-info.json +3 -3
  17. package/dist/cli/gateway-cli/run.js +36 -16
  18. package/dist/config/agent-tools-reconcile.js +47 -0
  19. package/dist/control-ui/assets/{index-BiGN9NNG.js → index-Bd75cI7J.js} +568 -592
  20. package/dist/control-ui/assets/index-Bd75cI7J.js.map +1 -0
  21. package/dist/control-ui/assets/index-BkymP95Y.css +1 -0
  22. package/dist/control-ui/index.html +2 -2
  23. package/dist/daemon/service-port.js +109 -0
  24. package/dist/gateway/server-http.js +5 -0
  25. package/dist/gateway/server-methods/config.js +44 -0
  26. package/dist/gateway/server-methods/web.js +13 -0
  27. package/dist/gateway/server.impl.js +15 -1
  28. package/dist/hooks/bundled/ride-dispatch/HOOK.md +57 -0
  29. package/dist/hooks/bundled/ride-dispatch/handler.js +450 -0
  30. package/dist/hooks/bundled/ride-dispatch/stripe-webhook.js +191 -0
  31. package/dist/infra/update-global.js +4 -1
  32. package/dist/infra/update-runner.js +8 -4
  33. package/dist/macos/gateway-daemon.js +26 -8
  34. package/dist/memory/internal.js +24 -1
  35. package/dist/memory/manager.js +15 -4
  36. package/dist/records/records-manager.js +7 -2
  37. package/package.json +1 -1
  38. package/skills/business-assistant/SKILL.md +1 -1
  39. package/skills/qr-code/SKILL.md +63 -0
  40. package/skills/sales-closer/SKILL.md +29 -0
  41. package/skills/sales-closer/references/close-tracking.md +86 -0
  42. package/skills/sales-closer/references/closing-framework.md +112 -0
  43. package/skills/sales-closer/references/objection-handling.md +101 -0
  44. package/templates/beagle-zanzibar/agents/admin/AGENTS.md +123 -5
  45. package/templates/beagle-zanzibar/agents/admin/BOOTSTRAP.md +34 -11
  46. package/templates/beagle-zanzibar/agents/admin/HEARTBEAT.md +1 -0
  47. package/templates/beagle-zanzibar/agents/public/AGENTS.md +110 -17
  48. package/templates/beagle-zanzibar/memory/public/knowledge-base.md +13 -0
  49. package/templates/beagle-zanzibar/memory/public/terms.md +81 -0
  50. package/templates/beagle-zanzibar/skills/beagle-zanzibar/SKILL.md +10 -7
  51. package/templates/beagle-zanzibar/skills/beagle-zanzibar/references/pin-qr.md +52 -0
  52. package/templates/beagle-zanzibar/skills/beagle-zanzibar/references/post-ride.md +13 -0
  53. package/templates/beagle-zanzibar/skills/beagle-zanzibar/references/ride-matching.md +46 -49
  54. package/templates/beagle-zanzibar/skills/beagle-zanzibar/references/route-learning.md +61 -0
  55. package/templates/beagle-zanzibar/skills/stripe/SKILL.md +28 -0
  56. package/templates/beagle-zanzibar/skills/stripe/references/payment-links.md +71 -0
  57. package/templates/customer/agents/admin/BOOTSTRAP.md +5 -1
  58. package/templates/customer/agents/public/AGENTS.md +1 -2
  59. package/templates/real-agent/skills/buyer-feedback/SKILL.md +111 -0
  60. package/templates/real-agent/skills/property-enquiry/SKILL.md +126 -0
  61. package/templates/real-agent/skills/valuation-booking/SKILL.md +182 -0
  62. package/templates/real-agent/skills/vendor-updates/SKILL.md +153 -0
  63. package/templates/real-agent/skills/viewing-management/SKILL.md +111 -0
  64. package/templates/taskmaster/agents/public/AGENTS.md +1 -1
  65. package/templates/taskmaster/agents/public/IDENTITY.md +1 -1
  66. package/templates/taskmaster/agents/public/SOUL.md +2 -2
  67. package/dist/control-ui/assets/index-BiGN9NNG.js.map +0 -1
  68. package/dist/control-ui/assets/index-l54GcTyj.css +0 -1
@@ -29,7 +29,7 @@ async function main() {
29
29
  const Long = mod.default ?? mod;
30
30
  globalThis.Long = Long;
31
31
  }
32
- const [{ loadConfig }, { startGatewayServer }, { setGatewayWsLogStyle }, { setVerbose }, { acquireGatewayLock, GatewayLockError }, { consumeGatewaySigusr1RestartAuthorization, isGatewaySigusr1RestartExternallyAllowed }, { defaultRuntime }, { enableConsoleCapture, setConsoleTimestampPrefix },] = await Promise.all([
32
+ const [{ loadConfig, resolveGatewayPort }, { startGatewayServer }, { setGatewayWsLogStyle }, { setVerbose }, { acquireGatewayLock, GatewayLockError }, { consumeGatewaySigusr1RestartAuthorization, isGatewaySigusr1RestartExternallyAllowed }, { defaultRuntime }, { enableConsoleCapture, setConsoleTimestampPrefix },] = await Promise.all([
33
33
  import("../config/config.js"),
34
34
  import("../gateway/server.js"),
35
35
  import("../gateway/ws-logging.js"),
@@ -46,13 +46,31 @@ async function main() {
46
46
  const wsLogStyle = wsLogRaw === "compact" ? "compact" : wsLogRaw === "full" ? "full" : "auto";
47
47
  setGatewayWsLogStyle(wsLogStyle);
48
48
  const cfg = loadConfig();
49
- const portRaw = argValue(args, "--port") ??
50
- process.env.TASKMASTER_GATEWAY_PORT ??
51
- (typeof cfg.gateway?.port === "number" ? String(cfg.gateway.port) : "") ??
52
- "18789";
53
- const port = Number.parseInt(portRaw, 10);
49
+ // Explicit --port arg: fixed for lifetime of process.
50
+ const portArgRaw = argValue(args, "--port");
51
+ const portArgOverride = portArgRaw !== undefined ? Number.parseInt(portArgRaw, 10) : null;
52
+ if (portArgRaw !== undefined &&
53
+ (portArgOverride === null || Number.isNaN(portArgOverride) || portArgOverride <= 0)) {
54
+ defaultRuntime.error(`Invalid --port (${portArgRaw})`);
55
+ process.exit(1);
56
+ }
57
+ // When no explicit --port is given, re-read on each restart so UI port changes
58
+ // take effect in-process. Config port is preferred over TASKMASTER_GATEWAY_PORT
59
+ // because the env var is set at daemon-install time and doesn't update when the
60
+ // user edits the port via the UI. Falls back to env var / default when the
61
+ // config key is absent.
62
+ const resolveStartPort = () => {
63
+ if (portArgOverride !== null)
64
+ return portArgOverride;
65
+ const currentCfg = loadConfig();
66
+ const cfgPort = currentCfg.gateway?.port;
67
+ return typeof cfgPort === "number" && Number.isFinite(cfgPort) && cfgPort > 0
68
+ ? cfgPort
69
+ : resolveGatewayPort(currentCfg);
70
+ };
71
+ const port = resolveStartPort();
54
72
  if (Number.isNaN(port) || port <= 0) {
55
- defaultRuntime.error(`Invalid --port (${portRaw})`);
73
+ defaultRuntime.error(`Invalid port (${port})`);
56
74
  process.exit(1);
57
75
  }
58
76
  const bindRaw = argValue(args, "--bind") ?? process.env.TASKMASTER_GATEWAY_BIND ?? cfg.gateway?.bind ?? "lan";
@@ -152,7 +170,7 @@ async function main() {
152
170
  // eslint-disable-next-line no-constant-condition
153
171
  while (true) {
154
172
  try {
155
- server = await startGatewayServer(port, { bind });
173
+ server = await startGatewayServer(resolveStartPort(), { bind });
156
174
  }
157
175
  catch (err) {
158
176
  cleanupSignals();
@@ -36,6 +36,26 @@ export function isMemoryPath(relPath) {
36
36
  return true;
37
37
  return normalized.startsWith("memory/");
38
38
  }
39
+ /**
40
+ * Ensure a relative path has the `memory/` prefix.
41
+ *
42
+ * Agents are sometimes instructed to strip the prefix (system prompt) while
43
+ * the storage layer requires it. Accept both forms so that either convention
44
+ * works:
45
+ * "public/data.md" → "memory/public/data.md"
46
+ * "memory/public/data.md" → "memory/public/data.md" (unchanged)
47
+ * "MEMORY.md" → "MEMORY.md" (root file, unchanged)
48
+ */
49
+ export function ensureMemoryPrefix(relPath) {
50
+ const normalized = normalizeRelPath(relPath);
51
+ if (!normalized)
52
+ return normalized;
53
+ if (normalized === "MEMORY.md" || normalized === "memory.md")
54
+ return normalized;
55
+ if (normalized.startsWith("memory/"))
56
+ return normalized;
57
+ return `memory/${normalized}`;
58
+ }
39
59
  async function exists(filePath) {
40
60
  try {
41
61
  await fs.access(filePath);
@@ -113,7 +133,10 @@ export async function buildFileEntry(absPath, workspaceDir) {
113
133
  // Binary files (PDF, DOCX, etc.) are hashed from raw bytes; text files from UTF-8.
114
134
  const binary = isBinaryMemoryFile(absPath);
115
135
  const hash = binary
116
- ? crypto.createHash("sha256").update(await fs.readFile(absPath)).digest("hex")
136
+ ? crypto
137
+ .createHash("sha256")
138
+ .update(await fs.readFile(absPath))
139
+ .digest("hex")
117
140
  : hashText(await fs.readFile(absPath, "utf-8"));
118
141
  return {
119
142
  path: path.relative(workspaceDir, absPath).replace(/\\/g, "/"),
@@ -16,7 +16,7 @@ import { DEFAULT_GEMINI_EMBEDDING_MODEL } from "./embeddings-gemini.js";
16
16
  import { DEFAULT_OPENAI_EMBEDDING_MODEL } from "./embeddings-openai.js";
17
17
  import { OPENAI_BATCH_ENDPOINT, runOpenAiEmbeddingBatches, } from "./batch-openai.js";
18
18
  import { runGeminiEmbeddingBatches } from "./batch-gemini.js";
19
- import { buildFileEntry, chunkMarkdown, ensureDir, extractMemoryFileContent, extractPeerFromPath, hashText, isBinaryMemoryFile, isMemoryPath, listMemoryFiles, normalizeRelPath, parseEmbedding, } from "./internal.js";
19
+ import { buildFileEntry, chunkMarkdown, ensureDir, extractMemoryFileContent, extractPeerFromPath, hashText, ensureMemoryPrefix, isBinaryMemoryFile, isMemoryPath, listMemoryFiles, normalizeRelPath, parseEmbedding, } from "./internal.js";
20
20
  import { bm25RankToScore, buildFtsQuery, mergeHybridResults } from "./hybrid.js";
21
21
  import { searchKeyword, searchVector } from "./manager-search.js";
22
22
  import { ensureMemoryIndexSchema } from "./memory-schema.js";
@@ -83,6 +83,17 @@ function expandPathTemplate(pattern, ctx) {
83
83
  function normalizePhoneInMemoryPath(relPath) {
84
84
  return relPath.replace(/^(memory\/users\/)(\d)/i, "$1+$2");
85
85
  }
86
+ /**
87
+ * Ensure group IDs in memory/groups/ paths include the canonical @g.us suffix.
88
+ * AI agents sometimes omit the suffix when constructing paths
89
+ * (e.g. "memory/groups/120363423828326592/members/..." instead of
90
+ * "memory/groups/120363423828326592@g.us/members/...").
91
+ * The session peer and filesystem convention always use the full JID with @g.us.
92
+ * Without this, scope patterns like memory/groups/{peer}/** won't match the path.
93
+ */
94
+ function normalizeGroupIdInMemoryPath(relPath) {
95
+ return relPath.replace(/^(memory\/groups\/)(\d{15,})(?!\S*@)(\/)/i, "$1$2@g.us$3");
96
+ }
86
97
  /**
87
98
  * Simple glob pattern matcher supporting * and **.
88
99
  * - * matches any characters except /
@@ -527,7 +538,7 @@ export class MemoryIndexManager {
527
538
  return this.syncing;
528
539
  }
529
540
  async readFile(params) {
530
- const relPath = normalizePhoneInMemoryPath(normalizeRelPath(params.relPath));
541
+ const relPath = ensureMemoryPrefix(normalizeGroupIdInMemoryPath(normalizePhoneInMemoryPath(normalizeRelPath(params.relPath))));
531
542
  if (!relPath || !isMemoryPath(relPath)) {
532
543
  throw new Error(relPath
533
544
  ? `invalid path "${relPath}" — must start with "memory/" (e.g. "memory/admin/file.md")`
@@ -567,7 +578,7 @@ export class MemoryIndexManager {
567
578
  * matching the session's scope configuration.
568
579
  */
569
580
  async writeFile(params) {
570
- const relPath = normalizePhoneInMemoryPath(normalizeRelPath(params.relPath));
581
+ const relPath = ensureMemoryPrefix(normalizeGroupIdInMemoryPath(normalizePhoneInMemoryPath(normalizeRelPath(params.relPath))));
571
582
  if (!relPath || !isMemoryPath(relPath)) {
572
583
  throw new Error(relPath
573
584
  ? `invalid path "${relPath}" — must start with "memory/" (e.g. "memory/admin/file.md")`
@@ -615,7 +626,7 @@ export class MemoryIndexManager {
615
626
  let relPath;
616
627
  if (params.destFolder) {
617
628
  // Explicit folder — use as-is (scope checking enforces access)
618
- relPath = normalizePhoneInMemoryPath(`${params.destFolder}/${params.destFilename}`);
629
+ relPath = normalizeGroupIdInMemoryPath(normalizePhoneInMemoryPath(`${params.destFolder}/${params.destFilename}`));
619
630
  }
620
631
  else {
621
632
  // Default: memory/users/{peer}/media/{filename}
@@ -32,9 +32,14 @@ export function listRecords(workspace) {
32
32
  }
33
33
  return records.sort((a, b) => a.name.localeCompare(b.name));
34
34
  }
35
- export function getRecord(id) {
35
+ export function getRecord(id, workspace) {
36
36
  const data = readFile();
37
- return data.records[id] ?? null;
37
+ const record = data.records[id] ?? null;
38
+ if (!record)
39
+ return null;
40
+ if (workspace && (record.workspace ?? "taskmaster") !== workspace)
41
+ return null;
42
+ return record;
38
43
  }
39
44
  export function searchRecords(query, workspace) {
40
45
  const q = query.toLowerCase();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rubytech/taskmaster",
3
- "version": "1.13.4",
3
+ "version": "1.16.0",
4
4
  "description": "AI-powered business assistant for small businesses",
5
5
  "publishConfig": {
6
6
  "access": "public"
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  name: business-assistant
3
3
  description: "Business assistant for small businesses. Handles customer enquiries, appointment booking, quote formatting, invoice generation, and daily briefings. Responds instantly, triages by urgency, and never lets a message fall through the cracks."
4
- metadata: {"taskmaster":{"always":true,"emoji":"💼","skillKey":"business-assistant"}}
4
+ metadata: {"taskmaster":{"always":true,"embed":true,"emoji":"💼","skillKey":"business-assistant"}}
5
5
  ---
6
6
 
7
7
  # Business Assistant
@@ -0,0 +1,63 @@
1
+ ---
2
+ name: qr-code
3
+ description: Generate QR codes for URLs, text, contact cards (vCard), or WhatsApp deep links, and send them as images.
4
+ metadata: {"taskmaster":{"emoji":"🔲"}}
5
+ ---
6
+
7
+ # QR Code Generation
8
+
9
+ Generate a QR code image and send it via the `message` tool.
10
+
11
+ ## When to activate
12
+
13
+ - User asks for a QR code for anything: a link, a phone number, a contact, a property, a business card
14
+ - Any use case where someone needs to scan-to-open: booking pages, listings, WhatsApp chats, driver ID cards
15
+ - User says "make a QR", "create a QR code", "I need a scannable link"
16
+
17
+ ## Workflow
18
+
19
+ 1. **Assemble the data string** (see Content Types below)
20
+ 2. **Call `qr_generate`** with the assembled string as `data`
21
+ 3. **Copy the `MEDIA:` path** from the tool result exactly
22
+ 4. **Call `message`** with that path as the `media` parameter and a short caption
23
+
24
+ Example caption: "Here's your QR code — scan to open the listing."
25
+
26
+ ## Content Types
27
+
28
+ ### URL
29
+ Pass the URL as-is.
30
+ ```
31
+ data: "https://example.com/property/123"
32
+ ```
33
+
34
+ ### Plain text
35
+ Pass the text directly. Good for booking references, PINs, short instructions.
36
+ ```
37
+ data: "Booking ref: BK-20240301-007"
38
+ ```
39
+
40
+ ### Contact card (vCard)
41
+ Assemble a vCard v3 block. Scannable into any phone's address book.
42
+ ```
43
+ data: "BEGIN:VCARD\nVERSION:3.0\nFN:Jamie Fisher\nORG:Real Agency\nTEL:+44 7700 900123\nEMAIL:jamie@realagency.com\nURL:https://realagency.com/team/jamie\nEND:VCARD"
44
+ ```
45
+ Only include fields you have. `FN` (full name) is required; all others are optional.
46
+
47
+ ### WhatsApp deep link
48
+ Opens a WhatsApp chat with the business when scanned. Use the E.164 number stripped of its leading `+` and all spaces or dashes (e.g. `447700900123`).
49
+ ```
50
+ data: "https://wa.me/447700900123"
51
+ ```
52
+ To pre-fill a message:
53
+ ```
54
+ data: "https://wa.me/447700900123?text=Hi%2C%20I%27d%20like%20to%20book%20a%20viewing"
55
+ ```
56
+
57
+ ## Fallback (if `qr_generate` is unavailable)
58
+
59
+ Share this URL directly — it resolves to a scannable PNG the user can open in their browser:
60
+ ```
61
+ https://api.qrserver.com/v1/create-qr-code/?data={URL_ENCODED_DATA}&size=300x300
62
+ ```
63
+ URL-encode the data before inserting it.
@@ -0,0 +1,29 @@
1
+ ---
2
+ name: sales-closer
3
+ description: "Detects buying signals in customer conversations and guides the agent to close sales effectively. Teaches acknowledge-then-anchor technique to prevent prospect pivots from derailing close attempts. Reinforces business owner offers and tracks close outcomes."
4
+ metadata: {"taskmaster":{"always":true,"embed":true,"emoji":"🎯","skillKey":"sales-closer"}}
5
+ ---
6
+
7
+ # Sales Closer
8
+
9
+ You are always watching for buying signals in customer conversations. When a prospect shows intent, your priority shifts from informing to closing.
10
+
11
+ ## Three Rules
12
+
13
+ 1. **Detect buying signals** — Enthusiasm, future-tense commitment, pricing/setup/next-step questions, returning after a gap, sharing detailed business info unprompted. When you detect these, enter closing mode.
14
+
15
+ 2. **Acknowledge-then-anchor** — When a prospect asks a question mid-close, give a brief answer, then immediately re-anchor to the commitment ask. Never let a question fully derail the close. The sequence is always: brief answer → re-anchor. Not: full answer → trailing close.
16
+
17
+ 3. **Reinforce the owner's offers** — When the business owner presents options in the conversation, actively frame and reinforce those options for the prospect. Never let an owner's offer go un-anchored.
18
+
19
+ ## What Closing Is Not
20
+
21
+ Never pressure, manipulate, or create false urgency. Never repeat the same close more than twice consecutively. Never close over genuine unresolved concerns — address them first. Never commit to pricing or terms the business owner has not authorised.
22
+
23
+ ## References
24
+
25
+ Load the relevant reference when the situation requires it:
26
+
27
+ - **Closing methodology** → `references/closing-framework.md` — buying signals, closing techniques, the golden rule, when to close vs when to wait
28
+ - **Objections & pivots** → `references/objection-handling.md` — distinguishing pivots from objections, acknowledge-then-anchor patterns, common objection types
29
+ - **Tracking & follow-up** → `references/close-tracking.md` — persisting close attempts to memory, CRM updates, follow-up scheduling, escalation after stalled closes
@@ -0,0 +1,86 @@
1
+ # Close Tracking
2
+
3
+ ## Why Track
4
+
5
+ Closing is iterative. A single conversation may have multiple close attempts, each with a different outcome. Tracking close attempts in memory enables:
6
+
7
+ - Pattern recognition: which techniques work for this prospect?
8
+ - Follow-up discipline: when did we agree to check back?
9
+ - Escalation context: if handing off to the owner, what has already been tried?
10
+ - Pipeline accuracy: is this prospect in "enquiry" or genuinely moving toward "booked"?
11
+
12
+ ## Where to Write
13
+
14
+ Close tracking data lives in the prospect's memory profile. The path depends on the session type:
15
+
16
+ - **DM sessions:** `memory/users/{phone}/profile.md` — append to the prospect's user profile
17
+ - **Group sessions:** `memory/groups/{groupId}/members/{phone}.md` — append to the prospect's group member profile
18
+
19
+ The public agent can only write to user-scoped memory (`memory/users/{peer}/**`) and group-scoped memory (`memory/groups/{peer}/**`) for the current session's peer. In a group conversation, write to the group member file — not to the user's DM profile.
20
+
21
+ ## What to Record
22
+
23
+ After each close attempt in a customer conversation, write a brief entry to the prospect's memory profile:
24
+
25
+ ```
26
+ ## Sales Close Log
27
+
28
+ ### [Date] — [Close technique used]
29
+ - **Ask:** [What was asked — e.g. "Shall we get you set up with a trial?"]
30
+ - **Outcome:** [committed / pivoted / objected / deferred]
31
+ - **Detail:** [What happened — e.g. "Prospect asked about CRM integration, acknowledged-then-anchored, prospect said they'd think about it"]
32
+ - **Follow-up:** [Next action — e.g. "Check back Thursday" or "None — committed"]
33
+ ```
34
+
35
+ Keep entries concise. The purpose is context for the next interaction, not a transcript.
36
+
37
+ ## When to Update the CRM Contact Record
38
+
39
+ The CRM pipeline (managed by the business-assistant skill via `contact_update`) should be updated at these moments:
40
+
41
+ | Close outcome | CRM action |
42
+ |--------------|------------|
43
+ | Prospect commits to a purchase/subscription | Update status to `booked` (or the appropriate stage) |
44
+ | Prospect commits to a trial | Update status to `booked`, add note: "Trial started [date]" |
45
+ | Quote accepted | Update status from `quoted` to `booked` |
46
+ | Prospect explicitly declines | Update status to `archived`, add note with reason |
47
+ | Prospect defers | Keep current status, add `lastContact` date and follow-up note |
48
+
49
+ Do not update the CRM status on pivots or mid-conversation objections — only on definitive outcomes.
50
+
51
+ ## Follow-Up Scheduling
52
+
53
+ When a prospect defers ("not right now", "let me think about it", "check back next week"):
54
+
55
+ 1. Agree on a specific follow-up date — ask: "When would be good to check back?"
56
+ 2. If no date given, default to 3 business days
57
+ 3. Write the follow-up date to memory: `Follow-up: [date] — [context]`
58
+ 4. When the follow-up date arrives and the agent receives a cron trigger or the prospect messages again, use the **Return Close** technique — they were interested, just needed time
59
+
60
+ ## Escalation Rules
61
+
62
+ Escalate to the business owner when:
63
+
64
+ 1. **Three unsuccessful close attempts in one session** — the agent has tried and the prospect is not moving. Summarise what was tried and hand off.
65
+ 2. **Pricing negotiation** — the prospect wants a different price or custom terms. The agent does not have authority.
66
+ 3. **Persistent objection** — an objection has been addressed but the prospect keeps raising it. The concern may need a human touch.
67
+ 4. **Prospect requests human contact** — always respect this immediately.
68
+
69
+ When escalating, send the business owner a structured summary:
70
+
71
+ ```
72
+ **Sales escalation — [Prospect name]**
73
+
74
+ What they need: [1-2 sentences]
75
+ Where we are: [Current pipeline stage]
76
+ What was tried: [Close techniques used and outcomes]
77
+ What's blocking: [The specific objection or stall]
78
+ Recommended next step: [Your suggestion for the owner]
79
+ ```
80
+
81
+ ## Integration with Quote Follow-Ups
82
+
83
+ The business-assistant skill's quoting reference already has follow-up nudges (3-day nudge, 7-day escalation). When a quote follow-up is triggered, apply closing intent — do not send a passive "just checking in" message. Instead:
84
+
85
+ - **3-day nudge:** Use the Summary Close — recap the quote, restate value, ask directly: "Shall I book this in?"
86
+ - **7-day escalation:** Escalate to the business owner with full context, not just "no response." Include: what was quoted, what follow-up has been sent, and a recommendation (chase, re-quote at different price, or close as lost).
@@ -0,0 +1,112 @@
1
+ # Closing Framework
2
+
3
+ ## Buying Signals
4
+
5
+ A buying signal is anything the prospect says or does that indicates they are mentally moving toward commitment. Not every signal means "close now" — but every signal means "pay attention, this person is leaning in."
6
+
7
+ **Verbal signals:**
8
+ - Enthusiasm: "love it", "sounds great", "exactly what I need", "brilliant"
9
+ - Future-tense commitment: "when we set this up", "I'd want to", "once I'm onboarded"
10
+ - Asking about pricing, packages, timelines, or next steps
11
+ - Asking about integration with their existing tools — they are mentally placing the product in their workflow
12
+ - Sharing detailed business information unprompted — they are investing in the relationship
13
+ - Asking operational questions: "who would I contact for support?", "how does billing work?"
14
+
15
+ **Behavioural signals:**
16
+ - Returning to a conversation after a gap — they have been thinking about it; they have already decided
17
+ - Responding quickly to messages — engagement indicates interest
18
+ - Introducing the product to colleagues or partners — social commitment
19
+ - Completing onboarding steps before being asked
20
+
21
+ **Critical insight:** When a prospect returns after a gap (hours or days), they are almost certainly ready. Do not re-sell. Confirm and close: "Great to have you back! Sounds like you're ready — shall we confirm [specific next step]?"
22
+
23
+ ## Closing Techniques
24
+
25
+ These are principles, not scripts. The agent adapts the language to the conversation's tone and the business's communication style.
26
+
27
+ ### Direct Close
28
+
29
+ Ask for the commitment directly. Use when buying signals are strong and there are no unresolved concerns.
30
+
31
+ > "Shall I get you set up?"
32
+ > "Ready to go ahead?"
33
+ > "Let's get you started — shall we?"
34
+
35
+ ### Choice Close
36
+
37
+ Present two options, both of which are a "yes." Use when the business owner has presented options, or when you need to move from "if" to "which."
38
+
39
+ > "Would you prefer the standard subscription or the Founders Club?"
40
+ > "Shall we start with the trial or go straight to full setup?"
41
+
42
+ ### Summary Close
43
+
44
+ Recap what the prospect has told you — their needs, pain points, and what the product solves — then close. Use when the conversation has been long and the prospect has shared a lot of detail.
45
+
46
+ > "So you need 24/7 coverage for enquiries, something to handle the hours Anneke isn't available, and a way to capture leads from your socials. That's exactly what we do — shall we get you set up?"
47
+
48
+ ### Next-Step Close
49
+
50
+ Propose a specific, concrete next step rather than an abstract commitment. Use when the prospect seems uncertain about the big picture but willing to take a small step.
51
+
52
+ > "The next step is getting your WhatsApp number configured — takes about 10 minutes. Shall we do that now?"
53
+ > "Joel can walk you through the pricing options — shall I get him to send those over?"
54
+
55
+ ### Return Close
56
+
57
+ When a prospect returns after a gap. They have been thinking about it — they are ready. Confirm, don't re-sell.
58
+
59
+ > "Great to hear from you! Sounds like you're ready to move forward — shall we confirm [next step]?"
60
+
61
+ ## The Golden Rule
62
+
63
+ **Close before answering the next question.**
64
+
65
+ When a prospect asks a question during a close attempt, the natural instinct is to answer the question fully — then append a close as an afterthought. This is wrong. The close gets buried and ignored.
66
+
67
+ The correct sequence:
68
+
69
+ 1. Briefly acknowledge the question (1-2 sentences max)
70
+ 2. Immediately re-anchor to the commitment ask
71
+ 3. If they still want a deeper answer, they will ask again — and now you can answer without losing the close
72
+
73
+ **Example (from the Alex session):**
74
+
75
+ Wrong:
76
+ > "Loop CRM doesn't have a direct integration yet, but here are three ways we can work around it... [detailed explanation]. So, ready to give it a go?"
77
+
78
+ Right:
79
+ > "Good question on Loop — no direct integration yet, but we've got workarounds that work well. We can dig into the details once you're set up. So — shall we get you started?"
80
+
81
+ The prospect who genuinely needs the CRM answer before committing will say so. The prospect who was just curious will accept the brief answer and move forward.
82
+
83
+ ## When to Close vs When to Wait
84
+
85
+ **Close when:**
86
+ - Buying signals are present
87
+ - Discovery is reasonably complete (you understand their needs)
88
+ - The business owner has presented or approved an offer
89
+ - The prospect returns after a gap
90
+
91
+ **Wait when:**
92
+ - The prospect has genuine unresolved concerns (not pivots — actual objections)
93
+ - You do not yet understand what they need (discovery incomplete)
94
+ - The business owner has not set pricing or terms
95
+ - The prospect has explicitly said "not now" and given a reason — respect it, set a follow-up
96
+
97
+ **Escalate when:**
98
+ - The prospect asks about pricing the agent is not authorised to quote
99
+ - The close has been attempted 3 times in one session without success
100
+ - The prospect wants to negotiate terms
101
+ - The prospect wants to speak to a human
102
+
103
+ ## Reinforcing the Owner's Offers
104
+
105
+ When the business owner presents an offer in the conversation (pricing, packages, special deals), the agent must:
106
+
107
+ 1. **Register the offer** — note exactly what was presented
108
+ 2. **Frame the options** — if the owner gave multiple options, present them clearly: "So you've got two options: [A] or [B]. Which sounds right for you?"
109
+ 3. **Anchor to the offer** — in subsequent messages, reference the specific offer rather than generic closing language
110
+ 4. **Never modify or add to the offer** — only reinforce what the owner actually said
111
+
112
+ If the owner's offer goes unanswered by the prospect, circle back to it within the conversation: "Joel mentioned the Founders Club option earlier — have you had a chance to think about that?"
@@ -0,0 +1,101 @@
1
+ # Objection Handling
2
+
3
+ ## Pivots vs Objections
4
+
5
+ The most important distinction in closing: is the prospect **pivoting** (changing topic to avoid commitment) or **objecting** (raising a genuine concern that needs resolving)?
6
+
7
+ **Pivots** look like:
8
+ - Asking a tangential technical question right after a close attempt
9
+ - Changing the subject entirely: "By the way, can it also do X?"
10
+ - Asking about features that were not previously discussed
11
+ - "That's interesting, what about...?"
12
+
13
+ **Objections** look like:
14
+ - Expressing a specific concern: "I'm worried about the cost"
15
+ - Raising a blocker: "I need to check with my business partner first"
16
+ - Questioning capability directly: "Can it actually handle our volume?"
17
+ - Asking for evidence: "Do you have any case studies?"
18
+
19
+ **The rule:** Handle pivots with acknowledge-then-anchor. Handle objections with empathy, resolution, then close.
20
+
21
+ ## Acknowledge-Then-Anchor (for Pivots)
22
+
23
+ The pattern:
24
+
25
+ 1. **Acknowledge** — Show you heard the question. Do not dismiss it.
26
+ 2. **Brief answer** — 1-2 sentences maximum. Enough to satisfy curiosity, not enough to derail.
27
+ 3. **Re-anchor** — Return to the commitment ask. Use the same close technique or a different one.
28
+
29
+ The key is that the brief answer is *genuine* — you are not dodging the question, you are prioritising the close while still being helpful.
30
+
31
+ If the prospect asks the same question again after the re-anchor, treat it as an objection and give a full answer. A repeated question is no longer a pivot — the prospect genuinely needs the answer.
32
+
33
+ ## Common Objection Patterns
34
+
35
+ ### Price: "I need to think about it" / "That's more than I expected"
36
+
37
+ This is usually about value, not money. The prospect is not sure the product is worth the price.
38
+
39
+ 1. Acknowledge the concern without defensiveness
40
+ 2. Restate the specific value that matches their stated needs (not generic features)
41
+ 3. If pricing authority exists, offer alternatives (different tier, trial period)
42
+ 4. If no pricing authority, hand off to the business owner: "Totally fair — let me get Joel to walk you through the options. He can find the right fit for your budget."
43
+
44
+ Never discount without owner authorisation. Never make the prospect feel bad for asking about price.
45
+
46
+ ### Timing: "Not right now" / "Maybe next month"
47
+
48
+ Respect the timing. Do not push against an explicit deferral.
49
+
50
+ 1. Acknowledge: "No problem at all"
51
+ 2. Set a concrete follow-up: "Shall I check back in with you on [specific date]?"
52
+ 3. Save the deferral to memory with the agreed follow-up date
53
+ 4. When the follow-up date arrives, use the Return Close — they were interested, just not ready
54
+
55
+ ### Capability: "Can it do X?" / "What about Y integration?"
56
+
57
+ If this comes mid-close, it is likely a pivot. If it comes before any close attempt, it is genuine discovery.
58
+
59
+ **Mid-close (pivot):**
60
+ > "Good question — [brief honest answer]. We can go deeper on that once you're set up. Shall we get you started?"
61
+
62
+ **Pre-close (genuine):**
63
+ Answer fully. This is discovery, not closing. The prospect needs to understand the product before they can commit. Do not try to close during discovery.
64
+
65
+ ### Authority: "I need to check with my partner/team"
66
+
67
+ This is a legitimate blocker — the prospect cannot commit alone.
68
+
69
+ 1. Respect it: "Of course — makes sense to discuss together"
70
+ 2. Make it easy: "Want me to put together a quick summary you can share with them?"
71
+ 3. Set a follow-up: "When do you think you'll have had a chance to chat? I can check back in"
72
+ 4. Save to memory: who the decision-maker is, when the follow-up should happen
73
+
74
+ ### Competition: "I'm also looking at [competitor]"
75
+
76
+ 1. Never disparage the competitor
77
+ 2. Ask what matters most to them — let them tell you what to sell
78
+ 3. Position on genuine differentiators, not feature lists
79
+ 4. If unsure of differentiators, hand off to the business owner
80
+
81
+ ### Trust: "How do I know it actually works?"
82
+
83
+ 1. Offer a trial or demo — the product selling itself is the strongest close
84
+ 2. Reference other customers if the business owner has shared testimonials (check memory)
85
+ 3. Offer to show a specific example relevant to their use case
86
+ 4. If no social proof exists, be honest: "We're a young product — the best way to see is to try it. That's why we offer [trial/guarantee]"
87
+
88
+ ## Escalation to Business Owner
89
+
90
+ Some objections require the human. Escalate when:
91
+
92
+ - The prospect wants to negotiate pricing or terms
93
+ - The prospect wants a personal guarantee or commitment the agent cannot make
94
+ - The prospect raises a concern the agent cannot resolve from memory or product knowledge
95
+ - The objection has been addressed but the prospect is still not moving forward after 3 attempts
96
+
97
+ When escalating, provide the business owner with context:
98
+ - What the prospect needs
99
+ - What objection was raised
100
+ - What has already been tried
101
+ - A recommended next step