Skip to content

fix(context): degrade scratchpad section when it exceeds the context budget - #735

Merged
razzant merged 4 commits into
razzant:ouroborosfrom
deebosh:ibl/scratchpad-budget-degrade
Sep 8, 2026
Merged

fix(context): degrade scratchpad section when it exceeds the context budget#735
razzant merged 4 commits into
razzant:ouroborosfrom
deebosh:ibl/scratchpad-budget-degrade

Conversation

@deebosh

@deebosh deebosh commented Sep 7, 2026

Copy link
Copy Markdown

What

`ouroboros/context.py::_warn_if_over_budget` only LOGS a warning when the scratchpad context section exceeds `SCRATCHPAD_SECTION_BUDGET_CHARS` (90,000 chars) — the raw file still gets shipped whole into every context build. Root cause: `_SCRATCHPAD_MAX_BLOCKS` is a block-COUNT cap only, with no byte-size cap, so N blocks can still add up to well past the char budget (observed 213KB / 2.4x budget in production).

Fix

  • `ouroboros/context_budget.py`: new `SCRATCHPAD_MAX_CONTENT_CHARS = 60_000` — a source-side content cap for `Memory.append_scratchpad_block`, ANDed with the existing block-count cap.
  • `ouroboros/memory.py`: `append_scratchpad_block` now evicts oldest-first (FIFO) until BOTH the count cap and the new content cap are satisfied, not just the count cap.
  • `ouroboros/context.py`: new `_render_scratchpad_for_context` degrades the CONSUMER side too — when the scratchpad section still exceeds budget (e.g. from a legacy/imported state that predates the source-side cap), it keeps the newest whole blocks that fit (block-boundary cuts only, never mid-string), always retains at least the single newest block, and appends an in-band gap-marker disclosure whenever older blocks were dropped or the retained block(s) alone still exceed budget — the raw `_warn_if_over_budget` log is preserved so the underlying condition stays visible.
  • `tests/test_scratchpad_byte_cap.py` (new): covers both the source-side content cap and the render-side degrade path (legacy fallback, gap marker on drop, gap marker on the always-retained-newest-alone case, block-boundary/no-mid-string invariants).

Notes for this branch

This fork's block-pinning feature (`Memory.pin_scratchpad_block`) doesn't exist on `ouroboros` upstream, so the eviction loop here is plain FIFO (`pop(0)`) rather than pin-aware; the corresponding pinning test was dropped from the cherry-picked test file for the same reason. Everything else is unchanged from the original fix.

Verification

  • `tests/test_scratchpad_byte_cap.py`, `tests/test_memory_blocks.py`, `tests/test_context.py`, `tests/test_memory_tool_hints.py` all green on this branch.
  • Broader `-k "context or memory or scratchpad"` sweep green; the one failure (`test_context_mode_guard_does_not_block_readonly_diagnostics`) is pre-existing/unrelated — needs a configured safety LLM provider, fails identically on a clean upstream checkout.

🤖 Generated with Claude Code

https://claude.ai/code/session_01AZGh7G7f2g7w8KkQW74Z4V

Линн and others added 4 commits September 8, 2026 01:29
…budget

Closes ibl-2b09abdadd25. context.py::_warn_if_over_budget only logged when
the scratchpad section exceeded SCRATCHPAD_SECTION_BUDGET_CHARS (90_000);
the raw file was still shipped whole into every context build.

- ouroboros/context.py: new _render_scratchpad_for_context degrades to the
  newest whole blocks that fit (block-boundary cuts only), always retains
  at least the single newest block, and appends an in-band gap-marker
  (BIBLE P1: no silent truncation) whenever older blocks were dropped OR
  the retained block(s) alone still exceed budget. The raw WARNING log is
  preserved (fires on the pre-trim value) so the rot class stays visible.
- ouroboros/context_budget.py: SCRATCHPAD_MAX_CONTENT_CHARS=60_000 —
  source-side content cap for Memory.append_scratchpad_block, ANDed with
  the existing _SCRATCHPAD_MAX_BLOCKS count cap (single-pass eviction).
- ouroboros/memory.py: append_scratchpad_block now evicts on whichever cap
  (count or content) is violated first; pinned blocks stay exempt from
  both, same FIFO+journal invariants as before.
- tests/test_scratchpad_byte_cap.py: new coverage for both caps and the
  render-side degrade path (legacy fallback, gap marker on drop, gap
  marker on the always-retained-newest-alone case).

Follow-up: closes ibl-d1ade41fe54d as well (that item tracked exactly this
gap-marker-not-firing bug, filed by a prior task attempt that ran out of
budget before landing the fix).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AZGh7G7f2g7w8KkQW74Z4V
The fork-local block-pinning feature (ibl-3d7b7b7d5dc9, Memory.pin_scratchpad_block)
does not exist on this branch, so the eviction loop in append_scratchpad_block
is adapted to plain FIFO (oldest-first, pop(0)) instead of skipping pinned
blocks, and test_content_cap_respects_pinning is dropped along with it.
Everything else (byte-size cap alongside the block-count cap, and the
render-side block-boundary degrade in context.py) is unchanged.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AZGh7G7f2g7w8KkQW74Z4V
- ouroboros/context.py: SCRATCHPAD_MAX_CONTENT_CHARS is only consumed by
  memory.py's eviction logic now, not referenced in context.py itself.
- tests/test_scratchpad_byte_cap.py: unused `import pytest` (no
  pytest.mark/pytest.raises usage in this file).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AZGh7G7f2g7w8KkQW74Z4V
…chpad content-cap growth

The scratchpad content-size cap (ibl-2b09abdadd25) added to
append_scratchpad_block's eviction loop pushes ouroboros/memory.py from
under 1000 lines into the 1001-1500 band, which this repo's size-ratchet
CI lane blocks without an explicit rationale.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AZGh7G7f2g7w8KkQW74Z4V
@razzant
razzant merged commit ec4c420 into razzant:ouroboros Sep 8, 2026
18 checks passed
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