# The embedding migration: source review

Reviewed: 19 October 2026.

## Scope

This is a manual, bounded review of an archived AugmentedWolf checkout, not a live portfolio scan, a runtime test, or a delivery benchmark. Imports establish direct dependencies; the three interactive scenarios are curated interpretations of the call sites below. No external provider was contacted for this review.

The client header records replacement of the local embedding service on 15 September 2026. Implementation returns the same field/vector/dim/model_version shape. Code vectors have 1536 dimensions and text vectors 1024. Search imports embeddings and reranking from this client; the worker imports embeddings and calls it with the text type.

## Decisions and review requirements

- Provider change: inspect search and background indexing, credentials, timeouts, and the external data boundary.
- Code-vector dimension change: code search is in scope. The shown worker calls the text model and has no direct code-vector use. Stored code-vector mappings and re-embedding must be reviewed separately; they are not fully inventoried here.
- Reranker change: search ordering is in scope. The shown worker does not call the reranker. The client logs a reranking error and returns the original order. This is the observed behavior, not a claim of universal fail-closed operation.

The review requirements are engineering judgments based on this code. They are not historical approvals. A named approver, delivery duration, spend, deployment verification and an autonomous-run audit were not available in the inspected evidence.

## Source excerpts

Paths are relative to the archived repository. SHA-256 values identify the complete files inspected. Excerpts are provided here so this website does not need to serve the private repository or credentials.

### aw/embed-client.js

SHA-256: `39cd121b92d10da0fdfec72d127c942d028c29add3d530452669008ff79e932b`

Lines 1–23:

```javascript
/**
 * embed-client.js — Jina AI embeddings client (remote API)
 *
 * Replaced the local MLX embedding service (localhost:18200) on 2026-09-15.
 * Same model family, now served via https://api.jina.ai:
 *   code → jina-code-embeddings-1.5b      (1536d, default task)
 *   text → jina-embeddings-v5-text-small  (1024d, task=retrieval.query —
 *          closest to the local MLX retrieval head; cos 0.977 vs local)
 *
 * Contract preserved for all callers (aw-index reconcile, os-embed-worker,
 * os-search, tree-governance, embed-text, os-reindex):
 *   getEmbedding(text, docType) → { field, vector, dim, model_version }
 *   getEmbeddingBatch(texts, docType) → [{ field, vector, dim, model_version }]
 *   isEmbedServiceAvailable() → boolean
 *   rerankResults(query, results, opts) → results sorted by relevance
 *
 * Fail loud: no BM25-only silent fallback at this layer — callers decide.
 * API key: aw/secrets/jina.env (JINA_API_KEY=...). Rate limit: token bucket
 * on requests/min (paid tier default 500; free tier 100 — set jina_rpm in
 * aw.config.json embedding_service).
 */
import https from 'https';
import { readFileSync } from 'fs';
```

Lines 51–58:

```javascript

// ── doc_type → model routing (unchanged semantics) ─────────────────────────
const CODE_TYPES = new Set(['code', 'code_file_summary', 'code_dir_summary', 'file_summary', 'dir_summary']);

const MODEL_SPECS = {
  code: { api: 'jina-code-embeddings-1.5b', task: null, dim: 1536, field: 'embedding_code' },
  text: { api: 'jina-embeddings-v5-text-small', task: 'retrieval.query', dim: 1024, field: 'embedding_text' },
};
```

Lines 138–172:

```javascript
export async function getEmbedding(text, docType) {
  const [vector] = await jinaEmbed([text], docType);
  const spec = MODEL_SPECS[modelForDocType(docType)];
  return { field: fieldForDocType(docType), vector, dim: spec.dim, model_version: spec.api };
}

export async function getEmbeddingBatch(texts, docType) {
  const vectors = await jinaEmbed(texts, docType);
  const spec = MODEL_SPECS[modelForDocType(docType)];
  return vectors.map(vector => ({ field: fieldForDocType(docType), vector, dim: spec.dim, model_version: spec.api }));
}

// ── rerank (replaces local /rerank; jina-reranker-v3.5 on API) ──────────────
// Same degradation contract as before: any failure → ORIGINAL order, never throws.
// opts.apiKey is injectable so tests can exercise the degradation path.
export async function rerankResults(query, results, { model = 'jina-reranker-v3.5', timeoutMs = 5000, apiKey, url = RERANK_BASE } = {}) {
  if (!results || results.length <= 1) return results || [];
  try {
    const documents = results.map((r) => r._rrtext || r.title || '');
    const res = await httpPostJSON(url, { model, query, documents, top_n: documents.length }, timeoutMs, apiKey);
    if (!res.ok) throw new Error(`rerank HTTP ${res.status}`);
    // API returns [{index, relevance_score}] sorted by score desc → rebuild scores[] aligned to input order
    const scores = new Array(documents.length).fill(NaN);
    for (const r of res.json.results || []) scores[r.index] = r.relevance_score;
    if (scores.some(Number.isNaN)) throw new Error(`rerank returned ${(res.json.results || []).length} scores for ${results.length} items`);
    return results
      .map((r, i) => ({ ...r, rerank_score: scores[i] }))
      .sort((a, b) => b.rerank_score - a.rerank_score);
  } catch (e) {
    console.error(`[embed-client] rerank degraded to original order: ${e.message}`);
    return results;
  }
}

// ── availability (ping, cached 5 min) ─────────────────────────────────────
```

### aw/os-search.js

SHA-256: `f358f69664d6d56599f6585f372e251908ab490995355ab596e940aaee51f557`

Lines 14–14:

```javascript
import { getEmbedding, isEmbedServiceAvailable as isEmbedAvailable, fieldForDocType, rerankResults } from './embed-client.js';
```

Lines 90–108:

```javascript
    // embedding_code uses code-model vector; embedding_desc uses text-model vector.
    // textVector is best-effort — fall back to code-only if unavailable.
    let codeVector = rocchioVector;
    if (!codeVector) {
      try {
        const codeEmb = await getEmbedding(query, 'code');
        codeVector = codeEmb.vector;
      } catch (e) {
        throw new Error(`[os-search] EMBED_SERVICE_DOWN: ${e.message}`);
      }
    }
    let textVector = null;
    try {
      const textEmb = await getEmbedding(query, 'text');
      textVector = textEmb.vector;
    } catch { /* text embedding optional — fall through with code only */ }

    if (textVector) {
      vectorQuery = {
```

Lines 230–237:

```javascript
  const ordered = rerankEnabled ? await rerankResults(query, mapped) : mapped;
  for (const r of ordered) delete r._rrtext;
  return ordered.slice(0, limit);
}


// rerankResults now lives in embed-client.js (Jina API reranker) — re-exported for callers/tests.
export { rerankResults };
```

### aw/os-embed-worker.js

SHA-256: `012e87e87c7d3e1ca48550075da930db51a2738f5e9f102486f3b08fc1246b11`

Lines 13–13:

```javascript
import { getEmbedding } from './embed-client.js';
```

Lines 140–143:

```javascript
        const emb = await Promise.race([
          getEmbedding(text, 'text'),
          new Promise((_, rej) => setTimeout(() => rej(new Error('embed timeout (120s) — serviciul agățat?')), 120_000)),
        ]);
```

