fix: [no-ticket] harbor - ops integrity (exhaust-budget instruction lever, fail-closed ledger restore)#36
Open
shehabyasser-scale wants to merge 3 commits into
Open
Conversation
…fails closed on corrupt restore) Two operational fixes: 1. instruct_exhaust_budget (default True): the instruction's "unspent budget is wasted" persistence bullet becomes a build-config lever, like instruct_multifidelity. On preserves current behavior; off makes stopping-early the agent's own choice, which is the ablation arm for measuring what the exhortation itself contributes to optimizer persistence. The "scores are noisy" fact stays unconditional. 2. _load_or_build_ledger fails CLOSED on a persisted ledger that exists but cannot be parsed: metered budgets restore with zero remaining, and the unreadable file is preserved as ledger.corrupt for the operator. The old fallback restored the CONFIGURED budgets, which refunded the agent everything already spent, so any crash that corrupted the flush minted budget. A missing file is still a fresh boot; admin and finalize are unaffected either way. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Two Greptile P2s on #30, fixed at the stack tip: - An empty transcript file no longer surfaces as "" feedback: empty candidates are skipped (an empty pane falls through to the trajectory), and if everything is empty the search moves to the next failed attempt rather than short-circuiting on "". - The no-verifier-rewards error branch (agent died before scoring) now attaches the failure transcript like any failed sample: a candidate edit that crashes the agent lands exactly here, and the transcript is the only way the optimizer can see the crash it caused. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… floor default, ordinal resume, SE naming) - Free-baseline flag is claimed BEFORE the eval await and refunded on failure: setting it only after success reopened a window where two concurrent baseline evals both resolved free (asyncio interleaves at await points). Claim-then-refund keeps both properties: concurrent callers see the claim, and a failed eval does not burn the freebie. - build_status defaults k_anonymity_floor to 5, matching the sidecar's enforcement default: a caller that forgets to pass the floor must not advertise a laxer one than gets enforced. - _route_results resumes the eval ordinal past surviving __eN dirs on a reused volume: a restarted sidecar started back at e1 and silently wiped the prior session's evidence, the exact erasure the versioned dirs exist to prevent. - score_se renamed to mean_score_se and documented: it is the SE of the zero-filled mean_score over n_samples, not of the n_scored subset. Co-Authored-By: Claude Opus 4.8 (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.
Stacked on #35 (harbor-8-honest-signals). Closes out the pre-paper hardening series (#34 access tiers, #35 honest signals, #36 ops).
Changes
1.
instruct_exhaust_budgetbuild-config lever (build/config.py,compiler.py,instruction.md.j2, mirrored into serve.json likeinstruct_multifidelity). The compiled instruction's "unspent budget is wasted: re-measure your champion or try one more variant" bullet was unconditional; it is now a flag, default on (current behavior byte-for-byte in spirit; the bullet splits from the "scores are noisy" fact, which stays unconditional). Off makes stopping-early the agent's own choice. This is the ablation arm for the persistence experiments: with the exhortation always baked in, there is no way to measure what it contributes to optimizer persistence vs what the optimizer does on its own.2. Ledger restore fails closed (
serve.py)._load_or_build_ledgerrestored the configured (full) budgets whenever the persistedledger.jsonexisted but could not be parsed. The persisted ledger exists precisely so a sidecar restart cannot refill spent budget; falling back to full on a parse failure refunds the agent everything already spent, so any crash that corrupts the flush mints budget. Now: a missing file is still a fresh boot (configured budgets), but an unreadable file restores every metered split with zero remaining, preserves the unreadable original asledger.corruptfor the operator, and logs at error level with the recovery instruction (deleteledger.jsondeliberately to boot fresh). Admin and finalize paths are unaffected; only agent metering closes.Tests
instruct_exhaust_budget=Falseomits it while keeping "Scores are noisy".ledger.corruptbyte-for-byte; structurally-malformed JSON entries (KeyError path) also fail closed.🤖 Generated with Claude Code
Greptile Summary
This PR closes out the pre-paper hardening series with two self-contained changes: a build-config lever (
instruct_exhaust_budget) that makes the "spend your remaining budget" instruction bullet optional for ablation experiments, and a fail-closed fix to ledger restore that treats a corruptledger.jsonas fully spent rather than silently refunding the agent everything it had already used.instruct_exhaust_budgetlever (build/config.py,compiler.py,instruction.md.j2,serve.py): the budget-exhaustion bullet is now conditional on a flag (default on, preserving current behavior); off gives the ablation arm needed to isolate what the exhortation contributes to optimizer persistence.serve.py): a missing file still boots with configured budgets; a file that exists but cannot be parsed now zeroes all meteredremaining_*fields, saves the corrupt original toledger.corrupt, and logs at error level with the deliberate-delete recovery instruction.server.pyandrunner.py: free-baseline claim is now atomically set before theawait(preventing concurrent double-free), volume-reuse ordinal scan resumes past surviving eval dirs on restart,score_seis renamed tomean_score_sefor precision, empty transcript files skip rather than surface as empty-string feedback, and crash transcripts are attached to the no-rewards error path.Confidence Score: 5/5
All changes are defensive fixes with no new attack surface; the most sensitive path (ledger restore) now fails closed rather than open.
Every changed path has a targeted test added alongside it. The ledger fail-closed logic is straightforward and the zeroing loop is easy to audit. The async free-baseline fix correctly covers the BaseException refund path. No pre-existing guarantees are weakened.
The score_se to mean_score_se rename in server.py is a breaking schema change to summary.json; worth confirming all downstream consumers are updated.
Important Files Changed
Flowchart
%%{init: {'theme': 'neutral'}}%% flowchart TD A[Sidecar startup] --> B{ledger.json exists?} B -- No --> C[Fresh boot: configured budgets] B -- Yes --> D{JSON parse ok?} D -- Yes --> E[Restore persisted remaining values] D -- No --> F[Copy to ledger.corrupt] F --> G[Reconstruct from config with zero remaining] G --> H[Log ERROR + recovery instruction] C --> I[BudgetLedger in memory] E --> I H --> I J[evaluate called] --> K{free baseline?} K -- Yes --> L[Claim freebie BEFORE await] L --> M[await engine.evaluate] M -- exception --> N[Refund freebie then re-raise] M -- success --> O[Route results to versioned dir] K -- No --> M P[_route_results called] --> Q{_eval_seq == 0?} Q -- Yes --> R[Scan results dirs for max ordinal] R --> S[Resume past max ordinal] Q -- No --> T[Increment and write dest dir] S --> T%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%% flowchart TD A[Sidecar startup] --> B{ledger.json exists?} B -- No --> C[Fresh boot: configured budgets] B -- Yes --> D{JSON parse ok?} D -- Yes --> E[Restore persisted remaining values] D -- No --> F[Copy to ledger.corrupt] F --> G[Reconstruct from config with zero remaining] G --> H[Log ERROR + recovery instruction] C --> I[BudgetLedger in memory] E --> I H --> I J[evaluate called] --> K{free baseline?} K -- Yes --> L[Claim freebie BEFORE await] L --> M[await engine.evaluate] M -- exception --> N[Refund freebie then re-raise] M -- success --> O[Route results to versioned dir] K -- No --> M P[_route_results called] --> Q{_eval_seq == 0?} Q -- Yes --> R[Scan results dirs for max ordinal] R --> S[Resume past max ordinal] Q -- No --> T[Increment and write dest dir] S --> TReviews (2): Last reviewed commit: "fix(harbor): review follow-ups from #34/..." | Re-trigger Greptile