wildfire-forager architecture (Wildfire Forager)

**Note** from Bead: Wildfire Forager · [canonical source](https://redfish.acequia.io/guerin/.agents/5b518c35-2ce7-474a-a5a8-ec83e2fd0e82/2026-06-21/notes/00-forager-architecture.md) · session 2026-06-21 · discussion: Talk: Wildfire Forager

> Read-first note for the `wildfire-forager` bead (`5b518c35-2ce7-474a-a5a8-ec83e2fd0e82`). > The pattern: **detection source → normalized ignition record → fan-out into foragers → catalog.**

## 1. Why this bead exists The `wildcad` bead opened to subscribe one source and outgrew its name. The reusable thing is not "WildCAD" — it is the **forager pipeline** that any early-detection source feeds. This bead owns that pipeline as documentation + a contract; concrete sources are subscriber beads mounted under it.

## 2. The pipeline ``` detection source(s) normalized fan-out (offer-only, crystallize ───────────────────── ignition record into forager docks) ──────────── WildCAD / WildWeb ┐ ┌──────────────┐ ┌─ imagery (b3461c66) ─┐ NASA FIRMS ├─ poll ──▶ │ {id,lat,lon, │ ──▶ ├─ media (58c0a5e0) ─┤ ──▶ catalog NIFC / IRWIN │ subscribe│ time,type, │ webhook├─ ADS-B (2986ff41) ─┤ (90f902e0, EGP / WatchDuty ┘ -<src> │ acres,...} │ /ndjson└─ (more foragers) ──┘ 97559992) └──────────────┘ ``` - **Source-subscribers** (`subscribe-<source>.mjs`, each in its own bead) poll a detection source and emit the normalized record. WildCAD's lives in [`bd08d6d4` wildcad](https://redfish.acequia.io/guerin/.agents/bd08d6d4-76a2-438e-94a3-82314bd70065/). - **The record is the wire.** Every subscriber emits the *same* schema (§3), so downstream foragers don't care which source fired. - **Fan-out is offer-only.** A subscriber POSTs the record into a forager bead's `uploads/` dock (or routes via [`4c6470f9` bead-orchestrator](https://redfish.acequia.io/guerin/.agents/4c6470f9-b74a-4aba-b436-5e998f469cce/)); it never writes into another bead's tree directly. This is the project_agent-coordination-pattern stigmergy. - **Foragers** turn `{lat,lon,time}` into assets: imagery for the bbox, ADS-B tracks for orbiting fire aircraft, scraped media footage. - **Catalogers** crystallize forage into one citable STAC catalog.

## 3. The normalized ignition record (the cross-source contract) Every source-subscriber MUST emit this shape (extra source-specific fields allowed under a namespaced prefix): ```json { "id": "MTBDC-260455", // source/center-prefixed; the dedupe key "source": "wildcad-wwe", // which subscriber/backend produced it "name": "Grade School Fire", "type": "Wildfire", "lat": 45.429444, "lon": -108.532778, // signed; null if unknown "acres": 0.1, "created": "2026-06-19T16:45:25Z", // UTC best-effort "comment": "...", "url": "<source page/api url>", "fetched": "2026-06-21T18:45:10Z", "event": "new" // "new" | "changed" } ``` Dedupe key = `id`. `lon` is **signed** (western hemisphere negative) — some sources (WildWeb-E) report unsigned magnitude and the subscriber must fix it before emitting, or every downstream bbox lands in the wrong hemisphere. See `wildcad`'s `westLon()`.

## 4. The subscriber pattern (`subscribe-<source>.mjs`) Each subscriber is a dependency-free Node (≥18) ESM poller that: 1. Fetches one or more "channels" of a source (a dispatch center, a FIRMS bbox, an ArcGIS where-clause). 2. Normalizes to the §3 record. 3. Dedupes against a persistable seen-set (`--state`). 4. Emits to stdout / NDJSON (`--ndjson`) / webhook (`--webhook`). Reference implementation: [`subscribe-wildcad.mjs`](https://redfish.acequia.io/guerin/.agents/bd08d6d4-76a2-438e-94a3-82314bd70065/2026-06-21/skills/subscribe-wildcad.mjs). New sources copy its flag surface so they are interchangeable on the command line and in cron/`/loop`. **Open design question:** one `subscribe-<source>.mjs` per source (current) vs one multi-source `subscribe-ignitions.mjs` with pluggable adapters. Leaning per-source-bead + shared contract, because it keeps each source's quirks (auth, rate-limit, parse) local and lets the orchestrator compose them.

## 5. Serving the output (json vs ndjson) See note 02 for the verified MIME test. Short version: **`.json`** (a single array) renders inline in a browser; **`.ndjson`** (one object per line) is the better append/stream format and is trivially parsed by NetLogo and line-oriented tools, but currently downloads (octet-stream) instead of previewing. Use NDJSON as the canonical wire/log; offer a `.json` array view when a human needs to eyeball it in a tab.

## 6. Open questions (gated on Stephen) 1. **Generality:** keep the name/scope wildfire, or rename the contract to a general `incident-forager` (storms via [`1c0f5851` storm-event](https://redfish.acequia.io/guerin/.agents/1c0f5851-d7f5-4ac4-846a-09b71feb82dc/), any geo-time incident)? 2. **Routing:** subscribers POST straight into forager docks, or always via the orchestrator? 3. **Subscriber granularity:** per-source beads (current) vs one multi-adapter subscriber (§4). 4. **Liveness rung:** cron/`/loop` on the CV node vs an OS-level animator daemon ([`fe7fbaf5`](https://redfish.acequia.io/guerin/.agents/fe7fbaf5-4c47-43b6-be1b-2f6ca15e8bfa/)). 5. **Catalog handshake:** what exact envelope a forager returns so the cataloger can bind assets back to the originating ignition `id`.

## References (bead cross-links) - Bead: Wildcad · [canonical](https://redfish.acequia.io/guerin/.agents/bd08d6d4-76a2-438e-94a3-82314bd70065/) - Bead: Orchestrator · [canonical](https://redfish.acequia.io/guerin/.agents/4c6470f9-b74a-4aba-b436-5e998f469cce/) - Bead: Storm Events Build · [canonical](https://redfish.acequia.io/guerin/.agents/1c0f5851-d7f5-4ac4-846a-09b71feb82dc/) - Bead: As Webdav Handler · [canonical](https://redfish.acequia.io/guerin/.agents/fe7fbaf5-4c47-43b6-be1b-2f6ca15e8bfa/)