An incident is more than an alert. It has a lifecycle, affected customers, status changes, communication history, a root cause, and work that must happen after service is restored.
This sample models the incident itself as a durable Telnyx Edge actor.
Code: https://github.com/team-telnyx/telnyx-code-examples/tree/main/network-incident-agent
Architecture
NetworkIncidentAgent(incidentId)
├── durable state: status, severity, services, counters
├── KV: affected customers
├── SQL: ordered incident timeline
├── Messaging: proactive customer SMS
├── Voice: incident-aware Call Control response
├── CloudFS: RCA JSON
└── schedule(): delayed recurrence check
Using the incident ID as the actor name makes routing deterministic. The same ID always resolves to the same durable entity.
Validate lifecycle transitions
The actor limits state changes to valid transitions. This prevents an operator or automation from silently moving a detected incident directly to closed.
const ALLOWED_TRANSITIONS = {
detected: ["investigating"],
investigating: ["restoring", "resolved"],
restoring: ["resolved", "investigating"],
resolved: ["closed", "investigating"],
closed: ["investigating"],
};
Every accepted transition updates state and inserts an event into the actor's SQL timeline.
Notify customers without exposing them
Affected phone numbers live in KV, but browser responses contain only masked values. In live mode, notifications use the Telnyx binding:
await this.env.TELNYX.messages.send({ from, to, text });
The dashboard starts in demo mode, where delivery is simulated. This makes the complete workflow safe to run during development or a screen recording. Real delivery requires an explicit live-mode selection and valid Telnyx configuration.
Use the same context for inbound calls
When a call.initiated webhook arrives, the application reads the incident ID from client_state, resolves the actor, and retrieves its current voice message. It then answers the call and speaks that context through Call Control.
SMS and voice therefore read from the same incident state rather than maintaining separate status copies.
Write the RCA and check for recurrence
After resolution, the actor serializes the incident, affected-service metadata, root cause, and SQL timeline into JSON. It writes the file to CloudFS through a temporary path and atomic rename.
It then schedules checkRecurrence:
await this.schedule(
delay,
"checkRecurrence",
{ incidentId },
{ id: `recurrence-${incidentId}` },
);
If the incident remains resolved, the task closes it. If its state has regressed, it records a recurrence.
Run the demo
git clone https://github.com/team-telnyx/telnyx-code-examples.git
cd telnyx-code-examples/network-incident-agent
cp .env.example .env
npm install
npm run build
mkdir -p /tmp/network-incident-cloudfs
CLOUDFS_MOUNT_PATH=/tmp/network-incident-cloudfs npm start
Open the local URL printed by telnyx-edge and run the incident lifecycle. Leave live SMS disabled unless you have configured an approved sender and opted-in test destinations.
Run the repeatable smoke test with:
DEMO_BASE_URL=http://localhost:8787 npm run smoke
Where to take it next
For production, add operator authentication, webhook signature validation at ingress, idempotency for commands and notifications, monitoring-system integrations, and authorization rules around customer-impact data.
The core pattern remains simple: give the long-lived entity a durable actor identity, then make AI and communication channels capabilities of that entity.
Top comments (0)