/** * Adapter between the concrete storage `Repos` and the narrow * `RetrievalRepos` surface the retrieval pipeline consumes. * * Keeping this translation in `core/pipeline/` means the retrieval module * stays decoupled from the storage schema — and the pipeline stays the * one place where we remember which repo serves which tier. */ import type { RetrievalRepos } from "../retrieval/types.js"; import type { Repos } from "../storage/repos/index.js"; import type { RuntimeNamespace, TraceId } from "../../agent-contract/dto.js"; import { isVisibleTo, visibilityWhere } from "../runtime/namespace.js"; export function wrapRetrievalRepos(repos: Repos, namespace: RuntimeNamespace): RetrievalRepos { return { skills: { searchByVector(query, k, opts) { return repos.skills.searchByVector(query, k, opts ?? {}); }, searchByText(ftsMatch, k, opts) { return repos.skills.searchByText(ftsMatch, k, opts ?? {}); }, searchByPattern(terms, k, opts) { return repos.skills.searchByPattern(terms, k, opts ?? {}); }, getById(id) { const row = repos.skills.getById(id); if (!row || !isVisibleTo(row, namespace)) return null; return { id: row.id, name: row.name, status: row.status, invocationGuide: row.invocationGuide, procedureJson: row.procedureJson, decisionGuidance: normaliseSkillDecisionGuidance(row.procedureJson), eta: row.eta, vec: row.vec, sourcePolicyIds: row.sourcePolicyIds, updatedAt: row.updatedAt, }; }, }, traces: { searchByVector(query, k, opts) { return repos.traces.searchByVector( query, k, mergeVisibility(opts, namespace), ); }, searchByText(ftsMatch, k, opts) { return repos.traces.searchByText( ftsMatch, k, mergeVisibility(opts, namespace, "t"), ); }, searchByPattern(terms, k, opts) { return repos.traces.searchByPattern( terms, k, mergeVisibility(opts, namespace), ); }, getManyByIds(ids) { const rows = repos.traces.getManyByIds(ids as readonly TraceId[]); return rows.filter((r) => isVisibleTo(r, namespace)).map((r) => ({ id: r.id, episodeId: r.episodeId, sessionId: r.sessionId, ts: r.ts, userText: r.userText, agentText: r.agentText, reflection: r.reflection, value: r.value, priority: r.priority, tags: r.tags, vecSummary: r.vecSummary, vecAction: r.vecAction, })); }, searchByErrorSignature(fragments, limit, opts) { const rows = repos.traces.searchByErrorSignature( fragments, limit, mergeVisibility(opts, namespace), ); return rows.filter((r) => isVisibleTo(r, namespace)).map((r) => ({ id: r.id, episodeId: r.episodeId, sessionId: r.sessionId, ts: r.ts, userText: r.userText, agentText: r.agentText, reflection: r.reflection, value: r.value, priority: r.priority, tags: r.tags, errorSignatures: r.errorSignatures ?? [], })); }, }, worldModel: { searchByVector(query, k, opts) { return repos.worldModel.searchByVector(query, k, opts ?? {}); }, searchByText(ftsMatch, k) { return repos.worldModel.searchByText(ftsMatch, k); }, searchByPattern(terms, k) { return repos.worldModel.searchByPattern(terms, k); }, getById(id) { const row = repos.worldModel.getById(id); if (!row || !isVisibleTo(row, namespace)) return null; return { id: row.id, title: row.title, body: row.body, policyIds: row.policyIds, vec: row.vec, }; }, }, // V7 §2.4.6 — expose just enough of the policies repo for retrieval // to look up `decisionGuidance` (preference / anti-pattern) attached // to traces / skills already chosen by tiers 1 + 2. policies: { searchByVector(query, k, opts) { return repos.policies.searchByVector(query, k, opts ?? {}); }, list(filter) { const rows = repos.policies.list( filter && filter.status ? { status: filter.status } : {}, ); return rows.filter((r) => isVisibleTo(r, namespace)).map((r) => ({ id: r.id, title: r.title, trigger: r.trigger, procedure: r.procedure, verification: r.verification, boundary: r.boundary, support: r.support, gain: r.gain, status: r.status, experienceType: r.experienceType ?? "success_pattern", evidencePolarity: r.evidencePolarity ?? "positive", salience: r.salience ?? 0, confidence: r.confidence ?? 0.5, skillEligible: r.skillEligible !== false, sourceEpisodeIds: r.sourceEpisodeIds, sourceFeedbackIds: r.sourceFeedbackIds ?? [], sourceTraceIds: r.sourceTraceIds ?? [], decisionGuidance: r.decisionGuidance, vec: r.vec, updatedAt: r.updatedAt, })); }, getById(id) { const row = repos.policies.getById(id); if (!row || !isVisibleTo(row, namespace)) return null; return { id: row.id, title: row.title, trigger: row.trigger, procedure: row.procedure, verification: row.verification, boundary: row.boundary, support: row.support, gain: row.gain, status: row.status, experienceType: row.experienceType ?? "success_pattern", evidencePolarity: row.evidencePolarity ?? "positive", salience: row.salience ?? 0, confidence: row.confidence ?? 0.5, skillEligible: row.skillEligible !== false, sourceEpisodeIds: row.sourceEpisodeIds, sourceFeedbackIds: row.sourceFeedbackIds ?? [], sourceTraceIds: row.sourceTraceIds ?? [], decisionGuidance: row.decisionGuidance, vec: row.vec, updatedAt: row.updatedAt, }; }, }, }; } function mergeVisibility; }>( opts: T | undefined, namespace: RuntimeNamespace, alias = "", ): T { const visible = visibilityWhere(namespace, alias); return { ...(opts ?? {}), where: opts?.where ? `(${opts.where}) AND (${visible.sql})` : visible.sql, params: { ...(opts?.params ?? {}), ...visible.params, }, } as T; } function normaliseSkillDecisionGuidance( procedureJson: unknown, ): { preference: string[]; antiPattern: string[] } { const proc = (procedureJson ?? {}) as { decisionGuidance?: { preference?: unknown; antiPattern?: unknown }; decision_guidance?: { preference?: unknown; anti_pattern?: unknown }; }; const dg = proc.decisionGuidance; const snakeDg = proc.decision_guidance; return { preference: dg && Array.isArray(dg.preference) ? dg.preference.map((s) => String(s)).filter(Boolean) : snakeDg && Array.isArray(snakeDg.preference) ? snakeDg.preference.map((s) => String(s)).filter(Boolean) : [], antiPattern: dg && Array.isArray(dg.antiPattern) ? dg.antiPattern.map((s) => String(s)).filter(Boolean) : snakeDg && Array.isArray(snakeDg.anti_pattern) ? snakeDg.anti_pattern.map((s) => String(s)).filter(Boolean) : [], }; }