fix(sandbox): bound phase-one memory prompts to model context - #4064
fix(sandbox): bound phase-one memory prompts to model context#4064GautamSharma99 wants to merge 4 commits into
Conversation
seratch
left a comment
There was a problem hiding this comment.
Before we can merge this, please replace the current approx_token_count() safety check. It computes ceil(UTF-8 bytes / 4), which is an average heuristic rather than a conservative upper bound. Token-dense JSON or code can therefore satisfy the new 70% assertion while the actual request still exceeds the model's input/context limit. The current end-to-end test verifies the same estimator used by the implementation, so it cannot catch that case.
Please use accounting that cannot undercount supported OpenAI input, add an independent token-dense regression that fails with the current implementation, and update the documentation to state the resulting guarantee accurately. The remaining model lookup, override, fallback, and compatibility shape can stay as proposed.
Address review feedback on phase-one memory budget enforcement. The 70% context check estimated tokens with ceil(UTF-8 bytes / 4), an average heuristic that undercounts token-dense JSON or code, so a request could satisfy the assertion yet still exceed the model's context window. Replace it with openai_token_count(), which uses the model's own tiktoken encoding when tiktoken is available (exact for OpenAI input) and a conservative UTF-8 byte-length upper bound otherwise. Neither path can undercount supported OpenAI input. Rollout content is truncated by bytes and each iteration re-measures the rendered prompt, converging via the prompt's measured byte-per-token density so dense content is not dropped entirely. Add an independent token-dense regression that counts real tokens with tiktoken directly (fails under the previous estimator) plus unit tests for the new counters, and state the guarantee in the config docstring. Remove the docs/sandbox/memory.md change so documentation lands after the feature is released. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
Thanks for the review @seratch — both points are addressed in the follow-up commit. Non-undercounting token accounting. I replaced the
Both the fixed overhead (instructions + structured-output schema) and the per-iteration prompt check use this helper, so token-dense JSON/code can no longer slip past the 70% budget. Rollout content is truncated by bytes and each iteration re-measures the rendered prompt, converging via the prompt's measured byte-per-token density so dense content isn't discarded wholesale. The model lookup, override, fallback, and compatibility shape are unchanged, as you suggested. Independent regression. Docs. I removed the
|
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 3abddc6e80
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
seratch
left a comment
There was a problem hiding this comment.
Thanks for the follow-up. The token-dense regression fixes the original bytes/4 blind spot for recognized OpenAI models, but the new tiktoken path introduces two blockers.
phase_one_model also accepts Model objects and unknown/custom provider names; both are currently counted with o200k_base, although the Model interface provides no tokenizer contract. That makes the "never undercounts" guarantee false for the custom-model override this PR adds. Also, tiktoken is not a core dependency, and get_encoding() can synchronously download BPE data on a cache miss; an encoding load failure currently propagates instead of using the conservative fallback.
Please narrow this to deterministic UTF-8 byte-length upper-bound accounting inside phase one, remove the guessed/optional tiktoken path and its shared exports, and retain a dense-input regression for that bound. This preserves the provider-neutral contract without introducing environment-dependent I/O. After that, this should be ready for another review.
Address review feedback: replace the tiktoken-based token accounting with a deterministic UTF-8 byte-length upper bound kept local to phase one. phase_one_model also accepts custom Model objects and unknown provider names, which the Model interface gives no tokenizer contract for. Counting those with tiktoken's o200k_base could undercount a denser tokenizer and break the "never undercounts" guarantee for the custom-model override this PR adds. tiktoken is also optional, and get_encoding() can synchronously download BPE data on a cache miss, adding environment-dependent I/O. Count tokens as UTF-8 byte length instead. A byte-level BPE tokenizer never emits more tokens than bytes, so this is a provider-neutral upper bound that never undercounts, with no tokenizer download or model lookup. Remove openai_token_count(), the tiktoken encoding helper, and their shared exports; keep the counting private to phase one. Enforce the 150k rollout ceiling on the measured (byte) rollout count, not just the total request, so token-dense content cannot retain far more than 150k rollout tokens under a large context budget. Update the dense-input regression and the end-to-end test to assert the byte-length bound, add a large-context cap regression, and state the deterministic guarantee in the config docstring.
|
Thanks — addressed in 8e77c8b. I removed the tiktoken path entirely and narrowed phase one to deterministic UTF-8 byte-length upper-bound accounting. Both Codex blockers + the tokenizer-contract concern. 150k rollout ceiling (Codex comment 2). The ceiling is now applied as a byte cap on the measured rollout ( Tests. The dense-input regression and the end-to-end test now assert the deterministic byte-length bound instead of a tokenizer, keeping the check provider-neutral and free of environment-dependent I/O. The config docstring states the guarantee.
|
seratch
left a comment
There was a problem hiding this comment.
Thanks, the latest revision fixes the tiktoken and custom-model tokenizer blockers, and all checks are green. One behavior regression remains.
In v0.19.1, _PHASE_ONE_ROLLOUT_TOKEN_LIMIT = 150_000 is passed to TruncationPolicy.tokens(), corresponding to a 600,000-byte truncation budget. The new code reinterprets the same constant as 150,000 bytes, so large-context models retain only one quarter of the previous rollout evidence even when the context budget has ample room. The new large-context test locks in that reduction.
Please preserve the released 600,000-byte-equivalent rollout capacity while keeping the new whole-request byte bound as the safety check, and add a regression proving that intermediate-sized rollout content is retained when the context budget permits it. Also narrow the comments and docstring: UTF-8 bytes are an upper bound for byte-level BPE tokenizers, not an arbitrary custom Model contract. After those focused changes, this should be ready to approve.
|
After reviewing the issue, I came to think we don't need to make substantial changes for the purpose. |
Summary
This pull request fixes sandbox-memory phase-one requests that could exceed the selected model's context window even after the rollout was truncated.
Previously, every phase-one extraction allowed up to 150,000 estimated rollout tokens regardless of the configured model. That limit excluded the sizeable extraction instructions, terminal metadata, user-message wrapper, structured-output schema, provider framing, and model output.
The new budget resolution keeps one provider-neutral path:
MemoryGenerateConfig.phase_one_model_context_window_tokensoverride takes precedence, which supports custom model objects and unknown provider model names.CompactionModelInfo, the sandbox subsystem's existing context-window source of truth.Phase one now uses the base
Agentbecause it only transforms supplied rollout text into structured output and does not need sandbox tools or filesystem instructions. Phase two retains its existing sandbox-agent behavior.The public field is optional and appended to
MemoryGenerateConfig, preserving existing positional argument meaning. English documentation explains automatic lookup, the custom-model override, conservative token estimation, and fallback semantics.Test plan
uv run pytest -q tests/sandbox/test_memory.py(74 passed)make build-docsbash .agents/skills/code-change-verification/scripts/run.shmake formatmake lintmake typecheckmake testsIssue number
Closes #4058
Checks
.agents/skills/code-change-verification/scripts/run.sh/reviewbefore submitting this PR