@rubytech/taskmaster 1.12.1 → 1.12.2

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 (32) hide show
  1. package/dist/agents/taskmaster-tools.js +5 -3
  2. package/dist/agents/tool-policy.js +1 -1
  3. package/dist/agents/tools/authorize-admin-tool.js +58 -17
  4. package/dist/agents/tools/bootstrap-tool.js +51 -0
  5. package/dist/agents/tools/cron-tool.js +20 -7
  6. package/dist/build-info.json +3 -3
  7. package/dist/commands/agents.config.js +1 -0
  8. package/dist/control-ui/assets/{index-4h8fLLNN.js → index-CP9IoaZp.js} +81 -70
  9. package/dist/control-ui/assets/index-CP9IoaZp.js.map +1 -0
  10. package/dist/control-ui/index.html +1 -1
  11. package/dist/gateway/protocol/schema/cron.js +8 -0
  12. package/dist/gateway/server-channels.js +6 -2
  13. package/dist/gateway/server-methods/cron.js +32 -0
  14. package/dist/gateway/server-methods/tailscale.js +9 -0
  15. package/dist/gateway/server-methods/workspaces.js +52 -0
  16. package/dist/web/auto-reply/monitor.js +3 -0
  17. package/package.json +1 -1
  18. package/skills/tailscale/SKILL.md +37 -9
  19. package/templates/beagle-taxi/agents/admin/AGENTS.md +25 -0
  20. package/templates/beagle-taxi/agents/admin/IDENTITY.md +9 -0
  21. package/templates/beagle-taxi/agents/admin/SOUL.md +31 -0
  22. package/templates/beagle-taxi/agents/public/AGENTS.md +54 -0
  23. package/templates/beagle-taxi/agents/public/IDENTITY.md +10 -0
  24. package/templates/beagle-taxi/agents/public/SOUL.md +32 -0
  25. package/templates/beagle-taxi/memory/public/knowledge-base.md +177 -0
  26. package/templates/beagle-taxi/skills/beagle-taxi/SKILL.md +44 -0
  27. package/templates/customer/agents/admin/BOOTSTRAP.md +2 -2
  28. package/templates/education-hero/agents/admin/BOOTSTRAP.md +2 -2
  29. package/templates/maxy/agents/admin/BOOTSTRAP.md +2 -3
  30. package/templates/taskmaster/agents/admin/BOOTSTRAP.md +2 -2
  31. package/templates/tradesupport/agents/admin/BOOTSTRAP.md +2 -2
  32. package/dist/control-ui/assets/index-4h8fLLNN.js.map +0 -1
@@ -6,7 +6,7 @@
6
6
  <title>Taskmaster Control</title>
7
7
  <meta name="color-scheme" content="dark light" />
8
8
  <link rel="icon" type="image/png" href="./favicon.png" />
9
- <script type="module" crossorigin src="./assets/index-4h8fLLNN.js"></script>
9
+ <script type="module" crossorigin src="./assets/index-CP9IoaZp.js"></script>
10
10
  <link rel="stylesheet" crossorigin href="./assets/index-CpaEIgQy.css">
11
11
  </head>
12
12
  <body>
@@ -117,38 +117,46 @@ export const CronUpdateParamsSchema = Type.Union([
117
117
  Type.Object({
118
118
  id: NonEmptyString,
119
119
  patch: CronJobPatchSchema,
120
+ accountId: Type.Optional(Type.String()),
120
121
  }, { additionalProperties: false }),
121
122
  Type.Object({
122
123
  jobId: NonEmptyString,
123
124
  patch: CronJobPatchSchema,
125
+ accountId: Type.Optional(Type.String()),
124
126
  }, { additionalProperties: false }),
125
127
  ]);
126
128
  export const CronRemoveParamsSchema = Type.Union([
127
129
  Type.Object({
128
130
  id: NonEmptyString,
131
+ accountId: Type.Optional(Type.String()),
129
132
  }, { additionalProperties: false }),
130
133
  Type.Object({
131
134
  jobId: NonEmptyString,
135
+ accountId: Type.Optional(Type.String()),
132
136
  }, { additionalProperties: false }),
133
137
  ]);
134
138
  export const CronRunParamsSchema = Type.Union([
135
139
  Type.Object({
136
140
  id: NonEmptyString,
137
141
  mode: Type.Optional(Type.Union([Type.Literal("due"), Type.Literal("force")])),
142
+ accountId: Type.Optional(Type.String()),
138
143
  }, { additionalProperties: false }),
139
144
  Type.Object({
140
145
  jobId: NonEmptyString,
141
146
  mode: Type.Optional(Type.Union([Type.Literal("due"), Type.Literal("force")])),
147
+ accountId: Type.Optional(Type.String()),
142
148
  }, { additionalProperties: false }),
143
149
  ]);
144
150
  export const CronRunsParamsSchema = Type.Union([
145
151
  Type.Object({
146
152
  id: NonEmptyString,
147
153
  limit: Type.Optional(Type.Integer({ minimum: 1, maximum: 5000 })),
154
+ accountId: Type.Optional(Type.String()),
148
155
  }, { additionalProperties: false }),
149
156
  Type.Object({
150
157
  jobId: NonEmptyString,
151
158
  limit: Type.Optional(Type.Integer({ minimum: 1, maximum: 5000 })),
159
+ accountId: Type.Optional(Type.String()),
152
160
  }, { additionalProperties: false }),
153
161
  ]);
154
162
  export const CronRunLogEntrySchema = Type.Object({
@@ -65,10 +65,12 @@ export function createChannelManager(opts) {
65
65
  ? plugin.config.isEnabled(account, cfg)
66
66
  : isAccountEnabled(account);
67
67
  if (!enabled) {
68
+ const disabledReason = plugin.config.disabledReason?.(account, cfg) ?? "disabled";
69
+ channelLogs[channelId].info?.(`[${id}] channel not starting: ${disabledReason}`);
68
70
  setRuntime(channelId, id, {
69
71
  accountId: id,
70
72
  running: false,
71
- lastError: plugin.config.disabledReason?.(account, cfg) ?? "disabled",
73
+ lastError: disabledReason,
72
74
  });
73
75
  return;
74
76
  }
@@ -77,10 +79,12 @@ export function createChannelManager(opts) {
77
79
  configured = await plugin.config.isConfigured(account, cfg);
78
80
  }
79
81
  if (!configured) {
82
+ const unconfiguredReason = plugin.config.unconfiguredReason?.(account, cfg) ?? "not configured";
83
+ channelLogs[channelId].warn?.(`[${id}] channel not starting: ${unconfiguredReason}`);
80
84
  setRuntime(channelId, id, {
81
85
  accountId: id,
82
86
  running: false,
83
- lastError: plugin.config.unconfiguredReason?.(account, cfg) ?? "not configured",
87
+ lastError: unconfiguredReason,
84
88
  });
85
89
  return;
86
90
  }
@@ -66,6 +66,14 @@ export const cronHandlers = {
66
66
  respond(false, undefined, errorShape(ErrorCodes.INVALID_REQUEST, "invalid cron.update params: missing id"));
67
67
  return;
68
68
  }
69
+ if (p.accountId) {
70
+ const jobs = await context.cron.list({ includeDisabled: true });
71
+ const target = jobs.find((j) => j.id === jobId);
72
+ if (!target || target.accountId !== p.accountId) {
73
+ respond(false, undefined, errorShape(ErrorCodes.INVALID_REQUEST, `cron job not found: ${jobId}`));
74
+ return;
75
+ }
76
+ }
69
77
  const job = await context.cron.update(jobId, p.patch);
70
78
  respond(true, job, undefined);
71
79
  },
@@ -80,6 +88,14 @@ export const cronHandlers = {
80
88
  respond(false, undefined, errorShape(ErrorCodes.INVALID_REQUEST, "invalid cron.remove params: missing id"));
81
89
  return;
82
90
  }
91
+ if (p.accountId) {
92
+ const jobs = await context.cron.list({ includeDisabled: true });
93
+ const target = jobs.find((j) => j.id === jobId);
94
+ if (!target || target.accountId !== p.accountId) {
95
+ respond(false, undefined, errorShape(ErrorCodes.INVALID_REQUEST, `cron job not found: ${jobId}`));
96
+ return;
97
+ }
98
+ }
83
99
  const result = await context.cron.remove(jobId);
84
100
  respond(true, result, undefined);
85
101
  },
@@ -94,6 +110,14 @@ export const cronHandlers = {
94
110
  respond(false, undefined, errorShape(ErrorCodes.INVALID_REQUEST, "invalid cron.run params: missing id"));
95
111
  return;
96
112
  }
113
+ if (p.accountId) {
114
+ const jobs = await context.cron.list({ includeDisabled: true });
115
+ const target = jobs.find((j) => j.id === jobId);
116
+ if (!target || target.accountId !== p.accountId) {
117
+ respond(false, undefined, errorShape(ErrorCodes.INVALID_REQUEST, `cron job not found: ${jobId}`));
118
+ return;
119
+ }
120
+ }
97
121
  const result = await context.cron.run(jobId, p.mode);
98
122
  respond(true, result, undefined);
99
123
  },
@@ -108,6 +132,14 @@ export const cronHandlers = {
108
132
  respond(false, undefined, errorShape(ErrorCodes.INVALID_REQUEST, "invalid cron.runs params: missing id"));
109
133
  return;
110
134
  }
135
+ if (p.accountId) {
136
+ const jobs = await context.cron.list({ includeDisabled: true });
137
+ const target = jobs.find((j) => j.id === jobId);
138
+ if (!target || target.accountId !== p.accountId) {
139
+ respond(false, undefined, errorShape(ErrorCodes.INVALID_REQUEST, `cron job not found: ${jobId}`));
140
+ return;
141
+ }
142
+ }
111
143
  const logPath = resolveCronRunLogPath({
112
144
  storePath: context.cronStorePath,
113
145
  jobId,
@@ -278,6 +278,15 @@ export const tailscaleHandlers = {
278
278
  catch (serveErr) {
279
279
  const errObj = serveErr;
280
280
  const combined = `${typeof errObj.stdout === "string" ? errObj.stdout : ""}\n${typeof errObj.stderr === "string" ? errObj.stderr : ""}`;
281
+ // Check for "Serve is not enabled" — extract the enable URL
282
+ if (combined.includes("Serve is not enabled") ||
283
+ combined.includes("not enabled on your tailnet")) {
284
+ const enableUrlMatch = combined.match(/https:\/\/login\.tailscale\.com\/f\/serve\S*/);
285
+ const enableUrl = enableUrlMatch?.[0] ?? "https://login.tailscale.com/admin/machines";
286
+ context.logGateway.warn(`tailscale.serve.enable: Serve not enabled on tailnet`);
287
+ respond(false, { enableUrl }, errorShape(ErrorCodes.INVALID_REQUEST, "Serve is not enabled on your Tailscale account. Open the link in the browser to enable it, then try again."));
288
+ return;
289
+ }
281
290
  context.logGateway.warn(`tailscale.serve.enable pre-flight failed: ${combined.slice(0, 500)}`);
282
291
  respond(false, undefined, errorShape(ErrorCodes.UNAVAILABLE, `Serve command failed — ${combined.trim().split("\n")[0] || "unknown error"}`));
283
292
  return;
@@ -12,6 +12,52 @@ import { normalizeAgentId } from "../../routing/session-key.js";
12
12
  import { resolveUserPath } from "../../utils.js";
13
13
  import { ErrorCodes, errorShape } from "../protocol/index.js";
14
14
  // ---------------------------------------------------------------------------
15
+ // Per-account agent tool sets
16
+ // ---------------------------------------------------------------------------
17
+ const ACCOUNT_ADMIN_TOOLS = {
18
+ allow: [
19
+ "skill_read",
20
+ "message",
21
+ "group:memory",
22
+ "group:documents",
23
+ "image",
24
+ "image_generate",
25
+ "web_search",
26
+ "web_fetch",
27
+ "current_time",
28
+ "sessions_list",
29
+ "sessions_history",
30
+ "session_status",
31
+ "cron",
32
+ "authorize_admin",
33
+ "revoke_admin",
34
+ "list_admins",
35
+ "bootstrap_complete",
36
+ "relay_message",
37
+ "browser",
38
+ "group:contacts",
39
+ "document_to_pdf",
40
+ "skill_draft_save",
41
+ "group:control-panel",
42
+ "api_keys",
43
+ "software_update",
44
+ ],
45
+ deny: ["exec", "group:fs", "group:runtime", "canvas"],
46
+ };
47
+ const ACCOUNT_PUBLIC_TOOLS = {
48
+ allow: [
49
+ "message",
50
+ "memory_search",
51
+ "memory_get",
52
+ "memory_write",
53
+ "memory_save_media",
54
+ "web_search",
55
+ "web_fetch",
56
+ "current_time",
57
+ ],
58
+ deny: ["exec", "group:fs", "group:runtime", "group:sessions", "group:automation", "browser"],
59
+ };
60
+ // ---------------------------------------------------------------------------
15
61
  // Helpers
16
62
  // ---------------------------------------------------------------------------
17
63
  function requireBaseHash(params, snapshot, respond) {
@@ -396,10 +442,16 @@ export const workspacesHandlers = {
396
442
  // Each agent's workspace points to its agent subdirectory, not the root.
397
443
  for (const agentName of scan.agents) {
398
444
  const agentId = normalizeAgentId(`${name}-${agentName}`);
445
+ const tools = agentName === "admin"
446
+ ? ACCOUNT_ADMIN_TOOLS
447
+ : agentName === "public"
448
+ ? ACCOUNT_PUBLIC_TOOLS
449
+ : undefined;
399
450
  cfg = applyAgentConfig(cfg, {
400
451
  agentId,
401
452
  name: `${name} ${agentName}`,
402
453
  workspace: path.join(workspaceDir, "agents", agentName),
454
+ tools,
403
455
  });
404
456
  agentIds.push(agentId);
405
457
  }
@@ -223,6 +223,7 @@ export async function monitorWebChannel(verbose, listenerFactory = monitorWebInb
223
223
  emitStatus();
224
224
  // Surface a concise connection event for the next main-session turn/heartbeat.
225
225
  const { e164: selfE164 } = readWebSelfId(account.authDir);
226
+ reconnectLogger.info({ accountId: account.accountId, selfE164: selfE164 ?? null, reconnectAttempts }, "WhatsApp connected");
226
227
  const connectRoute = resolveAgentRoute({
227
228
  cfg,
228
229
  channel: "whatsapp",
@@ -273,6 +274,8 @@ export async function monitorWebChannel(verbose, listenerFactory = monitorWebInb
273
274
  : null;
274
275
  const logData = {
275
276
  connectionId,
277
+ accountId: account.accountId,
278
+ selfE164: selfE164 ?? null,
276
279
  reconnectAttempts,
277
280
  messagesHandled: handledMessages,
278
281
  lastMessageAt,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rubytech/taskmaster",
3
- "version": "1.12.1",
3
+ "version": "1.12.2",
4
4
  "description": "AI-powered business assistant for small businesses",
5
5
  "publishConfig": {
6
6
  "access": "public"
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: tailscale
3
- description: Guide users through Tailscale remote access setup — installing the app, creating an account, and connecting their device.
3
+ description: Guide users through Tailscale remote access setup — installing the app, connecting their device, and enabling Serve for remote access to the control panel.
4
4
  metadata: {"taskmaster":{"emoji":"🔐"}}
5
5
  ---
6
6
 
@@ -13,10 +13,18 @@ Helps users set up remote access to their Taskmaster device using Tailscale. Tai
13
13
  - User asks about remote access, accessing their device from outside the local network, or connecting while away from home
14
14
  - User encounters issues during the Tailscale connection flow on the setup page
15
15
  - User sees a QR code on the setup page and doesn't know what to do
16
+ - User asks about enabling Serve or making the control panel accessible remotely
16
17
 
17
- ## Recommended path
18
+ ## Critical: what NOT to tell users
18
19
 
19
- The simplest setup path for non-technical users:
20
+ - **Never suggest CLI commands** — Taskmaster handles all Tailscale operations through the setup page. Users do not need to SSH into the Pi, open a terminal, or run any `tailscale` commands.
21
+ - **Never reference `tailscale serve`, `tailscale up`, `sudo`, or any shell commands** — the gateway runs these internally when the user clicks buttons in the UI.
22
+ - **Never suggest port numbers, `--bg` flags, or `--https` flags** — these are implementation details the user never sees.
23
+ - **Never suggest the Tailscale admin console as the primary path** — the setup page is the primary interface. The admin console link is only relevant when Serve or Funnel needs one-time tailnet-level enablement (the UI provides the link automatically).
24
+
25
+ ## Setup flow (from the setup page)
26
+
27
+ ### Step 1: Connect to Tailscale
20
28
 
21
29
  1. **Install the Tailscale app on your phone** — available on iOS App Store and Google Play Store. Search for "Tailscale" and install the official app.
22
30
  2. **Create a Tailscale account** — open the app and sign up. You can use Google, Microsoft, Apple, or email to create your account.
@@ -24,24 +32,44 @@ The simplest setup path for non-technical users:
24
32
  4. **Click "Connect" next to Remote Access** — a modal will appear showing progress while the connection starts.
25
33
  5. **Scan the QR code with your phone camera** — when the QR code appears, point your phone camera at it. This links your Taskmaster device to your Tailscale account.
26
34
  6. **Approve the device in the Tailscale app** — you may be asked to confirm the new device. Tap approve/allow.
27
- 7. **Done** — the modal will show a success message and close automatically. Your device is now accessible from anywhere you have Tailscale installed.
35
+ 7. **Done** — the modal will show a success message and close automatically.
36
+
37
+ ### Step 2: Enable Remote Access (Serve)
38
+
39
+ After connecting, the setup page shows Remote Access as "Connected (not enabled)." The next step:
40
+
41
+ 1. **Click the power button** next to Remote Access on the setup page.
42
+ 2. **If it succeeds** — remote access is now active. The setup page shows a green light and "Active."
43
+ 3. **If a link appears** — Serve must be enabled on your Tailscale account first. This is a one-time step. Click the link that appears (it opens the Tailscale admin page). Enable Serve, then come back and click the power button again.
44
+
45
+ Once enabled, remote access persists across restarts.
46
+
47
+ ### Step 3: Access from other devices
48
+
49
+ - Install Tailscale on any other device (laptop, tablet, phone) where you want to access the control panel.
50
+ - Sign in with the **same Tailscale account**.
51
+ - Your Taskmaster device will appear in the Tailscale app — access it using the Tailscale URL shown on the setup page.
52
+
53
+ ## Internet Access (Funnel) — optional
28
54
 
29
- ## After connecting
55
+ Funnel makes the control panel accessible to anyone on the internet, not just devices on your Tailscale network. This is optional and most users don't need it.
30
56
 
31
- - Install Tailscale on any other device (laptop, tablet) where you want to access the control panel
32
- - Sign in with the same Tailscale account
33
- - Your Taskmaster device will appear in the Tailscale network — access it using the Tailscale URL shown on the setup page
57
+ - After Remote Access is active, a globe icon appears on the setup page.
58
+ - Clicking it enables Funnel. If Funnel isn't enabled on your tailnet, a link appears — same one-click flow as Serve.
34
59
 
35
60
  ## Troubleshooting
36
61
 
37
62
  - **QR code doesn't appear**: The Tailscale service may not be running on the device. Check the setup page for error details. Restarting the gateway may help.
38
63
  - **Authentication times out**: The QR code is valid for a limited time. Click "Connect" again to generate a new one.
39
64
  - **"Tailscale not installed" message**: Tailscale needs to be installed on the Taskmaster device itself (the server). This is separate from installing it on your phone.
65
+ - **"Serve must be enabled" with a link**: Click the link to open Tailscale's admin page. Enable Serve for this device, then retry from the setup page.
40
66
  - **Connection drops**: Make sure both devices (phone/laptop and Taskmaster) have Tailscale running and are signed into the same account.
67
+ - **Remote Access shows "Enabled but not active"**: Tailscale may have disconnected. Try disabling and re-enabling from the setup page, or check that the device is still connected in the Tailscale app.
41
68
 
42
69
  ## Key concepts
43
70
 
44
71
  - **Tailscale** is a mesh VPN — devices connect directly to each other through encrypted tunnels
45
72
  - **Free for personal use** — up to 100 devices on the free plan
46
73
  - **No port forwarding needed** — works through firewalls and NAT automatically
47
- - **Internet Access** (Funnel) — an optional feature that gives your device a public URL reachable by anyone, not just Tailscale users. Enable this from the setup page after remote access is connected.
74
+ - **Serve** (Remote Access) — makes the control panel accessible over HTTPS to devices on the same Tailscale account. Private to your tailnet.
75
+ - **Funnel** (Internet Access) — makes the control panel accessible to anyone with the URL. Public.
@@ -0,0 +1,25 @@
1
+ # AGENTS.md — Admin Agent (Beagle Corporate Admin)
2
+
3
+ You help the operator manage the Beagle corporate site. You handle inbound interest, knowledge base maintenance, and operational oversight of the public-facing agent.
4
+
5
+ ## First-Run Check
6
+
7
+ **If BOOTSTRAP.md is present in your context, STOP and follow its instructions.** BOOTSTRAP.md means this workspace hasn't been set up yet. Complete the onboarding flow before doing anything else — no briefings, no memory searches, no normal session behaviour until onboarding is done.
8
+
9
+ ## Every Session
10
+
11
+ Before doing anything else:
12
+ 1. Read `SOUL.md` — this is who you are
13
+ 2. Read `IDENTITY.md` — your role and boundaries
14
+ 3. Check memory for recent inbound interest and any pending follow-ups
15
+
16
+ ## Tools
17
+
18
+ | Tool | Use |
19
+ |------|-----|
20
+ | `memory_search` | Find stored interest, enquiries, knowledge base content |
21
+ | `memory_get` | Read specific files |
22
+ | `memory_write` | Update knowledge base, store notes, flag follow-ups |
23
+ | `sessions_list` | Review recent conversations the public agent has had |
24
+ | `sessions_history` | Read specific past sessions for context |
25
+ | `current_time` | Timestamps for notes and follow-up scheduling |
@@ -0,0 +1,9 @@
1
+ # IDENTITY.md — Who Am I?
2
+
3
+ - **Name:** Beagle Admin
4
+ - **Role:** Operator assistant for the Beagle corporate site
5
+ - **Emoji:** 🐕
6
+
7
+ ---
8
+
9
+ I help the Beagle operator manage the corporate site — reviewing inbound interest (investors, partners, driver sign-ups), updating business information, and maintaining the knowledge base that the public agent draws from.
@@ -0,0 +1,31 @@
1
+ # SOUL.md — Who You Are
2
+
3
+ *You help the operator manage the Beagle corporate site. Direct, efficient, no fuss.*
4
+
5
+ ## Core Truths
6
+
7
+ **Be genuinely useful.** The operator runs the Beagle business. You manage the corporate-facing side — inbound interest, knowledge base accuracy, and site content. Keep it efficient.
8
+
9
+ **Know what's been said.** Check memory for recent inbound enquiries, stored interest, and any outstanding follow-ups before responding.
10
+
11
+ **Be proactive.** Flag unanswered investor enquiries, stale information in the knowledge base, or patterns in what people are asking about.
12
+
13
+ **Be candid.** If something needs attention, say so plainly.
14
+
15
+ ## What You Do
16
+
17
+ - Review and summarise inbound interest (investors, partners, drivers)
18
+ - Flag enquiries that need operator follow-up
19
+ - Help update the knowledge base with new market data, milestones, or corrections
20
+ - Draft responses for the operator to send manually
21
+ - Maintain accuracy of business information
22
+
23
+ ## What You Don't Do
24
+
25
+ - Talk to external visitors directly (that's the public agent)
26
+ - Make investment or partnership decisions
27
+ - Manage ride operations (that's a separate deployment per market)
28
+
29
+ ## Vibe
30
+
31
+ Internal. Direct. Like a chief of staff who keeps the inbox and the facts straight.
@@ -0,0 +1,54 @@
1
+ # AGENTS.md — Public Agent (Beagle Corporate)
2
+
3
+ You represent the Beagle business on its corporate site. You answer questions about the company, its ride matching model, and the markets it operates in.
4
+
5
+ ## Every Session
6
+
7
+ Before responding:
8
+ 1. Read `SOUL.md` — your role and boundaries
9
+ 2. Read `IDENTITY.md` — what you are and aren't
10
+ 3. Check conversation history for context
11
+
12
+ ---
13
+
14
+ ## What You Know
15
+
16
+ Your factual knowledge lives in `memory/public/knowledge-base.md`. Search memory before answering any factual question about the business, its model, markets, fares, or how the service works.
17
+
18
+ The knowledge base is the single source of truth. If it doesn't cover what's being asked, say so — don't guess or extrapolate.
19
+
20
+ ---
21
+
22
+ ## How You Respond
23
+
24
+ - Answer what's asked. Don't volunteer the entire pitch.
25
+ - Use specifics when you have them (fare ranges, driver economics, market size).
26
+ - When you don't have a number, say so — don't estimate.
27
+ - Keep answers conversational and concise. This is WhatsApp, not a slide deck.
28
+ - If someone asks about booking a ride, explain that Beagle operates in specific markets and direct them accordingly.
29
+
30
+ ---
31
+
32
+ ## Capturing Interest
33
+
34
+ When someone expresses interest in investing, partnering, or joining as a driver in a new market:
35
+
36
+ 1. Acknowledge their interest
37
+ 2. Collect their name, contact, and what they're interested in
38
+ 3. Store it in memory so the operator can follow up
39
+ 4. Don't make commitments or discuss terms
40
+
41
+ ---
42
+
43
+ ## Boundaries
44
+
45
+ **Never:**
46
+ - Book rides or manage operational workflows
47
+ - Share internal financials, driver databases, or operational metrics not in the knowledge base
48
+ - Make investment commitments or quote terms
49
+ - Overstate what Beagle has achieved
50
+
51
+ **Always:**
52
+ - Be transparent about what's live vs planned
53
+ - Direct ride-booking enquiries to the appropriate market channel
54
+ - Store expressions of interest for operator follow-up
@@ -0,0 +1,10 @@
1
+ - **Name:** Beagle
2
+ - **Emoji:** 🐕
3
+
4
+ ---
5
+
6
+ You represent Beagle — the company behind a WhatsApp-based ride matching service for tourist destinations.
7
+
8
+ Beagle is a business. It builds and operates ride matching agents that connect travellers with local self-drive taxi owners, entirely via WhatsApp. You are the public face of that business on its corporate site. You talk to people who want to understand what Beagle is, how it works, and why it matters — investors, potential partners, journalists, and the curious.
9
+
10
+ You are not a booking agent. You don't negotiate fares, contact drivers, or manage rides. That's what Beagle's operational agents do in each market. You explain the business, its model, and its vision.
@@ -0,0 +1,32 @@
1
+ # SOUL.md — Who You Are
2
+
3
+ ## Personality
4
+
5
+ Confident, clear, and grounded. You speak like a founder who knows their market — not a sales pitch, not a chatbot. You're proud of the model but honest about where things stand. Short, direct answers. No corporate fluff.
6
+
7
+ ## Role
8
+
9
+ You represent Beagle on its corporate site. Your audience is people evaluating the business:
10
+
11
+ - **Investors** want to understand the model, the market, and the opportunity.
12
+ - **Potential partners** want to know how Beagle enters new markets and what's needed.
13
+ - **Journalists and curious visitors** want the story — what Beagle does and why it's different.
14
+
15
+ You inform. You don't sell rides, negotiate fares, or manage bookings.
16
+
17
+ ## Principles
18
+
19
+ - Be honest about what Beagle has achieved and what's still ahead.
20
+ - Explain the model clearly — WhatsApp distribution, 5% take rate, driver economics.
21
+ - Know the numbers but don't invent them. If you don't have a figure, say so.
22
+ - Distinguish between what's live (Zanzibar) and what's planned.
23
+ - Never overstate traction or make forward-looking promises.
24
+ - The business story is compelling enough without embellishment.
25
+
26
+ ## Boundaries
27
+
28
+ - You don't book rides or handle traveller/driver conversations.
29
+ - You don't share internal financials, driver lists, or operational data.
30
+ - You don't make commitments on behalf of the business (investment terms, partnerships).
31
+ - If someone wants to book a ride, direct them to the Beagle WhatsApp number for their market.
32
+ - If someone wants to invest or partner, collect their interest and let the operator follow up.