Repository navigation
get_pvs_stats moves reading guidance off tools/list (#3898) - #4125
Merged
Merged
Conversation
Refs #3898. Converts the twin get_pvs_stats tool (Darling and Lite) to the head/tail split: a 606-char served head carries the guardrail facts, and get_tool_guide serves the rest on demand. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QQh8LczP1HGLXQYAx4oTZC
…he zero-denominator null Coordinator verification fixes. The head said "measured 0 MB = pct_of_database 0.00, never null", but PctReason also nulls the share when the database size is missing or 0, so a measured 0 MB against a missing size is null. The rule now names both null causes and the zero denominator. "no end time = still running" dropped the original's condition (a start time with no end time), and the head lacked lesson 5's "LATEST IS A TIME" for this latest-snapshot tool; it also read "at as_of" as if as_of were a parameter. The tail's "the denominator is absent" now reads "absent or zero" to match PctReason. Head 589, served 620 on both products. McpZeroIsAMeasurementTests re-points its pvs_measured pin to the head's new phrase; the tail keeps the original sentence. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NUU29PuGg9TUFBsACgZg2K
erikdarlingdata
marked this pull request as ready for review
September 24, 2026 04:11
erikdarlingdata
enabled auto-merge (squash)
September 24, 2026 04:11
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>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Refs #3898.
Why
Issue #3898: a client that loads Darling's whole MCP catalog pays for every tool description before it asks a
question.
get_pvs_statsis one of the twin tools that carries that cost on both products (1,156 servedcharacters each). This PR moves its reading guidance off
tools/listand into the per-tool guide thatget_tool_guide(tools[], topics[])serves on demand. Nothing is deleted. The full original text now lives in thetail, and
get_tool_guidereturns it by tool name.What changes
Darling/PerformanceMonitor.Darling.Service/Mcp/DarlingMcpPvsTools.csandLite/Mcp/McpPvsTools.cs: splitget_pvs_stats'sDescription(...)into a head (served ontools/list) and a tail (served byget_tool_guide), using the literal<<GUIDE>>marker. The two products' descriptions were byte-identicalbefore this change (confirmed with
desc.py) and stay byte-identical after it.Darling/Darling.Tests/McpToolGuideHeads.Pvs.csandLite.Tests/McpToolGuideHeads.Pvs.cs(new): head pins forthe tool, following the HealthParser and Data family pattern.
Darling/Darling.Tests/McpToolsListBudget/DarlingMcpPvsTools.txtandLite.Tests/McpToolsListBudget/McpPvsTools.txt: updated thetool get_pvs_statsline to the new served length.The two
paramlines are unchanged. No parameter was renamed, added, or removed.Before/after served characters
The served head (620 chars) is the new head text plus the 31-character pointer that
tools/listappends(" Reading guide: get_tool_guide."). The cap is 620.
Corrections
One, made during coordinator verification. The tail said pct_of_database "is null only when the numerator was
not measured or the denominator is absent".
PctReasonalso returns a reason, and the share is null, when thedatabase size is 0 (
DarlingMcpPvsTools.csPctReason, and Lite's twin inMcpPvsTools.cs). The tail nowsays "the denominator is absent or zero".
dropcheck.pylists that one sentence as changed on each product.D4 removals
None. The served text (head and tail) carries no issue reference and no anecdote to remove.
Re-pointed pins
One pin was re-pointed during coordinator verification.
Darling/Darling.Tests/McpZeroIsAMeasurementTests.csalready tests this toolthrough two helpers,
DescriptionOfandToolDescription. Both already resolved toMcpToolGuide.Split(...).Headbefore this PR. For an unconverted tool the head is the whole literal, so the pinspassed either way. The pin
Assert.Contains("pvs_measured says whether the DMV reported a size", darling, ...)in
BothSkusPvsDescriptions_StayByteIdentical_AndTeachPvsMeasurednow exercises the real head/tail split for thefirst time.
The coordinator's head rewrite teaches the flag inside the pct_of_database rule instead, so the pin now checks
"PVS size is unmeasured (pvs_measured false)". It still checks that the served head teaches
pvs_measured. Thetail keeps the original "pvs_measured says whether the DMV reported a size" sentence.
D9 traps
The served head alone must answer each of these correctly. Each phrase in "Prevents it" is in the head, word for
word.
get_pvs_statsreturnspct_of_database: nullfor a database. Does that mean the database has 0 percentPVS usage?
Correct: No. Null means the size was not measured, or the database size is missing or 0. It never means
zero.
Prevents it: "pct_of_database is null only if PVS size is unmeasured (pvs_measured false) or database size is missing or 0"
pvs_size_mbis 0. Is that the same aspct_of_databasecoming back null?Correct: No. A measured 0 MB against a known database size is a real, healthy value.
pct_of_databasecomes back 0.00, not null.
Prevents it: "else measured 0 MB = 0.00"
aborted_version_cleaner_end_timeis null whileaborted_version_cleaner_start_timehas a value. Is thatmissing data?
Correct: No. That pairing means the cleaner is mid-run. It is a normal state, not a gap.
Prevents it: "cleaner times (a start with no end = mid-run)"
trend_hours_backanchored to the sameas_ofas the snapshot, the way most windowed tools anchor toa supplied point in time?
Correct: No. It looks back from the call's current time. This tool takes no
as_ofparameter at all.Prevents it: "looks back from now, not from as_of"
trend_hours_back. Will a database that fell out of today's top 5 still show up forthe period when it was bigger?
Correct: No. The top-5 filter is evaluated by current size. A database that has since shrunk out of the top
5 never appears in the trend, even for its earlier high points.
Prevents it: "over the top-5 databases by current size"
fields in this system. Do I need to adjust them myself?
Correct: No. They are already de-skewed, so they compare directly against
as_ofwith no adjustment.Prevents it: "Cleaner times are de-skewed to UTC, like as_of."
status: "empty"forget_pvs_stats. Does that mean this target cannot collect PVSdata at all?
Correct: Not necessarily.
emptymeans the engine can collect PVS, but no PVS row for this server is in thestore, for example on a fresh registration. When the engine truly cannot collect PVS, the status is
not_collectedinstead.Prevents it: "No rows: not_collected if this engine can't collect PVS, else empty"
pct_of_databasea fraction from 0 to 1, the way some other ratio fields in this system are?Correct: No. The name and the healthy-case value both say percent on a 0 to 100 scale, not a fraction.
Prevents it: "% of database" and "else measured 0 MB = 0.00"
trend_hours_backdefault to some standard window, the wayhours_backdefaults to 1 or 24 hourson other tools?
Correct: No. The default is 0, which turns the trend off. A default call returns only the latest snapshot.
Prevents it: "trend_hours_back (0 = off)", and the parameter's "0 (default) returns the latest snapshot only"
Correct: No. The main read has no window. It is the single newest capture, stamped
as_of. Only theoptional trend adds a window, and only for the top-5 databases.
Prevents it: "LATEST IS A TIME: the newest snapshot, not a window"
aborted_transaction_counta delta over some period, like a per-window count elsewhere in thissystem?
Correct: No. This tool reports a single snapshot at
as_of. Every field, including the aborted transactioncount, is the value at that moment, not a change over a period.
Prevents it: "LATEST IS A TIME: the newest snapshot, not a window; as_of is when it was taken."
not_collectedis the answer, did the collector fail to run this cycle?Correct: No.
not_collectedmeans this server's engine cannot run PVS collection at all. Examples are aPostgreSQL target, and a SQL Server edition too old for Accelerated Database Recovery. It is a permanent fact
about the target, not a transient miss.
Prevents it: "not_collected if this engine can't collect PVS"
pvs_size_mb: 0,pvs_measured: true, andpct_of_database: null. Is that acontradiction?
Correct: No. The database size is missing or 0, so the share has no denominator.
pct_of_database_reasonsays which.Prevents it: "or database size is missing or 0 (pct_of_database_reason says why)"
as_ofto read the PVS state as it was during last night's incident?Correct: No. The tool takes no
as_ofparameter. It reads the newest snapshot, andas_ofin the answersays when that snapshot was taken.
Prevents it: "LATEST IS A TIME: the newest snapshot, not a window; as_of is when it was taken."
aborted_version_cleaner_start_timeandaborted_version_cleaner_end_timeare null. Is the cleanermid-run?
Correct: No. Mid-run needs a start time with no end time.
Prevents it: "a start with no end = mid-run"
as_ofis three days old. Is thepvs_size_mbshown the database's size right now?Correct: No. It is the size at
as_of, three days ago.Prevents it: "LATEST IS A TIME: the newest snapshot, not a window; as_of is when it was taken."
Coordinator verification
pct_of_databaseis null when the database sizeis missing or 0 (
DarlingMcpPvsTools.csandMcpPvsTools.cs:r.DatabaseDataSizeMb is > 0, andPctReason). The rule now reads "pct_of_database is null only if PVS size is unmeasured (pvs_measured false)or database size is missing or 0 (pct_of_database_reason says why); else measured 0 MB = 0.00".
it.
reads "a start with no end = mid-run".
GetPvsStatsLatestAsyncreads the rows atMAX(collection_time), and the head lacked "LATEST IS A TIME". It also said "at as_of", which reads like anas_ofparameter. The head now says "LATEST IS A TIME: the newest snapshot, not a window; as_of is when it wastaken."
trend_hours_backlooks back fromDateTime.UtcNow, not fromas_of. Thetrend's top 5 is by the newest snapshot's size,
DESC NULLS LAST(DarlingPvsReader.PvsTrendSql,LocalDataService.FinOps.Pvs.cs). The four cleaner times are de-skewed to UTC. No rows givesnot_collectedfirst, else
empty.and Q15 were answered "can't tell". Every other answer was correct.
total, 0 failed. The classes:
McpToolGuide*,McpToolsListBudget*,McpDescription*,McpZeroIsAMeasurement*,McpPayloadContractCensus*,MeasurementContractCensus*,McpPageContract*,*Pvs*,AsOfWindowAnchor*,McpLatestSnapshotStamp*,McpPayloadClockFrameDiscipline*,McpMissMessageParityPin*,ConsumedTimestampFrameDiscipline*.Test plan
dotnet build Darling/Darling.Tests/Darling.Tests.csproj -c Debug: Build succeeded, 0 Warning(s), 0Error(s).
dotnet build Lite.Tests/Lite.Tests.csproj -c Debug: Build succeeded, 0 Warning(s), 0 Error(s).McpToolGuideHeadsPvsTests,McpToolGuideTests,McpToolsListBudgetTests,McpZeroIsAMeasurementTests,McpPayloadContractCensusTests,DarlingMcpPvsToolsTests,McpLatestSnapshotStampTests,AsOfWindowAnchorTests,ConsumedTimestampFrameDisciplineTests,McpPayloadClockFrameDisciplineTests): Total 201, Failed 0.McpToolGuideHeadsPvsTests,McpToolGuideTests,McpToolsListBudgetTests,McpMissMessageParityPinTests,McpDescriptionTruthPinTests): Total 87, Failed 0.pvs_measuredhead phrase, confirmedMcpToolGuideHeadsPvsTestsfailed, restored it,confirmed green again.
python dropcheck.py origin/dev HEAD get_pvs_statsreportsget_pvs_stats [Darling] old=1156 new=1742 sentences=5 missing=0andget_pvs_stats [Lite] old=1156 new=1742 sentences=5 missing=0.git merge origin/dev: clean, no conflicts.Darling.Tests.exe, after the merge): Total 13393, Errors 0, Failed 0, Skipped 637, NotRun 1. The known non-elevated
DarlingInstallLocationTestsfailures did not occur in this run.Lite.Tests.exe, after the merge): Total 5270, Errors 0, Failed 2. The two failures,AnalysisPassTokenThreadingTests.TheReadLockWaitIsAbandonableWhileAWriterHoldsItandStatusBarSizeReadLockTests.GetUsedDataSizeMb_WhenTheWriteLockIsHeld_GivesUpInsteadOfBlocking, are lock-timingtests unrelated to this tool. Re-run alone, both classes passed (Total 7, Failed 0), which confirms they are
contention flakes from the full parallel run, not caused by this change.
Status routes (for the "not_collected" and "empty" words the head states)
rows.Count == 0and this server's engine cannot run PVS collection at all (checked first)rows.Count == 0and the engine can collect PVS, but no PVS row for this server is in the storeMcpHelpers.FormatError(same on both products)Handoff
None.
get_pvs_statsis fully converted and green on both products.🤖 Generated with Claude Code
https://claude.ai/code/session_01QQh8LczP1HGLXQYAx4oTZC