Skip to main content
Glama

Update monitor

update_monitor
DestructiveIdempotent

Modify a monitor by UUID, changing only the fields you provide while others remain. Use it to adjust schedules, alerts, or thresholds without replacing the full config.

Instructions

Requires the write scope or higher. Updates a monitor by UUID with merge-patch semantics: supplied fields change, omitted fields keep their stored value. tags, assertions (conditions a successful run's ping body has to satisfy, which catch a job that exits 0 having done nothing) and guards (ceilings on a number the job reports, which catch a looping agent) each replace the whole set; get_monitor returns the current sets. slug and ci_provider are immutable; only the ci_workflow and ci_branch filters change here.

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
idYesMonitor UUID.
tzNoIANA timezone for cron evaluation.
nameYesHuman-readable monitor name.
tagsNoComma-separated labels to set on this monitor, e.g. 'agent:claude,env:prod'. Replaces existing tags. Max 20 tags, each max 50 chars.
guardsNoMetric guards: ceilings on a number the job reports, checked on every ping; a total above the ceiling (equal does not trip) opens a 'runaway' incident. A JSON array as a string, e.g. '[{"name":"daily spend","path":"cost.usd","window_s":86400,"ceiling":50,"aggregation":"sum"}]'. The array replaces the whole set; '[]' removes all, omitted leaves it. Every entry needs name, path (dotted), window_s (trailing seconds), ceiling and aggregation ('sum', 'max' or 'avg'). Pings with no number at the path are skipped, not counted as zero. At most 5 guards, window_s at most 604800 (7 days); a malformed entry is rejected by name; the set stays as is.
grace_sNoGrace period in seconds.
agent_idNoAttaches the monitor to a registered agent, by the agent's id or slug (register_agent returns both). An agent that does not exist is an error (400 UNKNOWN_AGENT); no agent is created implicitly. Omitted: the current attachment (or lack of one) is unchanged.
period_sNoPing interval in seconds (for schedule_kind='simple').
ci_branchNoCI filter: only runs on this branch count, e.g. 'main'. Needs ci_provider (400 FIELD_NOT_IN_SHAPE without a CI binding). Same upsert exception as ci_workflow: create_monitor on an existing slug never writes it; update_monitor does. Unset, a run on any branch, a fork's pull request included, reports to the monitor. Omitted or an empty string (an API compatibility rule): unchanged; an explicit JSON null removes the filter.
cron_exprNo5-field cron expression (for schedule_kind='cron').
probe_urlNohttp monitors only: the absolute http/https URL to probe. Required when monitor_type='http'. The host is resolved at write time and rejected if it resolves only to private/link-local addresses. Omitted: unchanged.
assertionsNoOutput assertions: conditions a successful run's ping body has to satisfy; one that fails opens an incident with cause 'assertion' naming it. A JSON array as a string, e.g. '[{"name":"rows written","kind":"json_path","path":"result.rows_processed","op":"gt","value":"0"}]'. The array replaces the whole set; '[]' removes all, omitted leaves it. Fields: name, kind (both required), value, path, op. kind: contains, not_contains, matches (RE2, max 1000 bytes) or json_path (compares the value at a dotted path, e.g. 'a.b.c', with op: eq, ne, gt, gte, lt, lte; numeric when both sides are numbers, otherwise compared as strings). At most 20; a malformed entry is rejected by name; the set stays as is.
ci_workflowNoCI filter: only runs of the workflow, pipeline or job with this exact name count. Needs ci_provider: without a CI binding it is refused with 400 FIELD_NOT_IN_SHAPE (monitor_type='ci' alone binds nothing). Exception: create_monitor on an existing slug (an upsert) never writes it, and with ci_provider in the same call it is accepted and discarded; update_monitor persists it. Unset, every workflow in the repository reports to the monitor, so an unrelated workflow's failure opens an incident and its success clears one. Omitted or an empty string (an API compatibility rule): unchanged; an explicit JSON null removes the filter.
monitor_fromNoDormant until: an RFC 3339 timestamp before which no deadline is computed and no incident opens, for a monitor provisioned ahead of its job. The first-run deadline is monitor_from + grace_s. Unset (default): deadlines start immediately. Example: '2026-01-01T00:00:00Z'. Omitted: unchanged.
probe_methodNohttp monitors only: the HTTP method the probe sends: 'GET' (default), 'HEAD' or 'POST'. 'HEAD' returns no body, so probe_expected_body cannot match with it. Omitted: unchanged.
max_runtime_sNoMaximum seconds one run may take, from its start ping, before it is reported overdue (the 'overrun' rule). Unset falls back to grace_s. Example: grace_s=600 with max_runtime_s=14400 alerts 10 minutes after a missed ping but tolerates a 4-hour run. It replaces grace_s for the overrun deadline only; the silence rule and the first-run deadline still use grace_s. Range 60-31536000. Not supported on http monitors (400 MAX_RUNTIME_NOT_SUPPORTED); probe_timeout_s bounds a probe. Omitted: unchanged; 0 clears it (falls back to grace_s).
schedule_kindNo'simple', 'cron' or 'on_demand'. Not accepted on an http monitor, and neither are period_s, cron_expr and tz: its schedule comes from probe_interval_s (400 FIELD_NOT_IN_SHAPE). 'on_demand' has no cadence (period_s or cron_expr with it is a 400) and arms no absence deadline between runs: an agent that is never invoked again raises nothing unless expect_every_s is set, and an idle healthy agent never raises a false 'late'. Once a run starts, max_runtime_s, step_timeout_s and blocked_timeout_s still apply. 'simple'/'cron' fit work on a cadence; 'on_demand' fits irregular invocation.
trace_contentNoWhat this monitor's traces keep of prompt, command and tool content: 'dropped' (the default) removes it; 'redacted' stores prompt, command and tool content from the traced sessions, with secret-shaped values redacted on arrival. Omitted: unchanged.
expect_every_sNoSilence floor in seconds: a 'silence' incident opens when no ping of any kind (success, start, fail, step) arrives within this window, timed from the monitor's last activity. It is the only absence rule an 'on_demand' monitor has. It stands down while a run is in flight (the run clock owns that), and a 'blocked' ping pauses it up to blocked_timeout_s. On 'simple'/'cron' monitors it joins the schedule deadline as whichever is sooner, so it can only tighten detection. Unset (default): no floor. Range 60-31536000. Accepted on every monitor_type and schedule_kind. Omitted: unchanged; 0 clears it and turns the silence floor off.
step_timeout_sNoSeconds an armed run may go without a step before a 'stalled' incident opens, timed from the later of its start ping and its latest step. Steps are POST <ping_url>/step?rid=<run-id>&step=<name> (curl_step in get_ping_instructions). When set, a job that sends no steps opens a stalled incident on every run. The Claude Code hook and the reporting prompt send no steps. Unset (default): no stall detection. Range 10-86400, strictly below COALESCE(max_runtime_s, grace_s) (400 STEP_TIMEOUT_EXCEEDS_BUDGET). Not supported on http monitors (400 STEP_TIMEOUT_NOT_SUPPORTED). A step never extends max_runtime_s. Omitted: unchanged; 0 clears it and turns stall detection off.
probe_timeout_sNohttp monitors only: seconds one probe may take before it counts as a failure. Range 1-30, default 10. The http counterpart of max_runtime_s, which http monitors reject. Omitted: unchanged.
runaway_ceilingNoPing-rate ceiling: the most pings this monitor may receive in a rolling hour; exceeding it opens a 'runaway' incident. It catches a job or agent stuck in a loop, which keeps pinging and otherwise reads 'up'. A job every 15 minutes sends about 4 pings an hour, so 20 leaves room for retries. Rate-based: failure_threshold and run budgets do not gate it. Unset (default): the rule is off. Omitted: unchanged; 0 clears it and turns the runaway rule off.
notify_min_run_sNoNotification duration floor in seconds: a run shorter than this sends no info-class notification (success, every-run, note), so a trivial agent run does not notify a destination those events are routed to. It never suppresses a failure: down, fail, recovery, blocked and started always notify, as does an event whose duration was not measured (such as a success with no start ping). Unset (default): no floor. Range 60-31536000. Not supported on http monitors (400 NOTIFY_MIN_RUN_NOT_SUPPORTED). Omitted: unchanged; 0 clears it and turns the floor off.
probe_interval_sNohttp monitors only: how often to probe, in seconds. Required when monitor_type='http'. Range 30-86400. Omitted: unchanged.
blocked_timeout_sNoSeconds a run may stay 'blocked' (an agent reported it is waiting on a human) before a 'blocked' incident opens. Unset means the 24-hour default, not no limit. Separate from the 'blocked' notification a route delivers (held 10 minutes, sent at most once per blocked stretch of a run). Accepted on every monitor_type. Omitted: unchanged; 0 clears it (the 24h default applies).
failure_thresholdNoConsecutive failures required before an incident opens; default 1 (the first failure). 2-5 absorbs a job's occasional self-resolving failure. Any success resets the count. It gates only the 'fail' cause: silence, overrun, never_started and runaway are time- or rate-based and never delayed by it. A run ending on the model provider's API error (server_error, overloaded, rate_limit) neither counts nor resets it; 3 such runs in a row, or this many when higher, open an 'upstream' incident. Range 1-100. Omitted: unchanged.
probe_expected_bodyNohttp monitors only: a substring the response body has to contain for the probe to pass, e.g. '"status":"ok"'. A broken app's error page can still return 200 and pass a status-only check; this catches it. Case-sensitive substring, not a regex. Default empty: the body is not inspected. Omitted or an empty string: unchanged; an explicit JSON null stops inspecting the body.
probe_expected_statusNohttp monitors only: the exact HTTP status that counts as healthy; default 200, any other code fails the probe. E.g. 204 for a no-content endpoint, or 301 to check that a redirect still exists (with probe_follow_redirects=false; otherwise the probe sees the destination's status). Omitted: unchanged.
probe_follow_redirectsNohttp monitors only: whether the probe follows 3xx redirects; default false. When false, the redirect itself is compared with probe_expected_status, so a site that starts redirecting to a login wall or a parking page fails the probe instead of passing on the destination's 200. Omitted: unchanged; false turns following back off.

Schema Changelog

Changes observed during successful MCP inspections.

  1. Changed22 schema fields changedv0.1.17
    • changedInput schema / properties / agent_id / description
      Previous value: -"Attach this monitor to an agent from the registry, by the agent's id OR its slug (both are returned by register_agent). Omit for a monitor with no owning agent. Naming an agent that does not exist is an error — 400 UNKNOWN_AGENT — it is NEVER created implicitly; call register_agent first to get a valid agent_id. Omit to leave the monitor's current attachment (or lack of one) unchanged."New value: +"Attaches the monitor to a registered agent, by the agent's id or slug (register_agent returns both). An agent that does not exist is an error (400 UNKNOWN_AGENT); no agent is created implicitly. Omitted: the current attachment (or lack of one) is unchanged."
    • changedInput schema / properties / assertions / description
      Previous value: -"Output assertions: conditions the ping BODY of a successful run must satisfy, checked on every success ping. This is how you catch the job that exits zero having done nothing — a backup that wrote no rows, an export that produced an empty file. When an assertion fails, the success ping opens an incident with cause 'assertion' naming the assertion that did not hold, exactly as a real failure would. Supply a JSON ARRAY as a string, e.g. '[{\"name\":\"rows written\",\"kind\":\"json_path\",\"path\":\"result.rows_processed\",\"op\":\"gt\",\"value\":\"0\"}]'. REPLACE-THE-SET: the array you send becomes the monitor's complete assertion set — it is NOT merged with what is already there. Omit the argument entirely to leave the current assertions untouched; pass '[]' to remove all of them. Fields per entry: name (required, appears in the alert), kind (required), value, path, op. kind is one of 'contains' (body contains value as a substring), 'not_contains' (body does not contain it), 'matches' (body matches value as a Go RE2 regexp, max 1000 bytes), or 'json_path' (parse the body as JSON, read the value at path, compare it against value with op). contains/not_contains/matches require value; json_path requires path and op and ignores them otherwise. path is a DOTTED path only ('a.b.c') — the query syntax of a real JSONPath library ('[', '*', '$') is rejected. op is one of 'eq', 'ne', 'gt', 'gte', 'lt', 'lte'. Comparison rule for json_path: when BOTH the value read from the body and the value you supplied parse as numbers the comparison is numeric, otherwise both sides are compared as strings — so with op 'gt', value '3' beats '12.5' lexically but loses numerically, and 'rows_processed gt 0' means what it looks like it means. At most 20 assertions per monitor. A malformed entry (uncompilable regexp, a path carrying query syntax, an unknown kind or op) is rejected before anything is written and names the offending assertion."New value: +"Output assertions: conditions a successful run's ping body has to satisfy; one that fails opens an incident with cause 'assertion' naming it. A JSON array as a string, e.g. '[{\"name\":\"rows written\",\"kind\":\"json_path\",\"path\":\"result.rows_processed\",\"op\":\"gt\",\"value\":\"0\"}]'. The array replaces the whole set; '[]' removes all, omitted leaves it. Fields: name, kind (both required), value, path, op. kind: contains, not_contains, matches (RE2, max 1000 bytes) or json_path (compares the value at a dotted path, e.g. 'a.b.c', with op: eq, ne, gt, gte, lt, lte; numeric when both sides are numbers, otherwise compared as strings). At most 20; a malformed entry is rejected by name; the set stays as is."
    • changedInput schema / properties / blocked_timeout_s / description
      Previous value: -"Maximum seconds a run may sit in the 'blocked' state (an agent reported it is waiting on a human) before a 'blocked' incident opens. UNSET DOES NOT MEAN WAIT FOREVER: omitting this does not disable the timeout, it falls back to the default, which is 24 HOURS — an agent still blocked 24 hours after reporting so, with this field never set, gets a 'blocked' incident regardless. Lower it to be paged sooner when a stuck approval is urgent; raise it for work that legitimately waits on a human for longer than a day. This is distinct from the non-incident 'blocked' notification a route on the 'blocked' event type delivers (see set_route), which is held 10 minutes and sent only if the run is still blocked then, at most once per blocked stretch of a run; this field governs the separate incident that opens only if the wait outlives the timeout. Accepted on every monitor_type: unlike max_runtime_s/step_timeout_s it has no run-scoped precondition an http monitor could fail, so there is nothing to reject. Omit to leave the monitor's current value unchanged; pass 0 to clear it and fall back to the 24h default."New value: +"Seconds a run may stay 'blocked' (an agent reported it is waiting on a human) before a 'blocked' incident opens. Unset means the 24-hour default, not no limit. Separate from the 'blocked' notification a route delivers (held 10 minutes, sent at most once per blocked stretch of a run). Accepted on every monitor_type. Omitted: unchanged; 0 clears it (the 24h default applies)."
    • changedInput schema / properties / ci_branch / description
      Previous value: -"CI filter: only count runs on this branch, e.g. 'main'. REQUIRES ci_provider, and the API enforces it: without a CI binding the request is refused with 400 FIELD_NOT_IN_SHAPE rather than accepted and discarded. Subject to the SAME upsert exception as ci_workflow — create_monitor on an existing slug never writes this filter; use update_monitor. WITHOUT IT a run on ANY branch — a feature branch, a fork's pull request — reports to this monitor, so somebody else's broken branch marks your monitor down. Set it to the branch whose health you actually care about, which is almost always the default branch. Omit to leave the current filter unchanged; pass an explicit JSON null to remove it. An EMPTY STRING also leaves it unchanged — that is a deliberate API compatibility rule, not a bug, so an empty string cannot be used to clear the filter."New value: +"CI filter: only runs on this branch count, e.g. 'main'. Needs ci_provider (400 FIELD_NOT_IN_SHAPE without a CI binding). Same upsert exception as ci_workflow: create_monitor on an existing slug never writes it; update_monitor does. Unset, a run on any branch, a fork's pull request included, reports to the monitor. Omitted or an empty string (an API compatibility rule): unchanged; an explicit JSON null removes the filter."
    • changedInput schema / properties / ci_workflow / description
      Previous value: -"CI filter: only count runs of the workflow / pipeline / job with this exact name. REQUIRES ci_provider, and the API enforces it: without a CI binding this filter has nowhere to be stored, so the request is refused with 400 FIELD_NOT_IN_SHAPE rather than accepted and discarded. Note that monitor_type='ci' does NOT bind anything on its own — ci_provider does. ONE EXCEPTION, and it is on the path agents use most, so do not rely on the enforcement here: create_monitor on a slug that ALREADY EXISTS is an upsert, and the upsert never writes this filter. With ci_provider in the same call the request is accepted and the filter is silently discarded; without it the request is refused, and doing what the error advises — adding ci_provider — reaches the discarding case instead. Set this filter with update_monitor, which does persist it. WITHOUT IT, EVERY workflow in the repository reports to this monitor — so one unrelated failing workflow opens an incident against a job that is perfectly healthy, and a green run of a different workflow clears an incident the real job never recovered from. Set it whenever the repository has more than one workflow. Omit to leave the current filter unchanged; pass an explicit JSON null to remove it. An EMPTY STRING also leaves it unchanged — that is a deliberate API compatibility rule, not a bug, so an empty string cannot be used to clear the filter."New value: +"CI filter: only runs of the workflow, pipeline or job with this exact name count. Needs ci_provider: without a CI binding it is refused with 400 FIELD_NOT_IN_SHAPE (monitor_type='ci' alone binds nothing). Exception: create_monitor on an existing slug (an upsert) never writes it, and with ci_provider in the same call it is accepted and discarded; update_monitor persists it. Unset, every workflow in the repository reports to the monitor, so an unrelated workflow's failure opens an incident and its success clears one. Omitted or an empty string (an API compatibility rule): unchanged; an explicit JSON null removes the filter."
    • changedInput schema / properties / expect_every_s / description
      Previous value: -"SILENCE FLOOR in seconds: open a 'silence' incident if NO ping of any kind — success, start, fail, step — has arrived within this window, regardless of the schedule. It is anchored on the monitor's last activity, not on a cadence, which is what makes it the ONLY absence rule an 'on_demand' monitor can have: that schedule_kind arms nothing between runs, so without this field an on_demand monitor reads 'up' forever no matter how long the agent stays dark. Set it on any on_demand agent monitor you would be alarmed to find silent — that is what it is for. It does NOT fire mid-run: while a run is in flight (a start ping is outstanding) the floor stands down entirely and the run clock owns detection (max_runtime_s, step_timeout_s), so a legitimate 4-hour run that reports nothing is still not an incident. A 'blocked' ping also pauses it, bounded by blocked_timeout_s. On 'simple'/'cron' monitors it is a backstop rather than the main rule: it joins the existing deadline as whichever is SOONER, so it can tighten detection under a long cadence (a daily cron has a ~25-hour blind window) but can never loosen it. Default: unset, which means no floor and is exactly how every monitor behaved before this field existed. Range 60-31536000. Accepted on every monitor_type and every schedule_kind. Omit to leave the monitor's current value unchanged; pass 0 to clear it and turn the silence floor off."New value: +"Silence floor in seconds: a 'silence' incident opens when no ping of any kind (success, start, fail, step) arrives within this window, timed from the monitor's last activity. It is the only absence rule an 'on_demand' monitor has. It stands down while a run is in flight (the run clock owns that), and a 'blocked' ping pauses it up to blocked_timeout_s. On 'simple'/'cron' monitors it joins the schedule deadline as whichever is sooner, so it can only tighten detection. Unset (default): no floor. Range 60-31536000. Accepted on every monitor_type and schedule_kind. Omitted: unchanged; 0 clears it and turns the silence floor off."
    • changedInput schema / properties / failure_threshold / description
      Previous value: -"Number of consecutive failures required before an incident opens. Default 1 (open on the very first failure). This is how you stop a single transient blip from paging someone: set 2-5 on a job that fails occasionally for reasons that resolve themselves, and no incident opens until that many runs in a row have failed. Any success resets the count to zero. It gates the 'fail' cause ONLY — silence (a missed ping), overrun, never_started and runaway are time- or rate-based, so a consecutive count means nothing for them and they are never delayed by it. A run that ends on the model provider's API error (server_error, overloaded, rate_limit) neither counts toward it nor resets it: those open an 'upstream' incident once 3 runs in a row end that way, or this many when it is higher. Range 1-100. Omit to leave the monitor's current threshold unchanged."New value: +"Consecutive failures required before an incident opens; default 1 (the first failure). 2-5 absorbs a job's occasional self-resolving failure. Any success resets the count. It gates only the 'fail' cause: silence, overrun, never_started and runaway are time- or rate-based and never delayed by it. A run ending on the model provider's API error (server_error, overloaded, rate_limit) neither counts nor resets it; 3 such runs in a row, or this many when higher, open an 'upstream' incident. Range 1-100. Omitted: unchanged."
    • changedInput schema / properties / guards / description
      Previous value: -"Metric guards: CEILINGS on a number the job reports about itself, checked on every ping. An assertion catches a run that did nothing; a guard catches the opposite — an agent that loops, retries and burns money. Each guard reads one number out of the ping body at a dotted path, rolls it up across a trailing window, and opens an incident with cause 'runaway' when the total EXCEEDS the ceiling (equal does not trip). Supply a JSON ARRAY as a string, e.g. '[{\"name\":\"daily spend\",\"path\":\"cost.usd\",\"window_s\":86400,\"ceiling\":50,\"aggregation\":\"sum\"}]'. REPLACE-THE-SET: the array you send becomes the monitor's complete guard set — it is NOT merged with what is already there. Omit the argument entirely to leave the current guards untouched; pass '[]' to remove all of them. Fields per entry, all required: name (appears on the incident, and is the only thing that tells a tripped guard apart from the fixed pings-per-hour runaway ceiling), path (DOTTED path into the ping body parsed as JSON — 'cost.usd'; the query syntax of a real JSONPath library ('[', '*', '$') is rejected, exactly as for an assertion's path), window_s (trailing window in seconds), ceiling (number), aggregation (one of 'sum', 'max', 'avg'). Pings whose body is missing, is not JSON, or carries nothing numeric at that path are SKIPPED, not counted as zero — so a `start` ping never drags an average down. At most 5 guards per monitor, and window_s at most 604800 seconds (7 days). Both caps are cost, not policy: a guard re-aggregates every ping body in its window on every ping, so the per-ping work is linear in BOTH the window and the number of guards (measured: 4.2 ms/ping at a 1-hour window, 390 ms/ping at 30 days). A window longer than the 90-day ping retention would also aggregate over already-pruned rows and quietly under-report. A malformed entry is rejected before anything is written and names the offending guard."New value: +"Metric guards: ceilings on a number the job reports, checked on every ping; a total above the ceiling (equal does not trip) opens a 'runaway' incident. A JSON array as a string, e.g. '[{\"name\":\"daily spend\",\"path\":\"cost.usd\",\"window_s\":86400,\"ceiling\":50,\"aggregation\":\"sum\"}]'. The array replaces the whole set; '[]' removes all, omitted leaves it. Every entry needs name, path (dotted), window_s (trailing seconds), ceiling and aggregation ('sum', 'max' or 'avg'). Pings with no number at the path are skipped, not counted as zero. At most 5 guards, window_s at most 604800 (7 days); a malformed entry is rejected by name; the set stays as is."
    • changedInput schema / properties / max_runtime_s / description
      Previous value: -"Maximum seconds a single run may take before it is reported overdue (the 'overrun' rule), measured from the run's start ping. Omit to fall back to grace_s. This is how a long job avoids being flagged overdue while still being detected quickly if it goes silent: e.g. grace_s=600 with max_runtime_s=14400 alerts 10 minutes after a missed ping but tolerates a 4-hour run. It replaces grace_s for the overrun deadline ONLY — the silence rule and the first-run deadline still use grace_s. Range 60-31536000. Not supported on http monitors: a probe has no start/success pair, so the overrun rule can never fire and the API returns 400 MAX_RUNTIME_NOT_SUPPORTED (use probe_timeout_s to bound a single probe). Omit to leave the monitor's current value unchanged; pass 0 to clear it and fall back to grace_s."New value: +"Maximum seconds one run may take, from its start ping, before it is reported overdue (the 'overrun' rule). Unset falls back to grace_s. Example: grace_s=600 with max_runtime_s=14400 alerts 10 minutes after a missed ping but tolerates a 4-hour run. It replaces grace_s for the overrun deadline only; the silence rule and the first-run deadline still use grace_s. Range 60-31536000. Not supported on http monitors (400 MAX_RUNTIME_NOT_SUPPORTED); probe_timeout_s bounds a probe. Omitted: unchanged; 0 clears it (falls back to grace_s)."
    • changedInput schema / properties / monitor_from / description
      Previous value: -"DORMANT UNTIL: an RFC 3339 timestamp before which no deadline is computed and no incident can open — the monitor is fully configured but not yet armed. Use it when you provision ahead of the work: a monitor for a job that does not start running until next Monday is otherwise 'late' from the moment you create it, which is a false alert on day one. The first-run deadline is seeded as monitor_from + grace_s. Default: unset, meaning deadlines start immediately. Example: '2026-01-01T00:00:00Z'. Omit to leave the monitor's current value unchanged."New value: +"Dormant until: an RFC 3339 timestamp before which no deadline is computed and no incident opens, for a monitor provisioned ahead of its job. The first-run deadline is monitor_from + grace_s. Unset (default): deadlines start immediately. Example: '2026-01-01T00:00:00Z'. Omitted: unchanged."
    • changedInput schema / properties / notify_min_run_s / description
      Previous value: -"NOTIFICATION DURATION FLOOR in seconds: a run SHORTER than this does not produce an INFO-CLASS notification (success, started, every-run, note). This exists for exactly one problem: on an agent monitor, one run is one task you asked for, so asking the agent 'what's 2+2' produces a start and a success notification exactly like a 56-minute deploy does. If you have routed success/started/every-run/note to a destination, you WILL be paged for trivial runs unless you set this. IT NEVER SUPPRESSES A FAILURE. down, fail, recovery and blocked are alert-class and are never affected by this field, however short the run — a run that failed in two seconds is exactly what you need to hear about, and this field cannot silence that, structurally, no matter how it is set. It also never suppresses 'started': a run's duration does not exist yet the moment it begins, so started is always reported regardless of this floor. And it never suppresses an event whose duration could not be measured at all (e.g. a bare success with no preceding start ping) — an unknown duration always means 'notify', never 'suppress'. Default: unset, which means no floor and is exactly how every monitor behaved before this field existed. Range 60-31536000. Not supported on http monitors: an http probe has no start/success pair, so its run duration is never measured and the floor could never apply (the API returns 400 NOTIFY_MIN_RUN_NOT_SUPPORTED). Omit to leave the monitor's current value unchanged; pass 0 to clear it and turn the notification duration floor off."New value: +"Notification duration floor in seconds: a run shorter than this sends no info-class notification (success, every-run, note), so a trivial agent run does not notify a destination those events are routed to. It never suppresses a failure: down, fail, recovery, blocked and started always notify, as does an event whose duration was not measured (such as a success with no start ping). Unset (default): no floor. Range 60-31536000. Not supported on http monitors (400 NOTIFY_MIN_RUN_NOT_SUPPORTED). Omitted: unchanged; 0 clears it and turns the floor off."
    • changedInput schema / properties / probe_expected_body / description
      Previous value: -"http monitors only: a substring that MUST appear in the response body for the probe to count as healthy. THIS IS THE DIFFERENCE BETWEEN 'the server answered' AND 'the app works': a broken app that renders an error page still returns 200, passes a status-only check, and leaves the monitor green. Match on something only a healthy response contains, e.g. '\"status\":\"ok\"'. Substring match, not a regex, and case-sensitive. Default: empty, meaning the body is not inspected at all. Omit to leave unchanged; pass an explicit JSON null to stop inspecting the body. An empty string leaves it unchanged, so it cannot be cleared that way."New value: +"http monitors only: a substring the response body has to contain for the probe to pass, e.g. '\"status\":\"ok\"'. A broken app's error page can still return 200 and pass a status-only check; this catches it. Case-sensitive substring, not a regex. Default empty: the body is not inspected. Omitted or an empty string: unchanged; an explicit JSON null stops inspecting the body."
    • changedInput schema / properties / probe_expected_status / description
      Previous value: -"http monitors only: the EXACT HTTP status code that counts as healthy. Default 200; any other code fails the probe. Set it when the healthy answer is not 200 — 204 for a no-content health endpoint, or 301 when what you are checking is that a redirect still exists (pair that with probe_follow_redirects=false, or the probe will follow it and see the destination's status instead). Omit to leave unchanged."New value: +"http monitors only: the exact HTTP status that counts as healthy; default 200, any other code fails the probe. E.g. 204 for a no-content endpoint, or 301 to check that a redirect still exists (with probe_follow_redirects=false; otherwise the probe sees the destination's status). Omitted: unchanged."
    • changedInput schema / properties / probe_follow_redirects / description
      Previous value: -"http monitors only: whether the probe follows 3xx redirects. Default false. Leaving it false is usually what you want: the redirect itself is then compared against probe_expected_status like any other response, so a site that starts redirecting to a login wall, a parking page or an outage notice is CAUGHT rather than silently followed to a healthy-looking 200. Set true only when the URL you are checking is legitimately a redirect to the thing you actually care about. Omit to leave unchanged; pass false to turn following back off."New value: +"http monitors only: whether the probe follows 3xx redirects; default false. When false, the redirect itself is compared with probe_expected_status, so a site that starts redirecting to a login wall or a parking page fails the probe instead of passing on the destination's 200. Omitted: unchanged; false turns following back off."
    • changedInput schema / properties / probe_interval_s / description
      Previous value: -"http monitors only: how often to probe, in seconds. Required when monitor_type='http'. Range 30-86400. Omit to leave unchanged."New value: +"http monitors only: how often to probe, in seconds. Required when monitor_type='http'. Range 30-86400. Omitted: unchanged."
    • changedInput schema / properties / probe_method / description
      Previous value: -"http monitors only: the HTTP method the probe sends. One of 'GET', 'HEAD', 'POST'. Default 'GET'. Use 'HEAD' for a cheap liveness check when the body does not matter — but note it returns no body, so probe_expected_body cannot match anything. Omit to leave unchanged."New value: +"http monitors only: the HTTP method the probe sends: 'GET' (default), 'HEAD' or 'POST'. 'HEAD' returns no body, so probe_expected_body cannot match with it. Omitted: unchanged."
    • changedInput schema / properties / probe_timeout_s / description
      Previous value: -"http monitors only: how many seconds a single probe may take before it counts as a failure. Range 1-30, default 10. This is the http equivalent of max_runtime_s, which http monitors reject: it is the only way to say 'answering, but far too slowly to be healthy'. Omit to leave unchanged."New value: +"http monitors only: seconds one probe may take before it counts as a failure. Range 1-30, default 10. The http counterpart of max_runtime_s, which http monitors reject. Omitted: unchanged."
    • changedInput schema / properties / probe_url / description
      Previous value: -"http monitors only: the absolute http/https URL to probe. Required when monitor_type='http'. The host is resolved at write time and rejected if it resolves only to private/link-local addresses. Omit to leave unchanged."New value: +"http monitors only: the absolute http/https URL to probe. Required when monitor_type='http'. The host is resolved at write time and rejected if it resolves only to private/link-local addresses. Omitted: unchanged."
    • changedInput schema / properties / runaway_ceiling / description
      Previous value: -"PING-RATE CEILING: the maximum number of pings this monitor may receive in a rolling one-hour window. Exceeding it opens a 'runaway' incident. This is the rule that catches a job or agent stuck in a LOOP — the failure every other rule misses, because a looping agent is pinging enthusiastically and therefore reads 'up' the whole time it is burning tokens or money. Set it a little above the monitor's real cadence: a job that runs every 15 minutes sends about 4 pings/hour, so 20 absorbs retries and still catches a loop. It is RATE-based, so failure_threshold does not gate it and neither does any run budget. Default: unset, which disables the runaway rule entirely. Omit to leave the monitor's current value unchanged; pass 0 to clear it and turn the runaway rule off."New value: +"Ping-rate ceiling: the most pings this monitor may receive in a rolling hour; exceeding it opens a 'runaway' incident. It catches a job or agent stuck in a loop, which keeps pinging and otherwise reads 'up'. A job every 15 minutes sends about 4 pings an hour, so 20 leaves room for retries. Rate-based: failure_threshold and run budgets do not gate it. Unset (default): the rule is off. Omitted: unchanged; 0 clears it and turns the runaway rule off."
    • changedInput schema / properties / schedule_kind / description
      Previous value: -"'simple', 'cron', or 'on_demand'. NOT ACCEPTED on an http monitor, together with period_s, cron_expr and tz: its schedule is derived from probe_interval_s, so the API refuses all four with 400 FIELD_NOT_IN_SHAPE. 'on_demand' means no cadence at all: no period_s, no cron_expr — the API returns 400 if either is supplied — and, by default, NO ABSENCE DEADLINES ARE ARMED BETWEEN RUNS. What this trades away: nothing tells you if the agent is never invoked again; silence between runs is invisible unless you opt in to expect_every_s. What it buys: a healthy agent that nobody happens to invoke for a week never generates a false 'late' or 'down' for simply not having been asked to run. Only run-scoped detection still applies once a run starts — max_runtime_s (overrun), step_timeout_s (stall), blocked_timeout_s (stuck on a human) — because those are anchored to a run's own start ping, not to a cadence. IMPORTANT: if you would be alarmed to find this agent silent for hours, set expect_every_s as well — it is the silence floor, and it is the only thing that makes an on_demand monitor detect absence at all. Choose 'simple'/'cron' when the agent is supposed to run on a cadence; choose 'on_demand' when invocation is inherently irregular and a quiet stretch between runs is expected, not a symptom."New value: +"'simple', 'cron' or 'on_demand'. Not accepted on an http monitor, and neither are period_s, cron_expr and tz: its schedule comes from probe_interval_s (400 FIELD_NOT_IN_SHAPE). 'on_demand' has no cadence (period_s or cron_expr with it is a 400) and arms no absence deadline between runs: an agent that is never invoked again raises nothing unless expect_every_s is set, and an idle healthy agent never raises a false 'late'. Once a run starts, max_runtime_s, step_timeout_s and blocked_timeout_s still apply. 'simple'/'cron' fit work on a cadence; 'on_demand' fits irregular invocation."
    • changedInput schema / properties / step_timeout_s / description
      Previous value: -"Progress budget in seconds: how long an armed run may go without reporting a step before a 'stalled' incident opens (the stall rule). The clock is anchored on the LATER of the run's start ping and its most recent step, so a run that wedges before its first step is caught too. Reach for this when 'still running' and 'still making progress' are different things — a long agent loop, a multi-stage pipeline, a migration. max_runtime_s alone tells you nothing until the whole budget expires; step_timeout_s=300 on a 4-hour budget tells you within five minutes, and names the last step that reported. To use it the run must report steps: call get_ping_instructions and use curl_step (POST <ping_url>/step?rid=<run-id>&step=<name>). A monitor with step_timeout_s set whose job never reports a step will open a stalled incident on EVERY run — set the field and instrument the job in the same change. Stall detection needs your job to call /step: the Claude Code hook and the reporting prompt do not send steps, so leave step_timeout_s unset for them. Default: unset, which disables stall detection entirely; a monitor that sets nothing behaves exactly as it did before this field existed. Range 10-86400. Two constraints. (1) It must be strictly LESS than the effective run budget, COALESCE(max_runtime_s, grace_s), or the API returns 400 STEP_TIMEOUT_EXCEEDS_BUDGET — at or above the budget the run overruns first, so the stall rule could never fire. (2) Not supported on http monitors: a probe never arms a run and has no /step endpoint to call, so the API returns 400 STEP_TIMEOUT_NOT_SUPPORTED. A step resets the stall clock ONLY — it never extends max_runtime_s, so an agent that reports progress forever still overruns. Omit to leave the monitor's current value unchanged; pass 0 to clear it and disable stall detection."New value: +"Seconds an armed run may go without a step before a 'stalled' incident opens, timed from the later of its start ping and its latest step. Steps are POST <ping_url>/step?rid=<run-id>&step=<name> (curl_step in get_ping_instructions). When set, a job that sends no steps opens a stalled incident on every run. The Claude Code hook and the reporting prompt send no steps. Unset (default): no stall detection. Range 10-86400, strictly below COALESCE(max_runtime_s, grace_s) (400 STEP_TIMEOUT_EXCEEDS_BUDGET). Not supported on http monitors (400 STEP_TIMEOUT_NOT_SUPPORTED). A step never extends max_runtime_s. Omitted: unchanged; 0 clears it and turns stall detection off."
    • changedInput schema / properties / trace_content / description
      Previous value: -"What this monitor's traces keep of prompt, command and tool content. 'dropped' (the default) removes it; 'redacted' keeps it, with every secret-shaped value redacted when it arrives. Only a person should choose 'redacted': never set it on your own initiative, only when the person you work for has asked for content to be stored. Omit to leave the monitor's current value unchanged."New value: +"What this monitor's traces keep of prompt, command and tool content: 'dropped' (the default) removes it; 'redacted' stores prompt, command and tool content from the traced sessions, with secret-shaped values redacted on arrival. Omitted: unchanged."
  2. Changed3 schema fields changedv0.1.9
    • changedInput schema / properties / blocked_timeout_s / description
      Previous value: -"Maximum seconds a run may sit in the 'blocked' state (an agent reported it is waiting on a human) before a 'blocked' incident opens. UNSET DOES NOT MEAN WAIT FOREVER: omitting this does not disable the timeout, it falls back to the default, which is 24 HOURS — an agent still blocked 24 hours after reporting so, with this field never set, gets a 'blocked' incident regardless. Lower it to be paged sooner when a stuck approval is urgent; raise it for work that legitimately waits on a human for longer than a day. This is distinct from the immediate, non-incident 'blocked' notification a route on the 'blocked' event type delivers the moment the agent reports it (see set_route) — that fires right away; this field governs the separate incident that opens only if the wait outlives the timeout. Accepted on every monitor_type: unlike max_runtime_s/step_timeout_s it has no run-scoped precondition an http monitor could fail, so there is nothing to reject. Omit to leave the monitor's current value unchanged; pass 0 to clear it and fall back to the 24h default."New value: +"Maximum seconds a run may sit in the 'blocked' state (an agent reported it is waiting on a human) before a 'blocked' incident opens. UNSET DOES NOT MEAN WAIT FOREVER: omitting this does not disable the timeout, it falls back to the default, which is 24 HOURS — an agent still blocked 24 hours after reporting so, with this field never set, gets a 'blocked' incident regardless. Lower it to be paged sooner when a stuck approval is urgent; raise it for work that legitimately waits on a human for longer than a day. This is distinct from the non-incident 'blocked' notification a route on the 'blocked' event type delivers (see set_route), which is held 10 minutes and sent only if the run is still blocked then, at most once per blocked stretch of a run; this field governs the separate incident that opens only if the wait outlives the timeout. Accepted on every monitor_type: unlike max_runtime_s/step_timeout_s it has no run-scoped precondition an http monitor could fail, so there is nothing to reject. Omit to leave the monitor's current value unchanged; pass 0 to clear it and fall back to the 24h default."
    • changedInput schema / properties / failure_threshold / description
      Previous value: -"Number of consecutive failures required before an incident opens. Default 1 (open on the very first failure). This is how you stop a single transient blip from paging someone: set 2-5 on a job that fails occasionally for reasons that resolve themselves, and no incident opens until that many runs in a row have failed. Any success resets the count to zero. It gates the 'fail' cause ONLY — silence (a missed ping), overrun, never_started and runaway are time- or rate-based, so a consecutive count means nothing for them and they are never delayed by it. Range 1-100. Omit to leave the monitor's current threshold unchanged."New value: +"Number of consecutive failures required before an incident opens. Default 1 (open on the very first failure). This is how you stop a single transient blip from paging someone: set 2-5 on a job that fails occasionally for reasons that resolve themselves, and no incident opens until that many runs in a row have failed. Any success resets the count to zero. It gates the 'fail' cause ONLY — silence (a missed ping), overrun, never_started and runaway are time- or rate-based, so a consecutive count means nothing for them and they are never delayed by it. A run that ends on the model provider's API error (server_error, overloaded, rate_limit) neither counts toward it nor resets it: those open an 'upstream' incident once 3 runs in a row end that way, or this many when it is higher. Range 1-100. Omit to leave the monitor's current threshold unchanged."
    • changedInput schema / properties / step_timeout_s / description
      Previous value: -"Progress budget in seconds: how long an armed run may go without reporting a step before a 'stalled' incident opens (the stall rule). The clock is anchored on the LATER of the run's start ping and its most recent step, so a run that wedges before its first step is caught too. Reach for this when 'still running' and 'still making progress' are different things — a long agent loop, a multi-stage pipeline, a migration. max_runtime_s alone tells you nothing until the whole budget expires; step_timeout_s=300 on a 4-hour budget tells you within five minutes, and names the last step that reported. To use it the run must report steps: call get_ping_instructions and use curl_step (POST <ping_url>/step?rid=<run-id>&step=<name>). A monitor with step_timeout_s set whose job never reports a step will open a stalled incident on EVERY run — set the field and instrument the job in the same change. Default: unset, which disables stall detection entirely; a monitor that sets nothing behaves exactly as it did before this field existed. Range 10-86400. Two constraints. (1) It must be strictly LESS than the effective run budget, COALESCE(max_runtime_s, grace_s), or the API returns 400 STEP_TIMEOUT_EXCEEDS_BUDGET — at or above the budget the run overruns first, so the stall rule could never fire. (2) Not supported on http monitors: a probe never arms a run and has no /step endpoint to call, so the API returns 400 STEP_TIMEOUT_NOT_SUPPORTED. A step resets the stall clock ONLY — it never extends max_runtime_s, so an agent that reports progress forever still overruns. Omit to leave the monitor's current value unchanged; pass 0 to clear it and disable stall detection."New value: +"Progress budget in seconds: how long an armed run may go without reporting a step before a 'stalled' incident opens (the stall rule). The clock is anchored on the LATER of the run's start ping and its most recent step, so a run that wedges before its first step is caught too. Reach for this when 'still running' and 'still making progress' are different things — a long agent loop, a multi-stage pipeline, a migration. max_runtime_s alone tells you nothing until the whole budget expires; step_timeout_s=300 on a 4-hour budget tells you within five minutes, and names the last step that reported. To use it the run must report steps: call get_ping_instructions and use curl_step (POST <ping_url>/step?rid=<run-id>&step=<name>). A monitor with step_timeout_s set whose job never reports a step will open a stalled incident on EVERY run — set the field and instrument the job in the same change. Stall detection needs your job to call /step: the Claude Code hook and the reporting prompt do not send steps, so leave step_timeout_s unset for them. Default: unset, which disables stall detection entirely; a monitor that sets nothing behaves exactly as it did before this field existed. Range 10-86400. Two constraints. (1) It must be strictly LESS than the effective run budget, COALESCE(max_runtime_s, grace_s), or the API returns 400 STEP_TIMEOUT_EXCEEDS_BUDGET — at or above the budget the run overruns first, so the stall rule could never fire. (2) Not supported on http monitors: a probe never arms a run and has no /step endpoint to call, so the API returns 400 STEP_TIMEOUT_NOT_SUPPORTED. A step resets the stall clock ONLY — it never extends max_runtime_s, so an agent that reports progress forever still overruns. Omit to leave the monitor's current value unchanged; pass 0 to clear it and disable stall detection."
  3. Changed4 schema fields changedv0.1.6
    • changedInput schema / properties / ci_branch / description
      Previous value: -"CI filter: only count runs on this branch, e.g. 'main'. Requires ci_provider. WITHOUT IT a run on ANY branch — a feature branch, a fork's pull request — reports to this monitor, so somebody else's broken branch marks your monitor down. Set it to the branch whose health you actually care about, which is almost always the default branch. Omit to leave the current filter unchanged; pass an explicit JSON null to remove it. An EMPTY STRING also leaves it unchanged — that is a deliberate API compatibility rule, not a bug, so an empty string cannot be used to clear the filter."New value: +"CI filter: only count runs on this branch, e.g. 'main'. REQUIRES ci_provider, and the API enforces it: without a CI binding the request is refused with 400 FIELD_NOT_IN_SHAPE rather than accepted and discarded. Subject to the SAME upsert exception as ci_workflow — create_monitor on an existing slug never writes this filter; use update_monitor. WITHOUT IT a run on ANY branch — a feature branch, a fork's pull request — reports to this monitor, so somebody else's broken branch marks your monitor down. Set it to the branch whose health you actually care about, which is almost always the default branch. Omit to leave the current filter unchanged; pass an explicit JSON null to remove it. An EMPTY STRING also leaves it unchanged — that is a deliberate API compatibility rule, not a bug, so an empty string cannot be used to clear the filter."
    • changedInput schema / properties / ci_workflow / description
      Previous value: -"CI filter: only count runs of the workflow / pipeline / job with this exact name. Requires ci_provider. WITHOUT IT, EVERY workflow in the repository reports to this monitor — so one unrelated failing workflow opens an incident against a job that is perfectly healthy, and a green run of a different workflow clears an incident the real job never recovered from. Set it whenever the repository has more than one workflow. Omit to leave the current filter unchanged; pass an explicit JSON null to remove it. An EMPTY STRING also leaves it unchanged — that is a deliberate API compatibility rule, not a bug, so an empty string cannot be used to clear the filter."New value: +"CI filter: only count runs of the workflow / pipeline / job with this exact name. REQUIRES ci_provider, and the API enforces it: without a CI binding this filter has nowhere to be stored, so the request is refused with 400 FIELD_NOT_IN_SHAPE rather than accepted and discarded. Note that monitor_type='ci' does NOT bind anything on its own — ci_provider does. ONE EXCEPTION, and it is on the path agents use most, so do not rely on the enforcement here: create_monitor on a slug that ALREADY EXISTS is an upsert, and the upsert never writes this filter. With ci_provider in the same call the request is accepted and the filter is silently discarded; without it the request is refused, and doing what the error advises — adding ci_provider — reaches the discarding case instead. Set this filter with update_monitor, which does persist it. WITHOUT IT, EVERY workflow in the repository reports to this monitor — so one unrelated failing workflow opens an incident against a job that is perfectly healthy, and a green run of a different workflow clears an incident the real job never recovered from. Set it whenever the repository has more than one workflow. Omit to leave the current filter unchanged; pass an explicit JSON null to remove it. An EMPTY STRING also leaves it unchanged — that is a deliberate API compatibility rule, not a bug, so an empty string cannot be used to clear the filter."
    • changedInput schema / properties / schedule_kind / description
      Previous value: -"'simple', 'cron', or 'on_demand'. 'on_demand' means no cadence at all: no period_s, no cron_expr — the API returns 400 if either is supplied — and, by default, NO ABSENCE DEADLINES ARE ARMED BETWEEN RUNS. What this trades away: nothing tells you if the agent is never invoked again; silence between runs is invisible unless you opt in to expect_every_s. What it buys: a healthy agent that nobody happens to invoke for a week never generates a false 'late' or 'down' for simply not having been asked to run. Only run-scoped detection still applies once a run starts — max_runtime_s (overrun), step_timeout_s (stall), blocked_timeout_s (stuck on a human) — because those are anchored to a run's own start ping, not to a cadence. IMPORTANT: if you would be alarmed to find this agent silent for hours, set expect_every_s as well — it is the silence floor, and it is the only thing that makes an on_demand monitor detect absence at all. Choose 'simple'/'cron' when the agent is supposed to run on a cadence; choose 'on_demand' when invocation is inherently irregular and a quiet stretch between runs is expected, not a symptom."New value: +"'simple', 'cron', or 'on_demand'. NOT ACCEPTED on an http monitor, together with period_s, cron_expr and tz: its schedule is derived from probe_interval_s, so the API refuses all four with 400 FIELD_NOT_IN_SHAPE. 'on_demand' means no cadence at all: no period_s, no cron_expr — the API returns 400 if either is supplied — and, by default, NO ABSENCE DEADLINES ARE ARMED BETWEEN RUNS. What this trades away: nothing tells you if the agent is never invoked again; silence between runs is invisible unless you opt in to expect_every_s. What it buys: a healthy agent that nobody happens to invoke for a week never generates a false 'late' or 'down' for simply not having been asked to run. Only run-scoped detection still applies once a run starts — max_runtime_s (overrun), step_timeout_s (stall), blocked_timeout_s (stuck on a human) — because those are anchored to a run's own start ping, not to a cadence. IMPORTANT: if you would be alarmed to find this agent silent for hours, set expect_every_s as well — it is the silence floor, and it is the only thing that makes an on_demand monitor detect absence at all. Choose 'simple'/'cron' when the agent is supposed to run on a cadence; choose 'on_demand' when invocation is inherently irregular and a quiet stretch between runs is expected, not a symptom."
    • addedInput schema / properties / trace_content
      Added value: +{
      +  "description": "What this monitor's traces keep of prompt, command and tool content. 'dropped' (the default) removes it; 'redacted' keeps it, with every secret-shaped value redacted when it arrives. Only a person should choose 'redacted': never set it on your own initiative, only when the person you work for has asked for content to be stored. Omit to leave the monitor's current value unchanged.",
      +  "enum": [
      +    "dropped",
      +    "redacted"
      +  ],
      +  "type": "string"
      +}
  4. First observed

TDQS

A4.5/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Adds substantial behavior beyond the annotations: write-scope requirement, merge-patch survival of omitted fields, whole-set replacement semantics for tags/assertions/guards (the destructive behavior destructiveHint flags), which causes the sets catch, and that slug and ci_provider are immutable. This is exactly the context an agent needs before mutating a monitor.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

A single dense paragraph that front-loads the scope requirement and the merge-patch rule before the details. Every sentence carries weight; the only cost is density that could be broken into list form for scanning.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a 29-param mutation tool with no output schema, the description covers the dangerous cross-cutting semantics well while the fully-populated schema handles per-field detail. It does not restate return behavior, which is acceptable given no output schema, but a note on what a successful update returns would round it out.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 100%, so the baseline is 3, but the description adds cross-parameter semantics the schema cannot: omitted-vs-present-vs-null handling, merge-patch interaction, and the immutability boundary of slug/ci_provider. It meaningfully reduces misuse risk for a 29-parameter tool.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb and resource ('Updates a monitor by UUID') and names the exact semantics (merge-patch). An agent can distinguish this from create_monitor, delete_monitor, pause_monitor, and snooze_monitor without opening any schema.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Gives the precondition ('Requires the write scope or higher') and the operational context: supplied fields change, omitted fields keep their stored value, and get_monitor returns the current sets for tags/assertions/guards. It stops short of explicitly naming when to prefer pause_monitor/snooze_monitor over a field update, so it is clear but not a full when/when-not routing.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.