Skip to content

fix(reflect): preserve required recall when context fills (#4563) - #4747

Closed
PraveenKumarSridhar wants to merge 2 commits into
vectorize-io:mainfrom
PraveenKumarSridhar:codex/4563-preserve-forced-recall
Closed

PraveenKumarSridhar wants to merge 2 commits into
vectorize-io:mainfrom
PraveenKumarSridhar:codex/4563-preserve-forced-recall

Conversation

@PraveenKumarSridhar

Copy link
Copy Markdown
Contributor

fix(reflect): preserve required recall when context fills (#4563)

Fixes #4563.

Problem

During a mental-model refresh, a large search_observations result can trip the Reflect context guard before the required raw-fact recall step. The agent then synthesizes from observations alone, potentially missing newer facts and corrections.

Change

  • Compact earlier tool results in the model-facing conversation only when a required retrieval step needs room. Keep the full results for final synthesis, citations, and tracing.
  • Run the required retrieval before synthesizing. After compaction, synthesize from the complete evidence history rather than a shortened conversation.
  • Fail explicitly when the required step cannot fit, the provider rejects its context, or the iteration limit would skip it.
  • Reject a provider response that ignores the pinned recall choice, returns done or prose instead, or returns an invalid/error recall result. A named tool choice alone does not prove raw facts were retrieved.
  • Use the splitter's bounded single chunk for final synthesis, so an oversized individual result is not silently dropped.
  • Add red-first regressions for oversized observations, early recall, invalid recall output, provider noncompliance, context overflow, iteration limits, prompt caching, and final evidence retention.

Validation

  • uv run --no-sync pytest tests/test_reflect_agent.py tests/test_reflect_split_synthesis.py -q -n 0 -m 'not hs_llm_core' --disable-warnings --tb=short -o log_cli=false: 96 passed, 4 live LLM tests deselected.
  • Full blackbox system suite: 287 passed, including mental-model refresh and Reflect.
  • API-wide Ruff check/format, ty check hindsight_api, and git diff --check: passed.
  • Full local API run: 9,790 passed, 252 skipped, 5 xfailed, 9 failed, 10 setup errors. The failures were outside the changed agent/tests: five live-LLM tests lacked local credentials, several pg0 tests hit invalid macOS C.UTF-8 locale or stale local Alembic revision, and parallel database tests hit a deadlock/audit timing failure. The three database failures passed in isolation; the Reflect-adjacent per-operation config test passed when rerun with LC_ALL=en_US.UTF-8.

Related work

PR #4582 restores retrieval after fresh mental models. This change handles the context guard when a required retrieval step has not yet run. The two patches touch the Reflect agent and tests, so they may need conflict resolution if either merges first.

Limitations

No live LLM test was run in this worktree, and the full local API run was not green for the environment and unrelated failures noted above. A fresh mental model can still release the forced sequence under the existing policy, which PR #4582 addresses separately. The existing splitter still truncates a single indivisible result that exceeds the synthesis budget; this change keeps the complete tool result in history and trace, and ensures the bounded prefix reaches synthesis.

@nicoloboschi

Copy link
Copy Markdown
Collaborator

Thanks for this! We're tracking this problem in #4563 and will fix it from there, so I'm closing this PR.

We've stopped accepting pull requests from outside the team (see CONTRIBUTING.md). Any extra detail or steps to reproduce on the issue are very welcome.

@nicoloboschi

Copy link
Copy Markdown
Collaborator

Superseded by #5292. It keeps this PR's idea (blank earlier results so the required recall still fits, then answer from the full evidence) and drops the new hard failures, which broke 3 existing tests and could fail every reflect under a lowered step limit. Retrying an ignored forced step already landed on main in #5236.

nicoloboschi added a commit that referenced this pull request Oct 6, 2026
…#4563)

When an earlier retrieval step (observations, mental models) returned enough
text to hit the context budget, reflect stopped and answered right away, so
the forced raw-fact recall never ran and the answer could miss newer facts
and corrections.

Now, when a forced step is still pending and the conversation is over budget,
the earlier tool results are blanked in the model-facing conversation (oldest
first) until the call fits. The full results stay in the evidence history,
and once the forced steps finish the answer is written from that full
history, not from the shortened conversation. If blanking is not enough, the
old behaviour applies (answer from what was gathered); no new error paths.

Also: single-chunk final synthesis now uses the splitter's chunk, which
token-cuts an indivisible oversized result instead of dropping it.

Supersedes #4747, keeping its compaction idea and dropping its new hard
failures (noncompliance and step-limit errors).
nicoloboschi added a commit that referenced this pull request Oct 6, 2026
…#4563)

When an earlier retrieval step (observations, mental models) returned enough
text to hit the context budget, reflect stopped and answered right away, so
the forced raw-fact recall never ran and the answer could miss newer facts
and corrections.

Now, when a forced step is still pending and the conversation is over budget,
the earlier tool results are blanked in the model-facing conversation (oldest
first) until the call fits. The full results stay in the evidence history,
and once the forced steps finish the answer is written from that full
history, not from the shortened conversation. If blanking is not enough, the
old behaviour applies (answer from what was gathered); no new error paths.

Also: single-chunk final synthesis now uses the splitter's chunk, which
token-cuts an indivisible oversized result instead of dropping it.

Supersedes #4747, keeping its compaction idea and dropping its new hard
failures (noncompliance and step-limit errors).
nicoloboschi added a commit that referenced this pull request Oct 6, 2026
…#4563) (#5292)

When an earlier retrieval step (observations, mental models) returned enough
text to hit the context budget, reflect stopped and answered right away, so
the forced raw-fact recall never ran and the answer could miss newer facts
and corrections.

Now, when a forced step is still pending and the conversation is over budget,
the earlier tool results are blanked in the model-facing conversation (oldest
first) until the call fits. The full results stay in the evidence history,
and once the forced steps finish the answer is written from that full
history, not from the shortened conversation. If blanking is not enough, the
old behaviour applies (answer from what was gathered); no new error paths.

Also: single-chunk final synthesis now uses the splitter's chunk, which
token-cuts an indivisible oversized result instead of dropping it.

Supersedes #4747, keeping its compaction idea and dropping its new hard
failures (noncompliance and step-limit errors).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Context guard abandons pending forced retrieval step during mental-model refresh, synthesizing from observations only

2 participants