Dev.to WebDev 🛠 Dev 👁 0 📖 8 min read

Build an AI Website Monitor with TypeScript and Agent Browser

An uptime check can tell you that a page returned HTTP 200. It cannot tell you that the price changed unexpectedly, a form stopped submitting, or a client-side deployment removed an important field. Watchdog is an open-

An uptime check can tell you that a page returned HTTP 200. It cannot tell you that the price changed unexpectedly, a form stopped submitting, or a client-side deployment removed an important field.

Watchdog is an open-source TypeScript example that checks the experience, not only the response code. It remembers a healthy baseline, wakes on a schedule, drives a real browser through the page, records evidence, and sends an SMS when the result changes.

The application combines a Telnyx Edge Stateful Actor, Agent Browser, SQL, KV, AI Inference, and Messaging. This tutorial explains how those pieces fit together and why its retry rules matter.

Beta notice: Telnyx Agent Browser is currently in beta. Request shapes, actions, and error behavior may change. Check the current Agent Browser documentation before using this pattern in production.

What the watchdog checks

One actor owns one watched hostname. Its state includes:

  • the target URL and allowed domain
  • the expected price text and form labels
  • the most recent verdict
  • the daily schedule identifier
  • any browser session that still needs cleanup
  • the owner's phone number

The first scheduled run captures the baseline. Later runs:

  1. Create an isolated browser session.
  2. Navigate to the target.
  3. Observe the page and obtain semantic element references.
  4. Use an inference model to identify the relevant price and form refs.
  5. Fill and submit the form.
  6. Capture a screenshot artifact.
  7. Compare the pre-submit observation with the stored baseline.
  8. Write the result to SQL and send an alert when necessary.
  9. Close the browser session in a finally block.

The verdict is either healthy or broken with a reason. A broken verdict opens or updates an incident. A later healthy run closes that incident with before-and-after evidence.

One Stateful Actor per hostname

The actor identity comes from the target hostname:

export function watchId(targetUrl: string): string {
  return new URL(targetUrl).hostname
}

const actor = env.WATCHDOG.idFromName(watchId(targetUrl))

Two URLs on the same hostname belong to the same watchdog. A different hostname gets a separate actor with independent state, scheduling, and incident history.

This makes the watched property a durable entity instead of a row that a stateless cron task must reconstruct on every run.

When a watch is armed, the actor stores its configuration and schedules both the first check and the recurring daily check:

const dailyId = await this.every(
  86400,
  "runCheck",
  { reason: "scheduled" },
  { id: `daily-${ownerId}` },
)

await this.schedule(
  5,
  "runCheck",
  { reason: "first", run_key: firstRunKey },
  { id: `first-${ownerId}` },
)

The first run occurs shortly after setup. Subsequent checks run daily.

Create a policy-scoped browser session

The actor calls the Agent Browser REST API to create a short-lived Chromium session. The request restricts navigation to the watched hostname and applies hard lifetime limits:

{
  "policy": {
    "allowed_domains": ["shop.example.com"]
  },
  "limits": {
    "lifetime_s": 900,
    "idle_timeout_s": 120
  },
  "idempotency_key": "2026-11-06"
}

The actor polls the session until it becomes ready. A session returned by an idempotent replay may already be closed, failed, or expired, so the code checks for terminal states and creates a fresh session when needed.

Agent Browser provides isolated sessions plus structured actions for navigation, observation, interaction, and screenshots. The official Agent Browser overview describes the browser as short-lived and isolated, with semantic observations designed for model tool use.

Observe the page and select element refs

After navigation, an observe action returns an observation_id, a text representation of the page, and element refs. The model works with those refs rather than pixel coordinates.

The watchdog asks the configured inference model to identify four elements:

  • price
  • name field
  • email field
  • submit button

It then performs fill and click actions using both the current observation ID and the selected ref. The Agent Browser actions documentation requires observation_id for ref-based interactions and documents the available action types.

The model does not decide whether the HTTP request succeeded. It helps map the structured observation to the page elements the deterministic workflow needs.

Compare against a durable baseline

The first successful run stores the observed price and form labels as the baseline:

interface Baseline {
  price_text: string
  form_labels: string[]
}

Later runs compare the pre-submit observation with that baseline. The post-submit observation has a different purpose: it proves whether the interaction reached the expected success state.

Keeping those observations separate avoids a subtle mistake. A form submission may navigate or rerender the page, so the final page is not necessarily the correct source for pricing comparison.

Store runs and incidents in SQL

The Stateful Actor owns the current lifecycle, while WATCHDOG_DB stores an audit-friendly ledger.

The runs table records values including:

  • run key and date
  • verdict and reason
  • observed price text
  • screenshot artifact ID and URL
  • observation ID and selected refs
  • whether stale-ref recovery occurred

The incidents table records the first broken time, reason, evidence artifact IDs, and open or closed status.

Scheduled runs use the UTC date as their stable run key. Before starting, the actor checks whether that key already has a verdict:

const prior = await this.rows<{ verdict: string | null }>(
  "SELECT verdict FROM runs WHERE run_key = ?",
  runKey,
)

if (prior[0]?.verdict && (reason === "scheduled" || reason === "first")) return

That guard makes a restarted or replayed scheduled task a no-op instead of a duplicate browser run or duplicate incident.

Use KV for SMS routing

KV holds the small indexes needed to route incoming commands:

watch/<hostname> -> owner phone
owner/<phone>    -> watched hostnames
incident/<id>    -> watched hostname

The owner can control the watchdog by SMS:

watch https://shop.example.com/pricing
status
recheck now
recheck <incident_id>

Incoming messaging webhooks are Ed25519-verified in live mode. The sender is then checked against the watch owner before the actor can expose status or queue another browser run. Unknown senders cannot trigger paid sessions or read incident state.

Treat browser mutations carefully

The most reusable part of this sample is its error model. Browser automation is different from a read-only API request because retrying a mutation may repeat an effect.

Recover from stale refs by observing again

The DOM may change after an observation. Agent Browser reports stale_ref or stale_observation as an action failure inside an HTTP 200 response.

The watchdog does not retry the old ref. It observes again, asks for current refs, and retries once. A second consecutive stale result is recorded as an incident note rather than guessed away.

Do not blindly retry an unknown outcome

An execution_lost result does not prove that a fill or click failed to take effect. Repeating the action immediately could submit a form twice.

The actor first observes the current page and checks for the expected effect. It repeats the mutation only when the effect did not land, using a fresh observation. If the browser session is terminal, the actor reconciles the original outcome before using a new session for remaining work.

This follows the official recovery guidance: an unknown outcome must be inspected before a mutation is repeated.

Back off when session quota is full

A 429 quota_exceeded response means the concurrent browser-session quota is full. It is not handled with a tight retry loop.

The actor closes any session it owns, schedules runCheck for 60 seconds later with the same run key, and returns. The stable run key keeps the delayed attempt replay-safe.

Run the complete flow offline

Clone the sample and install its dependencies:

git clone https://github.com/team-telnyx/telnyx-code-examples.git
cd telnyx-code-examples/watchdog
npm install
cp .env.example .env

Leave this setting enabled initially:

DEMO_MODE=true

Demo mode replays recorded Agent Browser envelopes from demo/browser-fixtures.json. It exercises baseline capture, stale-ref recovery, a broken verdict, incident creation, and fix verification without opening a live browser session or sending an SMS.

Run the smoke test:

npm run smoke

The test covers the end-to-end demo loop, restart behavior, incident deduplication, owner authorization, live-mode webhook refusal without a valid signature, and fixture consistency.

Deploy and arm a watch

The sample's telnyx.toml declares:

  • the WATCHDOG actor namespace
  • WATCHDOG_DB SQL storage
  • WATCHDOG_INDEX KV storage
  • the pre-authenticated TELNYX binding
  • secrets for live browser access, webhook verification, configuration, and phone values

Replace the placeholder resource IDs and configure the required secrets before deployment. Use placeholders rather than committing credentials or real phone numbers.

Generate binding types and deploy:

npx telnyx-edge types
npm run deploy

Agent Browser runs in the cloud, so the target must be a public HTTPS URL. It cannot reach localhost or a private service. The sample includes a fixture page available from the deployed function at /fixture.

Arm a watch through the HTTP API:

curl -X POST "$PUBLIC_BASE_URL/watch" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://your-public-host.example.com/fixture",
    "owner": "<OWNER_PHONE_E164>"
  }'

For live mode, configure TELNYX_API_KEY, TELNYX_PUBLIC_KEY, WATCH_OWNER_PHONE, FROM_NUMBER, and a supported AI_MODEL, then set DEMO_MODE=false. Point the Messaging Profile webhook to:

https://<YOUR_DEPLOYED_HOST>/webhooks/messaging

Production considerations

The sample demonstrates the architecture, but production monitoring needs additional policy:

  • restrict targets to domains you own or are authorized to test
  • avoid submitting forms that create irreversible business actions
  • define screenshot and incident retention periods
  • redact sensitive page content before sending it to a model
  • add alert suppression and escalation rules
  • monitor browser-session quota and scheduled-run failures
  • version the baseline instead of silently replacing it
  • review the current Agent Browser beta contract before deployment

The reusable pattern

Watchdog is a website-monitoring example, but its pattern applies to any stateful browser workflow:

  1. Give each monitored entity a stable actor identity.
  2. Store the expected baseline with that entity.
  3. Use scheduled tasks with stable run keys.
  4. Keep browser sessions policy-scoped and short-lived.
  5. Separate evidence collection from model judgment.
  6. Reconcile uncertain mutations before retrying.
  7. Store an auditable history outside current actor state.
  8. Deliver the result through the channel the operator already uses.

The complete TypeScript implementation, recorded browser fixtures, API notes, and smoke test are available in the Watchdog code example.

📰 Read the original article on Dev.to WebDev

Originally published by Dev.to WebDev. Aggregated on AIWithGhost for educational purposes — full credit and traffic to the original publisher.