Skip to content

feat(alerts): ax alerts CLI — Activity Stream alerts + reminders MVP#53

Merged
madtank merged 16 commits intomainfrom
feat/alerts-cli-mvp
Apr 16, 2026
Merged

feat(alerts): ax alerts CLI — Activity Stream alerts + reminders MVP#53
madtank merged 16 commits intomainfrom
feat/alerts-cli-mvp

Conversation

@madtank
Copy link
Copy Markdown
Member

@madtank madtank commented Apr 15, 2026

Summary

First-slice MVP for task dfef4c92 "MVP: Activity Stream alerts and task reminders". Pure CLI work — zero backend/frontend changes required.

  • ax alerts send — fire an alert or reminder via POST /api/v1/messages with the existing metadata.alert + metadata.ui.cards[0].type="alert" envelope the frontend's AlertCardBody already renders.
  • ax alerts reminder — shortcut for --kind reminder (requires --source-task). Compact: no mcp_app widget, no task-board initial_data. Card resource_uri points at the linked task for clickthrough.
  • ax alerts ack / resolve / state — post a state-change reply (backend MessageEditBody only accepts content, metadata updates are silently dropped, so state transitions are first-class stream events linked via parent_message_id).

Why reply-based state transitions

Backend's /api/v1/messages/{id} PATCH schema is MessageEditBody { content: str } — adding metadata is silently ignored. Rather than wait for a backend change, ack/resolve post a structured reply with metadata.alert_state_change referencing the parent alert. This is an auditable stream event and lines up with the "verification receipts as stream events" spec callout on ebd63283. A small frontend follow-up can fold replies into the parent card's state badge.

Test plan

  • pytest tests/test_alerts_commands.py — 9 tests, all green (metadata shape, reminder compactness, severity normalization, state-change reply shape, non-alert error path, JSON output).
  • Full suite: 195 passed.
  • ruff check clean.
  • Dogfood on next.paxai.app:
    • msg 233089a7 — alert send landed with message_type=alert, cards[0].type=alert, target_agent=orion, source_task_id=dfef4c92….
    • msg 23c50444 — reminder send, kind=task_reminder, resource_uri=ui://tasks/dfef4c92…, no widget hydration.
  • Cross-agent ack/resolve (blocked on my side — backend refuses "reply to your own message", which is correct semantics: recipient acks, not firer).

Related tasks

  • dfef4c92 — parent MVP task (this PR = first slice)
  • 65c76d9b — CLI alert metadata gap (resolved by this PR)
  • 0dacbc1e — task reminders as app-backed signals (first shape landed here)
  • ebd63283 — Activity Stream taxonomy (consumed: alert, reminder, alert_state_change, task_reminder)
  • 68656c16 — scheduler architecture (deferred to slice 2)
  • 22064fad — CLI SSE activity contract (second priority per ChatGPT)

🤖 Generated with Claude Code

anvil and others added 16 commits April 15, 2026 05:10
… MVP

First-slice MVP for task dfef4c92 (Activity Stream alerts and reminders).
Thin wrapper over POST /api/v1/messages using the existing metadata.alert
path — zero backend schema changes, no scheduler dependency.

Commands:
- ax alerts send — fire an alert/reminder with severity, target, source_task,
  due_at, remind_at, evidence. Emits metadata.ui.cards[0].type="alert" so
  the frontend's AlertCardBody renders it (verified against AxMessageWidgets).
- ax alerts reminder — shortcut for --kind reminder (source_task required).
- ax alerts ack/resolve/state — post a state-change REPLY (backend PATCH
  only accepts content, not metadata, so state transitions become first-class
  stream events referencing the parent alert via parent_message_id).

Reminder cards stay compact: no mcp_app widget, no task-board initial_data.
The card's resource_uri points at the linked task so it's clickable.

Dogfooded against next.paxai.app:
- msg 233089a7 — alert send, card type=alert, target+source_task set.
- msg 23c50444 — reminder send, kind=task_reminder, resource_uri set.

Related: 65c76d9b (CLI alert metadata), 0dacbc1e (task reminders), ebd63283
(activity stream taxonomy). Scheduler (68656c16) deferred to slice 2.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Help text for ack/resolve/state now documents:
- the "recipient acks, not firer" boundary (backend refuses self-replies)
- task 247f7bf0 as the follow-up that enables in-place state transitions

Per aX feedback on ax-cli#53.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…th design

Refinement per ChatGPT 2026-04-15 on dfef4c92 / 0dacbc1e:
tasks are canonical reminder/workflow objects; alerts are Activity Stream
events generated from (or linked to) tasks.

Changes:
- Add `snoozed` to allowed states + `ax alerts snooze <id>` command.
  Scheduler (68656c16) will re-fire at remind_at / next cadence.
- When `--source-task` is set and `--target` is omitted, auto-resolve
  target from task.assignee_id → task.creator_id. Explicit --target still
  wins for escalation. Displays "target: orion (from task assignee)" so
  users see when auto-resolution fired.
- Module docstring now states the "task = source of truth" design rule
  and points at 0dacbc1e / 68656c16 / 34bfbf6b for the scheduler-driven
  follow-up (recurring / SLA / stale-task nudges).

4 new tests (13 total for alerts, 199 full suite). Dogfooded on prod:
msg 18bb003a — reminder auto-targeted orion from task dfef4c92 assignee.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…reminders

Per ChatGPT coordination update on ax-cli#53 / b911ea19:

- Reject any --remind-at / --due-at before 2020 with a clear error that
  names the likely root cause (runner with broken/frozen clock). Real
  case caught in msg b9fb15b6 where a remind_at landed as 2000-01-01.
- Reject malformed ISO-8601 with a typed message instead of letting
  garbage flow into the alert metadata.
- Default response_required=true for --kind reminder (they're work
  nudges — recipient is expected to ack/snooze). --kind alert stays
  opt-in via --response-required.

3 new tests (16 total alerts, 202 full suite). ruff format + check clean.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@madtank madtank merged commit fb0a9e5 into main Apr 16, 2026
6 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant