Skip to content

fix(learning): keep one outlier and agent sessions from distorting estimates - #83

Merged
Dunky13 merged 6 commits into
mainfrom
dunky13/fix-estimates-learning-grouping
Nov 13, 2026
Merged

Dunky13 merged 6 commits into
mainfrom
dunky13/fix-estimates-learning-grouping

Conversation

@Dunky13

@Dunky13 Dunky13 commented Nov 13, 2026 •

Copy link
Copy Markdown
Owner

Summary

Three defects made learned estimates far too high and kept exact history from accumulating.

A borrowed estimate was the largest peak any workload in the group ever measured, times 1.5. One runaway run set the estimate for the whole group:

 borrowed estimate for a workload, per metric (memory, cpu, duration)
-  peak = max over every run in the group
+  own    = max over the workload's own runs
+  others = each other workload's max, counted once
+  peak   = max(own, others at the 90th percentile)   # under ten others: their max, as before
   estimate = peak * 1.5, then floors and operator minimums (unchanged)

The exact variant hashed values that agents set per session, so the same command from another session was a new variant with no history:

 variant = digest(family, command, definition files, environment, toolchain)
   environment excludes
     PWD, TMPDIR, TERM, SSH_AUTH_SOCK, AGENT_HEAVY_*, PUEUE_*, ...
+    CLAUDECODE, AI_AGENT, CLAUDE_*, CODEX_*, T3CODE_*
+    XPC_SERVICE_NAME, XPC_FLAGS, __CFBundleIdentifier, __CF_USER_TEXT_ENCODING

History that fell out of the outer retention bound became a machine-wide floor under every estimate, and it could only rise:

 on(record run), when learning_retained exceeds 8,192 rows
-  fold the oldest rows' peaks into floors[machine]     # raises every estimate on the machine
-  fold floors of machines past 64 into floors['*']
+  delete the oldest rows                               # the workload starts again like one never seen
 floors already stored are still read

The group estimate in the learning view now describes a workload new to the group, and its caption says so. Group membership, schemas and the prediction contract are unchanged, and no stored row is rewritten. go.mod moves to Go 1.26.11, because the vulnerability scan fails on GO-2026-6859 in net/http of 1.26.10. Details, findings left alone and next steps: docs/handoffs/learning-estimates.md.

Evidence

Replayed in time order on a private copy of real history (4,065 runs, 3,190 workload families, 25 days, one 16 GiB machine); each estimate used earlier runs only.

Before After
Families in groups whose estimate exceeds the 13.6 GiB budget 555 16
Runs predicted over budget 10.9% 0.2%
Runs that peaked above their borrowed estimate 2.9% 4.3%
Largest group (343 families), group estimate 16.0 GiB 8.0 GiB
Group holding a 66 GiB runaway (47 families), group estimate 99 GiB 4.0 GiB

Variants: 3,635 of 3,800 had exactly one run, and 83% of reruns of the same command landed on a new variant. Four per-session agent values each had five distinct values across six job payloads from five sessions.

  • Before: TestProfileSimilarityUnusualSiblingDoesNotSetALargeGroupsEstimate fails on the old rule: nine ordinary 2 GiB workloads plus one 60 GiB runaway give a fresh workload a 90 GiB estimate.
    After: it passes with 3 GiB, and covers nine, ten, eleven and twenty other workloads, CPU trimmed on its own, a workload keeping its own peak, and batched and independent reads agreeing.
  • Before: two agent sessions running the same command got different variants.
    After: TestWorkloadIdentityIgnoresAgentInvocationMetadata gets one variant, and GOMAXPROCS still changes it.
  • Before: history past the bound left an 8 GiB, 6-core floor under an unrelated estimate. On the replayed eviction order the floor reached 3.4 GiB after 50 evicted rows and 8.2 GiB after 500. The table holds 1,776 of 8,192 rows today, so this was about six weeks away.
    After: TestProfileOuterRetentionForgetsTheOldestHistory gets the 1 GiB, 2-core cold floors and an empty floors table. TestProfileCollectionMatchesIndependentHistoryReads pins that a floor already stored still applies until a reset.
  • Before: govulncheck as CI runs it: 1 vulnerability on Go 1.26.10.
    After: none on 1.26.11.
  • Local: scripts/lint.sh, runtime partitions core-1 and core-2 with race detection, and scripts/test-native.sh passed. Other partitions, web and installer checks are left to hosted CI.
  • The Swift caption is a text-only change; no screenshot was taken.
  • Independent Standards and Spec reviews ran on the first commit and again on the retention change; their findings (none high) are fixed. Cross-provider review was not requested.

Merge Danger

Door: two-way

Reverting restores the old estimates. No stored data is rewritten. One part does not come back: history deleted past the retention bound while this runs is gone, where the old code would have kept its peak as a floor. A job submitted from an agent session gets a new variant once, in either direction.

Blast Radius: admission

Borrowed estimates in groups of ten or more other workloads drop, so Adaptive mode admits jobs it used to hold or refuse. Under-predictions rise from 2.9% to 4.3% of runs; live sampling, the pressure gates, the three-success overlap guard, operator minimums and machine floors are unchanged. A heavy workload that returns after its history was forgotten starts from its group or the 1 GiB prior once. Operator-shared groups are trimmed like automatic ones; a minimum reservation holds every member above a figure.

🤖 Generated with Claude Code

Dunky13 and others added 6 commits November 13, 2026 13:29
…timates

A workload borrowing from its group got the largest peak any member ever
measured, times 1.5, so one runaway run set the estimate for the whole
group. A borrowed estimate now keeps the workload's own peaks in full,
counts every other workload once and sets aside the highest tenth of
those. Groups with fewer than ten other workloads are unchanged.

The exact variant also hashed values agents set for themselves, such as
messaging sockets, tokens, process ids and thread ids, so the same command
from another session was a new variant with no history. The CLAUDE_,
CODEX_ and T3CODE_ namespaces are now incidental environment.

Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
Signed-off-by: Marc Went <marc@went.io>
…timate

Review of the estimate change found that XPC_SERVICE_NAME carries
per-launch process ids, so every app relaunch still reset exact variants.
The macOS launch values join the incidental environment. The caption under
the group estimate and the grouping notes said the figure was the complete
envelope, which is no longer true for groups of ten or more. The test now
pins nine, ten, eleven and twenty other workloads.

Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
Signed-off-by: Marc Went <marc@went.io>
Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
Signed-off-by: Marc Went <marc@went.io>
govulncheck reports GO-2026-6859 in net/http of Go 1.26.10.

Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
Signed-off-by: Marc Went <marc@went.io>
Rows evicted from learning_retained were folded into a machine-wide floor
that raised every estimate on the machine and could only rise. They are
now deleted, so a forgotten workload starts again like one never seen.
Floors already stored are still read.

Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
Signed-off-by: Marc Went <marc@went.io>
Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
Signed-off-by: Marc Went <marc@went.io>
@Dunky13
Dunky13 merged commit 55060e0 into main Nov 13, 2026
20 checks passed
@Dunky13
Dunky13 deleted the dunky13/fix-estimates-learning-grouping branch November 13, 2026 15:38
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