-
v0.64.0 Stable
released this
2026-09-29 19:27:45 -05:00 | 26 commits to main since this releaseThe fantasy tenant can change its own team, behind a new
ESPN_WRITESsetting that
defaults tooff. Underoffnothing changes: the same ten reads, name for name. Under
dry_runorliveit gains four tools, all acting onESPN_TEAM_IDalone.dry_run
runs every check and sends nothing.Added
ESPN_WRITES:off(default),dry_runorlive. Anything else fails the boot.
The boot summary'swrites=line says which, and with changes on, preflight adds
check=espn_owner, confirming the cookies manage the team.update_lineup: move players between starting slots,BENCHandIRin one
transaction. It takes effect immediately. It is refused, with nothing sent, when a
player cannot play the slot, is locked because his game has started, or would overfill
a slot.FLEXmeans the league's one flex slot and is refused where there are two.create_waiver_claim: add a player on waivers and drop one of the team's own. The
claim runs at the next waiver process, and the drop only happens if it succeeds. It
refuses a free agent, a FAAB league, a player on another roster, and a claim identical
to one already pending.list_waiver_claims: the team's pending claims in order, each with the
claim_idthe reorder takes, plus the team's waiver priority.update_waiver_claim_order: set the order, naming every pending claim once.preview: trueon all three changes: run every check and return what would be
sent, without sending it.- Every change names each player by id AND exact name, whitespace aside and case
kept, and refuses on a mismatch. Every change checks that the cookies own the team
before sending. Every change is read back and reportsverified. changeon every change response: the change in words, with ESPN's names for the
players and no status prefix. The bot shows it beside its Confirm button.- Tool labels. Every read carries
readOnlyHint: true. The four above carry
_meta.owner_only: true,list_waiver_claimsincluded, because pending claims are
private on ESPN. The bot decides who may use a tool from these labels. espn/write, a log phase of its own for every request that changes the team.
Measured before it was built
All live, on 2026-09-29, from gitignored probes. The checklist is in
docs/operations.md.- A lineup change is a
ROSTERtransaction POSTed to
lm-api-writes.fantasy.espn.com/.../transactions/, answeredstatus: EXECUTED. The
roster shows it on the very next read. Nine moves in one transaction all landed. - A waiver claim is a
WAIVERtransaction withADD/DROPitems andbidAmount: 0,
answeredstatus: PENDINGand appended to the end of the claim order. - Pending claims come from
mPendingTransactionsunder the keypendingTransactions,
ordered bysubOrder. Claims and reorders made in the ESPN app appear there too. - Claim order is the request ESPN's own site sends (
updateTransactionOrderin its web
bundle): every pending id with a 1-basedsubOrder, POSTed to
/teams/<id>/pendingTransactions. - A player filter with a
limitand no sort is a400(FILTER_LIMIT_MISSING_SORT).
Changed
- The server instructions and the
get_free_agentsdescription say what this deployment
can change, per mode. Underoffthey still say it cannot change anything. - The boot summary's
writesline reads the setting. It had been a constant.
Fixed
- A weather test failed every evening from 20:00 New York time. It counted "twelve
days out" from the UTC date, whileget_outlookcounts from the coordinate's own
date, so after UTC midnight its target was day 13 and outside the 6-10 day window it
was testing. It now counts the way the tool does. The tool was right throughout.
Downloads
-
Source code (ZIP)
0 downloads
-
Source code (TAR.GZ)
0 downloads
-
v0.63.0 Stable
released this
2026-09-24 10:16:54 -05:00 | 27 commits to main since this releaseEvery weather tool takes
unitsandtimezone, andget_model_rungains UV. An
answer can now be used as it arrives, with no °F↔°C or UTC→local conversion left to
the caller. Breaking for existing callers: the default is now metric, and every
field that carried a unit in its name was renamed. Tool descriptions changed, so the
connector needs a reconnect after the deploy.Added
unitson all eleven tools:metric(C, km/h, mm, m, hPa) orimperial(F, mph,
in, ft, mb). When a call omits it,WEATHER_DEFAULT_UNITSdecides, and that defaults
to metric. A misspelled value refuses to boot rather than quietly serving the default.timezoneon all eleven tools:local(the coordinate's own zone, the default),
utc, or an IANA name. Every timestamp in the answer is rendered in that zone with an
explicit offset, including those inside sentences. An unknown name is refused rather
than read as UTC.appliedon every response: the units and zone used, and where the zone came from
(nws_points,open_meteo_auto,caller, orfallbackwhen no lookup answered and
the answer is in UTC).- A
uvblock onget_model_run, with the hourly UV index and clear-sky index, a
daily maximum per local day, and the model named. It comes fromgfs_globalon its
own request. Measured across eleven models, GFS was the only one supplying UV, and
best_matchreturned GFS's values without naming a model. A failed UV request costs
the UV block only. - A
unitsblock wherever a response carries measured quantities without one:
get_hourly_forecast,get_taf,list_stations, normals, and distances. - Local time-zone lookup outside the US. Open-Meteo's
timezone=autoanswers with
the zone name alone, and the answer is cached for 30 days like/points.
Changed
- Field renames, so no name claims a unit:
temperature_f→temperature,
dewpoint_f→dewpoint,wind_speed_mph→wind_speed,wind_speed_high_mph→
wind_speed_high,wind_gust_mph→wind_gust,distance_miles→distance,
elevation_feet→elevation,model_cell_elevation_feet→model_cell_elevation
(withmodel_cell_elevation_unit),searched_miles_north_south→
searched_north_south(withsearched_north_south_unit), the normals'
normal_high_f/normal_low_f/normal_mean_f→normal_high/normal_low/
normal_mean,median_precipitation_in→median_precipitation,
normal_total_precipitation_in→normal_total_precipitation, a sky layer'sbase_ft
→base, and thesunblock'sdate_utc→date. - Five tools reported
"timezone": "UTC"for a US coordinate.get_model_run,
get_taf,get_observations,get_observation_historyandget_outlooknow answer in
the coordinate's zone like the rest. - Offset-less
startandendare read in the answer's zone, onget_observations
andget_observation_history, andget_outlookcounts days from the coordinate's
today. All three used the server'sTZbefore. - Sunrise and sunset are filed under local days. Open-Meteo requests name the zone
and ask for epoch seconds, because given a zone it applies one fixed offset to the whole
request. Measured across 2025-11-02, its local time strings run an hour off after a
DST change. A daily row's date is taken from the response's own offset, so the day
after a change is not labelled with the previous date. - Metric Open-Meteo requests omit the unit parameters, which is its default, and the
answer is checked against a metric echo table recorded on 2026-09-24. Metric
/forecast/hourlyis asked forunits=si, sowind_speed_textreads in km/h.
Removed
from_time_localandto_time_localon wind-shift hits, andissued_localon the
forecast discussion. Every time is now in the answer's zone, so each was a second
copy of the field beside it.
Notes
- Two premises of the plan were wrong, and both were caught by measuring before
building. The raw grid was not losing precision: its Celsius values are exact
conversions of whole Fahrenheit, so they convert back to whole numbers. And
Open-Meteo's heights and visibility were already in feet underprecipitation_unit=inch,
so converting them for imperial would have converted them twice. - The DST change cannot be checked live until late October, because the forecast
endpoint reaches only 16 days ahead. The suite pins it against recorded fixtures, and
docs/operations.mdcarries the live check with its date. WEATHER_DEFAULT_UNITS=imperialin the stack file restores the previous numbers
without a rebuild. The field renames stay either way.- 33 new tests, 1895 in all. Nine new guards were proved by breaking them: the
sourceskip, the daily-date offset, refusing an unknown zone, omitting metric unit
parameters,units=si, UV isolation, the boot refusal, the one rendering pass, and
reading the window in the answer's zone.
Downloads
-
Source code (ZIP)
0 downloads
-
Source code (TAR.GZ)
0 downloads
-
0.62.1 Stable
released this
2026-09-22 23:13:40 -05:00 | 31 commits to main since this releaseTwo fixes from the live pass on 0.62.0. No tool description changed, so a deploy is
enough and no reconnect is needed.Fixed
get_alertscould lead with a watch while a warning of the same severity was in
effect. At a point in Hudspeth County TX a Flood Watch and a Flash Flood Warning
were bothSeverewith the same onset, and the watch came first because onset was
the only tie-break. Ties now break on the issuing office's own urgency, then
certainty, then onset: the warning wasImmediateandLikely, the watchFuture
andPossible.- The weather boot line named three upstreams of six. It names all six now, and a
test reads the client's host constants so a new one cannot be left off.
Downloads
-
Source code (ZIP)
0 downloads
-
Source code (TAR.GZ)
0 downloads
-
0.62.0 Stable
released this
2026-09-22 22:50:09 -05:00 | 32 commits to main since this releaseStep 5 of the weather enhancement plan:
get_observation_history, the tenant's eleventh
tool. What a station actually reported over a past window, from the Iowa Environmental
Mesonet's METAR archive, which reaches back decades and works anywhere. Every rule below
was measured against the live archive first, and the live pass matched three KJFK routine
reports against the NWS feed for the same minutes field for field.Added
get_observation_history(latitude, longitude, start, end, station, count). Routine
and special reports, newest first, each with its raw METAR, sky layers, present
weather, hourly precipitation and peak wind, in the unitsget_observationsuses. A
field null on every report is named innot_reported_in_windowand left off the rows.
At most seven days per call; pastcountthe window is thinned to one report per hour
and the response says what that hides.- An empty window says which of four things it is: a gap in the record, a date
outside the span the archive holds for the station, a station the archive does not
carry (with the next nearest named), or an identifier that does not exist. - A seventh upstream,
mesonet.agron.iastate.edu, keyless.
Changed
- The client paces the archive's data endpoint to one request every 7 seconds. Its
changelog says a one-second throttle; measured, requests 3 and 5 seconds apart answered
429 and 6 and up answered 200. Two calls back to back now wait instead of failing. get_observationspoints at the archive for windows it cannot read and for the raw
METAR text. Its description changed, so the connector needs a reconnect.in_windowandlatest_per_hourtake any report with a time, so both tools thin
the same way.
Notes
- Outside the US the archive's precipitation is a fill, and it is refused. Heathrow's
1008 reports from 1 to 21 September carried 0.00 on every one, 89 of them in rain. An
amount is read only where the report's own remarks state one. - A special report carries the running amount since the last routine one, so adding
every row double-counts. The response says to add the routine reports only. - The plan's "strip the K or get nothing" no longer holds for the data endpoint, which
answersKJFKasJFK. The metadata endpoint still needsJFK, so the K comes off.
Downloads
-
Source code (ZIP)
0 downloads
-
Source code (TAR.GZ)
0 downloads
-
0.61.0 Stable
released this
2026-09-22 18:44:22 -05:00 | 33 commits to main since this releaseStep 4 of the weather enhancement plan:
get_outlook, the tenant's tenth tool. For a
date a week to a year out it returns the 1991-2020 normals and NOAA Climate Prediction
Center outlooks for the period around it. It never returns a forecast for the day.Added
get_outlook(latitude, longitude, date).point_forecastis always null, with a
sentence saying why.outlookslists every CPC product covering the date separately:
6-10 day, 8-14 day, monthly and the shortest seasonal lead. Each one has its category
(above_normal,near_normal,below_normal,equal_chances), CPC's probability as
"at least", its period, its issuance time and its own staleness.normalsgives the
daily and monthly 1991-2020 normals from the nearest station that carries them, named
with its distance.- Two upstreams, both keyless:
mapservices.weather.noaa.govfor the outlooks and
www.ncei.noaa.govfor normals. United States only, Alaska and Hawaii included.
Elsewhere the answer isoutside_nws_coverage.
Changed
- The plan named IEM for normals, and the tool uses NCEI. IEM's daily climatology
averages each station's whole record (133 years for its New York average), while
CPC's categories are against 1991-2020. NCEI serves exactly that baseline without a
token. envelopetakes aderived_note, so a composite answer can say what kind of
composite it is.search_wind_shiftskeeps its sentence.
Notes
- There is no Week 3-4 layer in NOAA's map services. Days 15 to 28 are answered by
the monthly and seasonal outlooks, and the response says so. - The live pass removed a computed probability band. The first draft named an upper
edge from CPC's contour levels; a 6-10 day value of 36 is not one of them.
Downloads
-
Source code (ZIP)
0 downloads
-
Source code (TAR.GZ)
0 downloads
-
0.60.0 Stable
released this
2026-09-22 18:23:33 -05:00 | 34 commits to main since this releaseStep 3 of the weather enhancement plan:
get_observationsreads a past window.
Measured against both upstreams first. The plan assumed passingstart,endand
limitthrough would be the whole job, and report volume made it more than that.Added
startandendonget_observations, as dates or ISO 8601 datetimes, read in
the server's zone when they carry no offset.enddefaults to now andstartto a
day beforeend; what was defaulted or clamped is named inwindow.adjusted.- A
windowblock: the span, how many reports it held, how many came back, and
flags for thinning, cutting and a full upstream page.
Changed
- A window with more reports than
countis thinned to the newest report per
hour. A busy field reports every five minutes, so a day is about 300 reports.
The response says what thinning hides.countdefaults to 48 with a window, up to
168, and is unchanged without one. - A window NWS no longer holds falls through to AWC's hourly reports, which
reached 30 days back when measured. NWS keeps about seven, and the response says
which feed answered. - A past window is not judged by the staleness ceiling, which would have raised on
every answer about yesterday. Every row is filtered to the window instead, and the
response says none of it is current.
Downloads
-
Source code (ZIP)
0 downloads
-
Source code (TAR.GZ)
0 downloads
-
0.59.0 Stable
released this
2026-09-22 17:43:44 -05:00 | 35 commits to main since this releaseSteps 1 and 2 of the weather enhancement plan: two models side by side with
precipitation and instability, and a grid that says what it publishes. Every
behaviour below was measured against the live upstreams before it was written, and three
of the measurements changed the design.Added
get_model_runreturns both configured models inmodels, the high-resolution
one and the global one, each with its own run block andrun_age. The envelope
describes the first that answered andenvelope_describesnames it. Outside the
high-resolution domain that entry saysavailable: false, outside_domain: true.- Sixteen more hourly fields: precipitation, rain, showers, CAPE, lifted index,
CIN, boundary layer height, UV (both forms), cloud cover total and low/mid/high,
visibility, weather code and freezing level. A field a model never fills is named in
variables_not_suppliedand dropped from its rows. - Sunrise and sunset per UTC day in
sun. get_forecast_gridlists what the gridpoint publishes:parameters_available
andparameters_published_empty, read from the document.probabilityOfThunderis
in the default set.
Changed
get_model_run's response shape.model_run,runandmodel_cellmoved into
each entry ofmodels. The description changed too, so the connector needs a
reconnect, not just a deploy.- One request per model, not a comma list in
&models=. Measured: over London a
comma list answers 200 with HRRR silently dropped and its keys unsuffixed. - Precipitation probability is not requested. HRRR's ran to hour 48 when its run
stopped at 45, and matched the GFS ensemble hour for hour, so it was never a value
from that run.not_requestedsays so in the response.is_dayis left out too.
Fixed
- Asking the grid for
weatherorhazardsraised a TypeError. Their values are
objects. The reader refuses a non-numeric value by type, and the tool names it in
parameters_not_numericwith a sentence pointing at the tools that carry it. - A grid parameter with
"uom": nullwas labelled"None". The key is present, so
a.getdefault never applied.probabilityOfThunderis null-uom at every office
sampled, and its percent is now named ingrid.UNDECLARED_UNITS.
Downloads
-
Source code (ZIP)
0 downloads
-
Source code (TAR.GZ)
0 downloads
-
v0.58.3 Stable
released this
2026-09-22 14:52:01 -05:00 | 37 commits to main since this releaseThe two items the live pass left open, closed. One by measurement that turned out
not to need days of waiting, and one by decision.Fixed
- The model stale line fired on about half of all normal reads. It was the update
interval plus the current run's own availability lag, with no slack, so a next run
slower than this one tripped it: ECMWF at 814 minutes against 794 on the live pass.
Open-Meteo's open-data bucket keeps about eight days of runs, and each run's
meta.jsonupload time gave the spread directly: the next run landed later on 48% of
ECMWF cycles (largest slip 0.85 h), 43% of GFS (2.58 h) and 55% of HRRR (1.18 h).
LAG_SLACK_SECONDSadds 1 h, 3 h and 1.5 h respectively, and the live case is a test. get_model_runput a second coordinate in its response.model_cellcarried
Open-Meteo's cell latitude and longitude, against the rule that the requested
coordinate is the only one in a response (live-pass check 13). It now carries
distance_milesto the cell instead, which is how station rows answer the same
question.
Downloads
-
Source code (ZIP)
0 downloads
-
Source code (TAR.GZ)
0 downloads
- The model stale line fired on about half of all normal reads. It was the update
-
v0.58.2 Stable
released this
2026-09-22 14:43:02 -05:00 | 38 commits to main since this releaseThe weather live pass ran, and every defect it found was a true field under a false
sentence. Fourteen checks fromdocs/operations.md, run through the connector against
0.58.1: eleven passed, one was partial for want of a front, and three responses told the
reader something the data did not support. No green suite could have seen any of them,
because the fields were right and the tests read fields.Fixed
- A US coordinate was told NWS did not cover it.
get_model_run,get_tafand an
explicit-stationget_observationsnever ask/points, and the shared location block
gave every caller the outside-coverage sentence, so a KJFK call said the US grid did not
cover JFK._location_without_pointnow takes a requiredoutside_coverageargument,
and a tool that never asked says it did not look rather than that the answer was no. search_wind_shiftssaid nothing in it was a forecast. It rode the station list's
no-issuance branch: "a mapping rather than a weather product... Nothing here is a
measurement or a forecast", on a scan over a model run and a TAF.envelopegains
derived, whose sentence says the scan has no issuance time of its own and points at
the per-source run and bulletin times. The fields are unchanged:issuednull and
staleexplicitly null.list_stationsreported airports as issuing no TAF when it never looked. From a
point on Long Island, KEWR and KTEB were listed observation-only; from KJFK the same two
were listed with a TAF. The TAF box stops widening once it answers and the NWS list
reaches further, so both arrived from NWS alone. A station outside a product's box now
carriesproducts_not_searched, and the description says a product named there is
unknown, not absent.- The empty scan's note ran two sentences together: "came back from gfs_hrrr The
terminal forecast read was...". The model clause now ends with a full stop.
Changed
docs/operations.mdrecords the pass: what passed, the partial, the three defects
above, and what it left open. The ECMWF stale line fired at 814 minutes against 794 on a
run that was simply not yet published, which is fourteen minutes of headroom and wants
several days of samples before it moves.get_model_run'smodel_cellis a second
coordinate in the response and keeping it is an operator decision. And the run was
Claude Code rather than the claude.ai app, so the three wording checks still want a run
there.CLAUDE.mdno longer calls the pass owed, and the station-distance section gains
the rule thatproductsis what a search found rather than what a station issues.
Downloads
-
Source code (ZIP)
0 downloads
-
Source code (TAR.GZ)
0 downloads
- A US coordinate was told NWS did not cover it.
-
v0.58.1 Stable
released this
2026-09-22 00:48:51 -05:00 | 40 commits to main since this release0.58.0's gate went red, and the check that caught it could not have caught it before the
commit. A coordinate-shaped literal — Open-Meteo's own model-cell position, quoted from a
live measurement into a docstring — went intoweather/model.py. The full suite was green
locally and red on the first CI run, and the difference is the whole finding:
test_no_pii.pyaskedgit ls-files, which lists what is tracked, so all eight files
0.58.0 added were invisible to every rule in that file until the commit that published
them.A guard whose scope is "what is published" has to run on what is ABOUT to be published.
One that waits for the commit reports the leak instead of preventing it — and reports it
from CI rather than from the keyboard, after the value has reached a public remote.Fixed
- The literal is gone from
weather/model.py, replaced by a description of the value's
shape. That is CLAUDE.md's own instruction for this case: describe the shape instead.
Nothing read the number; it was there to record what the endpoint answers. test_no_pii.pynow also walksgit ls-files --others --exclude-standard, so the set
it scans is what agit add -Awould put in the repo rather than what is already in it.
--exclude-standardapplies.gitignore, so every false positive that function's
docstring lists —.env,probes/, a scrub script — stays excluded exactly as before.
Proved by putting a coordinate in an untracked file and watching the rule go red, then
removing it.
Changed
- The 48 weather tests added in 0.58.0 are
async defrather thanasyncio.run, which
is what the other 107 in that file already were.pyproject.toml's ownfilterwarnings
comment says fastmcp's in-memory transport leaks one half of an anyio stream and that GC
timing decides which test the warning lands on, so left fatal it fails a different
unrelated test each run. Building and tearing down an event loop per test changes exactly
that timing, repo-wide, for no benefit — and it put those tests on a different footing
from every other test in the file.
No behaviour change. The nine tools, their responses and their descriptions are
byte-identical to 0.58.0; this is the gate and the test suite.0.58.0 published no image, because
imagehasneeds: gate. So the running container
is still 0.57.3 until this one goes green, and the deploy plus connector reconnect that
0.58.0's entry describes applies to this version instead.Downloads
-
Source code (ZIP)
0 downloads
-
Source code (TAR.GZ)
0 downloads
- The literal is gone from