Skip to content

#198: Grade budget — scale wall-clock with work + opt-in cost ceilings - #199

Merged
wjduenow merged 13 commits into
devfrom
feature/198-grade-budget-scaling
Jun 4, 2026
Merged

#198: Grade budget — scale wall-clock with work + opt-in cost ceilings#199
wjduenow merged 13 commits into
devfrom
feature/198-grade-budget-scaling

Conversation

@wjduenow

@wjduenow wjduenow commented Jun 4, 2026

Copy link
Copy Markdown
Owner

Summary

Re-calibrate the grade-stage budget (issue #198). The grade engine makes one LLM call per
(artifact × criterion) pair, dispatched concurrently; the old flat 300s wall-clock budget was
sized for the pre-#186 sequential era and undercounted realistic work by ~3–4×.

  • Part A — scale the wall-clock budget with the work. effective = budget_base_seconds(60) + budget_per_pair_seconds(20.0) × ceil(num_pairs / max_concurrent_calls). total_budget_seconds is
    reinterpreted as an optional absolute cap (int | None, default None); effective = min(scaled, total_budget_seconds) when set, preserving v0.1 semantics for pinned configs. The
    20.0 per-wave default is grounded in the Epic: Test Generation Expansion (#169 + #170 + #171) #179 baseline (~10s/Sonnet-judge-call) at ~2.25× headroom.
  • Part B — opt-in cost ceilings. max_grade_calls / max_grade_cost_usd / max_grade_tokens
    (all None = off). Whichever trips first stops scheduling new pairs; remaining pairs degrade
    (never raise) with a reason naming the ceiling. Cost via signalforge.llm.pricing.

Design shape

Ceilings degrade (don't raise) → no new typed errors, no exit-code/AST-scan churn, no CLI flags, no
audit_schema_version bump
. GradeBudgetExceededError stays reserved. Budget WARNING field renamed
total_budget_secondseffective_budget_seconds; new grade ceiling exceeded WARNING names the
tripped ceiling. Full decision record: plans/super/198-grade-budget-scaling.md (DEC-001…011).

Changes

  • grade/config.py — new fields + validator split (total_budget_seconds: int | None).
  • grade/engine.py — pure _compute_effective_budget helper; scaled budget wired into asyncio.timeout;
    soft/best-effort ceilings in _one (calls near-hard via pre-await slot reservation; cost/tokens
    post-call accumulators); cost pricing resolved once up front (fail-fast on unpriced SKU).
  • docs/grade-ops.md, .claude/rules/grade-layer.md, tests/fixtures/grade/example_config.yml — parity.

Testing

  • Full suite green: 3592 passed, 8 skipped; ruff / format / pyright clean.
  • Formula unit tests; per-ceiling degrade tests; renamed-WARNING shape test; ≥40-col "0 width-induced
    degradations under default config" AC test (488 pairs); unpriced-SKU fail-fast regression test.
  • Quality Gate: 4 review passes + a CodeRabbit-substitute pass that caught a real run-aborting bug
    (per-pair pricing lookup on an unpriced SKU) — fixed + regression-tested.

Compounding update

.claude/rules/grade-layer.md (scaled-budget + ceilings contract, DEC-029 enumeration), docs/grade-ops.md,
bd memory grade-runtime-budgets-198-scale-a-wall-clock.

Maintainer-only remaining (closing AC)

Re-run tests/research/179-runtime-benchmark/benchmark_runtime.py on weekly_query_cost + a ≥40-col
model (live Anthropic key) and record numbers in docs/research/179-runtime-benchmark.md.

Closes #198.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Scaled wall-clock grading budget (base + per-item) with optional absolute hard cap (total_budget_seconds optional).
    • Opt-in soft ceilings for grading calls, cost (USD), and tokens that stop dispatch for remaining items and emit separate warnings.
    • Fast-fail for unpriced models before work fan-out.
  • Documentation

    • Updated config and ops docs; budget warning field renamed to effective_budget_seconds.
  • Tests

    • Added/updated tests for effective-budget math, ceilings, warnings, and fail‑fast behavior.

@coderabbitai

coderabbitai Bot commented Jun 4, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: ac44deb3-797b-4d4c-8228-f210cb7cc458

📥 Commits

Reviewing files that changed from the base of the PR and between 7b59e91 and 4eb2df1.

📒 Files selected for processing (7)
  • .claude/rules/grade-layer.md
  • docs/grade-ops.md
  • plans/super/198-grade-budget-scaling.md
  • src/signalforge/grade/config.py
  • src/signalforge/grade/engine.py
  • tests/grade/test_config.py
  • tests/grade/test_engine.py
✅ Files skipped from review due to trivial changes (1)
  • plans/super/198-grade-budget-scaling.md
🚧 Files skipped from review as they are similar to previous changes (5)
  • .claude/rules/grade-layer.md
  • src/signalforge/grade/config.py
  • docs/grade-ops.md
  • tests/grade/test_engine.py
  • src/signalforge/grade/engine.py

📝 Walkthrough

Walkthrough

Redesigns grade-stage timing to compute an effective wall-clock budget (base + per-pair × waves) with optional hard cap, adds opt-in soft ceilings for calls/cost/tokens that preempt scheduling and degrade unstarted pairs, and updates config, engine, warnings, fixtures, tests, and documentation to the new contract.

Changes

Grade budget scaling and ceiling implementation

Layer / File(s) Summary
Policy, plan, and rules
plans/super/198-grade-budget-scaling.md, .claude/rules/grade-layer.md
Specifies effective-budget formula, fail-fast pricing requirement, locked degrade reasoning and WARNING field shapes, and TDD/acceptance criteria.
GradeConfig budget and ceiling model redesign
src/signalforge/grade/config.py, tests/grade/test_config.py, tests/fixtures/*, src/signalforge/_demo/signalforge.yml
Adds budget_base_seconds, budget_per_pair_seconds; makes `total_budget_seconds: int
Effective budget computation and timeout integration
src/signalforge/grade/engine.py, tests/grade/test_engine.py
Implements _compute_effective_budget(...) = base + per_pair × ceil(num_pairs / max_concurrent_calls) with optional cap, computes effective_budget once, wires asyncio.timeout(effective_budget), updates degraded reasoning and budget-warning fields to reference effective_budget_seconds, and adds unit tests for formula edge cases.
Per-pair ceiling checks and degradation logic
src/signalforge/grade/engine.py
Adds synchronous pre-call check-and-degrade for max_grade_calls, max_grade_cost_usd, max_grade_tokens: when met, immediately persist a degraded (score=None) result and skip the LLM call; reserves/increments calls_made before awaits when calls ceiling is enabled.
Cost and token accumulation with pricing resolution
src/signalforge/grade/engine.py
Resolves model pricing once up front when USD ceiling is enabled; after each per-pair result, increments token counters (input/output/cache) and cost accumulator using the pricing table and event token counts.
Budget-exceeded and ceiling-exceeded warnings
src/signalforge/grade/engine.py, docs/grade-ops.md
Budget-exceeded degraded reasoning embeds effective_budget seconds; budget-warning JSON renamed effective_budget_seconds; adds a distinct end-of-run grade ceiling exceeded WARNING reporting ceiling name/limit and completed/degraded counts.
Docs, demo, and fixtures
docs/grade-ops.md, src/signalforge/_demo/signalforge.yml, tests/fixtures/*, plans/*, .claude/rules/grade-layer.md
Docs updated to describe scaled budget, optional cap semantics, soft ceilings, warning-field rename, and examples/fixtures updated to include new fields and wording.
Unit & integration tests for budgets and ceilings
tests/grade/test_engine.py, tests/grade/test_config.py
Adds _compute_effective_budget unit tests; updates budget-warning contract tests to assert effective_budget_seconds; adds ceiling integration tests for calls, cost (including real-priced SKU and fast-fail unpriced SKU), tokens, wide-model completion, and no-ceiling regression.

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

  • wjduenow/SignalForge#193: Touches grading validation and model defaults which overlap with budget/validator changes here.

Poem

A rabbit counts the seconds, base and waves,
It scales the time while quiet watchers gaze,
When calls or coins or tokens cross the line,
Degraded pairs sleep, warnings locked in sign,
I nibble logs and hop—everything's fine. 🐰✨

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and specifically describes the two main changes: scaling wall-clock budget with work and adding optional cost ceilings, which are the core objectives of the PR.
Linked Issues check ✅ Passed All coding requirements from issue #198 are met: scaled budget formula implemented (budget_base_seconds + budget_per_pair_seconds × ceil(num_pairs/concurrency)), optional hard cap via total_budget_seconds, opt-in soft ceilings (max_grade_calls/cost_usd/tokens) with graceful degradation, cost estimation via pricing.lookup, config conventions (extra=forbid, validators), and comprehensive unit tests including formula and ceiling paths.
Out of Scope Changes check ✅ Passed All changes are directly aligned with PR #198 objectives: config schema updates, effective budget computation, ceiling enforcement, pricing integration, documentation updates, and test coverage for the new features. No unrelated modifications detected.
Docstring Coverage ✅ Passed Docstring coverage is 95.83% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch

Comment @coderabbitai help to get the list of available commands and usage tips.

@wjduenow
wjduenow force-pushed the feature/198-grade-budget-scaling branch from da8ce3c to 502847d Compare June 4, 2026 05:09
wjduenow added 9 commits June 4, 2026 09:42
…validator split

total_budget_seconds becomes int | None = None (optional absolute hard
ceiling; None routes to the scaled formula). Add budget_base_seconds=60,
budget_per_pair_seconds=20.0, and the three opt-in soft ceilings
max_grade_calls / max_grade_cost_usd / max_grade_tokens (all None=off).
Split the _positive field_validator: positive-only for max_output_tokens /
budget_base_seconds / budget_per_pair_seconds; allow-None-or-positive for
total_budget_seconds and the three max_grade_*. Existing fixtures (300/600
explicit ints) still load; extra=forbid still rejects typos.

Traces DEC-001/002/004/006 of plans/super/198-grade-budget-scaling.md.
…+ tighten config validator/round-trip tests
…run abort + demo comment parity

The cost ceiling looked up pricing per-pair inside the TaskGroup; a prefix-valid-
but-unpriced model (e.g. claude-opus-4-8) + max_grade_cost_usd raised
EstimateUnknownModelError mid-run (uncaught by the per-pair except), aborting the
run after billable calls. Resolve pricing ONCE up front so an unpriced SKU fails
fast at orchestrator entry before any LLM call; reuse the resolved pricing per pair.
Found by the CodeRabbit-substitute review pass.
@wjduenow
wjduenow marked this pull request as ready for review June 4, 2026 14:51
@wjduenow
wjduenow requested a review from Copilot June 4, 2026 14:52
@wjduenow wjduenow changed the title #198: Grade budget scaling + cost ceilings (plan) #198: Grade budget scaling + cost ceilings Jun 4, 2026

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR implements issue #198 by recalibrating the grade-stage wall-clock budget to scale with (artifact × criterion) work (and concurrency), and adds opt-in “soft” ceilings for grade calls / USD cost / tokens that stop further dispatch by degrading remaining pairs (rather than raising new typed errors). This fits into the grade engine’s existing “graceful degrade, never silent drop” contract and preserves pinned-config semantics when total_budget_seconds is explicitly set.

Changes:

  • Add scaled-budget terms to GradeConfig (budget_base_seconds, budget_per_pair_seconds) and reinterpret total_budget_seconds as an optional absolute cap (None default).
  • Wire a computed effective_budget into the engine timeout/degrade strings and rename the budget WARNING payload field to effective_budget_seconds.
  • Add opt-in ceilings (max_grade_calls, max_grade_cost_usd, max_grade_tokens) with a new grade ceiling exceeded WARNING, plus extensive unit tests and updated operator docs/fixtures.

Reviewed changes

Copilot reviewed 10 out of 10 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
src/signalforge/grade/engine.py Adds _compute_effective_budget, uses effective_budget for timeout/degrade/warnings, and implements calls/cost/tokens ceilings + warning.
src/signalforge/grade/config.py Updates budget semantics/defaults; adds scaled-budget and ceiling fields with validators.
tests/grade/test_engine.py Adds formula tests, wide-model acceptance test, and ceiling-behavior tests (incl. unpriced SKU fast-fail).
tests/grade/test_config.py Updates defaults expectations and adds validator coverage for new fields.
docs/grade-ops.md Documents scaled budget, optional cap semantics, ceilings, and updated warning shapes.
tests/fixtures/grade/example_config.yml Updates example grade config with scaled-budget terms and commented ceiling knobs.
tests/fixtures/dbt_project_austin/signalforge.yml Clarifies total_budget_seconds comment as absolute hard cap under new semantics.
src/signalforge/_demo/signalforge.yml Same comment clarification as the Austin fixture.
plans/super/198-grade-budget-scaling.md Adds the detailed plan/decisions/story breakdown for #198.
.claude/rules/grade-layer.md Updates grade-layer rules to reflect effective budget + ceilings and the warning rename/new warning.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/signalforge/grade/engine.py Outdated
Comment thread src/signalforge/grade/engine.py Outdated
Comment thread docs/grade-ops.md Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🧹 Nitpick comments (1)
tests/grade/test_engine.py (1)

2173-2177: ⚡ Quick win

Use a synthetic claude- sentinel SKU to keep this test future-proof.

This test can become flaky if that concrete SKU gets added to pricing.PRICES; a clearly synthetic prefix-valid value keeps the intent stable.

Suggested fix
-        model="claude-opus-4-8",  # claude- prefix (valid) but NOT in PRICES
+        model="claude-unpriced-test-sku",  # claude- prefix-valid sentinel, intentionally not priced
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/grade/test_engine.py` around lines 2173 - 2177, The test uses a
concrete model SKU "claude-opus-4-8" which could later be added to
pricing.PRICES and make the test flaky; update the GradeConfig model to a
clearly synthetic, prefix-valid sentinel SKU (e.g., a value starting with
"claude-" but not a real SKU such as "claude-sentinel-000") so the test
intention remains stable—modify the model parameter passed to GradeConfig in the
failing test block (the GradeConfig(...) instantiation) to use that synthetic
SKU.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@docs/grade-ops.md`:
- Around line 96-97: The docs currently state that GradeBudgetExceededError is
raised, but the grade-layer contract marks it as reserved/not raised; update the
GradeBudgetExceededError entry so it clearly indicates this error is reserved
and not emitted by the operator (e.g., "reserved — not raised by operator" or
similar), and adjust any accompanying text about behavior (such as references to
GradingReport.aggregate_complete) to reflect that a partial run will be reported
without raising this error; apply the same change to the other occurrence of
GradeBudgetExceededError further down the file.
- Around line 527-531: The markdown anchor "[Logging](`#logging`)" in the ceiling
note is invalid; update the link target or add a header: either change the
anchor text to point to the existing section (e.g., replace
"[Logging](`#logging`)" with "[Debugging](`#debugging`)" or the correct existing
heading) or add a new "## Logging" header in this document so the current link
resolves; edit the ceiling note containing the anchor to ensure it matches the
actual heading name.

In `@plans/super/198-grade-budget-scaling.md`:
- Line 47: Remove the trailing space inside the inline code span by editing the
backticked token `result.input_tokens / output_tokens / cache_* ` to
`result.input_tokens / output_tokens / cache_*` so the inline code has no space
before the closing backtick (fixes markdownlint MD038).

In `@src/signalforge/grade/config.py`:
- Around line 402-416: The validators _positive and _optional_positive currently
only check v <= 0 which lets NaN and ±Infinity slip through; update both
validators to explicitly reject non-finite float values (use math.isfinite or
equivalent) before the existing non-positive check (for _optional_positive, skip
validation when v is None). Apply this to the field validators used for
max_output_tokens, budget_base_seconds, budget_per_pair_seconds and the optional
ceilings like max_grade_cost_usd so NaN/Infinity raise ValueError("must be
finite") (or similar) rather than corrupting budget math.

---

Nitpick comments:
In `@tests/grade/test_engine.py`:
- Around line 2173-2177: The test uses a concrete model SKU "claude-opus-4-8"
which could later be added to pricing.PRICES and make the test flaky; update the
GradeConfig model to a clearly synthetic, prefix-valid sentinel SKU (e.g., a
value starting with "claude-" but not a real SKU such as "claude-sentinel-000")
so the test intention remains stable—modify the model parameter passed to
GradeConfig in the failing test block (the GradeConfig(...) instantiation) to
use that synthetic SKU.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: e8b03a94-d792-4464-ab2b-067f21939ce5

📥 Commits

Reviewing files that changed from the base of the PR and between a17ecdf and ce14143.

📒 Files selected for processing (10)
  • .claude/rules/grade-layer.md
  • docs/grade-ops.md
  • plans/super/198-grade-budget-scaling.md
  • src/signalforge/_demo/signalforge.yml
  • src/signalforge/grade/config.py
  • src/signalforge/grade/engine.py
  • tests/fixtures/dbt_project_austin/signalforge.yml
  • tests/fixtures/grade/example_config.yml
  • tests/grade/test_config.py
  • tests/grade/test_engine.py

Comment thread docs/grade-ops.md Outdated
Comment thread docs/grade-ops.md Outdated
Comment thread plans/super/198-grade-budget-scaling.md Outdated
Comment thread src/signalforge/grade/config.py
@wjduenow wjduenow changed the title #198: Grade budget scaling + cost ceilings #198: Grade budget — scale wall-clock with work + opt-in cost ceilings Jun 4, 2026
- engine: math.ceil (not int) on effective budget so a fractional per_pair never rounds the backstop DOWN
- engine: route ceiling-degrade audit write through run_in_executor+shield (DEC-017) so the fsync doesn't block the concurrent loop (was sync); set slot first to avoid double-audit
- config: reject non-finite floats (.nan/.inf) in budget_per_pair_seconds / max_grade_cost_usd (yaml parses them; nan<=0 is False so they slipped past the positivity guard)
- docs: GradeBudgetExceededError marked reserved/not-raised in v0.1 (both error reference + table); fix invalid #logging anchor -> #debugging; drop trailing space in plan code span
- tests: NaN/inf rejection, fractional-ceil (no round-down), cap-ceil
- grade-layer.md: note updated to shielded-executor ceiling-degrade write
@wjduenow

wjduenow commented Jun 4, 2026

Copy link
Copy Markdown
Owner Author

PR Review Summary — CodeRabbit + Copilot

All 7 threads addressed in 4eb2df1. Every comment was a real fix (no false positives); full suite green (3595 passed, ruff/format/pyright clean).

Fixed (7 items)

File Line Issue Fix
grade/config.py 416 (Major) float knobs accept NaN/±inf — nan <= 0/inf <= 0 are False, and YAML parses .nan/.inf; a NaN budget_per_pair_seconds would crash math.ceil(nan), max_grade_cost_usd: .inf silently no-ops the cap Reject non-finite floats in both validators + tests (4eb2df1)
grade/engine.py 497 int(...) truncates the effective budget toward zero (fractional per_pair → backstop rounded down → earlier trips) Use math.ceil on the final conversion (no-op for the integer-valued default) + fractional/cap tests (4eb2df1)
grade/engine.py 817 ceiling-degrade audit write was synchronous inside the concurrent TaskGroup → fsync blocks the loop & stalls in-flight coroutines (inconsistent with DEC-017) Route through run_in_executor + asyncio.shield (slot set first → no double-audit), matching the happy-path write (4eb2df1)
docs/grade-ops.md 96, 1097 GradeBudgetExceededError documented as raised, but it's reserved/never raised in v0.1 Marked reserved/not-raised in both the error reference and the table (4eb2df1)
docs/grade-ops.md 531 invalid markdown anchor [Logging](#logging) [Debugging](#debugging) (the existing heading) (4eb2df1)
plans/super/198-grade-budget-scaling.md 47 MD038 trailing space inside an inline code span Removed (4eb2df1)

False Positives

None — all comments were valid.

The grade-layer.md "ceiling-degrade write" note was updated in lockstep to describe the shielded-executor approach.

@wjduenow
wjduenow merged commit 48426b5 into dev Jun 4, 2026
6 checks passed
@wjduenow
wjduenow deleted the feature/198-grade-budget-scaling branch June 4, 2026 15:28
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.

2 participants