Notifications
AgentXchain now ships a first-class notification contract for governed lifecycle events. This is not another hook phase and it is not a Slack-specific integration. It is a stable event boundary that webhook relays, chat bridges, ticket routers, and audit collectors can consume.
Scope
This slice is deliberately narrow:
- top-level governed config:
notifications - one transport:
webhook - one evidence file:
.agentxchain/notification-audit.jsonl - one operator-facing blocker projection:
HUMAN_TASKS.md+.agentxchain/human-escalations.jsonl - only shipped lifecycle events, not aspirational ones
Delivery is best-effort. Notification failures are audited, but they do not block step, resume, accept-turn, approve-completion, or any other governed command.
Config
{
"notifications": {
"webhooks": [
{
"name": "ops_webhook",
"url": "https://ops.example.com/agentxchain/events",
"events": [
"run_blocked",
"operator_escalation_raised",
"escalation_resolved",
"phase_transition_pending",
"run_completion_pending",
"run_completed",
"approval_sla_reminder"
],
"timeout_ms": 5000,
"headers": {
"Authorization": "Bearer ${OPS_WEBHOOK_TOKEN}"
}
}
],
"approval_sla": {
"reminder_after_seconds": [3600, 14400, 86400]
}
}
}
Event Types
The shipped event set is intentionally small and code-backed:
run_blockedoperator_escalation_raisedescalation_resolvedhuman_escalation_raised— emitted when a human-owned blocker is promoted to a first-class escalation recordhuman_escalation_resolved— emitted when a human escalation is resolved viaagentxchain unblockphase_transition_pendingrun_completion_pendingrun_completedapproval_sla_reminder
run_failed is not documented here because the current governed runner does not expose a first-class persistent run-failed operator surface yet. Do not build consumers against undocumented event names.
Local Notifier Floor
Human escalation events always emit a structured stderr notice regardless of webhook configuration. This ensures operators see escalation signals even with zero webhooks configured:
[agentxchain] ⚠ HUMAN ESCALATION RAISED: hesc_abc12345
Type: needs_oauth
Action: Reconnect Linear OAuth and verify the session.
Unblock: agentxchain unblock hesc_abc12345
On macOS, set AGENTXCHAIN_LOCAL_NOTIFY=1 to also receive native desktop notifications via AppleScript when escalations are raised or resolved.
Approval SLA Reminders
When a run enters pending_phase_transition or pending_run_completion, the initial notification fires once. If the operator does not act, the approval sits silently by default. Approval SLA reminders add timed follow-up webhook notifications at configurable intervals.
Per DEC-APPROVAL-TIMEOUT-EXEMPT-001, approval-pending states are exempt from timeout enforcement. SLA reminders are a notification feature, not a timeout feature — they never block or mutate governed state.
Config
{
"notifications": {
"approval_sla": {
"reminder_after_seconds": [3600, 14400, 86400],
"enabled": true
}
}
}
reminder_after_seconds: ascending array of positive integers (seconds after the approval was requested). Minimum value: 300 (5 minutes). Maximum 10 entries.enabled: optional boolean, defaults totrue. Set tofalseto disable without removing config.- At least one webhook must subscribe to
approval_sla_reminderfor reminders to fire.
Evaluation
Reminders are lazily evaluated at operator interaction time:
agentxchain statusevaluates and fires due reminders as a side effect.- The dashboard browser shell calls
GET /api/pollimmediately on connect and every 60 seconds while the tab is visible. That heartbeat evaluates due reminders exactly once per poll tick. agentxchain stepevaluates before it exits on an already-pending approval.agentxchain runevaluates when the run loop enters a pending gate before the operator approves or holds it.
Each threshold fires once per pending approval. If a 1-hour threshold fires, it will not fire again until the approval is resolved and a new approval starts.
Payload
{
"event_type": "approval_sla_reminder",
"payload": {
"approval_type": "pending_phase_transition",
"requested_at": "2026-04-16T14:00:00.000Z",
"elapsed_seconds": 14400,
"threshold_seconds": 14400,
"reminder_index": 2,
"total_thresholds": 3,
"from_phase": "implementation",
"to_phase": "qa",
"gate": "require_approval"
}
}
Tracking
Reminder dedup state is tracked in .agentxchain/sla-reminders.json. This file is cleared per approval type when the approval is resolved (approve-transition / approve-completion).
Payload
Each webhook receives one JSON object:
{
"schema_version": "0.1",
"event_id": "notif_abc123",
"event_type": "run_blocked",
"emitted_at": "2026-04-04T02:00:00.000Z",
"project": {
"id": "agentxchain-dev",
"name": "AgentXchain.dev",
"root": "/abs/path/to/repo"
},
"run": {
"run_id": "run_123",
"status": "blocked",
"phase": "implementation"
},
"turn": {
"turn_id": "turn_456",
"role_id": "dev",
"attempt": 2,
"assigned_sequence": 7
},
"payload": {
"category": "dispatch_error",
"blocked_on": "dispatch:api_proxy_failure",
"typed_reason": "dispatch_error",
"recovery_action": "Resolve the dispatch issue, then run agentxchain step --resume",
"human_escalation": {
"escalation_id": "hesc_abc123",
"type": "needs_credential",
"service": "Anthropic",
"action": "Restore the required Anthropic credential and verify access.",
"resolution_command": "agentxchain unblock hesc_abc123"
}
}
}
Top-level fields:
schema_versionevent_idevent_typeemitted_atprojectrunturnpayload
turn is null when the event is run-scoped and no retained or targeted turn exists.
When a run_blocked event is human-owned, the payload also includes a human_escalation object. The same blocker is recorded durably in .agentxchain/human-escalations.jsonl and mirrored into HUMAN_TASKS.md.
Audit Evidence
Every delivery attempt appends one JSON line to .agentxchain/notification-audit.jsonl.
Recorded fields include:
event_idevent_typenotification_nametransportdeliveredstatus_codetimed_outduration_msmessage
This audit file is included in agentxchain export and counted in export summary as notification_audit_entries. agentxchain verify export validates that count against the exported .agentxchain/notification-audit.jsonl content.
The live local dashboard also exposes this audit through GET /api/notifications. The Notifications view summarizes configured webhook targets, approval SLA reminder config, aggregate failure/timeout counts, and the newest delivery attempts so operators do not need to tail the JSONL file by hand.
That endpoint is live-only. agentxchain replay export <export.json> does not expose historical notification telemetry through /api/notifications; replay mode returns replay_mode: true with a live-only message instead of stale audit rows. The exported artifact still contains .agentxchain/notification-audit.jsonl for reportability and verification, but the dashboard notification surface is intentionally scoped to the live local workspace.
Relationship To Hooks
Hooks remain the extensibility surface for policy and custom side effects. Notifications are different:
- hooks are tied to internal lifecycle phases such as
before_validationandafter_acceptance - notifications are tied to durable governed events such as
run_blockedandrun_completed - hooks can be blocking or advisory depending on phase
- notifications are always advisory and best-effort
If you want Slack, email, or ticketing delivery, build that on top of this webhook contract instead of hard-coding transport-specific assumptions into governed state transitions.