@adaptic/maestro 1.1.6 → 1.1.8
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.
- package/.claude/commands/init-maestro.md +225 -279
- package/README.md +19 -2
- package/docs/guides/email-setup.md +399 -0
- package/docs/guides/media-generation-setup.md +349 -0
- package/docs/guides/outbound-governance-setup.md +438 -0
- package/docs/guides/pdf-generation-setup.md +315 -0
- package/docs/guides/poller-daemon-setup.md +550 -0
- package/docs/guides/rag-context-setup.md +459 -0
- package/docs/guides/slack-setup.md +348 -0
- package/docs/guides/voice-sms-setup.md +698 -0
- package/docs/guides/whatsapp-setup.md +282 -0
- package/docs/runbooks/mac-mini-bootstrap.md +21 -0
- package/package.json +1 -1
- package/scaffold/config/caller-id-map.yaml +46 -0
- package/scripts/media-generation/README.md +2 -0
- package/scripts/pdf-generation/README.md +2 -0
- package/scripts/poller/slack-poller.mjs +22 -7
- package/scripts/poller/trigger.mjs +12 -1
- package/scripts/setup/boot-claude-session.sh +4 -8
- package/scripts/setup/configure-macos.sh +8 -4
|
@@ -0,0 +1,550 @@
|
|
|
1
|
+
# Poller, Daemon & Trigger Setup Guide
|
|
2
|
+
|
|
3
|
+
How the agent's autonomous event loop works: the lightweight poller, the reactive daemon, session management, launchd trigger scheduling, and the memory watchdog. This is the nervous system that makes the agent perpetually operational.
|
|
4
|
+
|
|
5
|
+
**Prerequisites**: Complete the [Mac Mini Bootstrap](../runbooks/mac-mini-bootstrap.md) and configure at least one communication channel (Slack, Gmail, etc.).
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Architecture Overview
|
|
10
|
+
|
|
11
|
+
The agent has three concurrent execution modes, powered by different subsystems:
|
|
12
|
+
|
|
13
|
+
```
|
|
14
|
+
┌─────────────────────────────────────────────────────────────────────┐
|
|
15
|
+
│ MODE 1: REACTIVE — Respond to Events │
|
|
16
|
+
│ │
|
|
17
|
+
│ ┌──────────┐ ┌─────────────┐ ┌────────────────────────────────┐ │
|
|
18
|
+
│ │ launchd │─▶│ Poller │─▶│ state/inbox/{slack,gmail, │ │
|
|
19
|
+
│ │ (60s) │ │ index.mjs │ │ calendar,sms,whatsapp}/*.yaml│ │
|
|
20
|
+
│ └──────────┘ └──────┬──────┘ └─────────────┬──────────────────┘ │
|
|
21
|
+
│ │ │ │
|
|
22
|
+
│ priority item? ┌────▼────────────┐ │
|
|
23
|
+
│ │ │ Inbox Processor │ │
|
|
24
|
+
│ ▼ │ (scheduled │ │
|
|
25
|
+
│ triggerSophie() │ trigger, 5 min) │ │
|
|
26
|
+
│ (immediate session) └─────────────────┘ │
|
|
27
|
+
├─────────────────────────────────────────────────────────────────────┤
|
|
28
|
+
│ MODE 1b: REACTIVE DAEMON (Alternative to Poller) │
|
|
29
|
+
│ │
|
|
30
|
+
│ ┌────────────────────────────────────────────────────────────┐ │
|
|
31
|
+
│ │ sophie-daemon.mjs (persistent Node.js process) │ │
|
|
32
|
+
│ │ │ │
|
|
33
|
+
│ │ Poll loop (60s): │ │
|
|
34
|
+
│ │ Slack + Gmail + Calendar → classify (Haiku) → dispatch │ │
|
|
35
|
+
│ │ │ │
|
|
36
|
+
│ │ Backlog sweep (2min): │ │
|
|
37
|
+
│ │ Scan queues → pick actionable items → dispatch │ │
|
|
38
|
+
│ │ │ │
|
|
39
|
+
│ │ Health check (1min): │ │
|
|
40
|
+
│ │ Write health dashboard, log metrics │ │
|
|
41
|
+
│ │ │ │
|
|
42
|
+
│ │ Dispatcher: │ │
|
|
43
|
+
│ │ Up to 10 parallel claude --print sessions │ │
|
|
44
|
+
│ └────────────────────────────────────────────────────────────┘ │
|
|
45
|
+
├─────────────────────────────────────────────────────────────────────┤
|
|
46
|
+
│ MODE 2: SCHEDULED — Run Cadence Workflows │
|
|
47
|
+
│ │
|
|
48
|
+
│ ┌──────────┐ ┌──────────────┐ ┌──────────────────────────┐ │
|
|
49
|
+
│ │ launchd │─▶│ run-trigger │─▶│ claude --print │ │
|
|
50
|
+
│ │ (schedule)│ │ .sh │ │ (non-interactive session)│ │
|
|
51
|
+
│ └──────────┘ └──────────────┘ └──────────────────────────┘ │
|
|
52
|
+
│ │
|
|
53
|
+
│ Triggers: morning-brief, midday-sweep, evening-wrap, │
|
|
54
|
+
│ backlog-executor, inbox-processor, meeting-prep, │
|
|
55
|
+
│ meeting-action-capture, weekly-*, quarterly-* │
|
|
56
|
+
├─────────────────────────────────────────────────────────────────────┤
|
|
57
|
+
│ MODE 3: PROACTIVE — Execute the Backlog │
|
|
58
|
+
│ │
|
|
59
|
+
│ backlog-executor trigger (every 10 min): │
|
|
60
|
+
│ Read all queues → select top 3-5 items → spawn parallel agents │
|
|
61
|
+
│ → review results → update queues → pick next batch │
|
|
62
|
+
├─────────────────────────────────────────────────────────────────────┤
|
|
63
|
+
│ SAFETY │
|
|
64
|
+
│ │
|
|
65
|
+
│ memory-watchdog.sh (every 30s): │
|
|
66
|
+
│ Monitor RAM + process count → kill runaways → emergency stop │
|
|
67
|
+
│ │
|
|
68
|
+
│ emergency-stop.sh: │
|
|
69
|
+
│ Creates .emergency-stop file → all triggers abort on check │
|
|
70
|
+
│ │
|
|
71
|
+
│ resume-operations.sh: │
|
|
72
|
+
│ Removes .emergency-stop → triggers resume │
|
|
73
|
+
└─────────────────────────────────────────────────────────────────────┘
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
---
|
|
77
|
+
|
|
78
|
+
## 1. The Poller (`scripts/poller/`)
|
|
79
|
+
|
|
80
|
+
The poller is a lightweight Node.js script that runs every 60 seconds via launchd. It checks all inbound channels and writes new items to the inbox.
|
|
81
|
+
|
|
82
|
+
### 1.1 Entry Point: `index.mjs`
|
|
83
|
+
|
|
84
|
+
Runs four service pollers in sequence:
|
|
85
|
+
|
|
86
|
+
| Service | Module | What it Polls | Credentials |
|
|
87
|
+
|---|---|---|---|
|
|
88
|
+
| Slack | `slack-poller.mjs` | DMs, channels, mentions | `SLACK_USER_TOKEN` |
|
|
89
|
+
| Gmail | `gmail-poller.mjs` | Agent's inbox (UNSEEN) | `GMAIL_APP_PASSWORD` |
|
|
90
|
+
| CEO Gmail | `mehran-gmail-poller.mjs` | Principal's inbox | `SECONDARY_GMAIL_APP_PASSWORD` |
|
|
91
|
+
| Calendar | `calendar-poller.mjs` | Upcoming events, changes | Google Calendar API |
|
|
92
|
+
|
|
93
|
+
### 1.2 IMAP Client (`imap-client.mjs`)
|
|
94
|
+
|
|
95
|
+
Shared IMAP connection wrapper used by both Gmail pollers:
|
|
96
|
+
- Manages connection pooling and reconnection
|
|
97
|
+
- Handles IMAP search queries (UNSEEN, date ranges)
|
|
98
|
+
- Parses email headers for threading information
|
|
99
|
+
|
|
100
|
+
### 1.3 Priority Detection (`utils.mjs`)
|
|
101
|
+
|
|
102
|
+
The `isPriorityItem()` function detects items that need immediate processing:
|
|
103
|
+
- CEO DMs (principal's Slack ID)
|
|
104
|
+
- Messages mentioning the agent by name
|
|
105
|
+
- Urgent keywords (urgent, ASAP, emergency)
|
|
106
|
+
- Messages tagged with priority emoji
|
|
107
|
+
|
|
108
|
+
When a priority item is detected, `triggerSophie()` spawns an immediate Claude session rather than waiting for the inbox processor cycle.
|
|
109
|
+
|
|
110
|
+
### 1.4 Trigger Module (`trigger.mjs`)
|
|
111
|
+
|
|
112
|
+
Spawns a Claude Code session for priority items:
|
|
113
|
+
- Runs `claude --print --dangerously-skip-permissions` with a targeted prompt
|
|
114
|
+
- The prompt includes the priority item's content and relevant context
|
|
115
|
+
- Session output is logged to `logs/polling/`
|
|
116
|
+
|
|
117
|
+
### 1.5 Intra-Session Check (`intra-session-check.mjs`)
|
|
118
|
+
|
|
119
|
+
Allows a running Claude session to check for new priority events mid-execution. Called by long-running sessions (like backlog executor) to detect urgent interrupts.
|
|
120
|
+
|
|
121
|
+
### 1.6 Poller Logs
|
|
122
|
+
|
|
123
|
+
- `logs/polling/YYYY-MM-DD-poller.jsonl` — Per-run summary (items found, errors, duration)
|
|
124
|
+
- Each run logs: timestamp, items per service, errors, whether a priority trigger fired
|
|
125
|
+
|
|
126
|
+
---
|
|
127
|
+
|
|
128
|
+
## 2. The Reactive Daemon (`scripts/daemon/`)
|
|
129
|
+
|
|
130
|
+
The daemon is an alternative to the poller that provides faster response times. Instead of running every 60 seconds as a separate process, it's a persistent Node.js process with built-in polling, classification, and dispatching.
|
|
131
|
+
|
|
132
|
+
### 2.1 Entry Point: `maestro-daemon.mjs`
|
|
133
|
+
|
|
134
|
+
- Agent-name-agnostic wrapper that sets `AGENT_DIR` environment variable
|
|
135
|
+
- Acquires a singleton lock (via `~/maestro/lib/singleton.js`) to prevent duplicate daemons
|
|
136
|
+
- Imports and runs `sophie-daemon.mjs`
|
|
137
|
+
|
|
138
|
+
### 2.2 Core Daemon: `sophie-daemon.mjs`
|
|
139
|
+
|
|
140
|
+
Three concurrent loops:
|
|
141
|
+
|
|
142
|
+
| Loop | Interval | Purpose |
|
|
143
|
+
|---|---|---|
|
|
144
|
+
| Poll | 60s | Check Slack, Gmail, Calendar for new items |
|
|
145
|
+
| Backlog | 2 min | Sweep queues for actionable items |
|
|
146
|
+
| Health | 1 min | Write health dashboard, log metrics |
|
|
147
|
+
|
|
148
|
+
**Poll loop flow:**
|
|
149
|
+
1. Run all four service pollers (same as standalone poller)
|
|
150
|
+
2. For each new item, classify it via `classifier.mjs`
|
|
151
|
+
3. Check if it's directed at the agent via `isDirectedAtSophie()`
|
|
152
|
+
4. For items needing response: build prompt → dispatch session
|
|
153
|
+
5. For quick replies: use `responder.mjs` for immediate response
|
|
154
|
+
|
|
155
|
+
### 2.3 Classifier (`classifier.mjs`)
|
|
156
|
+
|
|
157
|
+
Classifies incoming items using Claude Haiku (fast, cheap):
|
|
158
|
+
|
|
159
|
+
- **Priority**: critical / high / medium / low
|
|
160
|
+
- **Type**: question, request, notification, FYI, greeting, spam
|
|
161
|
+
- **Directed at agent**: yes / no / unclear
|
|
162
|
+
- **Quick reply**: yes (can respond immediately) / no (needs full session)
|
|
163
|
+
|
|
164
|
+
Classification takes ~0.5-1 second via Haiku API.
|
|
165
|
+
|
|
166
|
+
### 2.4 Dispatcher (`dispatcher.mjs`)
|
|
167
|
+
|
|
168
|
+
Manages parallel Claude Code sessions:
|
|
169
|
+
|
|
170
|
+
- Up to 10 concurrent sessions (`claude --print`)
|
|
171
|
+
- Tracks active sessions with PIDs and start times
|
|
172
|
+
- `availableSlots()` returns how many sessions can be spawned
|
|
173
|
+
- `canDispatchBacklog()` checks if there's capacity for proactive work
|
|
174
|
+
- `resetActiveSessions()` cleans up orphaned session records
|
|
175
|
+
- Each session gets a targeted prompt from `prompt-builder.mjs`
|
|
176
|
+
|
|
177
|
+
### 2.5 Prompt Builder (`prompt-builder.mjs`)
|
|
178
|
+
|
|
179
|
+
Constructs context-rich prompts for dispatched sessions:
|
|
180
|
+
|
|
181
|
+
- Includes the classified item (message content, sender, channel)
|
|
182
|
+
- Loads relevant user profile from `memory/profiles/`
|
|
183
|
+
- Includes standing instructions from the user profile
|
|
184
|
+
- Includes relevant queue context if the item references tracked work
|
|
185
|
+
- Adds the session protocol (what to do at session start/end)
|
|
186
|
+
|
|
187
|
+
### 2.6 Responder (`responder.mjs`)
|
|
188
|
+
|
|
189
|
+
Handles quick replies without spawning a full Claude session:
|
|
190
|
+
|
|
191
|
+
- `isQuickReply()` detects items that can be answered immediately (greetings, acknowledgments)
|
|
192
|
+
- `sendQuickResponse()` sends a fast reply via the appropriate channel
|
|
193
|
+
- `sendHoldingMessage()` sends "Got it, working on this" for complex requests
|
|
194
|
+
|
|
195
|
+
### 2.7 Session Lock (`session-lock.mjs`)
|
|
196
|
+
|
|
197
|
+
File-based locking to prevent duplicate processing:
|
|
198
|
+
|
|
199
|
+
- `acquireLock(itemId)` — claim exclusive processing of an item
|
|
200
|
+
- `acquireThreadLock(channel, thread)` — claim a thread for response
|
|
201
|
+
- `claimRequest(requestId)` — claim a specific request
|
|
202
|
+
- `updateLock(lockId, status)` — update lock with processing status
|
|
203
|
+
- `scanStaleLocks()` — find and clean locks older than TTL
|
|
204
|
+
- Lock directory: `state/locks/daemon/`
|
|
205
|
+
|
|
206
|
+
### 2.8 Health Monitor (`health.mjs`)
|
|
207
|
+
|
|
208
|
+
Tracks daemon performance:
|
|
209
|
+
|
|
210
|
+
- `recordPoll(results)` — log poll cycle results
|
|
211
|
+
- `recordClassification(item, classification)` — log classification outcomes
|
|
212
|
+
- `recordSession(sessionInfo)` — log dispatched session metrics
|
|
213
|
+
- `writeHealthDashboard()` — write `state/dashboards/daemon-health.yaml`
|
|
214
|
+
|
|
215
|
+
### 2.9 Context Compiler (`context-compiler.mjs`)
|
|
216
|
+
|
|
217
|
+
Pre-compiles session context to reduce prompt size:
|
|
218
|
+
|
|
219
|
+
- Reads recent interactions, queue state, and active items
|
|
220
|
+
- Compresses context to fit within token limits
|
|
221
|
+
- Enabled via `DAEMON_CONTEXT_COMPILER=1` in `.env`
|
|
222
|
+
|
|
223
|
+
---
|
|
224
|
+
|
|
225
|
+
## 3. Session Management
|
|
226
|
+
|
|
227
|
+
### 3.1 Spawning Sessions (`spawn-session.sh`)
|
|
228
|
+
|
|
229
|
+
Creates a new Claude Code session:
|
|
230
|
+
|
|
231
|
+
```bash
|
|
232
|
+
./scripts/spawn-session.sh "Process this incoming request" --session-id "req-12345"
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
- Sets `SOPHIE_SESSION_ID` for dedup tracking
|
|
236
|
+
- Checks `.emergency-stop` before proceeding
|
|
237
|
+
- Logs session start to `logs/sessions/`
|
|
238
|
+
|
|
239
|
+
### 3.2 Session Start (`session-start.sh`)
|
|
240
|
+
|
|
241
|
+
Initialises session state:
|
|
242
|
+
|
|
243
|
+
```bash
|
|
244
|
+
./scripts/session-start.sh
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
Called at the beginning of each Claude session to:
|
|
248
|
+
- Load the executive summary dashboard
|
|
249
|
+
- Check for unprocessed inbox items
|
|
250
|
+
- Load pending decisions from the decision queue
|
|
251
|
+
- Set up the session environment
|
|
252
|
+
|
|
253
|
+
### 3.3 Emergency Stop (`emergency-stop.sh`)
|
|
254
|
+
|
|
255
|
+
```bash
|
|
256
|
+
./scripts/emergency-stop.sh
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
Creates a `.emergency-stop` file in the repo root. All triggers check for this file before executing and abort if present. Use this to immediately halt all autonomous operations.
|
|
260
|
+
|
|
261
|
+
### 3.4 Resume Operations (`resume-operations.sh`)
|
|
262
|
+
|
|
263
|
+
```bash
|
|
264
|
+
./scripts/resume-operations.sh
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
Removes the `.emergency-stop` file, allowing triggers to resume.
|
|
268
|
+
|
|
269
|
+
---
|
|
270
|
+
|
|
271
|
+
## 4. LaunchD Triggers (`scripts/local-triggers/`)
|
|
272
|
+
|
|
273
|
+
### 4.1 How Triggers Work
|
|
274
|
+
|
|
275
|
+
Triggers are Markdown prompts in `schedules/triggers/` that launchd runs on a schedule. Each trigger invokes a Claude Code session with the prompt as input.
|
|
276
|
+
|
|
277
|
+
**Execution chain**: launchd → `run-trigger.sh <trigger-name>` → reads `schedules/triggers/<trigger-name>.md` → runs `claude --print --dangerously-skip-permissions <prompt>`
|
|
278
|
+
|
|
279
|
+
### 4.2 Run a Trigger Manually
|
|
280
|
+
|
|
281
|
+
```bash
|
|
282
|
+
./scripts/local-triggers/run-trigger.sh morning-brief
|
|
283
|
+
```
|
|
284
|
+
|
|
285
|
+
### 4.3 Generate Plist Files (`generate-plists.sh`)
|
|
286
|
+
|
|
287
|
+
Generates all launchd plist files from the agent's configuration:
|
|
288
|
+
|
|
289
|
+
```bash
|
|
290
|
+
./scripts/local-triggers/generate-plists.sh
|
|
291
|
+
```
|
|
292
|
+
|
|
293
|
+
This script:
|
|
294
|
+
1. Reads `config/agent.ts` to extract the agent's name
|
|
295
|
+
2. Generates plists for: daemon, poller, all scheduled triggers, watchdog
|
|
296
|
+
3. Uses the agent name in plist labels (e.g., `ai.adaptic.sophie.morning-brief`)
|
|
297
|
+
4. Sets correct working directory, Node.js path, and log paths
|
|
298
|
+
5. Saves plists to `scripts/local-triggers/plists/`
|
|
299
|
+
|
|
300
|
+
### 4.4 Install All Triggers (`install-all.sh`)
|
|
301
|
+
|
|
302
|
+
```bash
|
|
303
|
+
./scripts/local-triggers/install-all.sh
|
|
304
|
+
```
|
|
305
|
+
|
|
306
|
+
Copies all generated plists to `~/Library/LaunchAgents/` and loads them.
|
|
307
|
+
|
|
308
|
+
### 4.5 Standard Trigger Schedule
|
|
309
|
+
|
|
310
|
+
| Trigger | Cadence | Time | Purpose |
|
|
311
|
+
|---|---|---|---|
|
|
312
|
+
| `morning-brief` | Daily | 06:00 | CEO morning brief |
|
|
313
|
+
| `midday-sweep` | Daily | 12:00 | SLA check, loop closure |
|
|
314
|
+
| `evening-wrap` | Daily | 18:00 | End-of-day summary, queue cleanup |
|
|
315
|
+
| `backlog-executor` | Every 10 min | Continuous | Execute top queue items |
|
|
316
|
+
| `inbox-processor` | Every 5 min | Continuous | Classify and route inbox items |
|
|
317
|
+
| `meeting-prep` | Every 15 min | Continuous | Pre-meeting brief generation |
|
|
318
|
+
| `meeting-action-capture` | Every 30 min | Continuous | Extract actions from meetings |
|
|
319
|
+
| `weekly-priorities` | Weekly (Mon) | 09:00 | Priority review |
|
|
320
|
+
| `weekly-execution` | Weekly (Wed) | 09:00 | Execution review |
|
|
321
|
+
| `weekly-strategic-memo` | Weekly (Fri) | 15:00 | Strategic memo to principal |
|
|
322
|
+
| `weekly-hiring` | Weekly (Mon) | 11:00 | Hiring pipeline review |
|
|
323
|
+
| `weekly-engineering-health` | Weekly (Wed) | 08:00 | Engineering health check |
|
|
324
|
+
| `quarterly-self-assessment` | Quarterly | First Mon | Agent self-assessment |
|
|
325
|
+
|
|
326
|
+
Times are in the agent's configured timezone from `config/agent.ts`.
|
|
327
|
+
|
|
328
|
+
---
|
|
329
|
+
|
|
330
|
+
## 5. Memory Watchdog (`scripts/watchdog/`)
|
|
331
|
+
|
|
332
|
+
### 5.1 What It Does
|
|
333
|
+
|
|
334
|
+
The watchdog monitors system resources and prevents runaway Claude processes from consuming all RAM:
|
|
335
|
+
|
|
336
|
+
| Threshold | Level | Action |
|
|
337
|
+
|---|---|---|
|
|
338
|
+
| >60% RAM (Claude processes) | Warning | Log warning |
|
|
339
|
+
| >75% RAM (Claude processes) | Critical | Kill oldest subagent processes |
|
|
340
|
+
| >85% total system memory pressure | Emergency | Emergency stop all operations |
|
|
341
|
+
| >8 concurrent claude processes | Process limit | Kill excess processes |
|
|
342
|
+
|
|
343
|
+
### 5.2 Configuration
|
|
344
|
+
|
|
345
|
+
Environment variables (with defaults):
|
|
346
|
+
|
|
347
|
+
```bash
|
|
348
|
+
WATCHDOG_WARN_PERCENT=60 # Warning threshold
|
|
349
|
+
WATCHDOG_CRITICAL_PERCENT=75 # Critical threshold (kill runaways)
|
|
350
|
+
WATCHDOG_EMERGENCY_PERCENT=85 # Emergency threshold (full stop)
|
|
351
|
+
WATCHDOG_MAX_CLAUDE_PROCS=8 # Max concurrent claude processes
|
|
352
|
+
WATCHDOG_MIN_AGE=60 # Minimum age (seconds) before killing a process
|
|
353
|
+
```
|
|
354
|
+
|
|
355
|
+
### 5.3 Running the Watchdog
|
|
356
|
+
|
|
357
|
+
```bash
|
|
358
|
+
# Normal run (takes action if thresholds exceeded)
|
|
359
|
+
./scripts/watchdog/memory-watchdog.sh
|
|
360
|
+
|
|
361
|
+
# Check status only (no actions)
|
|
362
|
+
./scripts/watchdog/memory-watchdog.sh --check
|
|
363
|
+
|
|
364
|
+
# Dry run (show what would be killed)
|
|
365
|
+
./scripts/watchdog/memory-watchdog.sh --dry-run
|
|
366
|
+
```
|
|
367
|
+
|
|
368
|
+
### 5.4 Force Reboot
|
|
369
|
+
|
|
370
|
+
For severe cases where the watchdog can't recover:
|
|
371
|
+
|
|
372
|
+
```bash
|
|
373
|
+
./scripts/watchdog/force-reboot.sh
|
|
374
|
+
```
|
|
375
|
+
|
|
376
|
+
Kills all claude processes and restarts the daemon.
|
|
377
|
+
|
|
378
|
+
### 5.5 Watchdog Plist
|
|
379
|
+
|
|
380
|
+
The watchdog runs every 30 seconds via `ai.maestro.memory-watchdog.plist`. Generated automatically by `generate-plists.sh`.
|
|
381
|
+
|
|
382
|
+
---
|
|
383
|
+
|
|
384
|
+
## 6. Health Monitoring
|
|
385
|
+
|
|
386
|
+
### 6.1 Healthcheck (`healthcheck.sh`)
|
|
387
|
+
|
|
388
|
+
Quick system health verification:
|
|
389
|
+
|
|
390
|
+
```bash
|
|
391
|
+
./scripts/healthcheck.sh
|
|
392
|
+
```
|
|
393
|
+
|
|
394
|
+
Checks:
|
|
395
|
+
- launchd agents are loaded and running
|
|
396
|
+
- Disk space available
|
|
397
|
+
- Memory usage
|
|
398
|
+
- Log directory sizes
|
|
399
|
+
- Last successful trigger runs
|
|
400
|
+
- Daemon process status
|
|
401
|
+
|
|
402
|
+
### 6.2 System Verify (`system-verify.sh`)
|
|
403
|
+
|
|
404
|
+
Deep configuration verification:
|
|
405
|
+
|
|
406
|
+
```bash
|
|
407
|
+
./scripts/system-verify.sh
|
|
408
|
+
```
|
|
409
|
+
|
|
410
|
+
Validates all config files, environment variables, directory structure, and permissions.
|
|
411
|
+
|
|
412
|
+
### 6.3 Continuous Monitor (`continuous-monitor.sh`)
|
|
413
|
+
|
|
414
|
+
Long-running monitoring process:
|
|
415
|
+
|
|
416
|
+
```bash
|
|
417
|
+
./scripts/continuous-monitor.sh
|
|
418
|
+
```
|
|
419
|
+
|
|
420
|
+
Watches system metrics over time and logs trends.
|
|
421
|
+
|
|
422
|
+
### 6.4 Communications Monitor (`comms-monitor.sh`)
|
|
423
|
+
|
|
424
|
+
Monitors communication channel health:
|
|
425
|
+
|
|
426
|
+
```bash
|
|
427
|
+
./scripts/comms-monitor.sh
|
|
428
|
+
```
|
|
429
|
+
|
|
430
|
+
Verifies Slack tokens, Gmail connectivity, Twilio status, and tunnel health.
|
|
431
|
+
|
|
432
|
+
---
|
|
433
|
+
|
|
434
|
+
## 7. Choosing: Poller vs Daemon
|
|
435
|
+
|
|
436
|
+
| Aspect | Standalone Poller | Reactive Daemon |
|
|
437
|
+
|---|---|---|
|
|
438
|
+
| Response latency | 60s + inbox processor cycle (~5 min total) | ~2 min (poll + classify + dispatch) |
|
|
439
|
+
| Resource usage | Low (runs briefly every 60s) | Moderate (persistent Node.js process) |
|
|
440
|
+
| Complexity | Simple (one-shot script) | Complex (8 interconnected modules) |
|
|
441
|
+
| Parallel sessions | None (relies on triggers) | Up to 10 concurrent sessions |
|
|
442
|
+
| Built-in classification | No (inbox processor does it) | Yes (Haiku classifier) |
|
|
443
|
+
| Backlog execution | Separate trigger (every 10 min) | Built-in sweep (every 2 min) |
|
|
444
|
+
| Recommended for | Low-volume agents, simple roles | High-volume agents, executive roles |
|
|
445
|
+
|
|
446
|
+
**Default**: Use the daemon for production agents that need fast response times. Use the standalone poller for development/testing or low-volume agents.
|
|
447
|
+
|
|
448
|
+
---
|
|
449
|
+
|
|
450
|
+
## 8. Testing
|
|
451
|
+
|
|
452
|
+
| # | Test | How to Verify |
|
|
453
|
+
|---|---|---|
|
|
454
|
+
| 1 | Poller runs | `node scripts/poller/index.mjs` — should poll all services |
|
|
455
|
+
| 2 | Daemon starts | `node scripts/daemon/maestro-daemon.mjs` — should show poll loop starting |
|
|
456
|
+
| 3 | Singleton guard | Start daemon twice — second should exit with "Already running" |
|
|
457
|
+
| 4 | Priority detection | Send CEO DM during poller run — should trigger immediate session |
|
|
458
|
+
| 5 | Trigger execution | `./scripts/local-triggers/run-trigger.sh morning-brief` |
|
|
459
|
+
| 6 | Emergency stop | `./scripts/emergency-stop.sh` → try running a trigger → should abort |
|
|
460
|
+
| 7 | Resume | `./scripts/resume-operations.sh` → trigger should work again |
|
|
461
|
+
| 8 | Watchdog check | `./scripts/watchdog/memory-watchdog.sh --check` |
|
|
462
|
+
| 9 | Plist generation | `./scripts/local-triggers/generate-plists.sh` → check `plists/` directory |
|
|
463
|
+
| 10 | Healthcheck | `./scripts/healthcheck.sh` |
|
|
464
|
+
|
|
465
|
+
---
|
|
466
|
+
|
|
467
|
+
## 9. Troubleshooting
|
|
468
|
+
|
|
469
|
+
### Daemon won't start: "Already running"
|
|
470
|
+
|
|
471
|
+
1. Check for stale singleton lock: `ls /tmp/maestro-daemon.lock`
|
|
472
|
+
2. If the previous daemon crashed without releasing the lock, remove it: `rm /tmp/maestro-daemon.lock`
|
|
473
|
+
3. Check for orphaned node processes: `pgrep -f maestro-daemon`
|
|
474
|
+
|
|
475
|
+
### Triggers not firing
|
|
476
|
+
|
|
477
|
+
1. Verify plists are loaded: `launchctl list | grep adaptic`
|
|
478
|
+
2. Check plist logs: `cat ~/Library/LaunchAgents/com.adaptic.AGENT.*.plist`
|
|
479
|
+
3. Verify working directory in plist points to the right repo
|
|
480
|
+
4. Check `.emergency-stop` doesn't exist
|
|
481
|
+
5. Check trigger prompt file exists: `ls schedules/triggers/morning-brief.md`
|
|
482
|
+
|
|
483
|
+
### High memory usage
|
|
484
|
+
|
|
485
|
+
1. Run watchdog: `./scripts/watchdog/memory-watchdog.sh --check`
|
|
486
|
+
2. Count claude processes: `pgrep -f claude | wc -l` (should be ≤8)
|
|
487
|
+
3. Kill orphaned processes: `./scripts/watchdog/memory-watchdog.sh` (auto-kills above threshold)
|
|
488
|
+
4. Reduce `MAX_PARALLEL_SESSIONS` in daemon config if persistent issue
|
|
489
|
+
|
|
490
|
+
### Poller errors for a specific service
|
|
491
|
+
|
|
492
|
+
1. Check service credentials in `.env` (e.g., `SLACK_USER_TOKEN` for Slack)
|
|
493
|
+
2. Check poller logs: `tail logs/polling/$(date +%Y-%m-%d)-poller.jsonl`
|
|
494
|
+
3. Test the specific poller: import and run `pollSlack()` directly
|
|
495
|
+
4. Check for rate limiting (especially Slack — 60s interval helps)
|
|
496
|
+
|
|
497
|
+
### Sessions dispatched but no response sent
|
|
498
|
+
|
|
499
|
+
1. Check daemon logs: `tail logs/daemon/$(date +%Y-%m-%d)-sessions.jsonl`
|
|
500
|
+
2. Check session locks: `ls state/locks/daemon/`
|
|
501
|
+
3. Verify Claude CLI is accessible: `claude --version`
|
|
502
|
+
4. Check session output in logs for errors
|
|
503
|
+
|
|
504
|
+
---
|
|
505
|
+
|
|
506
|
+
## Key Files
|
|
507
|
+
|
|
508
|
+
| File | Purpose |
|
|
509
|
+
|---|---|
|
|
510
|
+
| **Poller** | |
|
|
511
|
+
| `scripts/poller/index.mjs` | Main poller entry point |
|
|
512
|
+
| `scripts/poller/slack-poller.mjs` | Slack event polling |
|
|
513
|
+
| `scripts/poller/gmail-poller.mjs` | Gmail inbox polling |
|
|
514
|
+
| `scripts/poller/calendar-poller.mjs` | Calendar event polling |
|
|
515
|
+
| `scripts/poller/mehran-gmail-poller.mjs` | CEO inbox polling |
|
|
516
|
+
| `scripts/poller/imap-client.mjs` | IMAP connection wrapper |
|
|
517
|
+
| `scripts/poller/trigger.mjs` | Priority item session spawner |
|
|
518
|
+
| `scripts/poller/utils.mjs` | Shared utilities (priority detection) |
|
|
519
|
+
| `scripts/poller/intra-session-check.mjs` | Mid-session event check |
|
|
520
|
+
| **Daemon** | |
|
|
521
|
+
| `scripts/daemon/maestro-daemon.mjs` | Generic daemon entry point |
|
|
522
|
+
| `scripts/daemon/sophie-daemon.mjs` | Core daemon logic (poll + backlog + health) |
|
|
523
|
+
| `scripts/daemon/classifier.mjs` | Haiku-based message classifier |
|
|
524
|
+
| `scripts/daemon/dispatcher.mjs` | Parallel session manager |
|
|
525
|
+
| `scripts/daemon/prompt-builder.mjs` | Context-rich prompt construction |
|
|
526
|
+
| `scripts/daemon/responder.mjs` | Quick reply and holding messages |
|
|
527
|
+
| `scripts/daemon/session-lock.mjs` | File-based dedup locking |
|
|
528
|
+
| `scripts/daemon/health.mjs` | Health monitoring and dashboard |
|
|
529
|
+
| `scripts/daemon/context-compiler.mjs` | Session context pre-compilation |
|
|
530
|
+
| **Triggers** | |
|
|
531
|
+
| `scripts/local-triggers/run-trigger.sh` | Execute a scheduled trigger |
|
|
532
|
+
| `scripts/local-triggers/generate-plists.sh` | Generate launchd plist files |
|
|
533
|
+
| `scripts/local-triggers/install-all.sh` | Install all plists to launchd |
|
|
534
|
+
| **Session** | |
|
|
535
|
+
| `scripts/spawn-session.sh` | Spawn a new Claude Code session |
|
|
536
|
+
| `scripts/session-start.sh` | Session initialisation |
|
|
537
|
+
| `scripts/emergency-stop.sh` | Emergency halt all operations |
|
|
538
|
+
| `scripts/resume-operations.sh` | Resume after emergency stop |
|
|
539
|
+
| **Watchdog** | |
|
|
540
|
+
| `scripts/watchdog/memory-watchdog.sh` | Resource monitoring and process management |
|
|
541
|
+
| `scripts/watchdog/force-reboot.sh` | Kill all claude processes and restart |
|
|
542
|
+
|
|
543
|
+
---
|
|
544
|
+
|
|
545
|
+
## Related Documents
|
|
546
|
+
|
|
547
|
+
- [Mac Mini Bootstrap](../runbooks/mac-mini-bootstrap.md) — Initial machine and launchd setup
|
|
548
|
+
- [Perpetual Operations](../runbooks/perpetual-operations.md) — How the system runs 24/7
|
|
549
|
+
- [Recovery and Failover](../runbooks/recovery-and-failover.md) — What to do when things break
|
|
550
|
+
- [Agent Persona Setup](agent-persona-setup.md) — Trigger schedule configuration
|