> ## Documentation Index
> Fetch the complete documentation index at: https://docs.genesiscomputing.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Automate Teams Alerts via the Scheduler

> Wire up the Microsoft Teams Webhook connector to fire automatically. The recommended scheduler-based pattern with a worked dbt-failure-alerter starter, design considerations (cadence, deduplication, failure modes), and an honest catalog of what does NOT work today.

The [Microsoft Teams Webhook connector](/docs/Workspaces/Microsoft-Teams/Teams-Webhook-Setup) ships the *delivery* layer — once a bot decides to send a card, it knows how to deliver it. The complementary question is what makes the bot decide in the first place, without a human typing the request every time. That trigger layer is built on top of Genesis's scheduler.

## The recommended pattern: scheduled bot job + agent-authored detection

The only fully-supported automatic-trigger path today is the Genesis scheduler (`scheduler_tools.scheduler_action`). The scheduler runs a fixed agent prompt on a cron / interval / one-shot schedule. The agent prompt contains both the **detection logic** (*"query X, check for Y"*) and the **action** (*"if found, call `teams_webhook_tools` with `SEND_ALERT`"*). Each fire is a normal LLM-driven thread turn — the agent picks up tools available to the running bot and posts the card via the same dispatch path documented in the setup guide.

### Concrete example

Ask the bot to schedule the job in a conversation:

```text theme={null}
Use scheduler_tools to ADD_JOB with:
  - job_id: 'contoso_pipeline_alerts'
  - what_to_do_prompt: 'Query Snowflake for failed dbt runs since the most recent timestamp in this thread (or last 10 minutes if none yet). For each failed run, call teams_webhook_tools with SEND_ALERT to secret_contoso_genesis_alerts. Use severity Critical, title "Pipeline failed: {dag_name}", and fields including dag_name, env, last_success_ts. If no failures, return without sending a card.'
  - trigger: {"type": "interval", "minutes": 5}
  - job_bot_id: 'Eve'
  - job_thread_id: 'contoso_pipeline_alerts'
```

The scheduler persists the job, fires every 5 minutes, and records each run in `scheduler_runs`. Run history is queryable via `scheduler_tools.scheduler_action(GET_HISTORY)` or the `/api/scheduler/prompt-jobs/history` HTTP endpoint.

### End-to-end proof

Validated against a real `*.api.powerplatform.com` webhook on a local Genesis stack:

| Fire | Started at | Duration | Status | Notes |
| - | - | - | - | - |
| 1 | 13:33:26 UTC | 15.85 s | success | Cold start: agent searched + activated `teams_webhook_tools` context, then dispatched `SEND_ALERT`. Microsoft returned 202 in \~950 ms |
| 2 | 13:35:26 UTC | 8.54 s | success | Warm: tool group already in thread context. Microsoft returned 202 in \~660 ms |

The interval trigger fired exactly on schedule; the agent's output included `{"job_result": "success"}` which the scheduler parses to record the outcome.

## Trigger design considerations

<AccordionGroup>
  <Accordion title="What goes in the `what_to_do_prompt`">
    The actual task the agent performs every fire, **not** a description of the schedule itself.

    * ✅ *"Query failed dbt runs in last 10 minutes; if any, send `SEND_ALERT`"*
    * ❌ *"Check for failures every 5 minutes"* — this describes the schedule, not the work; the agent would just try to create another scheduler entry
  </Accordion>

  <Accordion title="Cadence vs. cost">
    Every fire is an LLM call. A 5-minute interval is \~288 fires/day; with a typical alert agent loop that's a real cost line. Tune to detection-latency requirements:

    * 5-15 minutes is reasonable for most pipeline-failure use cases
    * 1-minute intervals are rarely warranted and have material cost impact
    * Daily/hourly scheduled reports cost effectively nothing
  </Accordion>

  <Accordion title="Deduplication">
    The scheduled prompt fires independently every interval. If the underlying failure is unchanged across intervals, the agent will keep alerting unless instructed otherwise. Common patterns:

    * **Thread-history dedup**: include *"only alert on runs newer than the last alert in this thread"* in the prompt — the bot uses thread history for state
    * **Scratch-table dedup**: write an `alerted_at` timestamp into a Snowflake / SQLite scratch table the agent reads at start
    * **Accept duplicates as a feature**: every fire is a "still broken" reminder, useful for incidents that should not be silently ignored
  </Accordion>

  <Accordion title="Tool group activation">
    The bot must have `teams_webhook_tools` in its active context. Two options:

    * **Bake it into the bot's persona instructions** *(recommended)* — no per-fire latency cost
    * **Rely on first-call discovery** — the agent will search the registry on each fire and add the tool group; works but adds \~7 seconds of cold-start latency (as the proof-of-concept table above shows)
  </Accordion>

  <Accordion title="Failure modes inside the mission">
    If the LLM call inside the scheduled job fails (timeout, model error), the run is recorded with `status: failed` in `scheduler_runs`. **There is no retry of the prompt itself.** For operationally-critical alerting, monitor `scheduler_runs` for runs in `failed` state and surface those to an operator dashboard.
  </Accordion>
</AccordionGroup>

## Worked starter: pipeline failure alerter

Pseudo-prompt for a dbt-failure alerter on a customer running dbt against Snowflake:

```text theme={null}
Query the dbt run-results table:

  SELECT model_name, status, completed_at, error_message
  FROM <customer_db>.<analytics_schema>.dbt_run_results
  WHERE status = 'error'
    AND completed_at > DATEADD(minute, -15, CURRENT_TIMESTAMP())

For each row, call teams_webhook_tools with action SEND_ALERT:
  - webhook_url: secret_contoso_genesis_alerts
  - title: "dbt model failed: {model_name}"
  - severity: Critical
  - fields: {dag: <model_name>, env: <customer_env>, error: <first 200 chars of error_message>}
  - action_url: <link to the customer's dbt cloud or BI dashboard run page if known>

If no rows match, do nothing — return success without sending.
```

The starter is intentionally not a canned code asset — the customer's dbt artifacts table name, environment, and dashboard URLs vary per deployment.

## What does *not* work today

Customers sometimes expect these paths; they do not exist in Genesis right now.

<Warning>
  Each row below has a known **workaround** — usually: "external system writes to a Snowflake table, the scheduled Genesis job polls that table."
</Warning>

| Path | Status | Why / workaround |
| - | - | - |
| **Snowflake `TASK` → Genesis HTTP** | Not supported | Genesis has no public HTTP endpoint that accepts Snowflake task callbacks. **Workaround:** a `TASK` can write a row to a state table; the scheduled Genesis job above reads it. |
| **Generic inbound webhook receiver** | Not supported | Genesis exposes only purpose-built inbound endpoints — `/jira/webhook` for Jira Cloud, `/servicenow/webhook` for ServiceNow, and the Bot Framework Teams message route — each of which understands one specific payload shape. There is no general-purpose receiver, so arbitrary external systems (dbt Cloud, Airflow, Monte Carlo, PagerDuty) cannot push events directly into Genesis to trigger agent runs. **Workaround:** external sender writes to a Snowflake / S3 / state table; scheduled Genesis job polls it. |
| **Composite tool emission** (one tool auto-firing another) | Not supported | Tools are independent. A "dbt failure check" tool does not automatically also call `teams_webhook_tools`. The agent decides what to do with the failure result, on each fire, based on its prompt. |
| **Reactive observability checker** (Genesis self-monitors `tool_observability_facts` and alerts on patterns) | Not built | No background process consumes the observability table to emit anomaly alerts. **Workaround:** a "Genesis is unhealthy" meta-alert would itself be a scheduled job that queries the observability table and alerts. |

The practical implication: **poll-based detection via the scheduler is the only first-class option today.** External systems should land their state in a Snowflake table (or any data store Genesis can read), and let the scheduled Genesis job poll it. Inbound push from external systems would require future work on a generic webhook receiver — a deliberate scoping choice, not an oversight.

## Related

* [Set Up the Microsoft Teams Webhook Connector](/docs/Workspaces/Microsoft-Teams/Teams-Webhook-Setup) — the connector itself; setup, actions reference, governance, troubleshooting
* [Activate Eve in Teams (Bot Framework)](/docs/Workspaces/Microsoft-Teams/Activate-Bot-Framework-Eve-in-Teams) — the bot-attributed inbound message path; Adaptive Card callbacks are pending end-to-end verification
* [Additional Tool Groups → Microsoft Teams Webhook Tools](/docs/Genbot-Toolkit/Additional-Tool-Groups) — full parameter list for each action
* [Process Scheduler Tools](/docs/Genbot-Toolkit/Process-Scheduler-Tools) — full `scheduler_action` API reference (every action, every parameter, every trigger spec)
