Skip to content

fix(context): recover semantic injection when scoped Chroma metadata misses matches (closes #2979)#3011

Open
rodboev wants to merge 16 commits into
thedotmack:mainfrom
rodboev:fix/2979-semantic-project-filter-fallback
Open

fix(context): recover semantic injection when scoped Chroma metadata misses matches (closes #2979)#3011
rodboev wants to merge 16 commits into
thedotmack:mainfrom
rodboev:fix/2979-semantic-project-filter-fallback

Conversation

@rodboev

@rodboev rodboev commented Jun 20, 2026

Copy link
Copy Markdown
Contributor

Summary

Semantic injection could still recover the right project-scoped context after the earlier route fix, but the widened hydration window escaped into general SearchManager.search() callers. A normal /api/search?...&type=observations&limit=5&format=json semantic read could therefore hydrate up to the Chroma batch ceiling instead of the requested limit, and a caller could also pass an arbitrarily large semanticLimit query param through the public search surface.

This update keeps the widened window bounded and internal without collapsing the semantic candidate set. SearchManager.search() now ignores public semanticLimit args, uses a bounded CHROMA_BATCH_SIZE semantic candidate and hydration window for Chroma observation searches, clamps any internal override to that same ceiling, and slices public JSON observation responses back to the caller-facing limit unless the semantic-context route explicitly asks to keep the wider result set for its own merge step. That preserves the semantic-context recovery behavior, keeps downstream recency and SQLite filters working over a wide semantic window, restores the public /api/search limit contract, and removes the unbounded public input.

Thanks to @amellman for the reproduction details in #2979.

Why

src/cli/handlers/session-init.ts:135-141 still POSTs { q, project, limit } to /api/context/semantic, and src/services/worker/http/routes/SearchRoutes.ts:464-513 still needs a wider semantic window to recover adopted rows and preserve global relevance when the project-scoped hydration path is incomplete.

The regression was in src/services/worker/SearchManager.ts:306-505. The prior head interpreted semanticLimit as part of the normalized public search args, defaulted it to CHROMA_BATCH_SIZE, and then reused that widened value for observation hydration on every semantic search. The first follow-up removed the public input, but it also made the caller-facing limit double as the Chroma candidate window for ordinary semantic searches. That restored the response-size contract, but it could starve the later recency and SQLite-side concept or file filters by truncating the semantic id set before those filters ran.

The fix keeps the widening internal without shrinking the semantic candidate set. src/services/worker/http/routes/SearchRoutes.ts:496-513 still passes { semanticHydrationLimit: CHROMA_BATCH_SIZE } on the semantic-context route's scoped and unscoped recovery reads so that route keeps the full widened observation set for its own ranking merge. src/services/worker/SearchManager.ts:306-315 now ignores public semanticLimit, falls back to CHROMA_BATCH_SIZE for the semantic candidate and hydration window when no internal override is supplied, and clamps any internal override to the same ceiling before queryChroma(...) and getObservationsByIds(...) use it. src/services/worker/SearchManager.ts:498-505 then slices public JSON observation arrays back to the requested limit unless the internal caller explicitly asked to preserve the wider window.

Scope

This does not change the semantic-context route's bounded two-pass recovery design, fallback ordering, or prompt-redaction behavior.

This does not change session-init, ChromaSync, or the general /api/search ranking logic beyond restoring the public limit contract while keeping the bounded semantic candidate window intact for observation searches.

This does not add a new public search parameter. The wider semantic window is now internal-only again.

Risk

The behavior change is narrow: only the SearchManager.search() execution contract, its JSON response shaping for observation searches, and the semantic-context route call sites changed. The main preserved invariants are that /api/context/semantic still gets the widened bounded semantic window it needs for adopted-row recovery, while public JSON observation searches stay on their requested response size and still evaluate recency and SQLite-side filters against a wide semantic candidate set. The remaining risk is limited to internal callers that might want a widened result array in JSON mode in the future, in which case they now have to opt in explicitly through the internal execution option.

Verification

  • bun test tests/worker/http/routes/search-routes-semantic-context.test.ts tests/worker/SearchManager.semantic-limit.test.ts
  • npm run build
  • npm run lint:hook-io
  • npm run lint:spawn-env
  • npm run strip-comments:check — existing repo baseline noise, Changed: 329

Closes #2979

@greptile-apps

greptile-apps Bot commented Jun 20, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

Restores the public /api/search observation limit contract while keeping semantic candidate reads bounded.

Keeps the widened semantic hydration window internal to /api/context/semantic recovery flows.

Ignores public semanticLimit input and clamps internal semantic hydration overrides to the Chroma batch ceiling.

Preserves relevance ordering by applying limits after Chroma ID order restoration.

Redacts raw semantic query text from Chroma failure logs by recording query length instead.

Confidence Score: 5/5

The changes are narrowly scoped to semantic search limits, internal hydration behavior, and response shaping, with no review comments requiring attention.

The implementation aligns with the described contract: public search results remain limited while internal semantic context recovery can keep a bounded widened window, and the changed behavior is covered by targeted tests plus build and lint checks.

T-Rex T-Rex Logs

What T-Rex did

  • Compared the base run to the head run for hydration and relevance-order checks; the base had hydration limit as the string "5" and relevance-order limit returned [2,1] with exit code 1, while the head run completed with all six checks passing and exit code 0.
  • Compared the base and head behavior for scoped search calls in late/adopted fallback scenarios; the base made one scoped search call and returned empty context with count 0, while the head made two calls (without semanticLimit) and, with semanticHydrationLimit 100, recovered late-project-hit/adopted-hit, excluded wrong-project rows, preserved scoped rows on filter_only/error fallback, and returned count 3 for a limit of 3.
  • Reviewed the command transcripts for HEAD^ and HEAD; both runs completed with exit code 0, and the after state showed Scenario A with limit 100 for queryChroma and getObservationsByIds, and Scenario B fallback searchObservations with limit 5, as described in the artifacts.

View all artifacts

T-Rex Ran code and verified through T-Rex

Reviews (17): Last reviewed commit: "fix(search): keep unscoped semantic cont..." | Re-trigger Greptile

Comment thread src/services/worker/http/routes/SearchRoutes.ts Outdated
Comment thread src/services/worker/http/routes/SearchRoutes.ts Outdated
Comment thread src/services/worker/http/routes/SearchRoutes.ts Outdated
Comment thread src/services/worker/http/routes/SearchRoutes.ts Outdated
Comment thread src/services/worker/http/routes/SearchRoutes.ts Outdated
Comment thread src/services/worker/http/routes/SearchRoutes.ts Outdated
Comment thread src/services/worker/http/routes/SearchRoutes.ts Outdated
@rodboev rodboev force-pushed the fix/2979-semantic-project-filter-fallback branch from 50fbc1e to a62ae10 Compare June 20, 2026 07:44
Comment thread src/services/worker/http/routes/SearchRoutes.ts Outdated
Comment thread src/services/worker/http/routes/SearchRoutes.ts Outdated
Comment thread src/services/worker/http/routes/SearchRoutes.ts Outdated
Comment thread src/services/worker/http/routes/SearchRoutes.ts
Comment thread src/services/worker/http/routes/SearchRoutes.ts Outdated
@rodboev rodboev force-pushed the fix/2979-semantic-project-filter-fallback branch from 6f29aad to 64575bc Compare June 22, 2026 03:39
Comment thread src/services/worker/http/routes/SearchRoutes.ts Outdated
Comment thread src/services/worker/SearchManager.ts Outdated
Comment thread src/services/worker/SearchManager.ts Outdated
Comment thread src/services/worker/SearchManager.ts Outdated
@rodboev

rodboev commented Jun 22, 2026

Copy link
Copy Markdown
Contributor Author

Follow-up pushed in b0589d3b.

The previous follow-up restored the public response-size contract, but it also let the caller-facing limit shrink the semantic candidate set before the recency and SQLite-side concept or file filters ran. This head keeps the bounded semantic window and the public limit contract at the same time. src/services/worker/SearchManager.ts:306-315 still ignores public semanticLimit args, but it now falls back to a bounded CHROMA_BATCH_SIZE semantic candidate and hydration window for Chroma observation searches, and src/services/worker/SearchManager.ts:498-505 slices public JSON observation arrays back to the requested limit unless an internal caller explicitly asks to preserve the wider result set.

That keeps the unbounded-input concern closed as well. Any internal override still clamps back to CHROMA_BATCH_SIZE, and the semantic-context route remains the only current caller that opts into preserving the widened observation array for its route-local merge logic.

tests/worker/SearchManager.semantic-limit.test.ts now covers the three relevant contracts: public semanticLimit=1000 is ignored, public JSON observation responses still stop at limit=5, and a later recent semantic hit at rank 6 still survives the recency filter because the semantic candidate window stays bounded at 100 instead of collapsing to 5. I reran bun test tests/worker/http/routes/search-routes-semantic-context.test.ts tests/worker/SearchManager.semantic-limit.test.ts, npm run build, npm run lint:hook-io, and npm run lint:spawn-env on this head.

Comment thread src/services/worker/SearchManager.ts Outdated
Comment thread src/services/worker/http/routes/SearchRoutes.ts
@rodboev rodboev force-pushed the fix/2979-semantic-project-filter-fallback branch from 38b7fe3 to cca5052 Compare June 25, 2026 20:41
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.

Semantic injection always returns empty: project-filter mismatch between Chroma sync and /api/context/semantic

1 participant