Repository navigation
fix(recall): honor the query contract bound and the remote embedding budget - #1700
Merged
Teingi merged 2 commits intoSep 21, 2026
Merged
Conversation
… byte bound The retrieval path normalized a transient query through `normalize_text`, the normalizer for a durable entry body, so the storage layer's 8192 UTF-8 byte bound applied to queries the HTTP contract accepts at up to 8192 characters. A 2731 character CJK query is 8193 bytes, so `/v1/memory/search` and `/v1/context/prepare` answered an internal Server error to input the contract declares valid. The remember path already wraps canonical failures as `InvalidMemoryCandidateError`, which the server maps to 422. Search raised the bare canonical error, which matched no handler and fell through to the generic one as 500. Add `normalize_query`, which only normalizes and rejects empty input, and wrap its failures as an invalid memory operation so an over-long query stays a 422.
…quest budget The host plugins default to a 1.0 s per-request timeout inside a 4.0 s total budget. Preparing context needs the query embedded, and a remote provider round trip measured 315-1019 ms, so `_prepare_context` failed on 7 of 20 calls at the 1.0 s ceiling. The hook fails open, so the shorter budget showed up as an empty recall rather than an error. Raise the request timeout to 3.0 s, roughly three times the slowest observed round trip, and the total budget to 6.0 s to leave room for the preceding scope resolution. Apply the same defaults to the workbuddy, claude-code, and codex plugins, which share the setting.
Contributor
Author
|
Context for reviewers: this PR covers the server side of the empty-recall symptom. The host side is #1702, fixed in #1703 — WorkBuddy joins the whole session into the hook prompt, which exceeds the query bound regardless of how the server measures it. The two changes are independent and can land in either order. |
This was referenced Sep 30, 2026
PsiACE
pushed a commit
that referenced
this pull request
Sep 30, 2026
…g budget (#1806) #1700 raised the hook request timeout from 1.0 s to 3.0 s and the total budget from 4.0 s to 6.0 s in the Python plugins (Codex, Claude Code, WorkBuddy), because preparing context embeds the query and a remote embedding round trip can exceed one second. The TypeScript Pi and OpenCode plugins kept 1000 ms and 4000 ms, so their recall still fails open to an empty result when the round trip is slow. Apply the same defaults to both, and update the Pi configuration tables. Rebuilding the OpenCode bundle also picks up `get_memory_capacity`, which its generated operations gained in 4f5e36e without a rebuild. Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Which issue or RFC does this PR close?
Closes #1699.
Rationale for this change
Two independent defects kept the host hooks from recalling anything for non-ASCII users. Both fail open, so neither produced a user-visible error.
1. Retrieval queries were held to the storage layer's byte bound.
MemoryService.searchnormalized a transient query throughnormalize_text, the normalizer for a durable entry body. Its 8192 UTF-8 byte limit therefore applied to queries the contract accepts at up to 8192 characters (#1699). A 2731 character CJK query is 8193 bytes, so both/v1/memory/searchand/v1/context/prepareanswered HTTP 500 to input the contract declares valid.The bare
_CanonicalValueErrorthat escaped matched no handler and fell through to the generic one. The remember path already wraps the same failure asInvalidMemoryCandidateError, which the server maps to 422 — that asymmetry is whyrememberanswers 422 whilesearchanswered 500 for the same class of input.2. The hook request budget was too tight for a remote embedding round trip. Preparing context embeds the query. Against a remote provider the round trip measured 315–1019 ms, while the plugins defaulted to a 1.0 s per-request timeout, so
_prepare_contextfailed on 7 of 20 calls at the ceiling. The hook fails open, so the tighter budget showed up as an empty recall rather than an error.What changes are included in this PR?
Memory normalization
normalize_queryinartifacts/memory/canonical.py. A query is never persisted, so it does not carry the durable entry body's byte bound; the function normalizes and rejects empty input only. The HTTP contract keeps its own bound, which is stated in characters.MemoryService.searchuses it and wraps failures as_InvalidMemoryOperationError("search-query"), so an over-long query stays a 422 instead of a 500.Host plugin budgets
request_timeout_secondsfrom 1.0 to 3.0 andhttp_budget_secondsfrom 4.0 to 6.0 in the workbuddy, claude-code, and codex plugins, which share the setting. 3.0 s is roughly three times the slowest observed round trip; the larger total budget covers the preceding scope resolution.The HTTP contract is unchanged:
maxLength: 8192was always a character bound, and the implementation now agrees with it.Are there any user-facing changes?
POST /v1/memory/remember.How was this change tested?
Two regression tests, both confirmed to fail against the previous behavior by temporarily restoring
normalize_textin the search path:tests/builtin/artifacts/memory/test_canonical.py::test_retrieval_query_is_not_held_to_the_entry_body_byte_boundcoversnormalize_querydirectly.tests/e2e/test_context_text_assembly.py::test_multibyte_query_stays_within_the_contract_bound_for_search_and_prepared_contextdrives a 2731 character CJK query through both/v1/memory/searchand/v1/context/prepareagainst a live server, and asserts the character/byte split that used to fail.End-to-end against a local server, before and after:
The 15000-byte query returns
"mode": "hybrid", so retrieval including the remote embedding call completes.Hook stability over 20 consecutive
_prepare_contextcalls against a remote embedding provider: 13/20 before, 20/20 after.Commands run:
AI usage statement
Built with WorkBuddy, an agentic coding assistant. The failing behavior, root cause, and before/after measurements were verified against a locally running server, and the regression tests were confirmed to fail without the fix.