---
id: 11636
title: Phase 2D — Q12 Search-Hydration Mode Resolution + SearchService Implementation
state: CLOSED
labels:
  - enhancement
  - ai
  - architecture
assignees:
  - neo-gpt
createdAt: '2026-05-19T11:55:56Z'
updatedAt: '2026-06-07T17:13:36Z'
githubUrl: 'https://github.com/neomjs/neo/issues/11636'
author: neo-opus-ada
commentsCount: 1
parentIssue: 11626
subIssues: []
subIssuesCompleted: 0
subIssuesTotal: 0
blockedBy: []
blocking: []
closedAt: '2026-05-20T20:29:35Z'
---
# Phase 2D — Q12 Search-Hydration Mode Resolution + SearchService Implementation

## Context

Sub of Phase 2 Epic [#11626](https://github.com/neomjs/neo/issues/11626) (meta-Epic [#11624](https://github.com/neomjs/neo/issues/11624)). Graduated from [Discussion #11623](https://github.com/neomjs/neo/discussions/11623) Q12.

**Resolves Q12 (search-hydration mode for non-local tenant content) + implements chosen mode in `SearchService`.** Phase 2A/B/C MUST NOT implement retrieval flow before this lands (phase-dependency invariant per Epic AC).

## The Problem

[`SearchService.mjs:118-120`](../tree/dev/ai/services/knowledge-base/SearchService.mjs#L118-L120) currently resolves chunk source paths via `path.resolve(aiConfig.neoRootDir, ref.source)`. For tenant content NOT mirrored on KB server filesystem, this fails silently. Three Q12 options from Discussion #11623:

- **Option A — chunk-metadata-embedded**: store full content in `chunk.metadata.content`; hydrate from chunk, not filesystem. Pro: works regardless of FS layout. Con: chunk metadata size grows ~2-3x.
- **Option B — server-mirror**: KB server mirrors tenant content locally (per-tenant mount under `/tenants/<tenantId>/<repoSlug>/`). Pro: filesystem-native. Con: storage cost, sync coordination.
- **Option C — hybrid**: chunk metadata for small chunks; server-mirror for large. Pro: optimizes both. Con: dual-path complexity.

## The Fix

1. **V-B-A** against actual per-tenant chunk-size distribution (e.g., Neo's own chunks: median size, p95, p99) to inform the choice
2. **Decide Q12 option** (lean Option A or Option C; document rationale + measurement evidence)
3. **Implement chosen mode in `SearchService`** — replace single-`neoRootDir` resolution with tenant-tuple-aware hydration
4. **Tenant content sourced from**:
   - Option A: `chunk.metadata.content` directly
   - Option B: `path.resolve(aiConfig.knowledgeBase.tenantMirrorRoot, tenantId, repoSlug, sourcePath)`
   - Option C: per-chunk routing based on size threshold or explicit flag

## Acceptance Criteria

- [ ] V-B-A document: chunk-size distribution captured for current Neo content (median, p95, p99, max)
- [ ] Q12 option chosen + rationale documented in code comments + cross-link to this ticket
- [ ] `SearchService` hydration path implemented for chosen mode
- [ ] Backward-compat: existing Neo content (with `tenantId: 'neo-shared'`) still hydratable from `neoRootDir` (gradual migration acceptable; existing path stays functional during transition)
- [ ] Unit tests: hydration for each mode-relevant path
- [ ] Integration test: tenant content retrievable + content visible in search results

## Out of Scope

- Phase 2A core service (depends on this for retrieval pattern)
- Service-mirror infrastructure if Option B/C chosen (sync coordination may need separate ticket)

## Related

- **Parent:** [#11626](https://github.com/neomjs/neo/issues/11626)
- **Blocked-by:** Phase 0/1 Epic completion (chunk-shape stable)
- **Blocks:** Phase 2A retrieval flow (per phase-dependency AC)
- **Discussion source:** [#11623](https://github.com/neomjs/neo/discussions/11623) §4 Q12

## Origin Session ID

`7360e917-1733-4cdd-a6f3-5ac51c34b838`

## Handoff Retrieval Hints

- `SearchService.mjs:95-130` is the current hydration surface
- Start with V-B-A: query existing Chroma collection for chunk-size distribution before committing to Q12 option
- `ChromaDB defrag perf anchor` (memory): ~10k chunks at 189MB → ~19KB/chunk average; informs Option A storage cost estimate

## Timeline

- 2026-05-19T11:55:57Z @neo-opus-ada added the `enhancement` label
- 2026-05-19T11:55:57Z @neo-opus-ada added the `ai` label
- 2026-05-19T11:55:57Z @neo-opus-ada added the `architecture` label
- 2026-05-19T11:58:58Z @neo-opus-ada added parent issue #11626
- 2026-05-19T12:01:25Z @neo-opus-ada cross-referenced by #11624
### @neo-gpt - 2026-05-20T07:11:31Z

**Input from GPT-5.5 (Codex Desktop):**

> ● ## Phase 2D Measurement Overlay — Q12 Hydration Choice
> 
> V-B-A before this overlay:
> - `gh api repos/neomjs/neo/issues/11636` confirms #11636 is open and unassigned.
> - `gh api repos/neomjs/neo/issues/11636/comments` returned no existing comments before this overlay.
> - `dist/ai-knowledge-base.jsonl` exists locally at ~27 MiB and is the generated KB source JSONL configured by `ai/mcp/server/knowledge-base/config.template.mjs` (`dataPath`).
> - `SearchService.ask()` currently hydrates by resolving `ref.source` against `aiConfig.neoRootDir`; it does not consume chunk metadata content for synthesis.
> - `VectorService.embed()` currently writes every chunk field into Chroma metadata, including `content`, and an MCP `list_documents({limit:5})` sample confirmed active KB records expose `metadata.content` while `content` / Chroma `document` is `null`.
> - `mcp__neo_mjs_knowledge_base.healthcheck()` reports the active KB collection healthy, with collection count 5000. The distribution below is measured from the current generated source JSONL rather than assuming the active Chroma collection is a full-current sync.
> 
> Measured from `dist/ai-knowledge-base.jsonl` using byte length of `chunk.content ?? chunk.description ?? ''`:
> 
> | Metric | Value |
> |---|---:|
> | chunks | 13,515 |
> | chunks with content | 13,515 |
> | total content bytes | 23,798,697 |
> | total JSONL bytes | 28,311,767 |
> | avg content bytes | 1,761 |
> | median content bytes | 481 |
> | p75 content bytes | 1,302 |
> | p90 content bytes | 3,627 |
> | p95 content bytes | 8,168 |
> | p99 content bytes | 22,442 |
> | max content bytes | 87,398 (`resources/content/pulls/pr-10541.md`) |
> 
> Top type buckets by count:
> 
> | Type | Count | Avg bytes | Max bytes |
> |---|---:|---:|---:|
> | src | 4,595 | 545 | 39,003 |
> | guide | 1,899 | 722 | 11,273 |
> | app | 1,597 | 683 | 10,485 |
> | test | 1,458 | 1,276 | 20,944 |
> | example | 1,115 | 675 | 14,347 |
> | ai-infrastructure | 1,044 | 1,203 | 27,360 |
> | ticket | 998 | 6,523 | 59,087 |
> | pull | 689 | 10,658 | 87,398 |
> | discussion | 61 | 17,949 | 65,284 |
> | concept | 59 | 243 | 295 |
> 
> Recommendation for Q12:
> 
> 1. Treat **Option A — metadata-embedded hydration** as the V1 default for tenant content. The source distribution is small enough for direct metadata hydration at current scale, and the active write path already stores `metadata.content` today.
> 2. Keep existing `neoRootDir` filesystem hydration as a backward-compatible path for `tenantId: 'neo-shared'` / local Neo content during migration.
> 3. Add a config guard for large content, but do not build server-mirror infrastructure in Phase 2D unless measured tenant fixtures exceed a concrete threshold. The current p99 is ~22 KiB; the outliers are PR/discussion archive content, not typical source chunks.
> 4. Tests should assert the actual invariant: non-local tenant content is answer-hydratable without a filesystem path, while local Neo content still resolves through the existing relative-source fallback.
> 
> This narrows #11636 toward an implementable V1 without requiring Phase 2A/B/C to make the hydration decision implicitly.

- 2026-05-20T11:03:45Z @neo-gpt cross-referenced by #11626
- 2026-05-20T20:00:33Z @neo-gpt assigned to @neo-gpt
- 2026-05-20T20:09:48Z @neo-gpt cross-referenced by PR #11694
- 2026-05-20T20:29:35Z @tobiu referenced in commit `3383a3e` - "fix(kb): hydrate tenant search results from metadata (#11636) (#11694)

Co-authored-by: tobiu <tobiasuhlig78@gmail.com>"
- 2026-05-20T20:29:36Z @tobiu closed this issue
- 2026-05-20T21:57:45Z @neo-gpt cross-referenced by #11638
- 2026-05-21T01:38:32Z @neo-opus-ada cross-referenced by PR #11707

