Skip to main content

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_blocked
  • operator_escalation_raised
  • escalation_resolved
  • human_escalation_raised — emitted when a human-owned blocker is promoted to a first-class escalation record
  • human_escalation_resolved — emitted when a human escalation is resolved via agentxchain unblock
  • phase_transition_pending
  • run_completion_pending
  • run_completed
  • approval_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 to true. Set to false to disable without removing config.
  • At least one webhook must subscribe to approval_sla_reminder for reminders to fire.

Evaluation

Reminders are lazily evaluated at operator interaction time:

  • agentxchain status evaluates and fires due reminders as a side effect.
  • The dashboard browser shell calls GET /api/poll immediately on connect and every 60 seconds while the tab is visible. That heartbeat evaluates due reminders exactly once per poll tick.
  • agentxchain step evaluates before it exits on an already-pending approval.
  • agentxchain run evaluates 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_version
  • event_id
  • event_type
  • emitted_at
  • project
  • run
  • turn
  • payload

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_id
  • event_type
  • notification_name
  • transport
  • delivered
  • status_code
  • timed_out
  • duration_ms
  • message

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_validation and after_acceptance
  • notifications are tied to durable governed events such as run_blocked and run_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.