Add the exec-aide-listener-no-running-tasks CloudWatch alarm (RunningTaskCount Minimum < 1, eval 3 / datapoints 2, ALARM-only, NOT_BREACHING) so we page if the Slack Socket Mode listener has no running task and the bot goes dark. RunningTaskCount is only emitted in the ECS/ContainerInsights namespace, so this commit also enables Container Insights on the exec-aide cluster (containerInsightsV2: ENABLED). That is a cost/config change (extra CloudWatch ingestion/storage for the cluster) and is isolated to this commit pending Adam's sign-off; the rest of the alarm coverage does not depend on it. Also refreshes the README CDK Constructs + new Monitoring & Alarms section (and drops the stale 'reminder' Lambda reference removed in #54).
8.5 KiB
Exec Aide (Lauren)
Personal AI executive assistant for Adam Moussa. Monitors Gmail inbox, classifies emails by urgency using Claude Haiku (Bedrock), delivers Slack alerts and daily digests, manages tasks and reminders, integrates with Google Calendar, and supports two-way conversation via Slack DM and @mentions in channels.
Architecture
EventBridge (15 min) → fetch-classify Lambda → Gmail API → Bedrock Haiku → DynamoDB + Slack DM
EventBridge Scheduler (5 PM ET M-F) → daily-digest Lambda → DynamoDB + Gmail API → Slack DM
Fargate (Socket Mode) → Slack listener → conversation Lambda → Bedrock Sonnet + tools → Slack reply
EventBridge Scheduler (one-shot) → reminder Lambda → Slack DM
Stack: CDK (TypeScript), stack name exec-aide, region us-east-1
CI/CD: CodePipeline + CodeBuild — auto-deploys on push to main. Pipeline stack: exec-aide-pipeline.
Services
- exec-aide-fetch-classify (Lambda): Polls Gmail via History API every 15 minutes, classifies each new message via Bedrock Haiku, filters out marketing, sends immediate Slack DM for HIGH priority, tracks thread state for unanswered detection.
- exec-aide-daily-digest (Lambda): Runs weekdays at 5 PM ET (DST-aware). Summarizes high-priority items, bypassed work orders, and unanswered threads older than 24h.
- exec-aide-conversation (Lambda): Bedrock Sonnet-powered conversational assistant. Handles multi-turn conversations with tool use for inbox queries, Gmail search, coordination threads, calendar management, tasks, reminders, and on-demand digest triggers. Channel-aware: declines private info in public channels. Invoked asynchronously by the listener.
- exec-aide-reminder (Lambda): Receives one-shot EventBridge Scheduler invocations and sends reminder text as a Slack DM to Adam.
- exec-aide-listener (Fargate): Socket Mode Slack bot. Handles DMs (with "Thinking..." placeholder UX), @mentions in channels (flat or threaded replies), and coordination thread routing. Runs in a dedicated VPC (10.30.0.0/16) on ARM64.
- DynamoDB
exec-aide: Single-table design (MSG#, THD#, META#, CONV#, COORD#, TASK# prefixes). GSIby-datefor daily digest queries. 90-day TTL (7-day for COORD).
Conversation Tools
The conversation Lambda has access to these tools:
| Tool | Description |
|---|---|
get_inbox_summary |
Today's email stats + recent high-priority items |
get_high_priority_emails |
HIGH-classified emails for a given date |
get_unanswered_threads |
Threads awaiting reply for 24h+ |
get_bypassed_work_orders |
Emails sent without CC'ing work-orders@ |
search_emails_by_sender |
Find emails from a specific sender |
get_email_thread_detail |
Full thread context from Gmail |
trigger_daily_digest |
Send a digest on demand |
search_inbox |
Full Gmail search with query syntax |
coordinate_with_user |
Start a coordination thread with someone in a channel |
create_task |
Add a task to Adam's to-do list |
list_tasks |
Show open tasks |
complete_task |
Mark a task as done |
delete_task |
Remove a task |
create_reminder |
Schedule a one-shot reminder DM at a specific time |
list_calendar_events |
List upcoming Google Calendar events |
create_calendar_event |
Create a new calendar event |
check_availability |
Check free/busy status for a time range |
CDK Constructs
lib/constructs/email-pipeline.ts— DynamoDB table, the three pipeline Lambda functions (fetch-classify, daily-digest, conversation), EventBridge schedules, and their CloudWatch alarms (Lambda + DynamoDB)lib/constructs/socket-mode.ts— VPC, ECS cluster, Fargate service, ECR repo (image built automatically viaContainerImage.fromAsset()), and the listener's CloudWatch alarms (CPU, memory, running-task count)
Monitoring & Alarms
All CloudWatch alarms are ALARM-only (no OK / InsufficientData actions), use treatMissingData: NOT_BREACHING, and publish to the shared cross-stack site-alerts SNS topic (arn:aws:sns:us-east-1:328440206208:site-alerts, managed by seahaven-account-baseline, encrypted with alias/seahaven-alarm-topics). The topic is imported once at the stack level and injected into both constructs via props. Alarm names follow exec-aide-<resource>-<signal>.
| Alarm | Resource | Condition |
|---|---|---|
exec-aide-<fn>-errors |
each Lambda | Errors Sum ≥ 1 over 5 min |
exec-aide-<fn>-throttles |
each Lambda | Throttles Sum ≥ 1 over 5 min |
exec-aide-<fn>-duration |
each Lambda | p99 Duration > ~80% of timeout (96000 ms for the 120 s fns, 144000 ms for conversation's 180 s), eval 3 / datapoints 2 |
exec-aide-table-throttles |
DynamoDB exec-aide |
ThrottledRequests Sum ≥ 1 (summed across the operations the app issues) |
exec-aide-table-system-errors |
DynamoDB exec-aide |
SystemErrors Sum ≥ 1 (summed across the operations the app issues) |
exec-aide-listener-cpu-high |
Fargate service | CPU Average > 80%, eval 3 / datapoints 2 |
exec-aide-listener-memory-high |
Fargate service | Memory Average > 80%, eval 3 / datapoints 2 |
exec-aide-listener-no-running-tasks |
Fargate service | RunningTaskCount Minimum < 1, eval 3 / datapoints 2 |
<fn> ∈ {fetch-classify, daily-digest, conversation}.
Notes:
- DynamoDB
ThrottledRequests/SystemErrorsare not published at the bareTableNamedimension — AWS keys them byOperation. The alarms use CDK's*ForOperationsmath helpers scoped to the six operations this single-table app issues (GetItem, PutItem, Query, Scan, UpdateItem, DeleteItem) to stay within CloudWatch's 10-metric math-expression limit. exec-aide-listener-no-running-tasksreadsRunningTaskCountfrom theECS/ContainerInsightsnamespace, which requires Container Insights to be enabled on theexec-aidecluster (a cost/config change).
Classification Rules
| Category | Trigger |
|---|---|
| HIGH | VIP sender, urgency keywords, escalation language, financial/legal matters |
| NORMAL | Standard business correspondence |
| LOW | FYI-only, calendar confirmations, informational notifications |
| MARKETING | Newsletters, promotions, bulk mailings, cold outreach (filtered out) |
| Bypassed WO | Email to Adam without work-orders@ in TO/CC |
| Unanswered | Thread where Adam hasn't replied in 24h+ |
Prerequisites
- AWS CDK CLI (
npm install -g aws-cdk) - Node.js 22+, Python 3.12
- Docker (for Lambda bundling and Fargate image builds)
- Gmail API enabled in Google Cloud Console (project ID:
332395266465) - Google Calendar API enabled in Google Cloud Console (same project)
- Slack app "Exec Aide" with Socket Mode enabled
One-Time Setup
1. Google OAuth (Gmail + Calendar)
pip install google-auth-oauthlib
python scripts/get_gmail_token.py --client-secrets-file ~/.ssh/client_secret_332395266465-mn83qrihoq45njpaiu448rsml1v02f5r.apps.googleusercontent.com.json
# Authorize as adam@seahavenind.com (grants gmail.readonly + calendar scopes)
# Copy output JSON to Secrets Manager
2. Secrets Manager
| Secret | Contents |
|---|---|
exec-aide/gmail-oauth |
{ client_id, client_secret, refresh_token, access_token, token_expiry } |
exec-aide/slack-credentials |
{ botToken, signingSecret, appToken } |
3. SSM Parameters
| Parameter | Value |
|---|---|
/exec-aide/adam-email |
adam@seahavenind.com |
/exec-aide/adam-slack-user-id |
Slack user ID |
/exec-aide/vip-senders |
JSON array of email addresses |
/exec-aide/vip-domains |
JSON array of domains |
/exec-aide/work-order-addresses |
["work-orders@seahavenind.com","work-orders@seahaven.com"] |
/exec-aide/unanswered-threshold-hours |
24 |
Deploy
Pushes to main trigger the CI/CD pipeline automatically. For manual deployment:
npm install
cdk deploy
Manual Testing
aws lambda invoke --function-name exec-aide-fetch-classify /dev/stdout
aws lambda invoke --function-name exec-aide-daily-digest /dev/stdout
Or DM the Exec Aide bot in Slack to test the conversational assistant.
Updating VIP List
Edit the SSM parameters — changes take effect on the next Lambda cold start:
aws ssm put-parameter --name /exec-aide/vip-senders --value '["new@example.com"]' --type String --overwrite