From 3b7aae6609fc769765490b41470ac1eb9097376d Mon Sep 17 00:00:00 2001 From: sprooty Date: Wed, 5 Aug 2026 02:03:06 +0000 Subject: [PATCH] fix: the session reviewer's context budget was a ceiling nobody could raise MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When the review helpers were shared between the executors (#167), the session side got `DEFAULT_CONTEXT_BUDGET` hardcoded at the call site. The headless executor has taken that number from configuration since #150. So a 272 KB file was always "too large to include", and the reviewer rejected for lacking exactly the evidence the harness had refused it: "web/src/main.tsx is not included in full and I cannot inspect the surrounding markup/context around that block here." That is the right answer to a partial view, and #156 exists precisely because a reviewer denied its surroundings rejects for the shape of its own prompt. Giving it the file back is not weakening the gate — it is the gate's own stated requirement. `SessionExecutor` now takes `context_budget`, defaulting to the same number so nothing changes for anyone not passing it, and `run --context-budget` reaches both executors rather than only one. Worth separating from the case next to it: R3's reviewer *had* the file it needed and rejected for a guard against hypothetical future serialization that the item never asked for. That one is a calibration question and is recorded as #171, deliberately unfixed. This one was a defect I introduced. Co-Authored-By: Claude Opus 5 (1M context) --- src/agent_harness/__main__.py | 1 + src/agent_harness/session_executor.py | 14 +++++++++++++- tests/test_reviewer.py | 25 +++++++++++++++++++++++++ 3 files changed, 39 insertions(+), 1 deletion(-) diff --git a/src/agent_harness/__main__.py b/src/agent_harness/__main__.py index e1a737e..089eead 100644 --- a/src/agent_harness/__main__.py +++ b/src/agent_harness/__main__.py @@ -724,6 +724,7 @@ def live_routes() -> dict[str, Chain]: github=GitHub(args.repo) if args.repo else None, base_branch=args.base, ui_base_url=args.session_host, + context_budget=args.context_budget, on_event=emit, push=not args.no_push, project_id=args.project, diff --git a/src/agent_harness/session_executor.py b/src/agent_harness/session_executor.py index 01ce3d7..c897868 100644 --- a/src/agent_harness/session_executor.py +++ b/src/agent_harness/session_executor.py @@ -174,6 +174,7 @@ def __init__( base_branch: str = "main", branch_prefix: str = "harness/", worktrees: Path | None = None, + context_budget: int = DEFAULT_CONTEXT_BUDGET, ui_base_url: str = "", session_max_age: float = DEFAULT_MAX_AGE_SECONDS, on_event: Callable[[dict[str, Any]], None] | None = None, @@ -195,6 +196,17 @@ def __init__( self.base_branch = base_branch self.branch_prefix = branch_prefix self.worktrees = Path(worktrees) if worktrees else self.repo.parent / ".harness-work" + #: How much of a touched file the reviewer may be shown. Hardcoded to + #: the default when the review helpers were shared, which meant a + #: 272 KB file was always "too large to include" — and the reviewer + #: said so and rejected, correctly, for evidence it had been denied: + #: + #: "web/src/main.tsx is not included in full and I cannot inspect + #: the surrounding markup" + #: + #: The headless executor has taken this from configuration since #150. + #: Session mode reviewing large files needs the same lever. + self.context_budget = context_budget self.ui_base_url = ui_base_url self.session_max_age = session_max_age self.on_event = on_event @@ -774,7 +786,7 @@ def _review( diff=diff[:20000], # The same helpers the headless reviewer uses, so a correction to # either cannot land in one executor and not the other (#167). - context=review_context(tree, diff, DEFAULT_CONTEXT_BUDGET), + context=review_context(tree, diff, self.context_budget), checks=review_checks_prompt(self.checks.commands), ) try: diff --git a/tests/test_reviewer.py b/tests/test_reviewer.py index e63c4c8..418c0ea 100644 --- a/tests/test_reviewer.py +++ b/tests/test_reviewer.py @@ -274,3 +274,28 @@ def test_a_short_verdict_is_kept_whole() -> None: from agent_harness.executor import review_reason assert review_reason("REJECTED\nToo narrow.") == "REJECTED\nToo narrow." + + +def test_the_session_reviewer_budget_is_configurable(tmp_path: Any) -> None: + """A 272 KB file was always "too large to include", so the reviewer was + denied the evidence it then correctly rejected for lacking: + + "web/src/main.tsx is not included in full and I cannot inspect the + surrounding markup" + + The headless executor has taken this from configuration since #150; when + the review helpers were shared the session side got the default hardcoded + instead, which is a ceiling no deployment could raise. + """ + from agent_harness.executor import DEFAULT_CONTEXT_BUDGET + from agent_harness.session_executor import SessionExecutor + from agent_harness.work import WorkQueue + + queue = WorkQueue(str(tmp_path / "w.sqlite")) + host = type("Host", (), {})() + + default = SessionExecutor(queue, host, tmp_path) + raised = SessionExecutor(queue, host, tmp_path, context_budget=700_000) + + assert default.context_budget == DEFAULT_CONTEXT_BUDGET, "unchanged by default" + assert raised.context_budget == 700_000, "and a deployment can raise it"