Skip to content

get_daily_summary_range/get_daily_summary move reading guidance off tools/list (#3898) - #4099

Merged
erikdarlingdata merged 3 commits into
devfrom
feature/3898-content-health
Sep 24, 2026
Merged

erikdarlingdata merged 3 commits into
devfrom
feature/3898-content-health

Conversation

@erikdarlingdata

@erikdarlingdata erikdarlingdata commented Sep 24, 2026 •

Copy link
Copy Markdown
Owner

Refs #3898.

Why

Darling's tools/list (about 330 KB) and Lite's (about 148 KB) load every tool description in full before an
agent asks its first question. get_daily_summary and get_daily_summary_range carried two of the longer
descriptions in the health-overview family. This PR moves each tool's reading guidance behind get_tool_guide,
so tools/list serves a short head and the full guide is a second call away. Nothing is deleted.

What changes

  • Darling/PerformanceMonitor.Darling.Service/Mcp/DarlingMcpHealthTools.cs and Lite/Mcp/McpHealthTools.cs:
    both tools now carry the <<GUIDE>> marker. The head is new, terse text. The tail is each product's own
    original description, unchanged, word for word.
  • Darling/Darling.Tests/McpToolGuideHeads.SqlCore.cs and Lite.Tests/McpToolGuideHeads.SqlCore.cs: a new
    McpToolGuideHeadsSqlCoreDailySummaryTests class in each file, appended after the existing
    McpToolGuideHeadsSqlCoreTests class. It pins the two heads' guardrail facts and pins the one product
    difference in the tails.
  • Darling/Darling.Tests/McpToolsListBudget/DarlingMcpHealthTools.txt and
    Lite.Tests/McpToolsListBudget/McpHealthTools.txt: the two tools' served-length lines, lowered to match. No
    parameter needed trimming. None is over the 200-character cap, so no parameter text moved to a tail.

Per-tool served characters (budget test numbers)

Tool Darling before Darling after Lite before Lite after
get_daily_summary 1,414 617 1,034 617
get_daily_summary_range 1,903 608 1,474 608

Both heads are byte-identical between Darling and Lite for the same tool. The generic cross-SKU pin in
McpToolGuideTests also enforces this.

Status routes (every status word the heads state, cited to the code)

get_daily_summary:

  • unavailable, data_state: purged: row.DataState == DailySummaryDataState.Purged, before retention_horizon.
    Darling: DarlingMcpHealthTools.cs:112. Lite: McpHealthTools.cs:95.
  • empty: !row.HasData. No row exists for the day at all in the nine-source spine: not purged, not
    no_run_record. DarlingHealthReader.cs:521-537 builds a synthetic HasData: false row only when the range
    query for that single day returned zero rows and the day is not before the horizon. Darling:
    DarlingMcpHealthTools.cs:126. Lite: McpHealthTools.cs:109.
  • Otherwise: data_state is collected, no_run_record or, before the horizon while a signal table still holds the day, past_horizon, from the shared DailySummaryRetention.StateFor
    (PerformanceMonitor.Common/DailySummaryDataState.cs:97-103), identical on both products. A no_run_record
    day's zeros are real measurements and the band stands (DailySummaryDataState.cs:43-49). Its error share has
    no denominator, because no run was recorded.

get_daily_summary_range:

  • Top-level unavailable: rows.Count == 0 and HasAnyCollectionLogAsync is false. Nothing was ever collected
    for the server. Darling: DarlingMcpHealthTools.cs:223,243-246. Lite: McpHealthTools.cs:205,224-227.
  • Top-level empty: rows.Count == 0 and some collection log entry exists, but none inside the requested span.
    Darling: DarlingMcpHealthTools.cs:223,239-242. Lite: McpHealthTools.cs:205,220-223.
  • Per-row data_state (not a top-level status): collected, no_run_record, past_horizon or purged, from
    the same shared DailySummaryRetention.StateFor. A purged row's zeros are absences
    (DailySummaryDataState.cs:148-151). A past_horizon row's non-zero counts are real, but a zero may be
    either a measurement or an absence, because some signal sources still hold the day and some do not
    (DailySummaryDataState.cs:152-155).

My first head draft said "zeros are absences" for both states. I checked it against DailySummaryRetention.Note
before committing and narrowed the past_horizon clause to match the code. The original shipped description
never made this over-broad claim, so this is not a D9 correction to prose that shipped; it is a note on how the
new head text was checked.

Corrections

None. Both products' original prose matches the tool bodies.

D4 removals

None. Neither description carried an issue reference or an anecdote.

Re-pointed pins

None needed.
DarlingMcpHealthToolsTests.DailySummaryDescriptions_NameTheHorizon_ThePurgedState_AndTheExactDateFormat reads
the raw DescriptionAttribute.Description: head, marker and tail together. Every substring it checks
(retention_horizon, days_before_horizon, data_state=purged, NEVER Healthy, unique_queries=null,
unique_queries is null, days_missing) is still present verbatim inside the tail, so the test passes
unchanged. No other test file that mentions either tool touches the Description attribute.

D9 traps

  • Q: get_daily_summary answers status "empty" for a date. Did the day fall before the server's retention_horizon?
    Correct: No. Empty means no row exists for that day at all. A day before retention_horizon answers "unavailable" with data_state purged instead.
    Prevents it: "status empty: no row for that day."
  • Q: get_daily_summary answers status "unavailable", data_state purged, and deadlock_count 0. Was the day actually quiet?
    Correct: There is no way to tell. Before retention_horizon, zeros are absences, not measurements, so no verdict is possible.
    Prevents it: "data_state purged, zeros are absences not measurements, no verdict."
  • Q: get_daily_summary answers data_state "no_run_record" with health_band Healthy. Is that band trustworthy even though no collector run was recorded?
    Correct: Yes. Inside retention every zero is a real measurement, so the band stands on the counts as read.
    Prevents it: "Inside retention: data_state collected or no_run_record; band stands on real zeros."
  • Q: get_daily_summary answers data_state "no_run_record" with collection_errors 0. Does that zero mean nothing failed?
    Correct: Not provably. A no_run_record day's error share has no denominator, so a zero there is not the same guarantee as on a collected day.
    Prevents it: "no_run_record's error share has no denominator."
  • Q: get_daily_summary_range answers status "unavailable" for the whole call. Does that mean every day in the span was purged?
    Correct: No. The range's top-level unavailable means the server has never had any collection recorded. A purged day still appears as an ordinary row inside days.
    Prevents it: "status unavailable: nothing was ever collected."
  • Q: get_daily_summary_range answers status "empty". Does that mean the server has no history at all?
    Correct: No. Empty means no collected day fell inside this span. The server can have history outside it.
    Prevents it: "status empty: no collected day in range but the server has history elsewhere."
  • Q: A date is missing from get_daily_summary_range's returned days. Does that mean the day was quiet, with nothing to report?
    Correct: No. Any day with any collection at all appears, even a quiet one, so a missing day is a collection gap, not a quiet day.
    Prevents it: "A day with ANY collection appears here even if quiet (Healthy), so a gap is a COLLECTION gap."
  • Q: A row in get_daily_summary_range has data_state "past_horizon" and deadlock_count 0. Is that zero a real measurement?
    Correct: It cannot be read from the field alone. Past_horizon rows are real where non-zero, but a zero there may be either a measurement or an absence.
    Prevents it: "past_horizon rows are real but a zero may be either."
  • Q: A row in get_daily_summary_range has data_state "purged" and blocking_events 0. Is that zero real?
    Correct: No. A purged row's zeros are absences, because the per-signal tables were purged for that day.
    Prevents it: "data_state purged rows' zeros are absences."
  • Q: Does get_daily_summary_range return one row per day requested by days_back, or only for days the store actually holds?
    Correct: Only for days the store holds. A collection gap means fewer rows than days_back.
    Prevents it: "so a gap is a COLLECTION gap."
  • Q: If as_of is omitted on get_daily_summary_range, what does the span end on?
    Correct: Now, the present moment. Not an error, and not a fixed epoch.
    Prevents it: "ending at as_of (default now)."
  • Q: Does get_daily_summary_range return a narrower set of per-day fields than get_daily_summary returns for one day?
    Correct: No. Each row carries the same per-day fields: health band, wait time, queries, deadlocks, blocking, CPU, memory, errors and alerts.
    Prevents it: "same per-day fields as get_daily_summary."
  • Q: Is get_daily_summary's total_wait_time_sec figure in milliseconds?
    Correct: No, it is seconds.
    Prevents it: "wait time (sec)."

Test plan

  • dotnet build Darling/Darling.Tests/Darling.Tests.csproj -c Debug: Build succeeded, 0 Warning(s), 0 Error(s).
  • dotnet build Lite.Tests/Lite.Tests.csproj -c Debug: Build succeeded, 0 Warning(s), 0 Error(s).
  • Targeted Darling classes: McpToolGuide*, McpToolsListBudget*, DarlingMcpHealthToolsTests,
    DailyHealthBandTests, DailySummaryNotCarriedTests, DailySummaryReadShapeTests,
    DarlingDailySummaryRangeTests, ServerPageTabsTests, and the new
    McpToolGuideHeadsSqlCoreDailySummaryTests. Total 294, 0 failed, 5 skipped (live PostgreSQL, no rig here).
  • Targeted Lite classes: McpToolGuide*, McpToolsListBudget*, McpMissMessageParityPinTests,
    DailyHealthBandTests, DailySummaryRangeToolTests. Total 195, 0 failed, 0 skipped.
  • Red-watch: broke get_daily_summary's head sentence. McpToolGuideHeadsSqlCoreDailySummaryTests failed as
    expected. Restored from git and rebuilt green.
  • dropcheck, both tools, both products: missing=0 in all four rows.
  • Full Darling.Tests suite (coordinator): Total 13,295, 0 failed, 632 skipped.
  • Full Lite.Tests suite (coordinator): Total 5,255, 1 failed, the budget test. The cause was my edit during the run, and the test passes on the final build (see Coordinator verification).
  • Plain-English checker on this body: first pass found 57 hits, mostly the exempt D9 label markup; the real
    long-sentence, semicolon, contraction and Latin-abbreviation hits are fixed in this version.

Coordinator verification

  • Synced with dev with no conflicts. dropcheck: missing=0 in all four rows. paramcheck: no parameter changed.
  • I checked every head fact against the code on both products. One was wrong. The get_daily_summary head said
    that a day before retention_horizon always answers unavailable with data_state purged. That is true only
    when no signal table still holds the day. DailySummaryRetention.StateFor (DailySummaryDataState.cs:97-103)
    returns past_horizon when one does. Both tools then return the row with band NoData, because the
    unavailable branch checks only Purged (DarlingMcpHealthTools.cs:112, McpHealthTools.cs:95). The head
    now names past_horizon, and the Status routes table above names it too.
  • D9, first run (the lane's 13 questions plus one about past_horizon): 1 misread, on Q4. My own interim edit
    caused it, because it took the no_run_record error-share clause out of the head. The clause is back.
  • D9, second run: 1 misread, on Q3. The reader took the error-share clause to mean that a no_run_record band
    cannot be trusted. The head now says that the band stands for no_run_record too, and that only its
    collection-error share has no denominator. I did not run a third round.
  • The head's field list is shorter, to stay under the 620-character cap. It says "errors, alerts" for
    "collection errors and alert count", and "composite", "type" and "events" are gone. The tail keeps the full
    original text.
  • Pins: the purged pin is re-pointed. New pins cover the past_horizon and no_run_record clauses on both
    products. Budget: get_daily_summary goes from 571 to 617 on both.
  • Full suites, on the first-fix build: Darling 13,295 total, 0 failed, 632 skipped. Lite 5,255 total, 1 failed:
    McpToolsListBudgetTests. I edited the budget file while the Lite suite ran, so the test compared the older
    binary with the newer budget line. On the final build the targeted classes pass, that test included: Darling
    307 (0 failed, 7 skipped) and Lite 208 (0 failed).

Handoff

  • Nothing is deferred. Both tools are converted, and the coordinator ran both full suites.

erikdarlingdata and others added 3 commits September 23, 2026 21:05
…ools/list (#3898)

get_daily_summary and get_daily_summary_range now serve a short head from
tools/list, with the full original description available on demand from
get_tool_guide. Both tools convert on Darling and Lite (D6 lockstep); the
heads are byte-identical, and each product's tail keeps its own original
prose verbatim.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QQh8LczP1HGLXQYAx4oTZC
…3898)

A day before retention_horizon answers unavailable/purged only when no signal
table still holds it; otherwise the tool returns a past_horizon row (band NoData,
non-zero counts real). The head now says so, and says a no_run_record band
stands with only its collection-error share lacking a denominator (D9 Q3/Q4).
Pins re-pointed and added on both products; budget 571 -> 617 on both.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QQh8LczP1HGLXQYAx4oTZC
@erikdarlingdata
erikdarlingdata marked this pull request as ready for review September 24, 2026 01:26
@erikdarlingdata
erikdarlingdata enabled auto-merge (squash) September 24, 2026 01:26
@erikdarlingdata
erikdarlingdata merged commit 98ac57a into dev Sep 24, 2026
16 of 18 checks passed
@erikdarlingdata
erikdarlingdata deleted the feature/3898-content-health branch September 24, 2026 01:33
erikdarlingdata added a commit that referenced this pull request Sep 24, 2026
* Add 48 buffered CHANGELOG entries in one splice

Adds 48 entries and 48 link refs (#4057, #4077, #4079, #4081, #4082, #4083, #4084, #4085, #4086, #4087, #4088, #4089, #4090, #4091, #4092, #4093, #4095, #4096, #4099, #4100, #4101, #4103, #4105, #4106, #4107, #4108, #4109, #4110, #4111, #4113, #4114, #4115, #4116, #4117, #4118, #4119, #4120, #4121, #4122, #4123, #4124, #4125, #4126, #4127, #4131, #4133, #4136, #4141). Each PR's entry was buffered, and this lands every entry whose PR was merged on origin/dev when it ran.

Entries found under a bare section line (none unless the old script ran again) move into the matching ### section, below the new entries.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QQh8LczP1HGLXQYAx4oTZC

* Changelog splice: add #4137's entry, which merged before the splice but was missed

#4137 (pg_plan_capture reads csvlog) merged at 05:57Z with a CHANGELOG entry section in its
body. It was neither spliced nor listed as needing no entry. Its entry goes under Fixed, right
after #4136's, and #4136's last sentence now points to it instead of saying plan capture is
unchanged.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NUU29PuGg9TUFBsACgZg2K

---------

Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant