@adaptic/maestro 1.6.1 → 1.7.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 (33) hide show
  1. package/package.json +1 -1
  2. package/scripts/continuous-monitor.sh +11 -6
  3. package/scripts/daemon/context-compiler.mjs +8 -8
  4. package/scripts/daemon/health.mjs +2 -2
  5. package/scripts/daemon/maestro-daemon.mjs +4 -3
  6. package/scripts/email_thread_dedup.py +4 -3
  7. package/scripts/huddle/huddle-server.mjs +50 -29
  8. package/scripts/llm_email_dedup.py +23 -15
  9. package/scripts/local-triggers/generate-plists.sh +2 -1
  10. package/scripts/media-generation/README.md +1 -1
  11. package/scripts/outbound-dedup-cleanup.sh +4 -4
  12. package/scripts/outbound-dedup.sh +4 -3
  13. package/scripts/pdf-generation/README.md +1 -1
  14. package/scripts/pdf-generation/templates/memo.latex +1 -1
  15. package/scripts/poll-slack-events.sh +4 -2
  16. package/scripts/poller/imap-client.mjs +11 -10
  17. package/scripts/poller/index.mjs +6 -6
  18. package/scripts/poller/intra-session-check.mjs +35 -18
  19. package/scripts/poller/mehran-gmail-poller.mjs +63 -29
  20. package/scripts/poller/slack-poller.mjs +45 -31
  21. package/scripts/poller/trigger.mjs +22 -5
  22. package/scripts/pre-draft-context.py +2 -2
  23. package/scripts/rag-indexer.py +3 -3
  24. package/scripts/send-sms.sh +7 -7
  25. package/scripts/send-whatsapp.sh +11 -11
  26. package/scripts/setup/configure-macos.sh +4 -2
  27. package/scripts/setup/init-agent.sh +1 -1
  28. package/scripts/slack-react.mjs +1 -1
  29. package/scripts/slack-typing.mjs +3 -3
  30. package/scripts/system-verify.sh +28 -15
  31. package/scripts/user-context-search.py +4 -4
  32. package/scripts/validate-outbound.py +29 -18
  33. package/scripts/sophie-inbox-poller.py +0 -406
@@ -1,19 +1,19 @@
1
1
  #!/bin/bash
2
- # send-sms.sh — Send outbound SMS via Twilio REST API as Sophie
2
+ # send-sms.sh — Send outbound SMS via Twilio REST API as the running agent
3
3
  # Usage: ./scripts/send-sms.sh --to "+1234567890" --body "message text"
4
4
  #
5
5
  # Environment variables (from .env):
6
6
  # TWILIO_ACCOUNT_SID — Twilio account SID
7
7
  # TWILIO_AUTH_TOKEN — Twilio auth token
8
- # TWILIO_PHONE_NUMBER — Sophie's Twilio phone number (default: +16282656712)
8
+ # TWILIO_PHONE_NUMBER — The agent's Twilio phone number
9
9
  set -e
10
10
 
11
11
  # Load environment
12
12
  SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
13
- SOPHIE_AI="$(dirname "$SCRIPT_DIR")"
13
+ AGENT_REPO_DIR="${AGENT_DIR:-$(dirname "$SCRIPT_DIR")}"
14
14
 
15
- if [ -f "$SOPHIE_AI/.env" ]; then
16
- source "$SOPHIE_AI/.env"
15
+ if [ -f "$AGENT_REPO_DIR/.env" ]; then
16
+ source "$AGENT_REPO_DIR/.env"
17
17
  fi
18
18
 
19
19
  # Defaults
@@ -110,7 +110,7 @@ MESSAGE_SID=$(echo "$RESPONSE_BODY" | grep -o '"sid": *"[^"]*"' | head -1 | sed
110
110
  STATUS=$(echo "$RESPONSE_BODY" | grep -o '"status": *"[^"]*"' | head -1 | sed 's/"status": *"//' | sed 's/"//')
111
111
 
112
112
  # Log to SMS log
113
- LOG_DIR="$SOPHIE_AI/logs/sms/$(date +%Y-%m-%d)"
113
+ LOG_DIR="$AGENT_REPO_DIR/logs/sms/$(date +%Y-%m-%d)"
114
114
  mkdir -p "$LOG_DIR"
115
115
  LOG_FILE="$LOG_DIR/$(date +%Y-%m-%d)-sms.jsonl"
116
116
 
@@ -121,7 +121,7 @@ LOGEOF
121
121
  echo "$LOG_ENTRY" >> "$LOG_FILE"
122
122
 
123
123
  # Log to audit trail
124
- AUDIT_DIR="$SOPHIE_AI/logs/audit"
124
+ AUDIT_DIR="$AGENT_REPO_DIR/logs/audit"
125
125
  mkdir -p "$AUDIT_DIR"
126
126
  AUDIT_FILE="$AUDIT_DIR/$(date +%Y-%m-%d)-actions.jsonl"
127
127
 
@@ -1,32 +1,32 @@
1
1
  #!/bin/bash
2
- # send-whatsapp.sh — Send outbound WhatsApp message via Twilio REST API as Sophie
2
+ # send-whatsapp.sh — Send outbound WhatsApp message via Twilio REST API as the agent
3
3
  #
4
4
  # Supports two modes:
5
5
  # 1. Sandbox mode (default until production WABA is active):
6
6
  # Uses Twilio sandbox number +14155238886
7
7
  # Recipients must have joined the sandbox first
8
8
  # 2. Production mode (--production flag or WHATSAPP_MODE=production):
9
- # Uses Sophie's own number +16282656712 as WhatsApp Business sender
9
+ # Uses the agent's own Twilio number as WhatsApp Business sender
10
10
  #
11
11
  # Usage:
12
- # ./scripts/send-whatsapp.sh --to "+971501234567" --body "Hello from Sophie"
12
+ # ./scripts/send-whatsapp.sh --to "+971501234567" --body "Hello"
13
13
  # ./scripts/send-whatsapp.sh --to "+971501234567" --body "Hello" --production
14
- # ./scripts/send-whatsapp.sh --to "+971501234567" --template "intro_greeting" --vars "Sophie,Adaptic"
14
+ # ./scripts/send-whatsapp.sh --to "+971501234567" --template "intro_greeting" --vars "Var1,Var2"
15
15
  #
16
16
  # Environment variables (from .env):
17
17
  # TWILIO_ACCOUNT_SID — Twilio account SID
18
18
  # TWILIO_AUTH_TOKEN — Twilio auth token
19
- # TWILIO_PHONE_NUMBER — Sophie's Twilio phone number
19
+ # TWILIO_PHONE_NUMBER — The agent's Twilio phone number
20
20
  # WHATSAPP_MODE — "sandbox" (default) or "production"
21
21
  # WHATSAPP_SANDBOX_NUMBER — Sandbox sender (default: +14155238886)
22
22
  set -e
23
23
 
24
24
  # Load environment
25
25
  SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
26
- SOPHIE_AI="$(dirname "$SCRIPT_DIR")"
26
+ AGENT_REPO_DIR="${AGENT_DIR:-$(dirname "$SCRIPT_DIR")}"
27
27
 
28
- if [ -f "$SOPHIE_AI/.env" ]; then
29
- source "$SOPHIE_AI/.env"
28
+ if [ -f "$AGENT_REPO_DIR/.env" ]; then
29
+ source "$AGENT_REPO_DIR/.env"
30
30
  fi
31
31
 
32
32
  # Defaults
@@ -77,7 +77,7 @@ while [[ $# -gt 0 ]]; do
77
77
  echo " --to Recipient phone number (E.164 format, required)"
78
78
  echo " --body Message text (required unless --template is used)"
79
79
  echo " --template WhatsApp message template name (for business-initiated messages)"
80
- echo " --vars Comma-separated template variables (e.g. \"Sophie,Adaptic\")"
80
+ echo " --vars Comma-separated template variables (e.g. \"Name,Company\")"
81
81
  echo " --media URL of media to attach (image, PDF, etc.)"
82
82
  echo " --production Use production WhatsApp Business number"
83
83
  echo " --sandbox Use Twilio WhatsApp sandbox (default)"
@@ -207,7 +207,7 @@ ERROR_CODE=$(echo "$RESPONSE_BODY" | grep -o '"code": *[0-9]*' | head -1 | sed '
207
207
  ERROR_MSG=$(echo "$RESPONSE_BODY" | grep -o '"message": *"[^"]*"' | head -1 | sed 's/"message": *"//' | sed 's/"//')
208
208
 
209
209
  # Log to WhatsApp log
210
- LOG_DIR="$SOPHIE_AI/logs/whatsapp/$(date +%Y-%m-%d)"
210
+ LOG_DIR="$AGENT_REPO_DIR/logs/whatsapp/$(date +%Y-%m-%d)"
211
211
  mkdir -p "$LOG_DIR"
212
212
  LOG_FILE="$LOG_DIR/$(date +%Y-%m-%d)-whatsapp.jsonl"
213
213
 
@@ -220,7 +220,7 @@ LOGEOF
220
220
  echo "$LOG_ENTRY" >> "$LOG_FILE"
221
221
 
222
222
  # Log to audit trail
223
- AUDIT_DIR="$SOPHIE_AI/logs/audit"
223
+ AUDIT_DIR="$AGENT_REPO_DIR/logs/audit"
224
224
  mkdir -p "$AUDIT_DIR"
225
225
  AUDIT_FILE="$AUDIT_DIR/$(date +%Y-%m-%d)-actions.jsonl"
226
226
 
@@ -30,7 +30,7 @@ AGENT_NAME=$(basename "$AGENT_DIR")
30
30
  CURRENT_USER=$(whoami)
31
31
 
32
32
  # The directory from which this script was invoked — used as the boot-time
33
- # Claude Code working directory (e.g. ~/sophie-ai, ~/wundr, etc.)
33
+ # Claude Code working directory (e.g. ~/agent-name, ~/wundr, etc.)
34
34
  CALLER_DIR="${PWD}"
35
35
 
36
36
  # Colours
@@ -419,7 +419,9 @@ configure_memory_watchdog() {
419
419
  local PLIST_DEST="$real_home/Library/LaunchAgents/ai.maestro.memory-watchdog.plist"
420
420
 
421
421
  # Install the plist (update paths for this agent directory)
422
- sed "s|/Users/sophie/maestro|$AGENT_DIR|g" "$PLIST_SOURCE" > "$PLIST_DEST"
422
+ # Template plists may contain `__AGENT_DIR__` (preferred) or the legacy
423
+ # `/Users/sophie/maestro` literal — substitute both for compatibility.
424
+ sed -e "s|__AGENT_DIR__|$AGENT_DIR|g" -e "s|/Users/sophie/maestro|$AGENT_DIR|g" "$PLIST_SOURCE" > "$PLIST_DEST"
423
425
  chown "$real_user:staff" "$PLIST_DEST" 2>/dev/null || true
424
426
 
425
427
  # Load it (as the real user)
@@ -31,7 +31,7 @@ set -e
31
31
 
32
32
  SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
33
33
  AGENT_DIR="$(cd "$SCRIPT_DIR/../.." && pwd)"
34
- AGENT_NAME=$(basename "$AGENT_DIR") # e.g. "sophie-ai", "jacob-ai"
34
+ AGENT_NAME=$(basename "$AGENT_DIR") # e.g. "ravi-ai", "jacob-ai"
35
35
 
36
36
  # Colours
37
37
  RED='\033[0;31m'
@@ -2,7 +2,7 @@
2
2
  /**
3
3
  * Slack Reaction Utility — Add emoji reactions to Slack messages.
4
4
  *
5
- * Used by Sophie's Claude Code sessions to react to messages.
5
+ * Used by the agent's Claude Code sessions to react to messages.
6
6
  * This is a thin wrapper around the Slack reactions.add API.
7
7
  *
8
8
  * Usage:
@@ -1,9 +1,9 @@
1
1
  #!/usr/bin/env /opt/homebrew/bin/node
2
2
  /**
3
- * slack-typing.mjs — Send Slack typing indicator as Sophie
3
+ * slack-typing.mjs — Send Slack typing indicator as the running agent
4
4
  *
5
- * Shows the "Sophie is typing..." indicator in a channel/DM before
6
- * a message is sent, making Sophie's presence feel human and real.
5
+ * Shows the "<Agent> is typing..." indicator in a channel/DM before
6
+ * a message is sent, making the agent's presence feel human and real.
7
7
  *
8
8
  * Approach: Connect to Slack RTM (WebSocket), send a `typing` event,
9
9
  * hold the connection for the specified duration, then disconnect.
@@ -1,12 +1,20 @@
1
1
  #!/bin/bash
2
- # Sophie System Verification — checks all components are operational
2
+ # Agent System Verification — checks all components are operational
3
3
  # Usage: ./scripts/system-verify.sh
4
4
  #
5
5
  # Run this anytime to verify the full system is working.
6
6
 
7
7
  set -e
8
- SOPHIE_AI_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
9
- cd "$SOPHIE_AI_DIR"
8
+ AGENT_REPO_DIR="${AGENT_DIR:-$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)}"
9
+ cd "$AGENT_REPO_DIR"
10
+
11
+ # Load identity (AGENT_LAUNCHD_PREFIX in particular) — the verification
12
+ # checks read launchd labels with the agent-specific prefix.
13
+ if [ -f "$AGENT_REPO_DIR/config/agent.env" ]; then
14
+ # shellcheck disable=SC1091
15
+ source "$AGENT_REPO_DIR/config/agent.env"
16
+ fi
17
+ LAUNCHD_PREFIX="${AGENT_LAUNCHD_PREFIX:-ai.adaptic.agent}"
10
18
 
11
19
  RED='\033[0;31m'
12
20
  GREEN='\033[0;32m'
@@ -38,23 +46,28 @@ warn_check() {
38
46
  }
39
47
 
40
48
  echo "╔══════════════════════════════════════════════╗"
41
- echo "║ SOPHIE SYSTEM VERIFICATION"
49
+ printf "║ %-44s║\n" "$(echo "${AGENT_FULL_NAME:-AGENT}" | tr '[:lower:]' '[:upper:]') SYSTEM VERIFICATION"
42
50
  echo "║ $(date '+%Y-%m-%d %H:%M %Z') ║"
43
51
  echo "╚══════════════════════════════════════════════╝"
44
52
  echo ""
45
53
 
46
54
  echo "=== DAEMONS ==="
47
- check "Poller daemon loaded" "launchctl list | grep -q sophie-poller"
48
- check "Inbox processor loaded" "launchctl list | grep -q sophie-inbox-processor"
49
- check "Meeting prep loaded" "launchctl list | grep -q sophie-meeting-prep"
50
- check "Meeting capture loaded" "launchctl list | grep -q sophie-meeting-action-capture"
51
- check "Midday sweep loaded" "launchctl list | grep -q sophie-midday-sweep"
52
- check "Weekly hiring loaded" "launchctl list | grep -q sophie-weekly-hiring"
53
- check "Weekly priorities loaded" "launchctl list | grep -q sophie-weekly-priorities"
54
- check "Weekly eng health loaded" "launchctl list | grep -q sophie-weekly-engineering-health"
55
- check "Weekly execution loaded" "launchctl list | grep -q sophie-weekly-execution"
56
- check "Weekly strategic memo loaded" "launchctl list | grep -q sophie-weekly-strategic-memo"
57
- check "Quarterly self-assessment loaded" "launchctl list | grep -q sophie-quarterly"
55
+ # Launchd labels start with AGENT_LAUNCHD_PREFIX (e.g. ai.adaptic.ravi).
56
+ # The legacy code searched for "sophie-poller" etc.; we now derive the
57
+ # agent-specific component from the prefix's last segment so the check
58
+ # works for any agent.
59
+ LAUNCHD_AGENT_SLUG="$(basename "$LAUNCHD_PREFIX" | tr -d ' ')" # e.g. "ravi" from "ai.adaptic.ravi"
60
+ check "Poller daemon loaded" "launchctl list | grep -q ${LAUNCHD_AGENT_SLUG}-poller"
61
+ check "Inbox processor loaded" "launchctl list | grep -q ${LAUNCHD_AGENT_SLUG}-inbox-processor"
62
+ check "Meeting prep loaded" "launchctl list | grep -q ${LAUNCHD_AGENT_SLUG}-meeting-prep"
63
+ check "Meeting capture loaded" "launchctl list | grep -q ${LAUNCHD_AGENT_SLUG}-meeting-action-capture"
64
+ check "Midday sweep loaded" "launchctl list | grep -q ${LAUNCHD_AGENT_SLUG}-midday-sweep"
65
+ check "Weekly hiring loaded" "launchctl list | grep -q ${LAUNCHD_AGENT_SLUG}-weekly-hiring"
66
+ check "Weekly priorities loaded" "launchctl list | grep -q ${LAUNCHD_AGENT_SLUG}-weekly-priorities"
67
+ check "Weekly eng health loaded" "launchctl list | grep -q ${LAUNCHD_AGENT_SLUG}-weekly-engineering-health"
68
+ check "Weekly execution loaded" "launchctl list | grep -q ${LAUNCHD_AGENT_SLUG}-weekly-execution"
69
+ check "Weekly strategic memo loaded" "launchctl list | grep -q ${LAUNCHD_AGENT_SLUG}-weekly-strategic-memo"
70
+ check "Quarterly self-assessment loaded" "launchctl list | grep -q ${LAUNCHD_AGENT_SLUG}-quarterly"
58
71
  echo ""
59
72
 
60
73
  echo "=== REMOTE TRIGGERS ==="
@@ -2,7 +2,7 @@
2
2
  """
3
3
  user-context-search.py — Per-user ring-fenced context search (Phase 1 + Phase 2)
4
4
 
5
- Searches sophie-ai files scoped to a user's access level.
5
+ Searches the agent repo's files scoped to a user's access level.
6
6
  Supports two backends:
7
7
  - grep (default): file-based grep with user-scoped directory paths (Phase 1)
8
8
  - sqlite: FTS5 full-text search over pre-indexed SQLite database (Phase 2)
@@ -15,7 +15,7 @@ Usage:
15
15
  python3 scripts/user-context-search.py --user unknown --query "adaptic" --format text
16
16
  python3 scripts/user-context-search.py --user mehran --query "flight" --backend sqlite
17
17
 
18
- Author: Sophie Nguyen, Chief of Staff
18
+ Author: Adaptic.ai
19
19
  Date: 2026-04-03
20
20
  """
21
21
 
@@ -30,7 +30,7 @@ from pathlib import Path
30
30
 
31
31
  # ── Constants ──────────────────────────────────────────────────────────────────
32
32
 
33
- BASE_DIR = Path(__file__).resolve().parent.parent # sophie-ai root
33
+ BASE_DIR = Path(os.environ.get("AGENT_DIR", str(Path(__file__).resolve().parent.parent))) # agent repo root
34
34
  CONFIG_PATH = BASE_DIR / "config" / "caller-id-map.yaml"
35
35
  DEFAULT_DB_PATH = BASE_DIR / "state" / "rag" / "search.db"
36
36
  DEFAULT_MAX_RESULTS = 10
@@ -569,7 +569,7 @@ def format_text(user_slug, user_data, access_level, paths_searched, matches, que
569
569
  def main():
570
570
  parser = argparse.ArgumentParser(
571
571
  description="Per-user ring-fenced context search (Phase 1 + Phase 2). "
572
- "Searches sophie-ai files scoped to a user's access level.",
572
+ "Searches the agent repo's files scoped to a user's access level.",
573
573
  epilog="Examples:\n"
574
574
  " %(prog)s --user mehran --query \"DFSA\"\n"
575
575
  " %(prog)s --user hootan --query \"portal\" --max-results 5\n"
@@ -4,8 +4,8 @@
4
4
  Checks outbound messages for:
5
5
  1. Relationship claims — "our project manager" must match confirmed status
6
6
  2. Title/role accuracy — titles must match org chart / profiles
7
- 3. AI self-disclosure — Sophie must never reveal she is AI
8
- 4. In-person meeting self-scheduling — Sophie must not attend physical meetings
7
+ 3. AI self-disclosure — the agent must never reveal it is AI
8
+ 4. In-person meeting self-scheduling — the agent must not attend physical meetings
9
9
  5. Unverified commitment claims — flag strong commitment language
10
10
 
11
11
  Usage:
@@ -47,7 +47,16 @@ except Exception:
47
47
 
48
48
  # Resolve paths relative to the repo root
49
49
  SCRIPT_DIR = Path(__file__).resolve().parent
50
- REPO_ROOT = SCRIPT_DIR.parent
50
+ REPO_ROOT = Path(os.environ.get("AGENT_DIR", SCRIPT_DIR.parent))
51
+
52
+ # Load agent identity (canonical SOT) so validation patterns can be
53
+ # parameterised by the running agent's first name.
54
+ try:
55
+ with open(REPO_ROOT / "config" / "agent.json") as _f:
56
+ _AGENT = json.load(_f)
57
+ except Exception:
58
+ _AGENT = {"firstName": "Agent", "fullName": "Agent"}
59
+ AGENT_FIRST_NAME = _AGENT.get("firstName", "Agent")
51
60
 
52
61
  # Data source paths
53
62
  CONTACTS_PATH = REPO_ROOT / "config" / "contacts.yaml"
@@ -914,7 +923,7 @@ def check_title_accuracy(message):
914
923
 
915
924
 
916
925
  def check_ai_self_disclosure(message):
917
- """Scan for language that reveals Sophie is AI.
926
+ """Scan for language that reveals the agent is AI.
918
927
 
919
928
  Must NOT flag general business discussion of Adaptic's AI products.
920
929
  Returns list of issue dicts.
@@ -941,8 +950,8 @@ def check_ai_self_disclosure(message):
941
950
  "Self-referential 'as an AI'"),
942
951
  (r'\b(?:my|I)\s+(?:training|training data|knowledge cutoff|neural network|parameters)\b',
943
952
  "AI-specific self-reference"),
944
- (r'\bSophie\s+is\s+(?:an?\s+)?(?:AI|bot|automated|virtual|artificial)\b',
945
- "Third-person AI disclosure about Sophie"),
953
+ (rf'\b{re.escape(AGENT_FIRST_NAME)}\s+is\s+(?:an?\s+)?(?:AI|bot|automated|virtual|artificial)\b',
954
+ f"Third-person AI disclosure about {AGENT_FIRST_NAME}"),
946
955
  (r'\b(?:automated|AI[- ]powered|AI[- ]operated|machine[- ]generated)\s+(?:assistant|chief of staff|executive|system)\b',
947
956
  "Role described as automated"),
948
957
  (r'\b(?:I don\'t have|I lack)\s+(?:feelings|emotions|a body|physical form|consciousness)\b',
@@ -971,7 +980,7 @@ def check_ai_self_disclosure(message):
971
980
  "severity": "block",
972
981
  "matched_text": matched_text,
973
982
  "description": f"AI self-disclosure detected: {description}",
974
- "suggestion": "Remove or rephrase to avoid revealing Sophie's AI nature.",
983
+ "suggestion": f"Remove or rephrase to avoid revealing {AGENT_FIRST_NAME}'s AI nature.",
975
984
  "context": context_window.strip(),
976
985
  })
977
986
 
@@ -979,27 +988,29 @@ def check_ai_self_disclosure(message):
979
988
 
980
989
 
981
990
  def check_in_person_self_scheduling(message):
982
- """Detect Sophie scheduling herself for in-person meetings.
991
+ """Detect the agent scheduling itself for in-person meetings.
983
992
 
984
- Should NOT flag scheduling for Mehran or team members.
993
+ Should NOT flag scheduling for the principal or team members.
985
994
  Returns list of issue dicts.
986
995
  """
987
996
  issues = []
988
997
 
989
- # Patterns where Sophie offers to physically attend
998
+ _principal_name = (_AGENT.get("principal") or {}).get("firstName", "the principal")
999
+
1000
+ # Patterns where the agent offers to physically attend
990
1001
  self_attend_patterns = [
991
1002
  (r'\bI\'ll\s+(?:attend|be there|come to|visit|meet you at|stop by|drop by|come by|swing by|show up)\b',
992
- "Sophie offering to physically attend"),
1003
+ f"{AGENT_FIRST_NAME} offering to physically attend"),
993
1004
  (r'\b(?:meet me at|see you at|I\'ll see you|I can meet|let me come|I\'ll come)\b',
994
- "Sophie scheduling herself for physical meeting"),
1005
+ f"{AGENT_FIRST_NAME} scheduling itself for physical meeting"),
995
1006
  (r'\b(?:I\'ll be in|I\'m in|I will be at)\s+(?:the office|DIFC|Innovation|your office|the meeting room|the boardroom)\b',
996
- "Sophie placing herself in physical location"),
997
- (r'\bSophie\s+(?:will attend|will be at|will meet|will visit)\b',
998
- "Third-person Sophie physical attendance"),
1007
+ f"{AGENT_FIRST_NAME} placing itself in physical location"),
1008
+ (rf'\b{re.escape(AGENT_FIRST_NAME)}\s+(?:will attend|will be at|will meet|will visit)\b',
1009
+ f"Third-person {AGENT_FIRST_NAME} physical attendance"),
999
1010
  (r'\bI\'ll\s+(?:fly|travel|drive|take a cab|take a taxi)\b',
1000
- "Sophie describing physical travel"),
1011
+ f"{AGENT_FIRST_NAME} describing physical travel"),
1001
1012
  (r'\b(?:looking forward to meeting you|see you (?:there|soon|Monday|Tuesday|Wednesday|Thursday|Friday|tomorrow|next week))\b',
1002
- "Sophie implying physical presence at meeting"),
1013
+ f"{AGENT_FIRST_NAME} implying physical presence at meeting"),
1003
1014
  ]
1004
1015
 
1005
1016
  # Exception patterns — scheduling others is fine
@@ -1029,7 +1040,7 @@ def check_in_person_self_scheduling(message):
1029
1040
  "severity": "block",
1030
1041
  "matched_text": matched_text,
1031
1042
  "description": f"In-person self-scheduling detected: {description}",
1032
- "suggestion": "Sophie cannot attend in-person meetings. Propose Mehran or a team member instead.",
1043
+ "suggestion": f"{AGENT_FIRST_NAME} cannot attend in-person meetings. Propose {_principal_name} or a team member instead.",
1033
1044
  "context": context_window.strip(),
1034
1045
  })
1035
1046