Changelog
Newest first. Dates are when the change reached the serving database — not when
the code landed. Anything built but not yet serving belongs under an
## Unreleased heading, and takes a date only once it is deployed.
That distinction is not pedantry. Two entries below were once dated for a day they had not reached: the artifact serving on that date carried neither, and a query for the tags they promised answered zero rows rather than an error. A date here is a claim about the database a caller is talking to.
Unreleased
-
The travel-time line is coloured by what each trip is for. The brief's writer sorts the places on the line into two to four named groups ("Errands", "Dog and climbing", "Ski"), each pin takes its group's colour, and a key under the line names them, so a reader sees at a glance that the errands sit close and the ski hills sit far. Walking trips stay hollow and are labelled "walk", so the mode never depends on colour. The numbers beside each place, which only matched the dots on the now-removed map, are gone.
-
Brief cards follow the week, not the wish list. The findings below the travel-time line are now two to four cards, each a slice of the household's week or season ("Mornings with the dog", "Winter: ski days"), instead of one card per thing they asked for. Each card is about four one-line bullets that name places and counts the line does not already show, plus at most one caveat, instead of a fact followed by a sentence explaining it. To give the cards names to use, every count the research takes now keeps the nearest few places it counted ("12 coffee shops, including The Bardo and Amethyst"). The mini-map and the "What Knowhere measured" table are gone: the line shows where things are, and the counts now sit in the cards. When no public record was found, the footer now says the listing facts were not checked against one. Briefs generated before this keep their existing cards when refreshed.
-
Briefs drop the "What this house asks of you" scores. The 0–100 walk, bike and car figures were measured on a fixed basket of everyday places, the same for every house, so they could disagree with the travel-time line drawn for this household's own places: one brief scored the airport 18 minutes away while its line said 30. The line is now the only travel-time answer on the page, and briefs are generated faster without the extra routing. On the line itself, a minute label a pin's stem would run through is left out, and the line is only as tall as the labels below it need, so the shaded range labels ("2–9 min") sit under their pins instead of far below them.
-
Briefs say each thing once and drop what is empty. The thresholds a household stated ("Costco within 20 minutes") now appear on the travel-time line with everything else. The "town centre" row is gone when the house is in that town, and the nearest hospital must have a name, so a Wheat Ridge brief no longer opens with "Wheat Ridge, 2 min" and an unnamed "Hospital". House facts the listing did not state are left out of the header instead of shown as "— not stated"; the listing-versus-record table is hidden when no public record was found; a figure already in the header is not repeated in the measured table; and "timed to a road 1 km away" now sits on that place's row instead of in "Check before you buy".
-
The travel-time component is a single line again. Every destination sits on one line from the front door, on a square-root scale so the errands under five minutes get room instead of piling up at the start. Labels sit in lanes above and below the line, shaded bands mark the clusters ("14–19 min"), and walk times are drawn as hollow pins and labelled "walk". If a brief has too many places too close together to label without overlap, it falls back to the previous one-row-per-place list. On a phone the line scrolls sideways rather than shrinking.
-
A brief times the places a local would name, not only the nearest ones. For each interest a household names, the research step now asks for the nearest match and for the one or two worth the drive (a named ski area or trail system), searching up to 200 km out. Each travel-time row now leads with the place's own name ("FlyteCo Brewing") and puts the kind of place beneath it. Before this, every row was the nearest match, so a Wheat Ridge brief topped out at 13 minutes and never showed what the household trades for the location.
-
A brief's "Check before you buy" list now reads the hazard data under the house. Radon potential, the flood zone, wildfire hazard and burn history, the FEMA National Risk Index ratings a buyer can act on (hail, tornado, hurricane, earthquake, landslide, flooding, heat), loud transport noise and frequent unhealthy-air days each become a checklist item with a next step. Before this the data was in the artifact and the checklist ignored it: a Wheat Ridge brief printed "No red flags surfaced" on a block with high radon and high hail risk. Each item names the area it describes (a census tract, a county), because these values screen a location rather than describe a lot.
-
A brief's nearest hospital treats emergencies, and its nearest vet is a vet. A hospital tagged with a specialty (psychiatric, rehabilitation) is skipped unless it is also tagged as taking emergencies, and a "veterinary" listing named as a groomer is skipped. A Wheat Ridge brief had listed a children's psychiatric centre as its hospital and a pet groomer as its vet.
-
Chain stores on a brief name their branch. "Costco (Arvada)" rather than "Costco", so the place named is the one the time was measured to.
-
Address suggestions name the city the address is in. The locality comes from the municipal boundary containing the point, and from the nearest town only outside every boundary. 3441 Fenton St had been labelled Mountain View, a neighbouring town whose centre is closer, when it sits in Wheat Ridge.
2026-09-23
-
A query that names a directive this engine does not read is now refused.
(bbox=...)parsed cleanly and was then dropped, so a misspelled spatial bound did not fail — it widened. Measured:nw[shop=supermarket](area=colorado)(bbox=...)answered 691 rows, the whole state, where the real(bb=...)holds 15. The same silence covered(wibble=1,2,3). Such a query now returnsSYNTAX_INVALIDnaming the directive it could not read and the four it can:area,around,bb,id. A query that was silently scanning an entire area will now say so rather than being slow and wrong. -
The reach figures on a brief are labelled by how you travel. The panel reads on foot, by bike and by car rather than borrowing the noun from a registered scale. What the figures measure has not changed, and the calibration references to published Walk Scores stay where they belong, in the provenance.
-
Routing has a third profile:
bike.routeanddrive_timesacceptprofile: "bike"(alsobicycle,cycling) and answer over a cycling network — cycleways and paths a car may not use, without the motorways and trunk roads it may — at a speed that varies by road class, 18 km/h on a cycleway down to 8 km/h on shared pavement. Where the build carries elevation, a bike edge also costs its climb: an ascent is charged about 5 s a metre and the matching descent gives back roughly a fifth of that, so the same stretch is not the same number in both directions. An area built without elevation returns flat bike times rather than refusing. Cycling ignores one-way restrictions, as walking does.walk_reachandwalk_loopstay foot-only: their bound assumes nothing travels faster than walking pace, and a bike inside it would quietly under-report rather than refuse. -
A brief says what the house asks of you, on foot, by bike and by car. Every brief now carries two 0-100 figures measured over the real road network from that address, on a basket of everyday places that is identical on every brief so two listings can be compared. The walk figure is fitted against four published Walk Scores spanning 81 to 0 and lands within five of each. The drive figure asks a different question — what the house forces you to drive far for — because the everyday basket measured by car scores about 97 everywhere and says nothing; it is our own judgement rather than a calibrated scale, and the page says so. Each figure names the category that cost it most, so "62" comes with "groceries 36 min away". An area with no foot graph gets a stated absence rather than a walk score built from straight lines, and briefs written before this change keep rendering with the component saying it was not measured. The bike figure needs an artifact carrying the bike routing graph; until one is serving, every brief states that rather than scoring a ride nothing measured. Both the bike and drive figures are our own judgement rather than calibrated scales, and the page says which. The bike curve is deliberately not fitted to published Bike Score: measured against four addresses, that scale is not a monotonic function of what this measures. Arvada is nearer than Granby on every category and is published only nine points higher, because a Bike Score also weighs hills and cycling infrastructure. The curve is set to what a bicycle's errand range is instead, and the residual is left standing.
2026-09-20
-
Writing a check asks two plain questions. The form was one long card: question, addresses, name and, for a super admin, model, stacked without a break. It is now the same numbered steps the brief form uses — What do you want to know? and Which addresses should we try it on?, with the optional name after them — and the submit says what the wait is for: the draft is tried on your addresses before you get an answer, so it takes a few minutes and you can close the page. Pressing it twice no longer buys two checks.
-
My checks reads as cards, and nothing on it is stuck. The checks list was a table of badges — Running, Stopped, Ready — and had no way to remove anything: a stopped job sat there for good, and deleting a check meant opening it and unfolding a section at the bottom. It is now a card per thing, in the same words the briefs list uses: the question it asks, the state as a sentence, and a ✕ on each one. A stopped job names the check it belongs to, offers "Try again", and can be cleared off the list. The navigation says My checks and Write a check, and these pages run at 18px like the rest of the household-facing ones.
-
A check's run page tells you how far along it is. Measuring a paste of addresses now draws a 0–100% bar from the addresses the run has actually finished — "40% · Measured 2 of 5 addresses" — with the time left worked out from the pace this run is going at, and a clock time in your own timezone. Writing a check gets no bar, because the agent has a turn budget rather than a measured typical and a bar drawn from a cap would be believed; it says which step it is on instead. The page updates in place rather than reloading, so scroll and zoom stay where you left them, and one that goes quiet says so rather than sitting still. A run that stopped says what happened in plain words and offers "Try again" on the addresses it already carries — the steps, the model and the raw error fold away for super admins — and once a retry exists, the failure it replaced leaves the list.
-
Sending a brief has a page of its own, and the document carries no form. The owner bar's "Send to someone" now opens a short page that says what a link means — anyone can open it, no account, no password — and makes it there. The document itself is served with
form-action 'none'again: its body is generated, and a form that turned up in it could otherwise have posted to a real route with the reader's session. Nothing about the link, Copy, Email, Text or Print changes. -
Run-page fixes. The five-second refresh was reloading the page out from under the live update, so scrolling down a running brief bounced back to the top every five seconds; the update now replaces the refresh rather than racing it, and keeps the clock time in the reader's timezone. A comparison run that was stuck said "About 5 minutes left" forever, and a failed one said "Your briefs are ready".
-
The progress bar's numbers are measured (#135). The weights and typicals behind the 0–100% bar now come from the runs in the serving store, read by
bin/brief-phases.sh: seeding every address is 176 s, the shared research 475 s, a house's own writing and reading-back 199 s, and the listing fetch 173 s — the slowest single step in a run. Two guesses were badly wrong: locating was set at 25 s when it covers the whole seed, and drive times at 70 s when they take under a second. The form's wait beside Start says "about 10 minutes" for one house now — the median of the runs measured end to end — instead of the "three to seven minutes" it used to claim. -
Signing in lands on My briefs, and the list reads as cards (#128). Where sign-in used to drop everyone on the MCP setup guide, an account on a server with briefs now starts on its briefs — new accounts too. The list itself is a card per house with the address, who it is for, and the state in words: Ready, Working on it, or "This one didn't finish" with a single "Try again" (resume-versus-restart and the raw error stay with super admins). "Private" and "Shared" now read "Only you can see this" and "Anyone with the link can see this", the navigation says My briefs and Make a brief, and these pages run at 18px with help text at 16px.
-
The brief itself is the destination, and carries a slim owner bar (#125). ← My briefs · the share link with Copy / Email / Text · Print · (for super admins) Transcript, spliced into the document when its owner opens it and absent from
/b/<token>, so what you send carries none of it. Print drops the bar. "Send to someone" creates the link on the spot — documents stay private until you do — and "Stop sharing" is beside it, quieter. The old summary pages in between (the brief's and the comparison's) are gone; their links now open the document, and delete moved to My briefs, where it still confirms and still breaks the share link. The document's strict policy is unchanged: the bar's one script is allowed by its own hash, so nothing else on the page may run. -
One page per run, with a 0–100% bar (#127). Submitting now lands on one page whether it is one house or four: a progress bar, the step in plain words ("Checking drive times to the places you care about"), the household's own questions once the research has proposed them ("Checking 13 things you asked about — a hospital within 20 minutes, a Costco, walkable coffee…"), "About 4 minutes left" with a clock time in your own timezone, and "You can close this page". When it finishes, one big link per house and "See them side by side". A house that didn't finish says so in plain words with one "Try again"; the raw error, the transcript and the token counts stay behind a fold for super admins. Every number comes from a recorded milestone, a counted model call, or a typical measured from past runs by
bin/brief-phases.sh— inside the research pass, which can run for minutes without a step, the bar tracks model calls made, and a frozen count says so instead of pretending. -
The new-brief form asks three plain questions (#126). Which house: one box per house takes a Zillow or Redfin link or a typed address, and a link is read back ("That's 1500 Illinois St, Golden, CO") before you start. A link we can't read is refused with "type the address instead", never guessed. Who it's for: saved people show as large cards, "Someone new" clears the boxes, and every submission saves the person — the "remember" checkbox is gone, and "Forget" moved to My briefs. Start: prints the wait, disables after one press, and a second identical submit within a minute joins the run already going instead of paying for another.
-
Picking a saved household no longer clears the addresses. The chip on the new-brief form reloaded the page to fetch the household's text, which dropped every address typed and every suggestion picked (#124). It now fills who and priorities in place. Without JavaScript the chip still reloads, as before.
-
A brief's page shows its progress again. Since every submission began researching once for all its addresses, that research — most of the three to seven minutes — was recorded on the run, not on each house, so a single-address brief sat on an empty "So far" and "Still working…" until writing began. A house's page now lists the run's steps from before it started, then its own.
2026-09-13
-
A comparison is half the length and leads with what the household asked for. The live two-house comparison ran 1,800 words, most of them the table read aloud or the research agent's reasons copied under every cell — and one of those cells named house 1's bike shop under house 2's minutes, because the shop's name had been folded into the note that rode the proposal to every house (#120). The name that answered is now stored on its own and printed only in its own column; the reason a row exists is printed once, under the row's label. Flags that restate a listing row ("Built 1964" beside Year built) and caveats on one destination's number are no longer rows of their own, the access-road row joins across houses instead of splitting by street name, and a two-house page marks no cell "nearest" — the smaller number is visible without help.
The table is grouped: the household's own asks first, each cell marked ✓ or ✗ against their threshold (a miss used to print in the body colour), then the listing, then travel times and counts, which fold once the page passes about fifteen rows — a folded group still says how many rows it holds and how many could not be measured. Rows every house answers alike are folded below. On a phone the question column stays put while the houses scroll under it.
The writer no longer gets a paragraph per house (#121): each house has one line on what choosing it gives up, at most four differences and four things that would change the call, and the budgets are checked like the figures are, so a draft over them comes back rather than going out.
-
A stated bound comes back as a criterion, or the proposal is sent back. "20 minute drive of a costco" produced no Costco row at all on a live comparison; "walking distance of a coffee shop" was timed with no threshold, so no pass or fail (#122). The research agent is now told, in its tool result and on its one retry, which bounds in the household's own words came back with no criterion, and that "walking distance" is 15 min on foot and "biking distance" 15 min by road. Labels are the kind of place ("Nearest climbing gym"), never the one found, so the same row can name a different gym for each house.
-
walk_looptakes severalpassingqueries, and says why a loop passes fewer places than matched. "A loop past the breweries and the ice cream shops" is two tag keys, and the query grammar has no OR across keys — every way of writing it was refused (#118).passingnow takes an array of queries as well as one, and pools their matches before snapping. A single query works exactly as before.A loop that fell back from every match to a pair used to say nothing about it: from RiNo, eleven breweries matched and every reply carried two stops, which reads as if the other nine were not there (#119). The reply now carries
fallbackwhen the loops pass less than was matched, naming the reason — the circuit through all of them would have been 4,100 m and retraced 0.38 of itself; two stops had no walkable route between them; more places matched than the 8-stop cap; some had no path within 150 m. The same rule asnone: a shortfall is an answer, not silence. -
An address's grade is now the average of its criteria, not the worst. A house that met five of six requirements read F exactly like one that met none, so the letter could not rank the houses a paste most needed ranked — and the verdict beside it already said "fail". The letter now averages the criterion grades (a tie rounds toward the worse letter), and a house that failed anything says so beside it: "B · 1 miss". Every past run re-grades the next time it is opened; nothing is re-measured.
-
A house that passes a check no longer grades F. When a check was written from a question with a limit — "a large hospital within 1 hour" — its grading scale was fitted to the handful of test addresses instead, so a hospital 25 minutes away failed the scale (cut at 20) while passing the question. Newly written checks now put the F boundary at the limit the question states, and split A–D across what passes. Existing checks keep their scales; retype the boundaries on the check's page and every past run re-grades.
2026-09-05
-
A listing that could not be read now says so. The listing sidecar drives one browser session and refuses a second caller outright, and a submission measures its houses in parallel — so four addresses with four listings fetched one and silently dropped three. Those briefs said no listing was supplied, which is indistinguishable on the page from not having supplied one. Listings are now retried while the sidecar is busy, and a listing that still cannot be read becomes a checklist item naming the link, rather than a page that quietly contains none of its facts.
-
A comparison carries what the listings said. Price, beds and baths, floor area, lot, year built, HOA, water, sewer and taxes now appear as rows beside the measured ones — where a listing supplied them. They were being fetched, stored on each house and rendered nowhere, so four houses were compared with the one number every buyer weighs the rest against missing from the page.
They are marked as what they are. The caption says which figures were measured from the house's own anchor and which are the listing's own claim, and the write-up is held to the same distinction — "listed at" and "measured at" are different verbs. Nothing marks a lower price as better: that is the weighting this document refuses everywhere else, and a cheaper house is not a better one without knowing what you want.
Four answers stay apart in a cell, because a buyer acts differently on each: the fact, a listing that does not state it, a link that was supplied and could not be read, and no link at all. The third is the one worth separating — reported as "no listing supplied" it tells somebody they did not do the thing they did, about our failure rather than theirs. A row no house can answer is left out entirely, so a run with no listings reads as a comparison of locations rather than one with a column of missing data.
-
Redfin listings are read. A brief takes a Redfin link alongside a Zillow one and credits whichever site it came from. Two things about Redfin made this more than adding a host. Its listing page publishes eight JSON-LD blocks and only one is the property you asked for — the rest are neighbours, published richer than the subject, carrying the floor area and room count the subject's own block omits. Reading the first match would describe the house down the road in the fields a reader checks first, so the subject is identified by type and the neighbours are never a fallback.
And a Redfin URL is not evidence of what comes back: it resolves a listing by the trailing numeric id and ignores the address in the slug, so a stale link answers 200 describing a different house. A request for a Golden, Colorado address returned one in Bryn Mawr, Pennsylvania. Every listing is now checked against the address being measured — house number and postcode, coarse enough that ordinary punctuation differences pass — and one that describes somewhere else is refused with a checklist item naming what the link actually resolved to, rather than having its price printed beside another house's drive times.
A Redfin link yields less than a Zillow one: price, address, year built and status, but not beds, baths, floor area, water source, sewer or HOA. Those are not on the page.
-
Checks grade A–F, not just pass and fail. A check can now carry a grading scale for each of its criteria — four boundaries, best first — and every address it measures gets a letter. Pass and fail cannot rank: two houses whose Costco is 4 and 19 minutes away are both a pass, and a paste of twenty-five addresses is asking which one to look at first. The authoring agent proposes a scale from the spread it actually measured, and the numbers are then yours to move on the check's own page.
The letter is arithmetic, not an opinion. The script exports the number it always did; the boundaries live on the check, beside the question; and the grade is worked out when the page is drawn. Two things follow. A letter can never disagree with the value printed next to it, which is the same reason a check has never been allowed to export its own overall pass/fail. And moving a boundary re-grades every run the check has ever made, instantly and with nothing re-measured — the numbers are already stored, and only what a B means has changed.
An address's grade is its worst criterion, not its average. One bad answer is not redeemed by four easy ones, and a mean could be lifted just by adding more criteria. Nothing is weighted or summed into a score, for the reason a brief comparison gives none: the weights would be yours to state, and we have not asked. A criterion that could not be measured — routing that stopped at an area boundary, an area with no foot graph — is never given a letter. It is counted separately and shown as "graded 3 of 5" beside the grade, because a house we could not measure is not a mediocre house.
2026-09-04
-
A brief run takes several addresses, and compares them. The form accepts up to four (
--brief-max-addresses). They are researched once rather than once each: every house is located and measured first, the research agent sees all of them together and proposes one set of places, thresholds and measurements for the household, and that one proposal is then resolved and measured separately against each house. Four addresses cost one research pass and four write-ups instead of four of everything, and because every house answered the same questions the run ends in a comparison document — one row per question, one column per house, with the rows that separate them above the rows that do not.What a comparison will not do is pick a winner. It marks which house is nearest on a given row, because that is reading the row, and it never adds those up into a score: the weights that would need are the household's. A question that could not be answered for one house — a route across an area boundary, an area with no foot graph — is a dash with the reason beside it, never a number borrowed from the house next door. Every figure in the prose is checked against the matrix before the document is written.
A single address behaves exactly as before and takes the same path.
-
Briefs that already exist can be compared. Select two or more written for the same household and Knowhere pools their questions and measures every house against all of them again, then writes the same comparison document. The re-measuring is the point: briefs written on different days ran their own research, so their rows share nothing but coincidence of phrasing, and joining them would produce a page that looks aligned without being so. Re-measuring costs seconds and no research pass; the briefs themselves are left untouched.
Briefs written before their measurements were stored cannot be compared this way, and say so rather than being compared badly.
2026-09-03
- Briefs carry a "what the listing says, and what the record says" table. The blueprint gained the component — a listing's claims held against the assessor record and sale history, with disagreements marked and unverifiable claims labelled as unverified rather than as false. The server-side pipeline renders the component but has no public record to fill it with yet: it retrieves the listing and nothing else, so every brief it writes today states that no record was retrieved. That is the point of shipping it in this state — "we did not check" and "they agree" are the two answers a buyer must never see collapsed into one, and a brief that silently omits the component reads as the second.
2026-09-02
-
A brief can no longer say something is missing unless it looked. A brief for an address in Edgewater reported "No Costco within 20 minutes" and "no coffee shop within walking distance". Both were wrong: a Costco was a 6.6 minute drive and a coffee shop a 0.99 mile walk, and a check run on the same address that day measured exactly that. The cause was silence being read as an answer — the research pass had collapsed, so those places never reached the writer, and the writer treated "not in front of me" as "not there". Three things changed. The research agent now names places by query ("a Costco") instead of by coordinate, and the server finds the nearest one by road — the agent had been picking a Costco 12.8 minutes away when a nearer one existed, and a coordinate a model chose is not something we can check. Whatever the household states as a requirement ("within a 20 minute drive of a Costco") is now measured as a criterion with a pass or fail attached, and a criterion measured and not met is the only thing that entitles a brief to report an absence. And a brief whose research pass produced nothing now says so in its own checklist instead of reading as complete.
-
A brief's measured panel now asks the household's questions, not a fixed list. Every brief used to measure the same six things — trail segments, bike-legal track, nearest trail among them — whoever was moving in; that was the first test property (a mountain ski/bike family) hardcoded into the pipeline. The research agent now proposes what to measure from the household's own words (daycare counts for toddlers, pharmacies for retirees, marinas for sailors), and the server validates, bounds, and runs each query itself, so counts and absences stay measured facts. Food retail within 5 km and the access road's own tags are still checked on every brief. The writer also gained the structure to match: two to six themed sections instead of always four, stat cells chosen per household after the house facts, and a rating vocabulary it may rename (ski-trail tiers for a ski family) while the three symbols stay fixed. School and pharmacy pins moved from always-on to household-driven; groceries, hospital, and town centre remain on every brief.
2026-09-01
-
What the climate is actually like, as seven
climate:*tags. From the PRISM 1991–2020 normals at 800 m — fine enough to separate a mountain town from its valley floor:climate:high_julandclimate:low_jan(whole °F),climate:temp_annual,climate:precip_annual(whole inches),climate:humidity_jul(July dew point — how Houston and Phoenix differ at the same 100 °F),climate:sunshine(percent clear-sky transmittance — the Seattle-versus-Denver number), andclimate:hardiness_zone(6a…, the vocabulary every plant is sold in, from the 2023 USDA map). These are 30-year normals: climate, not weather, and area climate, not microclimate. The six PRISM tags cover the lower 48 plus DC; hardiness also covers Alaska, Hawaii and Puerto Rico. There is deliberately no snow tag — no gridded snowfall normal exists, and interpolating station values across terrain would be confidently wrong in exactly the places it matters. -
Geologic radon potential, as
usgs:radon_potential. The USGS geologic provinces behind EPA's famous county radon map —high,moderate,low— at geology resolution, so the Front Range granite and the plains east of it answer differently inside one county. The caveat is EPA's own: potential maps predict area averages, and no map decides whether a home gets tested — every home can be high. This answers "how seriously should I take radon here", not "is this house safe". -
Air-quality history, as
epa:aqi_*. For counties with an EPA monitor:epa:aqi_unhealthy_daysandepa:aqi_sensitive_days(days per year, plus the sensitive-groups band),epa:aqi_median, averaged over the years named inepa:aqi_yearsso one smoke summer does not define a place. An absent tag means "no monitor here", not "clean air" — about two-thirds of counties are unmonitored, though the bias points the safe way: unmonitored correlates with rural, and rural with cleaner. -
fire:*now covers the modern era. The 2026-08-31 entry below shipped with a stated limitation: the interagency historical layer these tags were built from effectively ends at 2019, so Cameron Peak, East Troublesome, the Marshall Fire, Dixie and Caldor were absent andfire:last_burn_yearnever answered past 2019 in most areas. The mirror now merges NIFC's WFIGS perimeters — the current-era system of record, four to seven thousand wildfires a year from 2020 onward — deduplicated against the historical layer by IRWIN id so a fire recorded in both cannot doublefire:burn_count. "Treat these tags as pre-2020 history" no longer applies; the coverage-bias caveat (federal land mapped far better than private) still does.
2026-08-31
-
Regulated facilities with an enforcement history, as
echo:*entries. EPA keeps a record of every facility it regulates — plants, treatment works, waste handlers, water systems — and whether each one is in violation, how many of the last twelve quarters it has been out of compliance, when it was last inspected, and what it has been fined. Those are now entries in their own right, not tags on the places around them: a violation belongs to the facility. Sosearch,nearandwalk_reachall reach them, and the question they answer is one the artifact could not answer at all before — not "is there industry nearby", which OSM covers, but "does the plant down the road have a compliance record".Find them with
echo:facility=yes, and filter onecho:compliance(none,violation,significant_violation),echo:quarters_in_violation,echo:inspections,echo:last_inspection(ISO date) andecho:penalties_usd. The last four are present only when they say something — most facilities have never been fined, and a tag reading zero on 700,000 rows is noise.echo:registry_idis the id to look the facility up by on echo.epa.gov.Half of EPA's facilities are deliberately missing, and it is the half you would not want. Many ECHO records are geocoded to a state or county centroid rather than to the facility — the first row in the file sits in the middle of New Mexico — and imported blind those become thousands of phantom facilities stacked on centroids, indistinguishable from real ones. Anything whose coordinate is not good to 100 m is dropped rather than caveated, because
nearhas no way to express doubt. That keeps 720,538 facilities of the 1.66M active ones; colorado has 11,949, of which 612 are in violation and 216 significantly.Two more things absent by design: inactive facilities, and anything outside the US, which is what EPA regulates.
These carry a negative
osm_idand report as nodes. They are not OSM objects and do not pretend to be one — a negative id is OSM's own convention for an element that does not exist upstream. -
Where fire has already been, as
fire:last_burn_yearandfire:burn_count. Federal and state agencies publish the mapped outline of nearly every wildfire back to the early 1900s, and place-like entries now carry two facts from it: the most recent year a mapped fire covered this spot, and how many have. Wildfire hazard layers say where fire is possible; this says where it has already been, which is a fact rather than a model output — and the kind of thing a seller does not volunteer. Trynw[fire:last_burn_year>=2015](area=colorado), or read both off a result.Only wildfires count. Prescribed burns are filtered out at the source (3,774 of them), because a managed burn unit is a fact about land management rather than about what happened to this house, and counting it would say the opposite of what it means. So are the year field's sentinels — 268 perimeters are dated 9999 and five are dated 0, which a naive "most recent" reads as a fire in the year 9999.
An absent tag does not mean it never burned — and for recent fires it usually means the opposite of what it looks like. Two distinct gaps. First, federal land is mapped far better than private land, so absence in a suburb means "nobody mapped a fire here". Second, and larger: the interagency historical layer this is built from effectively ends at 2019 — measured at ingest, it holds 3,217 perimeters for 2019 and then 151, 55, 31, 60, 45 for the years after, nationally. Cameron Peak, East Troublesome, Marshall, Dixie and Caldor are absent from it entirely, so
fire:last_burn_yearnever answers past 2019 in most areas. The 2020-onward era lives in NIFC's separate WFIGS dataset and is planned as a supplement; until it lands, treat these tags as pre-2020 history. -
Wildfire hazard at 270 m, as
usfs:wildfire_hazard. The Forest Service models how likely every 270 m square of the country is to burn at high intensity, and place-like entries now carry that class as a tag:very_low,low,moderate,high,very_high. It answers the questionnri:wildfirecan only screen — NRI rates a whole census tract, so a house backing onto forest and a house three streets into a paved subdivision score the same, where this separates them. Trynw[usfs:wildfire_hazard=very_high](area=colorado), or read it off any result.It disagrees with
nri:wildfireoften enough to be worth having. Both tags are now on the same rows, and measured over oregon's 453,953 stamped entries: 9,685 sit somewhere NRI rates "not elevated" that this rates high or very high — answers the tract-level rating could not give at all — and 26,633 are the reverse, an elevated tract whose actual location is low or very low. Of the 23,145 entries inside a tract NRI callsvery_high, 1,657 are very high where they actually stand and 7,091 are low or very low. A tract is rated by its worst part; this is rated where you are.What it is not. Modelled potential, not a forecast: it says where high-intensity fire is possible given fuels and terrain, and nothing about ignition on a given day. It cannot see defensible space, roofing material, or whether the fuels burned last year — the same class of caveat as
bts:noise, and a relative signal rather than an absolute one.An absent tag means one of three things, and they are not distinguishable: outside coverage (Canada has none, and neither does Puerto Rico, which sits south of the raster's own edge), or the cell is water, or the cell is non-burnable — pavement, rock, open water — which is most of a city centre and is dropped rather than stamped, because "this building is non-burnable" is a statement about the ground under a parking lot.
2026-08-27
-
Resolving a place name in
list_areasis no longer slow. Askinglist_areasfor "seattle" — the first call the golden path tells you to make — took 2.7 seconds on the serving database, because a name that is not an area was looked up by searching all sixty-five areas in turn, one text query each. It now reads the cross-area place index the build already writes, which is one query for all of them. The per-area search stays behind it, for a database built before that index existed and for an area the index does not cover, so nothing that resolved before stops resolving. -
A city resolves to its centre, not to the middle of its bounding box. A place is often two entries in OpenStreetMap: a point where a mapper marked the centre, and an outline of its administrative boundary. We stored one coordinate for each, and for the outline that coordinate was the centre of its bounding box — which for a city shaped like Aurora, reaching east to the airport, is 13 km from anywhere anyone lives. The marked point now wins. Affects 237 places in colorado alone, which sat a median of 1.7 km from their own boundary's box centre.
-
A bare postal code now geocodes.
geocodewith nothing but80202used to fail — every lane read the digits as a house number with no street, or as a name — and now resolves to the centre of the addresses carrying that code. Results come back withkind: "postcode". That centre covers a whole region, so the brief and check pipelines refuse it for the same reason they refuse a town centre: any distance measured from it would be wrong. -
Accented street names are findable by their unaccented spelling. Searching "Canon City" used to return a point 50 km from Cañon City, confidently. Street names are now stored with accents folded, matching the search index, which had been folding them all along.
-
A named feature no longer loses to the signage named after it. "Denver International Airport" returned a
tourism=informationwelcome sign 7.4 km from the aerodrome, because both matched the name exactly and nothing else separated them. Features OpenStreetMap marks as canonical now win that tie. 901 colorado names are decided by it. -
A
geocoderesult for a place carriesosm_id,osm_typeand a bounding box. It can be handed straight tosearchornearinstead of being looked up again by hand. Absent fields mean the lane that answered cannot say. -
Postal-code centres are more accurate. They were the mean of every address carrying the code, which a handful of mistagged rows dragged a long way — 91 of colorado's 417 well-populated codes moved by more than 1 km when this was corrected, and the worst by 14.5 km.
-
New Mexico's addresses are New Mexico's. An OpenAddresses file published as Sandoval County, New Mexico actually contained San Diego County, California — 1.1 million addresses, five degrees west of the state. They had been loaded as New Mexico addresses in every build, which is 44% of that area's addresses; searching or measuring to one gave a confident wrong answer. Addresses that fall outside the area they are being loaded into are now refused, and the build says how many and from which source.
-
A broad tag with a tight radius is no longer slow. A query like "every amenity within 30 m of this point" had to read every amenity in the state and check each one — 597 ms on colorado through the serving path, and worse on larger areas. It now reads only the features near the point: 2.3 ms for the same query. Wide-radius searches are unchanged. No answers change.
-
Tag searches naming a value are faster. A filter like
[shop=yes]used to fetch about 43x more rows than it needed, becauseyesis a value on millions of features. Measured on the serving path,[shop=yes]on colorado went from 205 ms to 21 ms. No answers change, including for the few hundred tag values that contain an=of their own. -
nearreports an over-large side asTOO_MANY_RESULTS, notPAYLOAD_TOO_LARGE. The cap (100,000 features per side) is unchanged in value but is now applied inside the query rather than counted after both sides were already in memory, and the message names which offindornearoverran. -
walk_reachandwalk_loopinherit the 50,000-row cap on the query that selects their candidate features. Afindmatching more than that is now an error rather than an unbounded read. -
Results from
query.executeandquery.unioncome back in a different order. Neither ever promised one, and none of the tools built on them read it — but a script that happened to rely on the old order will see a different arrangement of the same rows. Sort explicitly if the order matters. (The old order was produced by a sort over two constant columns, which cost roughly half the query's time to compute nothing.) -
A script can be stopped when the server is nearly out of memory.
The row cap bounds one query. It cannot see what a script builds from several capped queries — an index keyed by grid cell, a list accumulated across a loop — and that is what actually exhausted the server: three live structures at once, only two of which were query results.
A
runtimescript that drives the Go heap past its ceiling (512 MB by default) is now interrupted with "server memory is nearly exhausted" instead of the process being killed. Hold less at once: query a smaller area, or aggregate as you go rather than collecting every row and filtering afterwards.A script blocked inside a query is not interruptible this way — that half is what the row cap is for.
-
A query that matches too much is refused rather than truncated.
query.executeandquery.unionin theruntimetool had no row limit at all. A statewide union ofbuilding=warehouse,building=industrialandbuilding=factorymatched enough rows to exhaust the server's memory: the process was killed and the site was unreachable for twenty minutes.Both now stop at 50,000 rows and return
TOO_MANY_RESULTSinstead of an answer. That is deliberately an error and not a quiet truncation — a script asking which industrial buildings have rail going in and none coming out computes a wrong answer from a truncated set, not a partial one, and nothing in the result would have told it which it got.If you hit it: narrow the area, add tag filters, or split the work by bounding box. If you only need how many there are,
query.count()never materialises a row and has no cap. -
One index for every area, instead of sixty-five. (needs a rebuild — the code is in, the speed arrives with the next database build.)
Every autocomplete keystroke and every geocode without a state asked a question no single area can answer: which of the sixty-five has a street starting like this? The only way to ask was to ask each area in turn, and the server is one shared core reading a compressed 37 GB file, so the answer was routinely "9 of 65 areas, nothing found".
The database now carries one street index and one place index covering every area, beside the per-area ones. Measured on a 65-area database through the same read path, on one core:
what a keystroke does before after blake st203 ms, 6-7 of 65 areas 40 ms, all 65 3101 blake st212-220 ms, 3-4 of 65 areas 138-149 ms, all 65 east colfax ave159-162 ms, 5-7 of 65 areas 57-71 ms, all 65 washington sto296-337 ms 41-65 ms 3101 blake st denver41 ms 41-50 ms, unchanged The important column is not the milliseconds, it is "of 65". A search that read a dozen areas and stopped had to say so — that is the "searched N of 65 areas" note under the dropdown — and a street in one of the other fifty simply did not appear. Cross-area searches are now complete rather than partial, so the note goes away.
Geocoding a city without its state now works.
geocodeused to refuse "3101 Blake St, Denver" and ask for a state, because resolving "Denver" meant probing all sixty-five areas on the critical path of every lookup. With one place index it is a single query, so the address resolves as written.Two things deliberately kept: a lookup that already knows its area still reads that area's own tables, which are a smaller haystack (3 ms against 11); and a database built before this index still works, falling back to the old fan-out, so the code and the data can ship on their own schedules.
The new index is about 1% of the artifact.
-
Autocomplete now narrows before it searches, which is why it answers at all. Typing
3101 blake st denverinto the address field returned nothing, with a note saying 9 of 65 areas had been searched — on an address the database holds. Everything about that was a cost problem rather than a parsing one.What it costs to answer a keystroke, measured on a 65-area database through the same compressed read path the server uses, on one core — which is what the server has:
what the search does cost read every area's street index 190 ms read one area's street index 3 ms read every area's place index (which city is this?) 22 ms The search used to read every street index first and only work out the city if that came back empty. On this hardware that spends the entire keystroke establishing nothing, and leaves the reading that would have answered with no time to run. It now asks the cheap question first: which areas have a Denver? Then it searches those. Same work, opposite order.
What changed for a person typing:
3101 blake st denver— was an empty list, now the address, complete rather than partial.3101 blake st denver co— a state costs no lookup at all, because the list of states is already in memory. About 15 ms end to end.3101 blake st— with nothing typed yet to narrow on, this is unchanged: the search reads what it can inside its budget and says how far it got.- The budget itself went from 150 ms to 400 ms, with the search-everything pass still capped at the old 150. An answer at 400 ms beats an empty list at 150, and the fragments that can be narrowed now finish well inside it.
What this does not fix: a fragment with no city in it still cannot read 65 street indexes inside any sensible budget. The structural answer is one index covering every area instead of 65 — measured at 10 ms against 190 — and it is the entry above this one, arriving with the next database build.
-
Address autocomplete now finishes the address, however it is written. Autocomplete is the dropdown under the address field on
/briefs: you type part of an address and it offers the real ones it could become, each carrying the coordinate it resolved to. It reads what you type with its own small parser — the full-address parser the geocoder uses refuses anything it cannot read whole, which is every keystroke before the last one — and that parser only understood a few of the ways people write an address.Measured against a real Colorado database: 120 addresses that are in it, each typed thirteen ways. Five of those ways used to fail, two of them completely — 0 of 120 — and every one of them is an ordinary thing to type. All thirteen now return the address.
What was broken, and what it does now:
- A postal code at the end emptied the dropdown.
150 s havana st aurora co 80012— a complete, correct address — returned nothing, because the code and the state were searched for as part of the street name. The state and the code are now recognised and taken off first, and the city is worked out from what is left. 0 of 120 before, all 120 now. - Cities of three words were unreachable without a comma.
Commerce City North,Old Colorado CityandPiney Creek Ranchesare real places; the field guessed at most two trailing words as a city name. Recognising the state and postal code separately freed the guess to run to three words. - A unit written before the address hid it.
Apt 4B, 1500 Illinois St, Denveris how a delivery form and a mail merge write it, and the field readApt 4Bas the whole address. The unit is now lifted off the front. - Abbreviations that are not the start of the word they stand for.
2550 h rdcould not reach2550 H Road, because a half-typed word is matched as a prefix andrdis not the start ofroad. The same applies toln/lane,ct/court,hwy/highway. Both spellings are now searched, so a person typing either finds the street. - A post office box offered nonsense.
PO Box 417, Aurora, COreturned a list of unrelated streets, because the box number was read as a house number and the search shortenedpo box 417until something matched. A box is a mail destination, not a place the database can locate, so the dropdown is now quiet.
Two more shapes that failed, both found by reviewing the fix rather than by typing addresses:
- Connecticut and Nebraska were invisible. Before the state is looked up,
a street fragment is folded onto the spelling the database stores — which
rewrites "ct" to "court" and "ne" to "northeast". Those are the only two US
state codes that are also street words, so
100 asylum st hartford ct 06103and1234 farnam st omaha ne 68102failed in exactly the way the headline fix was supposed to repair. The state is now read off the words as typed. - A state with no city after it narrowed nothing.
washington st coresolved the "co" and then threw it away, because a bare two-letter locality is deliberately refused — "de" is Delaware and also the second keystroke of Denver. The state now comes back under its full name, which that check accepts.
Two things it can do that it could not before:
- A typed postal code decides between identical addresses. Where the same
house number sits on the same street name in two places, the code sorts the
matching one to the top — across the whole answer, not just within one
state's share of it. It sorts rather than filters: the database's postal
codes come from whichever source published each address, so requiring one
would hide addresses that simply have none. Both sides are compared with
their case and spacing removed, so a Canadian code typed
h3b4w8matches the storedH3B 4W8. - Intersections complete.
washington st & winonow offersWashington Street & Winona Court, which is the form the geocoder resolves to the point where the two roads meet. The first street is named as the database spells it, not as it was typed —colfax & winoreads back asEast Colfax Avenue & …. Only roads that pass through the same ~20 km grid cell are paired, which rules out no crossing that exists and rules out every Colfax-in-Denver-with-a-Broadway-in-Cortez. Where the crossing cannot be found at all, the street that is offered instead is marked as not precise, so a brief re-checks the address rather than measuring from a point on one of the two roads.
One thing outside the dropdown changed with it: the geocoder read
PO Box 417, Denver, CO— a box with no postal code after it — as house number 417 on a street called "PO Box", and looked that street up. It now reads the box and answers with the place, which is the most a box can resolve to. The second half of that was found by geocoding a box against the running server rather than by reading the code: recognising the box was not enough, because the reading that produced it had also lost the state, and the lookup then refused for want of an area on an address that names one.What it costs. Every re-reading of the input runs only after the plain reading has already come back with nothing, inside the same time budget. Measured over the same 120 addresses typed one character at a time, arms alternated three times: the middle keystroke went from 1.25 ms to 1.35 ms and the 95th from 3.69 ms to 3.79 ms, while the share of keystrokes answering with an empty list fell from 36.2% to 30.7%. About a tenth of a millisecond, for one keystroke in eighteen that used to show nothing.
Both measurements are in the repository —
TestSuggestWrittenFormsandBenchmarkSuggestKeystrokes, run against a built database — rather than taken once and written down here. - A postal code at the end emptied the dropdown.
-
Addresses outside the US now parse. "Parsing" here means splitting a typed address into its parts — house number, street, city, postcode — which is what the geocoder needs before it can look anything up. That splitting is done by a set of hand-written patterns, one per address shape, and it only ever knew the shapes somebody had written down: mostly American ones. Measured against a 12,832-address reference set, 66.98% of addresses parsed exactly right; it is now 76.15% — about 1,180 more addresses.
Where the gains are. The United Kingdom went from 12% to 56%, France 14% to 92%, Australia 15% to 80%, Hungary 2% to 82%, New Zealand 4% to 59%, Russia 49% to 75%, Luxembourg and Taiwan from nothing at all to 88% and 70%. Taiwan is the clearest case: 177 addresses, none of them written in Latin script, and every existing pattern needed a comma, a US state or a numeric postcode to find its footing.
Nothing was traded away to get it. The countries the patterns already read well are unchanged or better — Denmark 99.7%, Norway 100%, Lithuania 97%, the United States 93%.
Four things did it. A postcode shape for the UK, which is the only anchor a British address reliably offers and which nothing understood. Address orders that were simply missing — the house number written before the street with the postcode after it (France), the city written first (Hungary), the postcode written last (Russia). Punctuation in town names: "lauf a.d.pegnitz", "zell (mosel)" and "sulzbach/saar" are ordinary German municipalities, and the city field accepted only letters and hyphens, so those addresses failed entirely and fell through to a pattern that read the trailing "de" as Delaware. And separating a suburb from its city, which is worth doing because the city is the field the search area is inferred from: "50181 bedburg kaster de" is Bedburg, not "Bedburg Kaster".
Some fields still cannot be recovered without a place-name database, which is the honest limit of a pattern-based approach. Germany sits at 80% rather than higher for exactly that reason.
-
Address autocomplete no longer needs a comma. Typing
3101 blake st denverused to return nothing at all. The field read the whole string as a street name — it looked for a street literally called "Blake Street Denver", which does not exist — so an address that is in the database came back with an empty dropdown. It now reads the input a second way whenever the first way finds nothing, taking the last word or two as a place rather than as part of the street:denver,denver coanddenver coloradoall work, with or without the comma, and the search narrows to the areas that carry that city exactly as the comma'd form already did. Two safeguards. The second reading only runs when the plain one found nothing, so a half-typed street is never mistaken for a city —washington stostill completes to Washington Stone Lane instead of jumping to Stockton, California. And when the trailing word names a place the database has never heard of (a typo, or a town too small to carry a record), the field drops it and shows the street anyway, which is a near miss rather than an empty list. Both readings share one time budget, so this does not make a keystroke slower to answer. -
Address autocomplete answers streets and addresses only. The dropdown used to offer settlements and postal codes as well. Both resolve to a centroid — the middle of a town, or the average of every address sharing a code — and a brief measured from one is wrong in every number, so the form refused them on submit. They were rows that could not be acted on. What the places index is genuinely good for on this surface is narrowing: name a city after the street and the search covers the areas that carry that name instead of all 65, which is unchanged.
2026-08-22
-
walk_loop: walking loops as a first-class tool. "I want to walk a mile in a loop" was a documented runtime recipe (#62); it is now one call.walk_loopspreads turnaround points around the origin and rejects any circuit that retraces more than 25% of itself — the tuned thresholds from the recipe, unchanged. Two things the recipe never had: a time target (target_minutes=60is "walk for an hour", at 5 km/h), and themed loops viapassing— a Knowhere query the circuit routes through, so "a mile loop that sees all the parks" ispassing="nw[leisure=park](area=...)". Passing matches are snapped to the path network (a park with no path within 150 m is not somewhere a walk can pass) and visited in a bearing sweep, all of them when the full circuit holds up, pairs of them otherwise. Each loop reports the length it actually came out at, andnonewith a reason is a real answer — a cul-de-sac subdivision genuinely has no mile loop. Foot-only, within one area, target capped at 10 km. Works against the artifact already serving; no rebuild involved. -
Address autocomplete on the brief form. The address field on
/briefsnow completes as you type — settlements, postal codes, streets and house numbers, across every area in the artifact rather than one you have to name first. Picking a suggestion carries the coordinate it resolved to into the brief, so the brief measures from what you chose instead of geocoding the text again and possibly landing on a different street of the same name. This is a web-form feature: no MCP tool changed, andgeocodeanswers exactly what it answered before.Two columns were added to the artifact to support it —
{area}_street_names.rank(how many addresses and road segments a name carries, which is what orders the suggestions) and a secondaliascolumn on{area}_street_searchholding the name without a leading directional or French type word, so "Colfax" finds "East Colfax Avenue" and "Sainte-Catherine" finds "Rue Sainte-Catherine Ouest". Canadian postal codes complete too, with or without the space they are written with.A typed unit does not derail it: "551 Castle Dr Apt 6" falls back to the building rather than matching nothing, and a building says how many units its source listed. Which unit is not offered — nothing downstream measures per-unit, so a drive time from apartment 4B is a drive time from the building.
A comma narrows the search. "Main St, Golden" or "Main St, CO" searches only the areas that carry that name — at most three — instead of all 64. That is both faster per keystroke and the difference between one Main Street and every Main Street on the continent. A city and a state written together ("Main St, Golden, CO") works too. A locality nothing answers to costs nothing: the search falls back to every area. Note that
areas_totalin the reply is how many areas the query was meant to cover, so a narrowed search that finished reports complete rather than partial.Unit counts changed with this build. A building's units are stored as bare identifiers now — "6", not "Unit 6". Sources spell one door several ways in the same file, and the step that counts a building's doors is a SQL
DISTINCT, which read "Unit 6" and "6" as two. Measured on colorado, 3,046 addresses carried both spellings, so their counts were too high and their unit lists held the same door twice. Both are right now. The cost is that the designator words ("Apt", "Ste", "#") are no longer kept, so the artifact can say a building has six doors but not that door six is a suite rather than an apartment.Nothing has to be in the list for the form to work. A "Use what you typed" row is always the last option, and picking it submits the address exactly as written — coverage is not total, so an address the artifact has never heard of is a normal case rather than a refusal.
The artifact's schema version is now 3. The database serving this was rebuilt from scratch to carry it; a server pointed at a version 2 artifact refuses to start rather than answering the streets lane with an error on every keystroke.
2026-08-21
-
routeandsnapnow say which road you are on. Everyroutereply names the road each endpoint landed on —from.road/to.road, carrying the OSM way id, the street name, the route number (ref, e.g. "I 70") and the road class (highway, e.g. motorway or residential). The router had always worked this out in order to pick a junction and then thrown it away, so callers who needed a street name went back tosearch, where a road is indexed by its bounding box: stand at one end of a kilometre of Main Street and its box centre is 500 m away, behind every side street you are not on. Asking the road network instead measures to the road's real shape. Costs nothing extra. Also onrouting.snap()androuting.route()in scripts. -
route(steps: true)returns the road log. The list of roads a trip runs along, in order, each with the distance it begins at and the motorway exit it was reached through:US 6 at 2.2 km | I 70 at 17.4 km | US 40 at 63.6 km (via exit 232)Consecutive stretches of the same road collapse into one entry, and slip roads fold into the road they lead onto, so a hundred-kilometre drive is a handful of lines rather than a few thousand. It is not turn-by-turn — no "turn left", no lane guidance, no turn angles. It answers "which roads does this drive use", which previously meant sampling the returned polyline every couple of kilometres and running a radius query per sample: 54 queries for one Denver drive, about 40% of which came back with no label. Opt-in, because it costs roughly half a route again.
-
list_areasstops quietly picking between places of the same name. Settlement names repeat — there is a Winter Park in Colorado and another in Florida — and filtering for one used to return whichever ranked highest by place type, with nothing to say the other existed. It now returnsresolved_candidates, every place that matched, best first, and a note saying the top one was a guess and on what grounds. Newnear_lat/near_lonparameters break the tie by distance instead, which is the right answer when the caller knows where they are asking from. The chosen answer has not changed; what changed is that the alternatives are visible. -
A failed query inside
runtimeexplains itself. Scripts that hit a bad query were interrupted withcould not execute query: "<your query>"— the string the caller already had, and no error code. The same query throughsearchreportedSYNTAX_INVALIDwith a hint. The sandbox now uses the server-wide<CODE>: <message> (hint: …)format like everything else. Parse errors also name the character they stopped at and, for the near-misses, the spelling that works:[name~Berthoud]now says the contains operator is=~, instead of failing with "unparsable query" and nothing further. -
House numbers from OpenAddresses. Address coverage previously came from OpenStreetMap alone. OpenAddresses — an open aggregation of government address registers — is now folded in alongside it, which is roughly eight times the address rows. Sources whose licence is share-alike are refused outright rather than mixed with ODbL data, so a few municipalities are deliberately absent;
knowhere://attributionlists what a given database actually contains. Addresses that exist in both sources are collapsed, and the units of one building fold into a single address row carrying its unit list rather than one row per apartment. -
The FEMA tags that 2026-08-18 announced are actually present.
fema:zone_subtypeand the eighteennri:*hazard ratings were documented on 2026-08-18 but never reached a served database — see the correction in that section. They are in this build: on colorado,fema:zone_subtypecovers 2,049,847 entries.fema:zone_subtypeis what makes zone X readable, separating the 500-year floodplain from genuinely minimal hazard. -
The
g/"geojson"element type is gone. Queries could name a fourth element type alongsidenode,wayandrelation, which selected features imported from a GeoJSON file rather than an OpenStreetMap extract. No served area was ever built that way — every one of the 65 areas comes from a PBF extract — sog[...]and{"$type": "geojson"}only ever matched zero rows. Both now fail as an unknown type instead of returning nothing, and*means the three real types. Nothing that returned results before returns fewer. -
The artifact is about a third smaller, and wide-area search is far faster — but one shape of query got slower. The database was rebuilt around a different spatial index, and the trade is uneven enough to be worth stating in full rather than summarising as a win.
What got faster is anything covering real ground. Measured on colorado:
amenity=cafewithin 20 km went from 725 ms to 9.6 ms, a restaurant search within 1 km from 88 ms to 37 ms, within 200 m from 10.2 ms to 6.9 ms. Through the serving path that 20 km query answers in 83 ms, where it used to be slow enough to feel broken.What got slower is a tight radius, and most of all a tight radius on a bare key.
amenity=restaurantwithin 30 m went from 0.74 ms to 6.7 ms — a large multiple of a number too small to perceive. But a bare[amenity]within 30 m went from 0.70 ms to 74 ms, and within 1 km from 95 ms to 380 ms; on the serving path those are about 1.8 s and 2.7 s. If you ask for a broad category within a short distance, expect seconds.The cause is structural rather than a tuning miss. The old index carried the location inside the text index, so one lookup pruned by tag and by place at once. They are separate now, and when the tag matches a great many features and the radius excludes almost all of them, the text side leads and the location index is reduced to checking its output row by row. Narrowing the tag (
amenity=caferather thanamenity) or widening the radius both avoid it.Plain tag lookups with no location at all are about 9% slower. The artifact itself went from 322.6 MB to 208.5 MB raw on wyoming, and from 34.7 GB to 21.9 GB compressed across all 65 areas, so a cold start downloads a good deal less.
-
The synthetic
geohashtag is gone from results. Every entry used to carry ageohashkey in its tags — not from OpenStreetMap, but written by the build to make spatial queries possible. It was never documented, never usable in a filter, and it is no longer there.tag_keysnever listed it and no query could match on it, so nothing that worked before stops working; but anything reading raw tag payloads and expecting that key will not find it. -
Results in the same rank order may come back in a different sequence. Ranking is unchanged, and so is paging: a query that ranks one result above another still does. What changed is the tiebreak among results that rank equally — the internal id they fall back on is now assigned in geographic order rather than in the order the source file happened to list them. Practically: page one of a broad query like
nw[amenity=restaurant](area=colorado)holds a different fifty than it did, all equally good matches. -
routeandwalk_reachreport a differentnode_id. The identifier for a snapped junction used to be its OpenStreetMap node id; it is now an internal index into the area's road graph. It has always been an opaque handle — it is only useful for passing back into another routing call in the same area, which still works — but it is no longer an OSM id and should not be treated as one. -
Travel times are quantised to a tenth of a second per road segment. Route durations are computed from fixed speeds per road class, so they were always estimates with minutes of slack; the stored precision now matches that rather than exceeding it. A reported duration can differ from before by a fraction of a second.
-
A server refuses to start against an artifact it cannot read. Previously a mismatch between the code and the database produced empty answers rather than errors — "nothing nearby" for a place surrounded by things. The artifact now records which generation of the build produced it, and the server checks before serving.
2026-08-19 (later)
-
geocodeguesses out loud when a name doesn't quite match. Real names disagree with real queries: OSM writes "Buckley Space Force Base" and you remember "Buckley Air Force Base"; an airport's formal name carries a county yours doesn't. Those lookups used to fail outright. Now, when no name carries every word, the search reruns on the query's most distinctive word and returns the candidates matching the largest share of the rest — every one flaggedpartial_name: true. The flag is the point: measured against a public corpus of real airport queries across three states, the relaxed pass recovers right answers and wrong ones in roughly a 2-to-1 ratio, so an unflagged partial match would just be a confident mistake. Anything ranking or summarising results should treat a flagged match as a lead to verify. Location checks now warn on it, and location briefs refuse to anchor on one — the same posture asoutside_context. Full matches always outrank partial ones, and a query that used to succeed returns exactly what it returned before. -
Accented names match their unaccented spellings in ranking. "San Jose" and "San José" now count as the same name when geocode decides whether a feature-name match is exact, matching what the search indexes already did.
2026-08-19
-
geocodeparses comma-less USPS-shaped addresses. An address pasted from a shipping label or a maps app —3377 Blake Street STE 113 Denver CO 80205— carries no commas, and the parser needed them to tell where the street ended. The whole line was read as a street name, the state was never seen, and the query failed withAREA_REQUIREDeven though it named one. The street-type word (Street,St,Ave, …) now marks the boundary instead, and a unit (STE 113,APT 4B,# 5) is recognised and set aside rather than searched for. A comma-less address without a street-type word (3377 Broadway Denver CO) still needs its commas: without either there is nothing to say where the street stops and the city starts. -
geocodefinds named places, not just addresses and streets. A point of interest — an airport, a stadium, a park, a supermarket — is not an address and is not a town, andgeocodehad no layer that could hold one. "Denver International Airport" returnedNO_PLACE_NEARBYno matter which area you passed. Worse, when a road happened to share the name, the road answered: "Costco, Denver" resolved to a street called Costco Avenue and reported it as confidently as a correct result. Both were the same missing layer, and it now exists.geocodesearches four layers together — house numbers, street names, settlements, and named features — and returns the best. Add a city to pick between branches of a chain:Costco, Denver. Results say which layer answered, inkind:address,street,placeor the newpoi. Two limits worth knowing. A name needs at least three characters to be searched, because shorter than that the index cannot narrow the search and the query has to read the whole area. And bus stops are excluded: they are named after the street they stand on ("Washington Ave & 16th St"), so leaving them in meant every street query was answered by a bus stop. -
Punctuation in a name no longer has to match. Names are matched a word at a time rather than as one exact run of characters, so a hyphen you did not type is no longer a dead end.
Durango La Plata County Airportfinds OSM'sDurango-La Plata County Airport; so doGunnison Crested Butte Regional AirportandAspen-Pitkin Co/Sardy Field. Measured against a published corpus of real airport names and coordinates (1,435 US airports from OpenFlights, via geocoder-tester), this took colorado from 57.1% of airports answered within their own tolerance to 75.0%, with no change to the city corpus scored alongside it as a control. -
Duplicate results are folded together. One building can be mapped many times over — the building outline, each entrance, each business inside — and every copy carries the same street address.
1701 Wynkoop St, Denverreturned 28 of them, so a single unambiguous building read as an ambiguous answer and filled the wholealternativeslist with itself. Candidates that share a name and sit within 100 m of a better-ranked one are now dropped. Places that merely share a name are untouched — two towns' Main Streets stay two answers. -
geocodeerrors say what to do about them. Its errors were being explained with advice written for other tools.AREA_REQUIREDtold callers to add an(area=...)directive or$area, which issearchsyntax thatgeocodedoes not accept — it takes anareaparameter.NO_PLACE_NEARBYtold them to check their latitude and longitude order, which they had not supplied. Both now describegeocode, and both point atlist_areas(filter: "<place name>")as the way to find which area holds a place when you do not know.
2026-08-18
Correction (2026-08-21). The two FEMA entries in this section —
nri:*andfema:zone_subtype— did not reach the serving database on this date and have not reached it since. The artifact published on 2026-08-18 at 07:18 UTC is still the one being served; the overlay configuration that produces both tags landed at 13:42 and 17:11 the same day, hours after that build was cut, and no build was published between then and this correction. Checked against the live server:fema:flood_zonereturns 2,171,042 rows for colorado, whilefema:zone_subtype,nri:hailandnri:wildfireeach return 0. Both tags are carried by the artifact now built and pending deployment; they will be dated here when it is serving.
-
Natural-hazard ratings, as
nri:*. FEMA's National Risk Index rates 18 hazards for every US census tract, and the elevated ones now ride along as tags:nri:wildfire,nri:hail,nri:tornado,nri:earthquake,nri:riverine_flood,nri:landslide,nri:heat_waveand eleven more. Two values only —highandvery_high, NRI's own "Relatively High" and "Very High" — because a tract carries a rating for all 18 and the quiet ones are most of them. An absent tag means "not elevated, or not rated". NRI writes two kinds of unknown — "No Rating" and "Insufficient Data", 15,507 and 1,061 tracts respectively for wildfire — and those are dropped the same way a "Very Low" is, so absence is not proof of safety. Answers which hazards are worth asking about at all:nw[nri:wildfire=very_high](area=colorado), or read them off a result. Two caveats worth stating. A census tract is coarse — a whole neighbourhood shares one rating — so this screens a question rather than settling it for a building. And the ratings are relative to the rest of the country, so ahighin one state is not the same absolute risk as ahighin another. NRI's composite "Risk Index" is deliberately not included: it folds social vulnerability and community resilience into the score, so two tracts with identical hazard rate differently by who lives in them. -
fema:zone_subtypemakes zone X readable. A third tag from the FEMA National Flood Hazard Layer, and the one that decides whatfema:flood_zoneactually means. 76% of the flood layer is zoneX, and 76% of that is the 0.2%-annual-chance (500-year) floodplain rather than the "minimal hazard" the bare letter suggests — soXalone pointed the wrong way on the base rate. Calltag_values(area, "fema:zone_subtype")for the vocabulary an area actually holds —0.2pctandminimalare most of it, withfloodway,coastal,shallowand four flavours ofleveebehind them. A rare subtype is left untagged rather than stamped as a category nobody can query for.floodwayis worth knowing separately: it sits inside zoneAEand is where building is effectively prohibited. Trynw[fema:zone_subtype=floodway](area=pennsylvania). Insurance requirements still followfema:sfha, which has not changed. -
Numeric comparisons compare numbers. Tag values are stored as text, and SQLite orders every text value above every number regardless of what the text says, so
nw[bts:noise>=70](area=colorado)returned all 518,832 entries carrying the key — the same answer as>=99999— while<=matched none andnw[bts:noise=114]found none of the nine entriestag_valuesreports.>>=<<=(and$gt$gte$lt$lte) now read the number out of the stored value and skip values that do not start with one;=and!=against a numeric literal are an exact text match, matching whattag_valuesshows. Queries written to work around the old behaviour by quoting the value are unaffected.
2026-08-17
-
Transportation noise, as
bts:noise. Every place-like entry in a US area now carries the modelled 24-hour average loudness at its location in A-weighted decibels, from the US DOT National Transportation Noise Map (2022/2023):nw[bts:noise](area=colorado). Roughly one entry in five has a value — the model floors at 45 dBA and covers road, rail and air traffic only, so a quiet address has no tag rather than a low number. It is a good relative signal and a poor absolute one: DOT states it "should not be used to evaluate noise levels in individual locations", and the model ignores shielding from terrain and barriers, so it skews high. Canadian areas have none; the source is US-only. -
attributionsrecords which snapshot of a source an artifact holds. Two new columns:retrieved_at, when this build acquired the source, andversion, the vintage where a source declares one (bts-2022). An artifact could previously say that it used OpenStreetMap and FEMA but not which extract or revision, so two builds of the same pipeline could disagree with nothing recording why.versionis null for live services, which have no vintage to state. -
A brief credits the sources its own area was built from. The footer used to carry a hard-coded "Map data © OpenStreetMap contributors (ODbL)", which was true when OpenStreetMap was the only source and would have quietly stopped being the whole truth as soon as another one landed. It now reads the artifact's
attributionstable for the area the brief covers and names everything that requires a credit. Scoped to the area on purpose: a brief is about one address, and listing every source in the database would be a paragraph of credit for data the document never touched. Briefs rebuilt from a stored input, or generated without a database, fall back to the original sentence. -
knowhere://docs/legalnow describes OpenAddresses as a possible source of house numbers and points atknowhere://attributionfor which sources a given database actually contains. No served database carries OpenAddresses data yet.
2026-08-16
-
Attribution, privacy, and terms now ship with the server. New resource
knowhere://docs/legalcarries the OpenStreetMap/ODbL credit, the privacy policy, and the terms of service from knowhere.live. The ODbL credit and the "these answers measure the data, not the world" caveat also went into the server instructions, so they reach every session without a client having to fetch a resource — a credit nobody reads is not a credit.knowhere://attributionis unchanged and still reports what this particular artifact was built from, per source and per area. -
around=stops losing features just over a cell boundary. The radius filter narrows the candidate set with a geohash prefix index before it measures anything, and it was choosing a cell one size too small — for an 800 m radius, a cell 610 m tall. Features past that edge were dropped before the distance check ever saw them, with no error and no warning: a flood-zone screen around a Denver address reported nothing within 800 m while a Zone AE feature sat 709 m away. The radius had to be inflated roughly 3.5x before the feature appeared. The cell is now always at least as large as the radius asked for, soaround=rand a distance computed on the returned rows agree.bb=picked its cells the same way and had the same silent loss near a box edge; it is fixed by the same change. Wider radii now scan more candidate rows — the distance test is unchanged and still exact, so results only gain the rows that were wrongly missing.The largest geohash cell is 45 degrees across, so a
bbwider or taller than 90 degrees (or anaroundradius of a few thousand kilometres) has no cell big enough to hold it. Those queries now skip the geohash step entirely rather than settle for the biggest cell and drop everything outside it — a whole-worldbbused to return only what sat near (0°, 0°), losing every entry in the Americas. -
around=names a bad radius instead of leaking a database error. A radius or centre that is not a finite number (1e309overflows to infinity,NaN) used to slip past the range checks and reach SQLite as a bare word, which came back asno such column: Inf. It now returns the same "radius must be > 0 metres" / "centre out of range" error as any other bad value.
2026-08-15
-
Multiple bounding boxes work. A query with more than one
bbgroup ((bb=a...,b...)) either errored ("table does not support scanning") or silently applied the other filters to only the first box, because the boxes were OR'd into the WHERE clause without parentheses. The index prune had the same problem in reverse: it required a match in every box at once, so two boxes that do not overlap returned nothing. Both now treat the boxes as a union: an entry matches if it falls in any box, and every other filter still applies. -
Multi-word values need a tag key. The any-key form (
[*=...],[*=~...], and their negations) searches the whole entry rather than one tag, so nothing downstream can check its answer. With a value of two or more words it can no longer tell "Bobs Burgers" from an entry named Bobs whose operator is Burgers, so it now returns an error naming the problem instead of answering with the wrong entries. Single-word values are unaffected, and naming the key ([name="Bobs Burgers"]) always works. -
Negative filters no longer drop matches by accident.
!=,!~, and[!key]used to exclude an entry when the negated key and value appeared anywhere in it, even under different tags —[cuisine!=pizza]dropped a restaurant named "Pizza Hut" withcuisine=italian, and[!cuisine]dropped entries whose only cuisine-ish key wascuisine:vegan. Also fixed:[key!~a,b]now requires the value to contain none of the terms (it could pass on a value containing both), several[*!=...]filters in one query now all apply, and the bare-key forms[!*]and[*>n]— which always errored — plus a negation-only[*!=...]query — which was silently ignored — now return a clear error. -
FEMA flood data. Every place-like entry in the US areas now carries two tags from the FEMA National Flood Hazard Layer:
fema:flood_zone, the zone code (X,AE,A, ...), andfema:sfha,yesornofor whether the entry sits in a Special Flood Hazard Area — the 1%-annual-chance floodplain where flood insurance is typically required. Filter on them like any other tag:nw[fema:sfha=yes](area=texas). Zone geometry is not stored, only the per-entry answer.