Skip to content

feat(groomer): give repository exploration its own budget - #918

Merged
joryirving merged 1 commit into
mainfrom
feat/groomer-exploration-budget
Sep 2, 2026
Merged

feat(groomer): give repository exploration its own budget#918
joryirving merged 1 commit into
mainfrom
feat/groomer-exploration-budget

Conversation

@joryirving

Copy link
Copy Markdown
Contributor

Summary

  • Give repository exploration its own byte budget, per-file cap and timeout instead of borrowing maxContextBytes and timeoutMs from the single grooming call.
  • Size it three ways: individual DISPATCH_GROOMER_EXPLORE_* overrides, DISPATCH_GROOMER_MODEL_CONTEXT_TOKENS (derived from the model's real window), or DISPATCH_GROOMER_CONTEXT_MODE = small / medium / large.

Why

Exploration is a multi-turn tool loop but was budgeted like a single call. With
the borrowed defaults the loop got 8 KB total and 4 KB per file, so two file
reads exhausted it. Across 62 runs on one deployment that produced 86
repository exploration hit its byte budget warnings and 26 timeouts, with the
loop stopping at ~6.8 of its 12 permitted tool calls — it was running out of
bytes, not turns, and reporting fewer files than it had found.

Sizing

Mode Total bytes Per file Timeout
small 8 KB 4 KB 90s
medium (default) 24 KB 8 KB 150s
large 96 KB 24 KB 300s

medium is aimed at a modest self-hosted model rather than any particular
deployment. Operators with a larger window should prefer
DISPATCH_GROOMER_MODEL_CONTEXT_TOKENS over guessing a preset — it reserves
65% of the window for the system prompt, issue context and output, and derives
the rest. That mirrors model_context_tokens in pr-reviewer-action, which
exists for the same reason: named presets assume windows that self-hosted
models do not have.

A single file can never exceed the total budget, however the two are set.

Observability

contextSummary.exploration.budget now records the resolved values and which
of the three paths produced them, so a starved run can be diagnosed from the
record rather than inferred from warnings.

Verification

  • npx vitest run — 2480 passed, 5 skipped (18 new)
  • npx tsc --noEmit — clean
  • npm run lint — 0 errors
  • NODE_ENV=development npm run build — succeeds

https://claude.ai/code/session_01YSuDvZq9ncvyX85Uzx3cQh

Exploration borrowed maxContextBytes and timeoutMs from the single grooming
call. Those were sized for one pre-computed context and one request, not a
multi-turn tool loop, so the loop exhausted its budget partway through an
investigation and stopped early — reporting fewer files than it had found.

Size it three ways, most specific first: the individual DISPATCH_GROOMER_EXPLORE_*
overrides, DISPATCH_GROOMER_MODEL_CONTEXT_TOKENS deriving from the model's real
window, or DISPATCH_GROOMER_CONTEXT_MODE (small|medium|large).

The resolved budget and which path produced it are recorded on every run under
contextSummary.exploration.budget.

Claude-Session: https://claude.ai/code/session_01YSuDvZq9ncvyX85Uzx3cQh

@its-saffron its-saffron Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

AI Automated Review

Full PR review.

Analysis engine: MiniMax-M2.7@https://litellm.jory.dev/v1 (anthropic) — primary route

Recommendation: Approve

This PR introduces a dedicated exploration budget for the groomer's repository exploration tool loop. The code is well-structured, well-tested, and solves a documented real-world problem (86 budget warnings and 26 timeouts across 62 runs).

Change-by-Change Findings

New: src/lib/groomer/exploration-budget.ts

Introduces resolveExplorationBudget() and deriveFromContextTokens(). Resolution follows a clear priority: individual env overrides → model context tokens derivation → named mode (small/medium/large). The code includes defensive guards: maxFileBytes is capped at maxTotalBytes regardless of how the budget was set, and deriveFromContextTokens applies a floor of 4,096 bytes for tiny windows.

New: src/lib/groomer/exploration-budget.test.ts

18 new tests covering: defaults, named modes, monotonic growth across modes, fallback on unrecognized mode, context-token derivation (both large and small windows), the per-file cap safety, individual overrides, and invalid/non-numeric input handling. Tests are thorough and deterministic.

Modified: src/lib/groomer/config.ts

Wires ExplorationBudget into HostedGroomerConfig and calls resolveExplorationBudget() to populate it.

Modified: src/lib/groomer/run.ts

Switches the exploration loop from config.timeoutMs/config.maxContextBytes/config.maxFileBytes to config.exploration.*. Also records the resolved budget in contextSummary.exploration.budget for observability.

Documentation: README.md, docs/hosted-groomer.md

New environment variables (DISPATCH_GROOMER_CONTEXT_MODE, DISPATCH_GROOMER_MODEL_CONTEXT_TOKENS, DISPATCH_GROOMER_EXPLORE_MAX_BYTES, DISPATCH_GROOMER_EXPLORE_MAX_FILE_BYTES, DISPATCH_GROOMER_EXPLORE_TIMEOUT_MS) are documented with a sizing table. Includes rationale for preferring DISPATCH_GROOMER_MODEL_CONTEXT_TOKENS for self-hosted models.


Standards Compliance

The AGENTS.md standards file does not prescribe conventions specific to budget configuration or groomer internals. The PR follows general patterns observed in the codebase:

  • New env vars documented in README and docs/hosted-groomer.md — consistent with other DISPATCH_GROOMER_* variables.
  • Default mode is "medium" — aligned with the PR's stated goal of suiting "a modest self-hosted model."
  • Observability via contextSummary.exploration.budget — consistent with the existing contextSummary pattern.
  • No commit of secrets — no secrets or .env changes.
  • TypeScript clean — confirmed by npx tsc --noEmit passing.

Required Check: Path Traversal Vulnerabilities

Verified not applicable. This PR operates on budget configuration (byte limits, timeouts, mode strings) and does not handle filesystem paths. The tool loop that reads files from the repository lives in a separate module (repository-context.ts), not modified by this PR. The new ExplorationBudget interface only describes numeric limits and a source discriminator — no path strings are constructed or traversed.


Required Check: Edge-Case Paths (null bytes, symlinks)

Verified not applicable. The exploration-budget.ts module has no filesystem interaction. It parses environment variable strings into numbers (parseIntEnv) and mode strings (parseMode), neither of which are used to open or read files. Null-byte injection and symlink traversal are concerns for the file-reading layer, which is outside this PR's scope.


Tool Harness Findings

No native tool calls were issued. The harness found no issues to report.


CI Results

All 11 CI checks passed (Lint, Tests, Database migrations, Build, smoke, Docker Build (MCP), Typecheck, Database integration, Coverage, npm audit, Docker Build).


Unknowns / Needs Verification

None. The PR body includes self-verification results (vitest, tsc, lint, build), and the CI confirms all checks pass. Documentation is complete and consistent.

@joryirving
joryirving merged commit fe138e8 into main Sep 2, 2026
12 checks passed
@joryirving
joryirving deleted the feat/groomer-exploration-budget branch September 2, 2026 17:55
@its-miso its-miso Bot mentioned this pull request Sep 2, 2026
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