Alarme, die ein Agent schreiben kann
Ein Dashboard sagt Ihnen etwas, wenn Sie es öffnen. Ein Alarm sagt es Ihnen, wenn Sie gerade nicht hingesehen haben — und genau das ist der Teil, den ein Harness nicht aus eigener Kraft erzeugen kann: er braucht gehosteten Zustand, frische Daten, eine Wanduhr und irgendwohin zuzustellen.
Ein Alarm besteht aus fünf Entscheidungen, deshalb hat die API fünf Felder:
| Feld | Was es entscheidet |
|---|---|
when | Das SQL, dessen Zeilen die Verletzung sind |
say | Der Satz, den die Empfängerin liest, gefüllt aus der ersten Zeile |
every | Wie oft die Bedingung geprüft wird |
capture | Welches Bild aufgenommen wird, in Playwright |
to | Wer ihn erhält |
Das durchgearbeitete Beispiel
from vibedasher import Vibedasher
client = Vibedasher()
alert = client.alerts.create(
viz_id=1430,
name="Margin watch",
when="""SELECT region, ROUND(margin*100,1) AS margin_pct
FROM margins WHERE margin < 0.10 ORDER BY margin LIMIT 5""",
say="Profit margin {margin_pct}% is below 10% in {region}",
every="0 7 * * *",
capture="""await page.goto('?f.region={region}');
await expect(page.getByTestId('chart-rendered')).toBeVisible();
await page.locator('[data-capture-root]').screenshot();""",
to=["finance@acme.com"],
)
client.alerts.verify(alert.id) # jeder Selektor löst auf — nimmt nichts auf, sendet nichts
client.alerts.test(alert.id) # nimmt wirklich auf UND MAILT an finance@acme.com
Derselbe Aufruf in TypeScript, wo das SDK camelCase verwendet:
const alert = await client.alerts.create({
vizId: 1430,
name: 'Margin watch',
when: 'SELECT region, ROUND(margin*100,1) AS margin_pct FROM margins WHERE margin < 0.10 ORDER BY margin LIMIT 5',
say: 'Profit margin {margin_pct}% is below 10% in {region}',
every: '0 7 * * *',
capture: "await page.goto('?f.region={region}');\nawait expect(page.getByTestId('chart-rendered')).toBeVisible();\nawait page.locator('[data-capture-root]').screenshot();",
to: ['finance@acme.com'],
});
await client.alerts.verify(alert.id); // jeder Selektor löst auf — nimmt nichts auf, sendet nichts
await client.alerts.test(alert.id); // nimmt wirklich auf UND MAILT an finance@acme.com
Über MCP ist es create_alert mit denselben fünf Namen, danach verify_alert und
test_alert.
Der Truthy-Vertrag
Null Zeilen heißt nicht ausgelöst. Eine Zeile oder mehr heißt ausgelöst. Es gibt keinen
Vergleichsoperator, den man verkehrt herum setzen kann, und keinen Schwellwert, den man mit
der Abfrage synchron halten müsste: die WHERE-Klausel ist die Bedingung.
Die erste zurückgegebene Zeile sind die Metadaten der Nachricht, und say macht Worte daraus.
Schreiben Sie die Verletzung und ihre Erklärung als eine einzige Abfrage, damit der Satz nichts
behaupten kann, was die Daten nicht hergeben:
SELECT region,
ROUND(margin*100,1) AS margin_pct,
10 AS threshold_pct
FROM margins
WHERE margin < 0.10
ORDER BY margin
Was die Empfängerin liest (der Satz wird vom Programm erzeugt und bleibt daher englisch):
Profit margin 8.2% is below 10% in West, and 3 more
Weitere Zeilen sind Kontext, gedeckelt bei fünf. Ein say-Platzhalter, der eine Spalte nennt,
die die Abfrage nicht per SELECT liefert, wird schon beim Anlegen des Alarms abgelehnt —
mit dem Namen der Spalte und einer Liste der verfügbaren; nie eine Lücke, und nie das Literal
{region}, das jemandem zugemailt wird, als wäre es ein Text, den Sie geschrieben hätten.
Aliase zählen: ROUND(margin*100,1) AS margin_pct ist es, was {margin_pct} auflösen lässt.
Die ältere Form — comparator plus threshold über eine einzelne numerische Zelle — wird
weiterhin als syntaktischer Zucker akzeptiert. Bevorzugen Sie die Truthy-Form: „null Zeilen“
ist eindeutig auf eine Weise, wie „die Zahl liegt nicht unter dem Schwellwert“ es nicht ist.
Das Bild zeigt die Verletzung, nicht die Standardansicht
{column} funktioniert auch in capture, und genau darum geht es. Ein Platzhalter dort ist
ein Wert: ?f.region={region} heftet den Filter an die Region, die tatsächlich ausgelöst
hat — der Alarm über West ist also ein Bild von West. Der Link in der Nachricht wird aus
demselben Rezept gebaut, Bild und Link stimmen daher überein.
capture: await page.goto('?page=overview&f.region={region}');
await expect(page.getByTestId('chart-rendered')).toBeVisible();
await page.locator('[data-capture-root]').screenshot();
getByTestId('<name>') adressiert ein Bedienelement über den Konfigurationsnamen, den Sie
ihm gegeben haben — describe_dashboard_controls(viz_id) über MCP oder
GET /v1/viz/{id}/controls listet die Namen und Werte auf, die ein Dashboard tatsächlich
akzeptiert. Niemals ein roher CSS-Pfad: die werden abgelehnt, weil sie still brechen, sobald
sich das Markup das nächste Mal verschiebt.
Das Rezept wird geparst, niemals ausgeführt. page.evaluate, require/import,
Template-Literale, String-Verkettung, Kommentare und CSS-Pfade kommen jeweils als Ablehnung
zurück, die die Zeile benennt. Was läuft, ist ein kleines deklaratives Rezept, das die
Capture-Spur anwendet, indem sie die URL baut — deterministisch auf eine Weise, wie ein Klick
es nicht ist.
every: ein Cron-String oder eine Dauer
every="0 7 * * *" # 07:00 Uhr an jedem Tag
every="15m" # alle fünfzehn Minuten
every="2h" # alle zwei Stunden
every="1d" # einmal täglich um Mitternacht
Akzeptiert werden nur Schritte, die ihre Einheit gleichmäßig ausfüllen. 7m wird abgelehnt:
Cron startet einen Schritt zur vollen Stunde neu, */7 würde also um :00, :07 … :56 feuern und
dann schon vier Minuten später wieder — nicht „alle sieben Minuten“. Die Ablehnung listet die
Schritte auf, die funktionieren.
Ein Cron-Ausdruck, der gültig ist und nie eintreten kann — 0 0 30 2 *, der den 30. Februar
verlangt — wird ebenfalls abgelehnt. Nachgelagert würde sich nie etwas darüber beschweren: der
Alarm bliebe active, nie fällig, nie ausgewertet, und jede Oberfläche würde ihn für immer als
gesund melden.
Lassen Sie every weg, während when gesetzt ist, und der Alarm wird im eigenen Takt des
Schedulers ausgewertet.
to: der Kanal wird an der Adresse abgelesen
to=[
"finance@acme.com", # E-Mail
"https://hooks.slack.com/services/T/B/X", # eingehender Slack-Webhook
"usr_1a2b3c", # eine Benutzer-ID — In-App-Benachrichtigung
]
Die drei Formen sind disjunkt, es gibt also keinen Kanal, den man neben der Adresse deklarieren müsste. Etwas, das wie eine Adresse aussieht und keine von beiden ist, wird abgelehnt, statt als Benutzer-ID abgelegt zu werden, die es gar nicht gibt.
Die Cache-Null-Garantie
Eine Bedingung läuft immer mit umgangenem SQL-Cache. Sie fordern das nicht an, und Sie können es nicht abschalten. Ein Alarm, der auf einem gecachten Ergebnis auslöst, wäre kein Alarm — er wäre der Bericht über etwas, das irgendwann einmal wahr war. Kein Cache-Lesen, kein Hochzählen der Trefferzahl, kein Cache-Schreiben: die Abfrage geht bei jeder Auswertung an die Engine.
Auf den Übergang feuern, nicht bei jeder Prüfung
Das Eintreten in die Verletzung feuert einmal. Darin zu verharren ist bewusst still. Der Alarm schärft sich neu, wenn die Bedingung sich auflöst. Eine Metrik, die um ihren Grenzwert flattert, sendet eine Nachricht, nicht eine pro Tick.
Der Kanarienvogel: verify nach jeder Dashboard-Änderung
verify ist der eine Aufruf, der Ihnen sagt, dass Ihr Rezept noch auflöst.
result = client.alerts.verify(alert.id)
if not result.verification.resolved:
for step in result.verification.steps:
print(step.index, step.status, step.detail)
Es nimmt nichts auf, sendet nichts, schreibt keine Zeile und bewegt keine Spalte. Es lässt denselben Executor laufen, den eine echte Aufnahme benutzt, im Nur-Auflösen-Modus, gegen den generierten Code des Dashboards selbst — es kann also nicht von dem abdriften, was die Aufnahme tun wird. Jeder Schritt wird gemeldet, nicht nur der erste: benennen Sie drei Selektoren um, und Sie erfahren von dreien.
Machen Sie es sich zur Gewohnheit, denn der Fehler, den es fängt, ist sonst unsichtbar:
Sie bearbeiten ein Dashboard. Sie benennen einen Filter um. Das Rezept des Alarms zeigt weiterhin auf den alten Namen. Nichts schlägt fehl — bis eine Empfängerin eine E-Mail mit dem Bild der falschen Sache darin öffnet.
Ein roter Schritt ist die brechende Änderung, die Sie gerade gemacht haben. Führen Sie
verify nach jeder Dashboard-Bearbeitung aus, die eine Seiten-ID, einen Selektornamen oder einen
Filterwert berührt, und Sie finden sie, solange Sie sie noch beheben können.
Jede Ablehnung benennt die Behebung
Ein 400 von diesen Endpunkten ist zum Lesen und Handeln gedacht, nicht zum Wiederholen (die
Meldungen unten sind die, die die API zurückgibt, auf Englisch):
say= references {margin_pct}, but the when= query does not return that column.
It returns: region. Fix: SELECT it (an alias counts, e.g.
'ROUND(margin*100,1) AS margin_pct'), or reference one of the columns above.
every='7m' does not divide the hour evenly, so it would not actually run every
7 minutes — cron restarts the step at :00, making the last gap of each hour
shorter. Fix: use one of 5m, 6m, 10m, 12m, 15m, 20m, 30m, or pass the cron
expression you mean.
Cron '0 0 30 2 *' is a valid expression that never occurs — no date matches its
day-of-month, month and day-of-week together (e.g. '30 2' asks for February
30th). An alert on it would stay 'active' and silent forever. Fix: name a date
that exists, or use a duration like every='1d'.
Die Reihenfolge, in der man arbeitet
describe_dashboard_controls(viz_id)— die Namen und Werte, die dieses Dashboard tatsächlich akzeptiert. Es zu überspringen heißt, Selektornamen zu raten.- Schreiben Sie das SQL. Seine Zeilen sind die Verletzung; seine Spalten sind der Satz.
- Schreiben Sie die Aufnahme. Nutzen Sie
{column}, damit das Bild die Verletzung ist. verify— bevor Sie ihm vertrauen, und erneut nach jeder Dashboard-Bearbeitung.test— nimmt wirklich auf und gibt die Zustellzeile zurück, die man beobachten kann. Es mailt den Empfängern der Warnung: ein Testlauf ist ein echter Versand, und nur die Buchführung unterscheidet sich von einem echten Auslösen (lastFiredAtbewegt sich nicht, weil nichts ausgelöst hat). Führen Sie es aus, wenn eine echte Nachricht an diese Adressen genau das ist, was Sie wollen;verifyist der Schritt, der niemanden erreicht.
Berechtigungen
Einen Alarm anzulegen, zu bearbeiten, zu löschen und zu testen erfordert die Rolle creator
oder admin und Bearbeitungszugriff auf das Dashboard. Einen zu lesen — und verify, das
nichts ableitet, was Sie nicht ohnehin lesen könnten — erfordert Lesezugriff. Ein Dashboard, das
Sie nicht bearbeiten dürfen, antwortet mit 404, nicht 403: die Ablehnung bestätigt nicht,
dass die ID existiert.