Skip to content

get_pg_log_events/get_pg_index_bloat/get_pg_deadlocks move reading guidance off tools/list (#3898) - #4068

Merged
erikdarlingdata merged 2 commits into
devfrom
feature/3898-content-pg-a
Sep 23, 2026
Merged

erikdarlingdata merged 2 commits into
devfrom
feature/3898-content-pg-a

Conversation

@erikdarlingdata

@erikdarlingdata erikdarlingdata commented Sep 23, 2026 •

Copy link
Copy Markdown
Owner

Refs #3898.

Why

A client loading Darling's whole MCP catalog pays for every tool description before its first question. These
three tools were among the largest descriptions on the wire (4,199 / 3,491 / 2,839 served characters). The
guidance in them is not deleted, it moves off tools/list into each tool's get_tool_guide tail, which a
client reads on demand.

What changes

Converted, Darling only (none of the three has a Lite twin):

  • get_pg_log_events (DarlingMcpPgLogEventTools.cs)
  • get_pg_index_bloat (DarlingMcpPgIndexTools.cs)
  • get_pg_deadlocks (DarlingMcpPgDeadlockTools.cs)

Each head states the guardrail facts a model needs to choose the tool and not misread an empty or ambiguous
answer: per-family gating and the two silent whole-tool preconditions for get_pg_log_events; estimated vs.
measured, the skipped-reason semantics and the answerless-rows-sort-first trap for get_pg_index_bloat; and
the "empty does not mean none occurred" / transport-dependent times_seen traps for get_pg_deadlocks. Every
sentence of the original text is preserved verbatim in the tail (dropcheck confirms below); order was not
changed.

D2 (over-cap parameters). Four parameters were over the 200-char cap for a converted tool. Each was trimmed
to its guardrail sentence plus "See the tool's reading guide."; the removed text moved, verbatim or with a
pronoun made explicit for standalone reading, into the tool's own tail:

  • get_pg_log_events.min_severity: 233 -> 147 chars. Moved: "WARNING is the 'errors only' setting; connection
    and lock_wait lines are LOG and are excluded by anything above it."
  • get_pg_index_bloat.limit: 285 -> 65 chars. Moved: "This is what bounds the page - read truncated to know
    whether the census held more indexes than were returned; it is observed by fetching one row past this cap,
    never inferred from a full page, and the coverage field is measured independently of it."
  • get_pg_index_bloat.answered_only: 563 -> 129 chars. Moved (this parameter's Description was built from
    concatenated string literals; the whole concatenation was replaced with one literal): "answered_only defaults
    to false, which leaves the answerless-first order alone. Set answered_only when you want the RANKING:
    answerless rows sort first by design, so on a server whose answerless population exceeds the 1000-row maximum
    the answers cannot be reached at any limit - measured at 1,779 answerless ahead of 726 answers covering 213.8
    GB. The coverage census is unaffected by this, so a filtered ranking still carries the same denominator; read
    the reach field to see which situation you are in."
  • get_pg_deadlocks.limit: 245 -> 70 chars. Moved: "This is what bounds the page - read truncated to know
    whether the window held more distinct deadlocks than were returned; it is observed by fetching one row past
    this cap, never inferred from a full page."

Topics. No prose repeats verbatim across these three tools (each is a different domain: log events, index
bloat, deadlocks), so McpToolGuideTopics.PgA.cs's s_pgA array is untouched (still empty).

Before / after (served characters, Darling only — no Lite twins)

Tool Before After (head, pointer included)
get_pg_log_events 4,199 619
get_pg_index_bloat 3,491 613
get_pg_deadlocks 2,839 605

Corrections

None. All three original descriptions were re-checked against the tool bodies while drafting the heads; no
factual errors found.

D4 removals

None. None of the three tools' wire text (head or tail) carried an issue reference, an anecdote, or a WIRE
CHANGE notice.

Re-pointed pins

  • McpPageContractTests.EveryPgPagedToolThroughTheHelper_FetchesOnePastTheCap_AndObservesThroughBoundPage: its
    limit parameter pin (the get_pg_server_config counted the page and called it the server: non_default_count is now the snapshot's, computed above the cap, truncated is observed off a limit + 1 fetch, and its three sibling pages stop inferring truncation from a full page (#3653 A3 residue) #3679 "This is what bounds the page" / truncated wording) failed for
    get_pg_deadlocks and get_pg_index_bloat once their limit parameters were trimmed under D2. Re-pointed to
    read the tool's tail when the tool carries the <<GUIDE>> marker, falling back to the parameter description
    for every other (unconverted) tool in that roster. Reason: the wording is still on the wire, just moved from
    the parameter to the tail, which is exactly what D2 asks for.
  • No other existing test (McpDescriptionTruthPinTests, McpZeroIsAMeasurementTests,
    McpPayloadContractCensusTests, PgCappedReadSurfaceTests, PgIndexBloatCoverageTests,
    PgIndexBloatGridReachTests, PgDeadlockNormalizationTests, PgLogEventsPipelineTests,
    PgLoggingCollectorOffTests, DarlingMcpPgLoggingAuditToolsTests, PgLogEventMetricsTests) needed
    re-pointing: each either reads the raw (unsplit) Description attribute directly (so head+tail both still
    satisfy a substring check), or pins something unrelated to these three tools' description text (SQL text,
    registration census, a different tool's description, or an unrelated Description property on an analysis
    finding).

D9: known traps for the misreading check

  1. Q: get_pg_log_events with family=connection came back empty: no connections happened?
    Correct: log_connections/log_disconnections may simply be off on the target; the tool does not audit
    that.
    Prevents it: the head's "Each family is gated by its own logging setting; an off family is EMPTY here,
    unaudited."
  2. Q: Every family of get_pg_log_events came back empty at once: the server is quiet?
    Correct: could also be a non-UTC log_timezone (the whole log refused) or a non-English lc_messages
    (nothing matches).
    Prevents it: the head's "Also needs log_timezone = UTC (else refused whole) and English lc_messages
    (else empty) - an all-family empty can mean either, not 'no events'."
  3. Q: min_severity=WARNING on get_pg_log_events: does that just filter out low-priority noise the same
    way log_min_messages=WARNING would?
    Correct: no, the two orderings disagree (a lock_wait LOG-level line and a suppression-order LOG are not
    the same rank).
    Prevents it: the head's "min_severity ranks by SERIOUSNESS, not log_min_messages' order."
  4. Q: get_pg_log_events shows times_seen: 1 on an RDS target for a lock_wait entry: was it captured just
    once and might there be more updates coming?
    Correct: 1 is the normal steady state on RDS/Aurora (consume-once log API); it never climbs the way a
    self-hosted re-read does.
    Prevents it: the head's "times_seen is a sighting count, not an occurrence count." plus the tail's
    transport-specific detail.
  5. Q: get_pg_index_bloat rows show est_bloat_pct but not est_tuple_bytes — no wait, est_tuple_bytes
    is populated: does that mean the row has a usable bloat estimate?
    Correct: no; est_tuple_bytes is populated even on 100% of skipped (answerless) rows. Only
    skipped_reason IS NULL means there is an answer.
    Prevents it: the head's "skipped_reason present = NO answer, not healthy" plus the tail's trust-predicate
    detail (kept verbatim, not in the head due to the 620 cap).
  6. Q: get_pg_index_bloat with the default limit returns 25 rows, all with a skipped_reason: the server
    has no reclaimable bloat?
    Correct: answerless rows sort first by design; raising the limit does not reach the answers on a big
    server. answered_only=true is the only route to the ranking.
    Prevents it: the head's "Answerless rows sort FIRST by design: read coverage before judging reach, and
    pass answered_only=true to reach real answers."
  7. Q: get_pg_index_bloat's measurement_kind is "measured" on a row: was that walked just now?
    Correct: no; "measured" rows are an OLDER pgstatindex reading still inside the retention window, not a
    fresh walk.
    Prevents it: the head's "measurement_kind: estimated (stats model) or measured (an older pgstatindex
    reading in retention)."
  8. Q: get_pg_index_bloat ranks a 64 kB index above a 10 GB index: is that a bug?
    Correct: no, ranking is by reclaimable BYTES, never percentage, by design.
    Prevents it: the head's "ranked by reclaimable BYTES, never percentage."
  9. Q: get_pg_deadlocks returns empty on a target known to have contention: the server had no deadlocks?
    Correct: not necessarily; log_error_verbosity=terse or a non-English lc_messages both produce the
    same empty result as a quiet server.
    Prevents it: the head's "An empty answer does not mean none occurred: log_error_verbosity=terse drops the
    DETAIL field this reads, and a non-English lc_messages ... matches nothing - both look like a quiet server."
  10. Q: Which reading confirms deadlocks are truly absent rather than unreadable?
    Correct: get_pg_database_stats' cumulative counter, an independent source that doesn't depend on log
    settings.
    Prevents it: the head's "Check both, or get_pg_database_stats' counter, first."
  11. Q: get_pg_deadlocks shows times_seen: 1 on an RDS target: was this deadlock seen only once so far,
    with more sightings likely?
    Correct: 1 is the ordinary steady state on RDS/Aurora, not a partial count; it is not the same signal as
    a climbing value on a self-hosted target.
    Prevents it: the head's "times_seen is a sighting count, not a deadlock count: it depends on transport -
    self-hosted climbs on re-reads, RDS/Aurora normally stays 1 (normal there, not partial)."
  12. Q: get_pg_deadlocks returned 25 rows (the default limit) with truncated unset in the visible payload
    fields listed in the head: was that the whole window?
    Correct: unknown from the head alone; the tail's moved parameter text says truncated is observed by an
    over-fetch of one row past the cap, never inferred from a full page — read the tail or the payload's
    truncated field, don't assume a full page means more exist.
    Prevents it: the tail's moved D2 text: "read truncated to know whether the window held more distinct
    deadlocks than were returned; it is observed by fetching one row past this cap, never inferred from a full
    page."
  13. Q: get_pg_index_bloat with answered_only=true returns status: no_answers: is that the same as an
    empty page with the default (answerless-first) ordering?
    Correct: no; no_answers means nothing on the server is measurable at all, a stronger and different
    statement than "the page you asked for happened to be empty."
    Prevents it: the tail's "answered_only over a server where NOTHING has an answer returns status
    no_answers rather than an empty" (kept verbatim in the tail; not in the head due to the 620-char cap, listed
    here as a residual trap for the coordinator's awareness).

Test plan

  • dotnet build Darling/Darling.Tests/Darling.Tests.csproj -c Debug: 0 Warning(s), 0 Error(s).
  • Targeted: McpToolsListBudgetTests, McpToolGuideTests, McpToolGuideHeadsPgATests (new),
    McpPageContractTests, plus every other test class found by
    grep -rl -E "get_pg_log_events|get_pg_index_bloat|get_pg_deadlocks" Darling/Darling.Tests narrowed to the
    ones reading Description/McpServerTool (22 classes examined, 458 tests run) — all green except the one
    re-pointed pin above, now green after the fix.
  • Red-watch: temporarily shortened get_pg_index_bloat's head below its HeadFacts guardrail phrase,
    confirmed McpToolGuideHeadsPgATests failed, restored.
  • python dropcheck.py origin/dev HEAD get_pg_log_events get_pg_index_bloat get_pg_deadlocks: all three
    missing=0.
  • Coordinator, after a merge of origin/dev (d7b8aa0): dropcheck shows missing=0 for all three tools. D9: 13/13, with no misreading. Five answers were hedged, and the model marked get_tool_guide as needed. Full Darling.Tests 13,222 total, 0 failed. Full Lite.Tests 5,232 total, 0 failed.
  • Plain-English check on this PR body: the lane did not run it. The coordinator rewrote the CHANGELOG entry, and a batch rewrite of lane PR bodies will fix this body.

Handoff

All three assigned tools (get_pg_log_events, get_pg_index_bloat, get_pg_deadlocks) are fully converted and
green on every targeted test. Nothing was left unconverted. Outstanding before merge: git merge origin/dev,
rebuild, and one full Darling suite run (not done in-lane due to the context watchdog); the plain-English
checker against this PR body and the CHANGELOG entry.

CHANGELOG entry written to
changelog-buffer/changelog_entries/<PR>-reported.txt per the brief (SECTION: Changed).

🤖 Generated with Claude Code

https://claude.ai/code/session_01M7r1CXmEcBmsrTV34CCLfF

erikdarlingdata and others added 2 commits September 23, 2026 16:13
…idance off tools/list (#3898)

Head/tail split for the pgA family (Darling only, no Lite twins). Heads carry the gating,
empty/null and sighting-count guardrails; full original prose moves to each tool's
get_tool_guide tail. D2: min_severity, get_pg_index_bloat.limit/answered_only and
get_pg_deadlocks.limit were over the 200-char parameter cap, trimmed to a guardrail
sentence with the overflow moved verbatim into the tool's tail.

Re-points McpPageContractTests' page-contract pin (the #3679 "This is what bounds the
page" / truncated wording) to read the tail when the tool is #3898-converted, since the
wording moved off the now-shortened limit parameter for get_pg_deadlocks and
get_pg_index_bloat.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01M7r1CXmEcBmsrTV34CCLfF
@erikdarlingdata
erikdarlingdata marked this pull request as ready for review September 23, 2026 20:21
@erikdarlingdata
erikdarlingdata enabled auto-merge (squash) September 23, 2026 20:21
@erikdarlingdata
erikdarlingdata merged commit 573c39f into dev Sep 23, 2026
16 of 18 checks passed
@erikdarlingdata
erikdarlingdata deleted the feature/3898-content-pg-a branch September 23, 2026 20:30
erikdarlingdata added a commit that referenced this pull request Sep 23, 2026
…ntries in their sections (#4080)

Adds 42 entries and 42 link refs (#3992, #3995, #3996, #3998, #4001, #4002, #4003, #4007, #4010, #4011, #4013, #4015, #4020, #4022, #4025, #4029, #4030, #4031, #4036, #4038, #4039, #4040, #4044, #4047, #4048, #4049, #4050, #4051, #4055, #4061, #4063, #4064, #4065, #4066, #4067, #4068, #4069, #4070, #4071, #4073, #4074, #4078). Each PR's entry was buffered, and this lands every entry whose PR was merged on origin/dev when it ran.

#3989 left 26 entries under bare 'Changed' and 'Fixed' lines above '### Added'. They move into '### Changed' and '### Fixed', below the new entries, and one blank line stays under [Unreleased].


Claude-Session: https://claude.ai/code/session_01Ua31ugERL5DmhFVRtf6keQ

Co-authored-by: Claude Opus 5.5 <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