@adaptic/maestro 1.1.8 → 1.5.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 (47) hide show
  1. package/.claude/commands/init-maestro.md +304 -8
  2. package/README.md +28 -0
  3. package/bin/maestro.mjs +258 -56
  4. package/docs/guides/agents-observe-setup.md +64 -0
  5. package/docs/guides/ccxray-diagnostics.md +65 -0
  6. package/docs/guides/claude-mem-setup.md +79 -0
  7. package/docs/guides/claude-pace-setup.md +56 -0
  8. package/docs/guides/claudraband-sessions.md +98 -0
  9. package/docs/guides/clawteam-swarm.md +116 -0
  10. package/docs/guides/code-review-graph-setup.md +86 -0
  11. package/docs/guides/self-optimization-pattern.md +82 -0
  12. package/docs/guides/slack-setup.md +4 -2
  13. package/docs/guides/twilio-subaccounts-setup.md +223 -0
  14. package/docs/guides/webhook-relay-setup.md +349 -0
  15. package/package.json +2 -1
  16. package/plugins/maestro-skills/plugin.json +16 -0
  17. package/plugins/maestro-skills/skills/agents-observe.md +110 -0
  18. package/plugins/maestro-skills/skills/ccxray-diagnostics.md +91 -0
  19. package/plugins/maestro-skills/skills/claude-pace.md +61 -0
  20. package/plugins/maestro-skills/skills/code-review-graph.md +99 -0
  21. package/scaffold/CLAUDE.md +64 -0
  22. package/scaffold/config/agent.ts.example +2 -1
  23. package/scaffold/config/known-agents.json +35 -0
  24. package/scripts/daemon/classifier.mjs +264 -50
  25. package/scripts/daemon/dispatcher.mjs +109 -5
  26. package/scripts/daemon/launchd-wrapper-generic.sh +96 -0
  27. package/scripts/daemon/launchd-wrapper-slack-events.sh +37 -0
  28. package/scripts/daemon/launchd-wrapper.sh +91 -0
  29. package/scripts/daemon/lib/session-router.mjs +274 -0
  30. package/scripts/daemon/lib/session-router.test.mjs +295 -0
  31. package/scripts/daemon/prompt-builder.mjs +51 -11
  32. package/scripts/daemon/responder.mjs +234 -19
  33. package/scripts/daemon/session-lock.mjs +194 -0
  34. package/scripts/daemon/sophie-daemon.mjs +16 -2
  35. package/scripts/email-signature.html +20 -4
  36. package/scripts/local-triggers/generate-plists.sh +62 -10
  37. package/scripts/poller/imap-client.mjs +4 -2
  38. package/scripts/poller/slack-poller.mjs +104 -52
  39. package/scripts/setup/init-agent.sh +91 -1
  40. package/scripts/setup/install-dev-tools.sh +150 -0
  41. package/scripts/spawn-session.sh +21 -6
  42. package/workflows/continuous/backlog-executor.yaml +141 -0
  43. package/workflows/daily/evening-wrap.yaml +41 -1
  44. package/workflows/daily/morning-brief.yaml +17 -0
  45. package/workflows/event-driven/agent-failure-investigation.yaml +137 -0
  46. package/workflows/event-driven/pr-review.yaml +104 -0
  47. package/workflows/weekly/engineering-health.yaml +154 -0
@@ -297,7 +297,7 @@ Do NOT modify these sections (keep them exactly as they are, except for agent na
297
297
  - product-leader: product roadmap, user research, feature delivery, product-market fit, design system
298
298
  - operations-leader: process automation, operational efficiency, fund operations, organisational design, vendor management
299
299
 
300
- ### Sub-agent 4: Update package.json and scripts
300
+ ### Sub-agent 4: Update package.json, scripts, and identity-baked content
301
301
 
302
302
  **Instruction to sub-agent:**
303
303
 
@@ -307,9 +307,57 @@ Do NOT modify these sections (keep them exactly as they are, except for agent na
307
307
  - Variable names like `SOPHIE_AI_DIR` -> `{UPPER_FIRSTNAME}_AI_DIR`
308
308
  - Path references like `/Users/sophie/sophie-ai` -> `/Users/{lowercase-firstname}/{repoSlug}`
309
309
  - LaunchD labels like `ai.adaptic.sophie-` -> `ai.adaptic.{lowercase-firstname}-`
310
+ - Pronouns: if the new agent's gender differs from the scaffolding template, update he/she/him/her/his/hers/himself/herself across system prompts, comments, and documentation. Be surgical — do NOT change pronouns inside generic regex patterns or third-party detection logic.
310
311
 
311
312
  3. **LaunchD plists** in `scripts/local-triggers/plists/` -- Update labels and paths in all `.plist` files.
312
313
 
314
+ 4. **Identity-baked content rewrites (CRITICAL — full overwrites, not grep-replace).** These files contain the agent's outbound identity (name, title, email, phone, signature) and MUST be fully rewritten with the new agent's values. Do not rely on grep-replace alone — read each file, then OVERWRITE it with content that uses these exact values:
315
+
316
+ - `firstName + lastName` (e.g., "Lucas Ferreira")
317
+ - `title` (e.g., "VP, Regulatory & Licensing")
318
+ - `email` (e.g., "lucas@adaptic.ai")
319
+ - `phone` (e.g., "+61 478 964 324" — use the spaced pretty form for human-facing display, the E.164 form for code)
320
+ - `companyName` (e.g., "Adaptic.ai")
321
+ - `companyAddress` (use the company's primary office address — typically Adaptic's DIFC office unless explicitly different)
322
+
323
+ **Files to rewrite:**
324
+
325
+ a. **`scripts/email-signature.html`** — The HTML signature appended to all outbound emails by `send-email.sh` and the Python send scripts. Must contain: name (bold, 14px), title (grey, 13px), Adaptic logo (`https://adaptic.ai/logo.png`), email, phone, company address line, full confidentiality disclaimer footer. Pattern matches the template in `~/maestro/scripts/email-signature.html` — use placeholders {{AGENT_NAME}}, {{AGENT_TITLE}}, {{AGENT_EMAIL}}, {{AGENT_PHONE}}, {{COMPANY_ADDRESS}} and substitute them.
326
+
327
+ b. **`scripts/email-signature-mehran.html`** — Principal's signature block (used by `send-email-as-mehran.py` or equivalent send-as-principal scripts). Update with the principal's values: `principal.fullName`, `principal.title`, `principal.email`. If the principal doesn't have a phone in config/agent.ts, omit the phone line.
328
+
329
+ c. **`scripts/send-email.sh`** — Hardcoded `From:` header and inline signature fallback. Update both. The From header should be in the form `"Lucas Ferreira" <lucas@adaptic.ai>`.
330
+
331
+ d. **`scripts/send-email-threaded.py`** — `USER`, `From` header construction, inline signature, argparse description. All must reflect the new agent.
332
+
333
+ e. **`scripts/send-email-with-attachment.py`** — Same as above.
334
+
335
+ f. **`scripts/pdf-generation/build-document.mjs`** — Default `author` value (used in PDF metadata) and the help text describing the default. Set to the new agent's full name.
336
+
337
+ g. **`scripts/pdf-generation/templates/memo.latex`** — Footer line "Prepared by ... Chief of Staff" — replace with "Prepared by {fullName}, {title}".
338
+
339
+ h. **`scripts/daemon/responder.mjs` `FALLBACK_PREAMBLE`** — System prompt that introduces the agent to Claude. Identity intro line must reference the new agent. Preserve the operational rules.
340
+
341
+ i. **`scripts/daemon/prompt-builder.mjs` `FALLBACK_PREAMBLE`** — Same treatment.
342
+
343
+ j. **`scripts/daemon/classifier.mjs` `SYSTEM_PROMPT`** — Identity intro line. Preserve everything else.
344
+
345
+ k. **`scripts/huddle/huddle-server.mjs` `HUDDLE_SYSTEM_PROMPT`** — Voice agent identity line.
346
+
347
+ l. **`scripts/spawn-session.sh`** — Sub-session bootstrap prompt that names the agent.
348
+
349
+ m. **`scripts/continuous-monitor.sh`** — Channel monitor agent prompt.
350
+
351
+ n. **`scripts/llm_email_dedup.py`, `scripts/comms-monitor.sh`, `scripts/archive-email.sh`, `scripts/poller/gmail-poller.mjs`, `scripts/poller/imap-client.mjs`** — Hardcoded `LUCAS_EMAIL`/`USER`/`gmail_user` constants. Set to the new agent's email.
352
+
353
+ o. **`scripts/{firstname}-inbox-poller.py`** — Rename file from `sophie-inbox-poller.py` (or current scaffolding name) to `{firstname}-inbox-poller.py`. Update internal `LUCAS_EMAIL` constant. Update any plist references to the new filename.
354
+
355
+ p. **`scripts/rag-indexer.py`, `scripts/user-context-search.py`** — Author docstring at the top. Set to the new agent's full name.
356
+
357
+ q. **`scripts/validate-outbound.py`** — Three test/regex references to "Sophie Nguyen" or `lookup_entity("Sophie Nguyen")`. Replace with the new agent's full name. Leave the generic third-party pronoun regex (around line 1007) UNCHANGED — it's a detector, not an identity reference.
358
+
359
+ **Verification step**: After rewrites, run `grep -rn "sophie@adaptic\|Sophie Nguyen\|Chief of Staff" scripts/ docs/business-synthesis/executive-operating-model.md 2>&1` and confirm only intentional peer references remain (Sophie Nguyen as a real Chief of Staff peer, NOT as the agent's own outbound identity). Report any remaining matches you couldn't safely auto-resolve so the main agent can decide.
360
+
313
361
  ### Sub-agent 5: Update agent definitions
314
362
 
315
363
  **Instruction to sub-agent:**
@@ -436,6 +484,123 @@ If yes, run:
436
484
  sudo ./scripts/setup/configure-macos.sh
437
485
  ```
438
486
 
487
+ ### Step 4: External SSD configuration (REQUIRED if /Volumes/{name}-SSD is mounted)
488
+
489
+ The maestro daemon and its launchd-spawned trigger jobs should write all runtime data — Claude Code per-cwd temp dirs, daemon logs, state, outputs, memory, knowledge — to an external SSD when one is available. This keeps the internal disk free for macOS and avoids wear on the system disk.
490
+
491
+ **Two macOS hurdles need to be cleared before the SSD redirect actually works for launchd-spawned processes:**
492
+
493
+ #### 4a. Enable file ownership on the volume
494
+
495
+ By default, external volumes have Owners disabled, which makes file permissions advisory rather than enforced. The daemon's wrapper writes per-agent log files, and that requires real owners.
496
+
497
+ Detect the SSD and enable owners:
498
+
499
+ ```bash
500
+ SSD_VOLUME=""
501
+ for v in /Volumes/*-SSD /Volumes/*SSD* /Volumes/maestro-data; do
502
+ if [ -d "$v" ] && [ "$v" != "/Volumes/Macintosh HD" ]; then SSD_VOLUME="$v"; break; fi
503
+ done
504
+
505
+ if [ -n "$SSD_VOLUME" ]; then
506
+ # Tell the user we found an SSD and need sudo to enable owners
507
+ echo "Found external SSD at $SSD_VOLUME — enabling file ownership."
508
+ echo "Please run this in your terminal (it needs sudo):"
509
+ echo " sudo diskutil enableOwnership \"$SSD_VOLUME\""
510
+ echo "Reply 'done' when complete."
511
+ fi
512
+ ```
513
+
514
+ Wait for the user to confirm. Then verify:
515
+
516
+ ```bash
517
+ diskutil info "$SSD_VOLUME" | grep "Owners" | grep -q "Enabled" && echo "OK" || echo "FAIL"
518
+ ```
519
+
520
+ #### 4b. Grant Full Disk Access to bash and node (TCC)
521
+
522
+ Even with owners enabled, **macOS TCC blocks launchd-spawned processes from writing to /Volumes/ unless the binary has Full Disk Access**. This is the single most common cause of "Operation not permitted" errors when you run a daemon under launchd that tries to write to an external volume.
523
+
524
+ You cannot grant Full Disk Access programmatically without disabling SIP (which is unsafe). The user must do this via System Settings UI:
525
+
526
+ ```
527
+ 1. Open System Settings → Privacy & Security → Full Disk Access
528
+ 2. Click the + button
529
+ 3. Press Cmd+Shift+G to "Go to Folder", then enter:
530
+ /bin/bash
531
+ Press Enter, select 'bash', click Open
532
+ 4. Click + again, then:
533
+ /usr/bin/node OR ~/.nvm/versions/node/v24.11.1/bin/node
534
+ (whichever node binary the wrapper uses)
535
+ 5. Make sure both toggles are ON
536
+
537
+ The toggles take effect immediately. No restart needed.
538
+ ```
539
+
540
+ After the user confirms, test that launchd can now write to the SSD:
541
+
542
+ ```bash
543
+ cat > /tmp/ssd-tcc-test.plist <<EOF
544
+ <?xml version="1.0" encoding="UTF-8"?>
545
+ <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
546
+ <plist version="1.0">
547
+ <dict>
548
+ <key>Label</key><string>ai.adaptic.ssd-tcc-test</string>
549
+ <key>ProgramArguments</key><array>
550
+ <string>/bin/bash</string><string>-c</string>
551
+ <string>touch "$SSD_VOLUME/.tcc-test" && echo "ok=$?" > /tmp/ssd-tcc-result.log || echo "fail=$?" > /tmp/ssd-tcc-result.log</string>
552
+ </array>
553
+ <key>RunAtLoad</key><true/>
554
+ </dict>
555
+ </plist>
556
+ EOF
557
+ launchctl load /tmp/ssd-tcc-test.plist
558
+ sleep 2
559
+ cat /tmp/ssd-tcc-result.log
560
+ launchctl unload /tmp/ssd-tcc-test.plist
561
+ rm -f "$SSD_VOLUME/.tcc-test"
562
+ ```
563
+
564
+ If you see `ok=0`, TCC is configured correctly and the daemon can use the SSD. If you see `fail=1`, TCC is still blocking — repeat the System Settings step and make sure the toggles are ON.
565
+
566
+ #### 4c. Set up SSD layout and symlinks
567
+
568
+ ```bash
569
+ AGENT_NAME="$(grep firstName config/agent.ts | head -1 | sed 's/.*[\x27\"]\([a-zA-Z]*\)[\x27\"].*/\1/' | tr A-Z a-z)"
570
+ SSD_AGENT_ROOT="$SSD_VOLUME/maestro/$AGENT_NAME"
571
+ mkdir -p "$SSD_AGENT_ROOT"/{state,outputs,memory,knowledge,claude-tmp,logs,tmp}
572
+
573
+ # Symlink runtime data dirs from the agent repo to the SSD.
574
+ # IMPORTANT: do NOT symlink logs/ — launchd's StandardErrorPath cannot follow
575
+ # symlinks to external volumes. The daemon's wrapper writes its own log file
576
+ # directly to the SSD via shell redirection (see launchd-wrapper.sh).
577
+ for d in state outputs memory knowledge; do
578
+ if [ -d "$d" ] && [ ! -L "$d" ]; then
579
+ rsync -a "$d/" "$SSD_AGENT_ROOT/$d/"
580
+ rm -rf "$d"
581
+ ln -sfn "$SSD_AGENT_ROOT/$d" "$d"
582
+ fi
583
+ done
584
+
585
+ # Create internal-disk logs/ as a real directory (NOT a symlink)
586
+ mkdir -p logs/{daemon,polling,workflows,sessions,audit,security,evolution,huddle,infra,monitor,phone,sms,whatsapp,email,launchd,cloudflared}
587
+ ```
588
+
589
+ The wrapper scripts (`scripts/daemon/launchd-wrapper.sh` and `launchd-wrapper-generic.sh`) handle the runtime side: they detect the SSD, set `CLAUDE_CODE_TMPDIR`, and redirect daemon stdout/stderr to a log file on the SSD. They gracefully fall back to internal-disk paths if the SSD isn't writable (e.g. if TCC isn't granted yet).
590
+
591
+ #### 4d. Verify
592
+
593
+ ```bash
594
+ # Daemon should now be writing to the SSD log file
595
+ launchctl unload ~/Library/LaunchAgents/ai.adaptic.${AGENT_NAME}-daemon.plist
596
+ launchctl load ~/Library/LaunchAgents/ai.adaptic.${AGENT_NAME}-daemon.plist
597
+ sleep 4
598
+ ls -la "$SSD_AGENT_ROOT/logs/daemon/" | tail -5
599
+ ls -la "$SSD_AGENT_ROOT/state/inbox/" | tail -5
600
+ ```
601
+
602
+ You should see the daemon log file growing and inbox directories populating. If you don't, repeat steps 4a–4b (most often it's TCC).
603
+
439
604
  ## Phase 4: Autonomous Service Configuration
440
605
 
441
606
  This phase sets up all third-party integrations **autonomously**. Use Playwright MCP for web-based setup (Slack API portal, Twilio Console, Google Account, ElevenLabs, Deepgram) and Bash for local scripts. Only ask the user for input when genuinely required (existing credentials, 2FA codes, payment authorisation).
@@ -537,14 +702,145 @@ source .env && python3 -c "import imaplib,os; m=imaplib.IMAP4_SSL('imap.gmail.co
537
702
  4. Note and report the sandbox join keyword to the user
538
703
  5. Write `WHATSAPP_MODE=sandbox`, `WHATSAPP_PORT=3002` to `.env`
539
704
 
540
- ### Step 4: Cloudflare Tunnels — per `docs/guides/voice-sms-setup.md` § 7
705
+ ### Step 4: Webhook Relay (Railway) — per `docs/guides/webhook-relay-setup.md`
706
+
707
+ **This is the canonical pattern. Do NOT use Cloudflare Tunnels for new agents** — they were a transitional approach. Each agent gets its own Railway-deployed webhook relay. The local Mac mini polls the relay every 5 seconds and never needs an inbound tunnel.
708
+
709
+ The relay handles:
710
+ - `POST /slack/events` — Slack Events API (HMAC verified via SLACK_SIGNING_SECRET)
711
+ - `POST /sms` — Twilio SMS inbound (HMAC verified via TWILIO_AUTH_TOKEN)
712
+ - `POST /whatsapp` — Twilio WhatsApp inbound
713
+ - `POST /whatsapp/status` — Twilio WhatsApp delivery status
714
+ - `GET /events`, `/sms/messages`, `/whatsapp/messages` — drained by Mac mini poller
715
+ - `GET /health` — service status
716
+
717
+ **Source code** is already in the repo at `services/webhook-relay/` (copied from the maestro framework). It's a ~250-line Node 20 HTTP server, no dependencies, deployable straight to Railway.
718
+
719
+ **Prerequisites:**
720
+ - Railway CLI installed: `brew install railway 2>/dev/null || true`
721
+ - User must run `railway login` once (interactive — opens browser)
722
+ - User must have admin rights in the company's Railway workspace (e.g., "Adaptic")
723
+
724
+ **Deploy steps (run from the agent's repo root):**
725
+
726
+ ```bash
727
+ # 1. Create the project in the company's Railway workspace
728
+ cd services/webhook-relay
729
+ railway init --name {firstname-lower}-webhook-relay --workspace {Company}
730
+
731
+ # 2. Add the service and deploy
732
+ railway up --service {firstname-lower}-webhook-relay --detach
733
+
734
+ # 3. Generate a public domain
735
+ railway domain --service {firstname-lower}-webhook-relay
736
+ # Captures: https://{firstname-lower}-webhook-relay-production.up.railway.app
737
+
738
+ # 4. Set env vars (must include the agent's own SLACK_SIGNING_SECRET and TWILIO_AUTH_TOKEN)
739
+ source ../../.env
740
+ railway variables --service {firstname-lower}-webhook-relay \
741
+ --set "SLACK_SIGNING_SECRET=$SLACK_SIGNING_SECRET" \
742
+ --set "TWILIO_AUTH_TOKEN=$TWILIO_AUTH_TOKEN" \
743
+ --set "PUBLIC_HOSTNAME={firstname-lower}-webhook-relay-production.up.railway.app" \
744
+ --set "BUFFER_TTL_MS=600000" \
745
+ --set "MAX_BUFFER_SIZE=1000"
746
+
747
+ # 5. Trigger redeploy so the running container picks up the new env vars
748
+ railway up --service {firstname-lower}-webhook-relay --detach
749
+
750
+ # 6. Wait until /health returns slack_signature: true and twilio_signature: true
751
+ for i in 1 2 3 4 5 6 7 8 9 10 11 12; do
752
+ RESP=$(curl -sf -m 5 https://{firstname-lower}-webhook-relay-production.up.railway.app/health)
753
+ if echo "$RESP" | grep -q '"slack_signature":true' && echo "$RESP" | grep -q '"twilio_signature":true'; then
754
+ echo "Relay live with signature verification"
755
+ break
756
+ fi
757
+ sleep 10
758
+ done
759
+ ```
760
+
761
+ **Configure external services to point at the relay:**
762
+
763
+ ```bash
764
+ # Twilio SMS webhook (uses Twilio API directly, no UI)
765
+ RELAY_URL="https://{firstname-lower}-webhook-relay-production.up.railway.app"
766
+ curl -s -u "$TWILIO_ACCOUNT_SID:$TWILIO_AUTH_TOKEN" -X POST \
767
+ "https://api.twilio.com/2010-04-01/Accounts/$TWILIO_ACCOUNT_SID/IncomingPhoneNumbers/$TWILIO_PHONE_SID.json" \
768
+ --data-urlencode "SmsUrl=$RELAY_URL/sms" --data-urlencode "SmsMethod=POST"
769
+ ```
770
+
771
+ For **Slack Events Subscription**: use Playwright to update via the App Manifest editor (more reliable than the events page). Navigate to `https://app.slack.com/app-settings/{TEAM_ID}/{APP_ID}/app-manifest`, read the JSON via the CodeMirror instance, add this block to `settings`, and click Save Changes:
772
+
773
+ ```json
774
+ "event_subscriptions": {
775
+ "request_url": "https://{firstname-lower}-webhook-relay-production.up.railway.app/slack/events",
776
+ "bot_events": [
777
+ "app_mention",
778
+ "message.channels",
779
+ "message.groups",
780
+ "message.im",
781
+ "message.mpim"
782
+ ]
783
+ }
784
+ ```
785
+
786
+ After save, navigate to the Event Subscriptions page and check for the yellow "Click here to verify" button — click it. Then **reinstall the app** at `https://api.slack.com/apps/{APP_ID}/install-on-team` so the new event scopes activate.
787
+
788
+ For **Twilio WhatsApp sandbox**: this requires a per-agent Twilio sub-account (see Phase 4 Step 3.5). Cannot share with other agents because the sandbox webhook is account-wide.
789
+
790
+ **Update local poll script:**
791
+
792
+ ```bash
793
+ # Edit scripts/poll-slack-events.sh and scripts/comms-monitor.sh
794
+ # Set EVENTS_URL to https://{firstname-lower}-webhook-relay-production.up.railway.app/events
795
+ ```
796
+
797
+ **Install the launchd job that polls the relay every 5 seconds:**
798
+
799
+ ```bash
800
+ cat > scripts/local-triggers/plists/ai.adaptic.{firstname-lower}-poll-relay.plist <<EOF
801
+ <?xml version="1.0" encoding="UTF-8"?>
802
+ <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
803
+ <plist version="1.0">
804
+ <dict>
805
+ <key>Label</key><string>ai.adaptic.{firstname-lower}-poll-relay</string>
806
+ <key>ProgramArguments</key><array>
807
+ <string>/bin/bash</string>
808
+ <string>{REPO_ROOT}/scripts/poll-slack-events.sh</string>
809
+ </array>
810
+ <key>WorkingDirectory</key><string>{REPO_ROOT}</string>
811
+ <key>StartInterval</key><integer>5</integer>
812
+ <key>RunAtLoad</key><true/>
813
+ <key>StandardOutPath</key><string>{REPO_ROOT}/logs/polling/poll-relay-stdout.log</string>
814
+ <key>StandardErrorPath</key><string>{REPO_ROOT}/logs/polling/poll-relay-stderr.log</string>
815
+ </dict>
816
+ </plist>
817
+ EOF
818
+ cp scripts/local-triggers/plists/ai.adaptic.{firstname-lower}-poll-relay.plist ~/Library/LaunchAgents/
819
+ launchctl load ~/Library/LaunchAgents/ai.adaptic.{firstname-lower}-poll-relay.plist
820
+ ```
821
+
822
+ **Add the relay URL block to `.env`:**
823
+
824
+ ```bash
825
+ cat >> .env <<EOF
826
+
827
+ # ─── RAILWAY WEBHOOK RELAY ──────────────────────────────────────────────────
828
+ WEBHOOK_RELAY_URL=https://{firstname-lower}-webhook-relay-production.up.railway.app
829
+ WEBHOOK_RELAY_SLACK_EVENTS=https://{firstname-lower}-webhook-relay-production.up.railway.app/slack/events
830
+ WEBHOOK_RELAY_SMS_INBOUND=https://{firstname-lower}-webhook-relay-production.up.railway.app/sms
831
+ WEBHOOK_RELAY_WHATSAPP_INBOUND=https://{firstname-lower}-webhook-relay-production.up.railway.app/whatsapp
832
+ WEBHOOK_RELAY_POLL_EVENTS=https://{firstname-lower}-webhook-relay-production.up.railway.app/events
833
+ WEBHOOK_RELAY_POLL_SMS=https://{firstname-lower}-webhook-relay-production.up.railway.app/sms/messages
834
+ WEBHOOK_RELAY_POLL_WHATSAPP=https://{firstname-lower}-webhook-relay-production.up.railway.app/whatsapp/messages
835
+ EOF
836
+ ```
837
+
838
+ **End-to-end test:**
541
839
 
542
- 1. `brew install cloudflared 2>/dev/null || true`
543
- 2. Determine needed ports: SMS=3001, WhatsApp=3002, Slack Events=3100
544
- 3. Ask: quick tunnel (temporary) or named tunnel (persistent)?
545
- 4. For quick: start tunnels in background, capture URLs
546
- 5. For named: `cloudflared tunnel login` (may require browser auth), `cloudflared tunnel create {agent-name}`, write config
547
- 6. Go back and update Twilio webhook URLs with the tunnel URLs (Steps 3 SMS/WhatsApp)
840
+ 1. Have Lucas (or any user) send a Slack message to a channel where the bot is a member, OR @-mention the bot in a public channel
841
+ 2. Within ~5 seconds, the local Mac mini should fetch the buffered event and write a YAML file to `state/inbox/slack/`
842
+ 3. The inbox processor picks it up and routes it
843
+ 4. Verify `railway logs --service {firstname-lower}-webhook-relay` shows `[slack] buffered ...`
548
844
 
549
845
  ### Step 5: Voice / Huddle — per `docs/guides/voice-sms-setup.md` § 5
550
846
 
package/README.md CHANGED
@@ -373,6 +373,14 @@ Security policies are defined in `policies/` and `docs/governance/`.
373
373
  - [RAG & Context Setup](docs/guides/rag-context-setup.md) -- SQLite FTS5 search, pre-draft context, entity indexing
374
374
  - [PDF Generation Setup](docs/guides/pdf-generation-setup.md) -- Pandoc + XeLaTeX branded document generation
375
375
  - [Media Generation Setup](docs/guides/media-generation-setup.md) -- Gemini/Veo image and video generation
376
+ - [Claude-Mem Setup](docs/guides/claude-mem-setup.md) -- Persistent session memory with semantic recall
377
+ - [Claude-Pace Setup](docs/guides/claude-pace-setup.md) -- Real-time rate limit tracking and burn rate awareness
378
+ - [ccxray Diagnostics](docs/guides/ccxray-diagnostics.md) -- Token/cost analysis and session debugging
379
+ - [Claudraband Sessions](docs/guides/claudraband-sessions.md) -- Persistent sessions, daemon mode, HTTP API
380
+ - [ClawTeam Swarm](docs/guides/clawteam-swarm.md) -- Multi-agent coding swarm orchestration via git worktrees
381
+ - [Agents Observe](docs/guides/agents-observe-setup.md) -- Real-time multi-agent observability dashboard
382
+ - [Code-Review-Graph](docs/guides/code-review-graph-setup.md) -- Tree-sitter structural knowledge graph for codebases
383
+ - [Self-Optimization Pattern](docs/guides/self-optimization-pattern.md) -- AutoAgent-inspired benchmark-driven self-improvement
376
384
 
377
385
  ### Architecture & Governance
378
386
 
@@ -381,6 +389,26 @@ Security policies are defined in `policies/` and `docs/governance/`.
381
389
  - [Action Approval Model](docs/governance/action-approval-model.md) -- Communication governance and approval levels
382
390
  - [Communications Policy](docs/governance/communications-policy.md) -- Voice modes, autonomy model, escalation rules
383
391
 
392
+ ### Dev Tooling
393
+
394
+ Approved third-party tools for agent development and observability. Install with:
395
+
396
+ ```bash
397
+ ./scripts/setup/install-dev-tools.sh --all
398
+ ```
399
+
400
+ | Tool | Purpose | Install |
401
+ |------|---------|---------|
402
+ | **claude-pace** | Rate limit status line tracker | `--tool claude-pace` |
403
+ | **agents-observe** | Multi-agent observability dashboard | `--tool agents-observe` |
404
+ | **ccxray** | Token/cost observability proxy | `--tool ccxray` |
405
+ | **ClawTeam** | Git worktree swarm orchestrator | `--tool clawteam` |
406
+ | **code-review-graph** | Tree-sitter codebase knowledge graph | `--tool code-review-graph` |
407
+
408
+ ### Cross-Agent Message Routing
409
+
410
+ When multiple agents monitor the same Slack channels, the daemon classifier uses `config/known-agents.json` to prevent cross-agent message interception. If a message @-mentions a specific agent, only that agent's daemon will respond. Update this file when agents are added or removed.
411
+
384
412
  ### Runbooks
385
413
 
386
414
  - [Mac Mini Bootstrap](docs/runbooks/mac-mini-bootstrap.md) -- Hardware setup and initial configuration