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).
Add ALARM-only CloudWatch alarms routed to the shared site-alerts SNS
topic (imported once via Topic.fromTopicArn and injected into both
constructs via props). All alarms use treatMissingData NOT_BREACHING and
have no OK / InsufficientData actions, mirroring the proposal-system
alarm construct.
Lambda (fetch-classify, daily-digest, conversation):
- Errors (Sum >= 1, eval 1)
- Throttles (Sum >= 1, eval 1)
- Duration (p99, eval 3 / datapoints 2, ~80% of timeout:
96000ms for the 120s fns, 144000ms for conversation's 180s)
-- thresholds pending Adam sign-off.
DynamoDB exec-aide table:
- ThrottledRequests and SystemErrors. These metrics are NOT published at
the bare TableName dimension (CDK's metricThrottledRequests /
metricSystemErrors are deprecated as invalid); they are keyed by the
Operation dimension. Used the *ForOperations math helpers scoped to the
6 operations this single-table app issues (GetItem/PutItem/Query/Scan/
UpdateItem/DeleteItem) to stay within the 10-metric math-expr cap.
ECS exec-aide-listener Fargate service (AWS/ECS, no Container Insights):
- CPU and Memory utilization (Average > 80%, eval 3 / datapoints 2).
- Service assigned to a const (logical id 'Service' unchanged) so metrics
can reference it.
The RunningTaskCount alarm (requires Container Insights) is intentionally
deferred to a separate sign-off-gated commit.
* Remove dead exec-aide-reminder Lambda and its IAM role
The reminder Lambda had 0 invocations in 90 days and no EventBridge
rule targets it. Removing the Lambda, its execution role, the
dedicated ReminderSchedulerRole, and the scheduler:CreateSchedule /
iam:PassRole grants from the conversation Lambda's policy.
The conversation Lambda's REMINDER_FN_ARN and REMINDER_SCHEDULER_ROLE_ARN
env vars and the create_reminder tool are removed alongside the
infrastructure. The src/reminder/ handler directory is also deleted.
Adam approved the deletion (INFRA-37).
Refs: INFRA-37
* fix: remove unused os import in conversation/tools.py
Left dangling after the exec-aide-reminder removal (INFRA-37); ruff F401.
INFRA-37
Switch the exec-aide table from the AWS-owned key to the shared
customer-managed CMK (alias/seahaven-dynamodb, imported via SSM
/seahaven/dynamodb/cmk-arn). In-place UpdateTable, no replacement.
CDK auto-grants kms to the 5 in-stack consumer roles (4 pipeline
Lambdas + SocketMode Fargate task role). Deployed + verified:
SSEType=KMS, scan decrypts, ECS service healthy.
INFRA-95
The task runs ARM64, but the image asset had no explicit platform, so
the amd64 CD runner built an amd64 image (QEMU alone does not change
the default target). Revision :8 crash-looped with "exec format
error" (50 failed task starts); CloudFormation hung on service
stabilization until cancelled. Local arm64 builds masked this -
deploys from this Mac always produced the right image.
Same fix pattern as the org-wide QEMU+platform rule for arm64 Lambda
bundling.
* Fix flat replies in DMs
DM replies were threaded under Adam's message instead of appearing flat.
Root cause: listener fell back to event["ts"] as thread_ts for new DMs,
causing say() to post as a threaded reply. Removed the fallback so DMs
always use flat messages with placeholder update.
Closes#4
* Add synchronous digest trigger for inline stats
Changed trigger_daily_digest from async (fire-and-forget) to synchronous
invocation so Lauren can report summary stats inline while the full Block
Kit digest arrives as a separate DM.
Closes#1
* Add tasks and reminders
- DynamoDB task CRUD with TASK#{ulid} prefix
- 5 new tools: create_task, list_tasks, complete_task, delete_task, create_reminder
- New exec-aide-reminder Lambda triggered by EventBridge Scheduler one-shots
- CDK: reminder Lambda, scheduler IAM role, conversation Lambda permissions
- Updated system prompt with task/reminder capabilities
Closes#3
* Add Google Calendar integration
- New src/shared/calendar.py: OAuth service builder, list_events,
create_event, check_availability (reuses gmail-oauth secret)
- 3 new tools: get_calendar_events, create_calendar_event, check_availability
- Extended OAuth scopes to include calendar in gmail.py and token script
- Updated system prompt with calendar capabilities
Requires re-running scripts/get_gmail_token.py to grant the calendar scope
and updating the exec-aide/gmail-oauth secret with the new refresh token.
Closes#2
* Add CodeBuild CI/CD pipeline
CodePipeline triggers on pushes to main, CodeBuild runs cdk deploy via buildspec.
* Update README for tasks, reminders, calendar, and CI/CD pipeline
Listener posts "Thinking..." placeholder and async-invokes a new
exec-aide-conversation Lambda that runs a Bedrock Sonnet tool-use loop
over 7 email tools, then updates the Slack message with the response.
Bedrock Haiku now classifies marketing/promotional emails as MARKETING
and they are filtered out before saving or alerting. This replaces
Gmail label-based filtering for more accurate classification.
Also: enable Fargate listener (desiredCount=1), update README for CDK,
update Notion AWS Architecture Map and Slack Apps Inventory.
SAM is for simple serverless stacks; this project has ECS, VPC, and
multi-service composition which requires CDK. Migration brings
ContainerImage.fromAsset() for automatic Docker builds on deploy,
matching the seahaven-slack-bot pattern.
Also fixes: Bedrock model ID (add version suffix), Gmail history API
parameter (labelId not labelIds), classify JSON extraction (handle
markdown fences), and digest block limit (cap sections at 5 items
to stay under Slack's 50-block limit).