Warning, /swf-monitor/docs/SNAPPER_ERRORS.md is written in an unsupported language. File is not indexed.
0001 # Snapper Errors — error-state component and errors view
0002
0003 PanDA job errors become part of the recorded system history: a curated
0004 error-state component captured in Snapper snaps, and a dedicated errors
0005 view over that history showing the time development of errors by
0006 category — error floods as they rise — with drilldown from any moment
0007 into the error breakdown and the underlying jobs. A per-task reading of
0008 the same history is a filter on the view, and the PanDA task page links
0009 to the filtered view, answering "when did this error show up in this
0010 task" directly.
0011
0012 This is a design document; the sections below are the agreed plan of
0013 record for implementation. It builds on the Snapper concepts and SWF
0014 deployment in [SNAPPER.md](SNAPPER.md) and the generic package
0015 documentation in the snapper-ai repository
0016 ([INTEGRATION.md](https://github.com/BNLNPPS/snapper-ai/blob/main/docs/INTEGRATION.md)).
0017 The error machinery it records is the PanDA monitoring layer of
0018 swf-monitor — platform infrastructure per the architecture map
0019 ([ARCHITECTURE_MAP.md](https://github.com/BNLNPPS/swf-epicprod/blob/main/docs/ARCHITECTURE_MAP.md)),
0020 serving every PanDA-using domain.
0021
0022 ## Error categorization
0023
0024 The category vocabulary is the foundation; the view reports whatever
0025 the vocabulary distinguishes.
0026
0027 PanDA records job errors in seven component fields, each an error code
0028 plus a diagnostic string: brokerage, ddm, executor, dispatcher, pilot,
0029 supervisor, and taskbuffer (`ERROR_COMPONENTS`,
0030 `monitor_app/panda/constants.py`). The existing error summary
0031 (`error_summary`, `monitor_app/panda/queries.py`; the
0032 `/panda/errors/` page and `panda_error_summary` MCP tool) aggregates
0033 failed jobs into patterns of component × code × leading diagnostic
0034 text, with a classified mode that attributes each job to its first
0035 nonzero component so one job lands in one category.
0036
0037 The Snapper category is **component × error code**:
0038
0039 - The code is the stable classification the producing subsystem
0040 itself assigned; it is available on every failed job record with no
0041 latency.
0042 - Category labels come from the component code catalogs — the pilot's
0043 `errorcodes.py` message table and the equivalent server-side code
0044 definitions — so categories read as named conditions, not bare
0045 numbers.
0046 - Diagnostic strings vary per job (paths, hostnames, identifiers) and
0047 are therefore moment detail presented in the breakdown, never part
0048 of category identity.
0049
0050 Every failed job row carries `jeditaskid` alongside the error fields,
0051 so the same categorization aggregates per task, per site, or per any
0052 other job attribute without additional classification work.
0053
0054 ### Terminal states
0055
0056 The job's terminal status is a second classification axis, recorded
0057 per entry. PanDA's own kill path assigns it with distinct semantics
0058 (`job_complex_module.py`, panda-server):
0059
0060 - **failed** — the job ran and ended in error: an actual error.
0061 - **cancelled** — a person or controlling system deliberately killed
0062 the job.
0063 - **closed** — the server disposed of the job for its own workflow
0064 reasons: pending expiry, reassignment, rebrokerage, task-done
0065 kills. By design not an actual error; a flood of closures signals
0066 an infrastructure condition (a stalled daemon, generation
0067 outrunning dispatch), not a payload problem.
0068
0069 Error presentations therefore exclude closed jobs by default and
0070 offer the terminal states as a filter, discovered from the recorded
0071 data with per-state counts always visible — a closure storm announces
0072 itself in its count without displacing the actual errors. The
0073 recorded history and the retrieval surface carry every state; the
0074 default applies to presentation only.
0075
0076 ### Progressive refinement
0077
0078 Two refinement tiers improve the vocabulary over time without changing
0079 the recorded history's structure:
0080
0081 - **Log-derived classification.** The Bamboo
0082 ([BNLNPPS/bamboo-mcp](https://github.com/BNLNPPS/bamboo-mcp))
0083 `classify_failure` analysis — already applied per job in
0084 `panda_study_job` — fetches a pilot-log excerpt and keyword-matches
0085 it together with the error fields into semantic failure categories
0086 such as `stagein_timeout`. Applied once per new pattern signature
0087 (one representative job, result cached), it annotates categories
0088 with a log-informed reading at a latency the 5-minute capture
0089 cadence absorbs.
0090 - **Error-state knowledge base.** Accumulated category annotations,
0091 representative cases, and their resolutions form the error
0092 knowledge base foreseen for the site-canary buildout. The snap
0093 history is its evidence store; nothing in this design needs to
0094 change to feed it.
0095
0096 ## The error-state component
0097
0098 A component, internal name `errors`, in the epicprod scope, published
0099 by a maintainer module beside the existing PanDA activity maintainer
0100 (`monitor_app/snapper_panda.py`) on the same 5-minute System-status
0101 refresh. Five minutes is the floor; the cadence is governed by the
0102 existing SysConfig capture policy and is raised, not lowered, if the
0103 component proves heavy.
0104
0105 Each publication records the error events of one interval:
0106
0107 - **Interval** — the half-open interval (start, end] the publication
0108 covers, running from the previous publication's source time to this
0109 one's.
0110 - **Entries** — one row per job that ended faulty in the interval:
0111 PanDA job id, JEDI task id, category, event time, and terminal
0112 status, as arrays in a declared column order. The event time is the
0113 job's end time, with one exception: a lost-heartbeat failure
0114 (dispatcher code 100) records the last heartbeat as its end time
0115 and the failure instant only as its modification time, so the
0116 failure instant is its event time — otherwise a kill storm appears
0117 on the plots hours before it happened. A job reports errors once,
0118 upon completion, so each failed job appears in exactly one
0119 interval.
0120 - **Overflow** — absent normally. An interval exceeding the entry bound
0121 (2,000 rows) keeps the earliest rows and folds the exact remainder
0122 into counts keyed `category@status`, so status-resolved aggregate
0123 counts never lose a job while the per-job listing stays bounded in
0124 storm floods.
0125
0126 The publisher is stateless: each pass reads the interval's faulty jobs
0127 from the PanDA job records. Counts over any period are sums of entry
0128 counts over the intervals it spans, and per-task readings filter the
0129 same entries by task id — no counters are stored. An interval with no
0130 errors is affirmed unchanged, advancing the source time with no new
0131 snap, so quiet periods cost nothing while the interval chain stays
0132 gapless. A missed or delayed publication loses nothing: the following
0133 interval covers the gap.
0134
0135 ### Backfill
0136
0137 The recorded job history carries end times, terminal statuses, and
0138 error fields for every failed job, so the interval record is
0139 reconstructible for any past period. The backfill script (`scripts/backfill-errors-entries.py`)
0140 writes synthetic errors snaps on the 5-minute grid over the trailing
0141 30 days: one snap per non-empty interval, with capture policy
0142 `backfill-errors-v1` marking reconstructed evidence as distinct from
0143 observed snaps. The backfilled record tiles exactly against the start
0144 of the first live interval, so each failed job lands in exactly one
0145 interval across the seam. The script is idempotent — a re-run
0146 replaces prior backfill — and dry-run by default. The deployment
0147 order is maintainer first, backfill after the first live publication.
0148
0149 ## The errors view
0150
0151 A dedicated Snapper page for error history, using the focus-view
0152 mechanism that serves the campaign page: its own clean path under the
0153 epicprod scope, its own curve families, and its own detail rendering.
0154 The error families are not added to the epicprod Time history report
0155 page, which carries its own distinct information; dashboard
0156 compositions may combine elements of both.
0157
0158 **Plot.** The category flood quilt: recorded error events by
0159 category, stacked. The server bins events once, by each job's end
0160 time, into sparse bins at the native 5-minute cadence; the page bins
0161 those into the display rung — the smallest of 5, 10, 15, 20, 30, and
0162 60 minutes keeping the plotted extent at or under 720 columns — and
0163 re-bins in place as the view zooms, down to the native bins, with no
0164 further server work. Every rung is an exact sum of native bins. A
0165 grouping selector switches between the seven-component grouping and
0166 the full component × code categories. Member tick boxes are omitted:
0167 identification lives in hover and the breakdown below.
0168
0169 **Share donut.** Category shares of the accruals within the display
0170 bounds, rendered with the annular SVG donut the site view's detail
0171 section established (`_site_outcomes_pie`,
0172 `monitor_app/snapper_providers.py`) — the at-a-glance signal beside
0173 the tables. The donut follows the display bounds and the active
0174 filters.
0175
0176 **Detail below the plot: the error breakdown.** A click on the plot is
0177 a time cut. The detail section renders the error breakdown around
0178 that moment, integrated over a window at least an hour wide — a
0179 single 5-minute interval is too sparse to read — organized by
0180 category:
0181
0182 - error counts and shares per category within the window;
0183 - the window's top diagnostic patterns, aggregated live from the job
0184 records;
0185 - representative job links — the job page, payload log, and job study;
0186 - the affected tasks, as detail within each category's section;
0187 - a link to the `/panda/errors/` pattern table windowed to the same
0188 bounds, for the full aggregation over live job records.
0189
0190 The breakdown is organized by error, not by task; the task reading
0191 comes from the filter.
0192
0193 **Terminal-state filter.** A chip row beside the grouping selector
0194 filters the view by terminal state. The chips are discovered from the
0195 loaded data — a state appears exactly when the record holds it — and
0196 each carries its count over the visible range in parentheses, so an
0197 excluded closure storm remains visible in its chip while the plot
0198 shows the actual errors. Closed is off by default (see Terminal
0199 states above); rows recorded before the status column report as
0200 `unrecorded` and age out of the window. The selection lives in the
0201 URL, filters client-side from the per-state breakdowns the bins
0202 carry (no refetch), and the breakdown, donut, and diagnostic
0203 patterns follow it.
0204
0205 **Task filter.** A task selection (URL parameter, so the view is
0206 bookmarkable and linkable) narrows the plot, donut, and breakdown to
0207 that task's events — the per-task error history is the overall view
0208 filtered, not a separate surface. The parameter is open: any task id
0209 reached by link is valid, and no task list is offered on the view
0210 itself. The PanDA task page (`panda/tasks/<jeditaskid>/`) links to
0211 its filtered errors view. Refinements specific to the per-task
0212 reading come later; the filter is the mechanism from the start.
0213
0214 ## Proactive storm response
0215
0216 Planned, the next stage of this design: the recorded error stream is
0217 the trigger surface for automatic storm response — detection,
0218 notification, and bounded automatic investigation that prepares
0219 information for human evaluation. Investigation latency is accepted
0220 by design; the alternative cost is operator time.
0221
0222 ### Storm detection
0223
0224 A detector rides the component publisher: every publication counts
0225 one interval, and the detector evaluates it against the trailing
0226 baseline. Detection covers every terminal state, closed included —
0227 the presentation default that filters closures out of the errors
0228 view never applies here, because a closure flood is itself an
0229 infrastructure signal. A storm starts when an interval's errors exceed the larger
0230 of an absolute floor and a multiple of the trailing median; it ends
0231 after a run of quiet intervals. Detection is stateful and emits on
0232 transitions — storm start, escalation, storm end — never per
0233 interval. Thresholds are SysConfig keys, present at their defaults.
0234 The detector makes the maintainer a canary in place: it already
0235 reads every interval of the production error stream, so no separate
0236 sentinel is needed.
0237
0238 Silence is itself a signal. An infrastructure incident can stall the
0239 maintainer's own pass, and the canary then reports nothing precisely
0240 when it matters most. A freshness watch on the errors publication
0241 emits through the same router when the component goes quiet beyond a
0242 few cycles, at a severity distinct from a storm.
0243
0244 ### Notice and alarm
0245
0246 Storm start emits a Capcom notice through the notice router carrying
0247 the attribution reading — the category, task, and site concentration
0248 verdicts the breakdown card computes — and a link to the errors view
0249 windowed to the storm. Storm end reports totals. An alarm fires only
0250 above a second, higher threshold, itself a configuration value.
0251
0252 ### Bounded drilldown
0253
0254 Storm start also triggers the deterministic investigation tier
0255 through the production-operations agent: Bamboo log classification
0256 (the classify_failure analysis of the refinement tiers above) on one
0257 representative job per top diagnostic pattern, with results entering
0258 the action stream and enriching the notice. Directed canary probes
0259 join this tier when canary jobs exist (site-canary). The work is
0260 bounded per storm: a fixed number of representative jobs, one pass
0261 per transition.
0262
0263 ### AI analysis tier
0264
0265 An AI pass over the mined material — summarizing, hypothesizing,
0266 drafting the evaluation brief — is the tier where analysis beyond
0267 deterministic tooling can add value, applied only where the
0268 deterministic tiers stop. The apparatus exists: a threshold-gated,
0269 case-specific action launches a corun-ai evaluation of the storm
0270 dossier — the attribution reading, the diagnostic patterns, and the
0271 drilldown results — and the returned evaluation document links from
0272 the notice for human review. Spend and gating remain explicit
0273 operator configuration; the tiers above stand on their own without
0274 it.
0275
0276 ### Bounded action
0277
0278 The escalation path beyond evaluation: the storm AI first notifies,
0279 then proposes, then — within explicit bounds — acts. The proposal
0280 stage uses the established AI-proposals pattern (AI_PROPOSALS.md):
0281 the evaluation concludes with a concrete deterministic action, such
0282 as pausing the dominant task, that a human approves in one step.
0283 Bounded autonomous action comes later and stays within a curated
0284 vocabulary of reversible operations — task pause is the model case,
0285 and the task-operation set already excludes irreversible actions —
0286 gated by its own thresholds and an explicit allowlist. The goal
0287 condition: within minutes of a major failure, the flow of jobs into
0288 a failing configuration is stopped, and the human reviews an action
0289 that one step undoes.
0290
0291 ## Retrieval
0292
0293 The component rides the existing Snapper retrieval surface unchanged:
0294 the REST endpoints and MCP tools (`snapper_component_history`,
0295 `snapper_state_at`, `snapper_changes_between`) answer when a category
0296 appeared, grew, or stopped in a scope or a task, with the standard
0297 evidence envelopes. Assessment harnesses and other AI consumers read
0298 the same history the view renders.
0299
0300 ## Implementation notes
0301
0302 - Maintainer module `monitor_app/snapper_errors.py`, invoked from the
0303 System-status refresh beside `publish_panda_activity`; publication
0304 errors fail visibly per the existing maintainer convention.
0305 - Provider additions in `monitor_app/snapper_providers.py`: event
0306 extraction from the interval entries into category, component, and
0307 per-task curves; the errors focus view with its grouping selector
0308 and open task parameter; the breakdown card; the donut context.
0309 - Generic mechanisms in the snapper-ai package: the event_values
0310 provider hook and event-flow rendering (`snapper_ai/series.py` and
0311 the observatory template), and the open_option focus hook
0312 (`snapper_ai/views.py`).
0313 - Curve identifiers carry the category vocabulary; any vocabulary
0314 change bumps the series cache version per the standing rule.
0315 - Component bounds: entries capped per interval with exact overflow
0316 folding. The component stays curated, bounded JSON per the Snapper
0317 design contract.
0318
0319 ## Related
0320
0321 - [SNAPPER.md](SNAPPER.md) — Snapper operations in SWF: capture
0322 scheduler, component maintainers, web presentation.
0323 - [ARCHITECTURE_MAP.md](https://github.com/BNLNPPS/swf-epicprod/blob/main/docs/ARCHITECTURE_MAP.md)
0324 — platform placement of the PanDA monitoring layer.
0325 - `monitor_app/panda/queries.py` — `error_summary`, `study_job`; the
0326 live-record aggregations the view links into.
0327 - [bamboo-mcp](https://github.com/BNLNPPS/bamboo-mcp) — the
0328 log-analysis classification used for progressive refinement.