Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 1 addition & 16 deletions src/__tests__/memory-route-config-seam.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,7 @@ import express from 'express';
import { afterAll, beforeAll, beforeEach, describe, expect, it, vi } from 'vitest';
import { createMemoryRouter } from '../routes/memories.js';
import type { MemoryService } from '../services/memory-service.js';

interface BootedApp {
baseUrl: string;
close: () => Promise<void>;
}
import { type BootedApp, bindEphemeral } from './test-helpers.js';

interface MutableRouteConfig {
retrievalProfile: string;
Expand All @@ -32,17 +28,6 @@ interface MutableRouteConfig {
repairLoopEnabled: boolean;
}

async function bindEphemeral(app: ReturnType<typeof express>): Promise<BootedApp> {
const server = app.listen(0);
await new Promise<void>((resolve) => server.once('listening', () => resolve()));
const addr = server.address();
const port = typeof addr === 'object' && addr ? addr.port : 0;
return {
baseUrl: `http://localhost:${port}`,
close: () => new Promise<void>((resolve) => server.close(() => resolve())),
};
}

describe('memory route config seam', () => {
let booted: BootedApp;
let routeConfig: MutableRouteConfig;
Expand Down
22 changes: 22 additions & 0 deletions src/__tests__/test-helpers.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
/**
* Shared test utilities for integration tests that spin up Express servers.
*/

import type express from 'express';

export interface BootedApp {
baseUrl: string;
close: () => Promise<void>;
}

/** Bind an Express app to an ephemeral port and return its base URL + close handle. */
export async function bindEphemeral(app: ReturnType<typeof express>): Promise<BootedApp> {
const server = app.listen(0);
await new Promise<void>((resolve) => server.once('listening', () => resolve()));
const addr = server.address();
const port = typeof addr === 'object' && addr ? addr.port : 0;
return {
baseUrl: `http://localhost:${port}`,
close: () => new Promise<void>((resolve) => server.close(() => resolve())),
};
}
22 changes: 1 addition & 21 deletions src/app/__tests__/composed-boot-parity.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,31 +29,11 @@ import { MemoryService } from '../../services/memory-service.js';
import { createMemoryRouter } from '../../routes/memories.js';
import { createCoreRuntime } from '../runtime-container.js';
import { createApp } from '../create-app.js';
import { type BootedApp, bindEphemeral } from '../../__tests__/test-helpers.js';

const __dirname = dirname(fileURLToPath(import.meta.url));
const TEST_USER = 'composed-boot-parity-user';

interface BootedApp {
baseUrl: string;
close: () => Promise<void>;
}

/**
* Bind an Express app to an ephemeral port and return its base URL plus
* a close handle. Mirrors the listen pattern in route-validation.test.ts
* but isolated here so each test app shuts down independently.
*/
async function bindEphemeral(app: ReturnType<typeof express>): Promise<BootedApp> {
const server = app.listen(0);
await new Promise<void>((resolve) => server.once('listening', () => resolve()));
const addr = server.address();
const port = typeof addr === 'object' && addr ? addr.port : 0;
return {
baseUrl: `http://localhost:${port}`,
close: () => new Promise<void>((resolve) => server.close(() => resolve())),
};
}

/**
* Build the singleton-backed reference app — what server.ts looked like
* before Phase 1A. Used as the parity baseline.
Expand Down
18 changes: 10 additions & 8 deletions src/routes/memories.ts
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,14 @@ function registerIngestHandler(
});
}

function resolveSearchPreamble(body: ReturnType<typeof parseSearchBody>, configRouteAdapter: RuntimeConfigRouteAdapter) {
const scope = toMemoryScope(body.userId, body.workspace, body.agentScope);
const requestLimit = body.limit === undefined
? undefined
: resolveEffectiveSearchLimit(body.limit, configRouteAdapter.current());
return { scope, requestLimit };
}

function registerSearchRoute(
router: Router,
service: MemoryService,
Expand All @@ -152,10 +160,7 @@ function registerSearchRoute(
router.post('/search', async (req: Request, res: Response) => {
try {
const body = parseSearchBody(req.body);
const scope = toMemoryScope(body.userId, body.workspace, body.agentScope);
const requestLimit = body.limit === undefined
? undefined
: resolveEffectiveSearchLimit(body.limit, configRouteAdapter.current());
const { scope, requestLimit } = resolveSearchPreamble(body, configRouteAdapter);
const retrievalOptions: { retrievalMode?: typeof body.retrievalMode; tokenBudget?: typeof body.tokenBudget; skipRepairLoop?: boolean } = {
retrievalMode: body.retrievalMode,
tokenBudget: body.tokenBudget,
Expand Down Expand Up @@ -196,10 +201,7 @@ function registerFastSearchRoute(
router.post('/search/fast', async (req: Request, res: Response) => {
try {
const body = parseSearchBody(req.body);
const scope = toMemoryScope(body.userId, body.workspace, body.agentScope);
const requestLimit = body.limit === undefined
? undefined
: resolveEffectiveSearchLimit(body.limit, configRouteAdapter.current());
const { scope, requestLimit } = resolveSearchPreamble(body, configRouteAdapter);
const result = scope.kind === 'workspace'
? await service.workspaceSearch(scope.userId, body.query, body.workspace!, {
agentScope: scope.agentScope,
Expand Down
134 changes: 33 additions & 101 deletions src/services/__tests__/search-pipeline-runtime-config.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,17 @@ vi.mock('../conciseness-preference.js', () => ({

const { runSearchPipelineWithTrace, generateLinks } = await import('../search-pipeline.js');

function twoLowSimilarityResults() {
return [
createSearchResult({ id: 'memory-1', score: 0.4, similarity: 0.4 }),
createSearchResult({ id: 'memory-2', score: 0.39, similarity: 0.39 }),
];
}

function createVectorRepo(results: ReturnType<typeof createSearchResult>[]) {
return { searchSimilar: vi.fn().mockResolvedValue(results) } as any;
}

describe('runSearchPipelineWithTrace runtime config', () => {
beforeEach(() => {
vi.clearAllMocks();
Expand All @@ -115,28 +126,11 @@ describe('runSearchPipelineWithTrace runtime config', () => {
});

it('uses runtime config to disable cross-encoder reranking', async () => {
const initialResults = [
createSearchResult({ id: 'memory-1', score: 0.4, similarity: 0.4 }),
createSearchResult({ id: 'memory-2', score: 0.39, similarity: 0.39 }),
];
const repo = {
searchSimilar: vi.fn().mockResolvedValue(initialResults),
} as any;

const initialResults = twoLowSimilarityResults();
const result = await runSearchPipelineWithTrace(
repo,
null,
'user-1',
'runtime config query',
2,
undefined,
undefined,
{
runtimeConfig: {
...mockConfig,
crossEncoderEnabled: false,
} as any,
},
createVectorRepo(initialResults), null, 'user-1', 'runtime config query', 2,
undefined, undefined,
{ runtimeConfig: { ...mockConfig, crossEncoderEnabled: false } as any },
);

expect(result.filtered).toHaveLength(2);
Expand All @@ -145,122 +139,60 @@ describe('runSearchPipelineWithTrace runtime config', () => {

it('uses runtime config to enable cross-encoder reranking even when module config disables it', async () => {
mockConfig.crossEncoderEnabled = false;
const initialResults = [
createSearchResult({ id: 'memory-1', score: 0.4, similarity: 0.4 }),
createSearchResult({ id: 'memory-2', score: 0.39, similarity: 0.39 }),
];
const initialResults = twoLowSimilarityResults();
const rerankedResults = [...initialResults].reverse();
mockRerankCandidates.mockResolvedValue(rerankedResults);
const repo = {
searchSimilar: vi.fn().mockResolvedValue(initialResults),
} as any;

const result = await runSearchPipelineWithTrace(
repo,
null,
'user-1',
'runtime config rerank query',
2,
undefined,
undefined,
{
runtimeConfig: {
...mockConfig,
crossEncoderEnabled: true,
} as any,
},
createVectorRepo(initialResults), null, 'user-1', 'runtime config rerank query', 2,
undefined, undefined,
{ runtimeConfig: { ...mockConfig, crossEncoderEnabled: true } as any },
);

expect(result.filtered).toEqual(rerankedResults);
expect(mockRerankCandidates).toHaveBeenCalledWith(
'runtime config rerank query',
initialResults,
{
crossEncoderModel: 'module-cross-encoder',
crossEncoderDtype: 'q8',
},
{ crossEncoderModel: 'module-cross-encoder', crossEncoderDtype: 'q8' },
);
});

it('uses runtime config to enable agentic retrieval even when module config disables it', async () => {
const initialResults = [
createSearchResult({ id: 'memory-1', score: 0.4, similarity: 0.4 }),
createSearchResult({ id: 'memory-2', score: 0.39, similarity: 0.39 }),
];
const repo = {
searchSimilar: vi.fn().mockResolvedValue(initialResults),
} as any;
const initialResults = twoLowSimilarityResults();
const agentic = await import('../agentic-retrieval.js');
vi.mocked(agentic.applyAgenticRetrieval).mockResolvedValue({
memories: initialResults,
triggered: false,
subQueries: [],
reason: 'strong-initial-results',
memories: initialResults, triggered: false, subQueries: [], reason: 'strong-initial-results',
});

await runSearchPipelineWithTrace(
repo,
null,
'user-1',
'runtime config agentic query',
2,
undefined,
undefined,
{
runtimeConfig: {
...mockConfig,
agenticRetrievalEnabled: true,
crossEncoderEnabled: false,
} as any,
},
createVectorRepo(initialResults), null, 'user-1', 'runtime config agentic query', 2,
undefined, undefined,
{ runtimeConfig: { ...mockConfig, agenticRetrievalEnabled: true, crossEncoderEnabled: false } as any },
);

expect(agentic.applyAgenticRetrieval).toHaveBeenCalled();
});

it('threads runtime reranker model and dtype through rerank and trace metadata', async () => {
const initialResults = [
createSearchResult({ id: 'memory-1', score: 0.4, similarity: 0.4 }),
createSearchResult({ id: 'memory-2', score: 0.39, similarity: 0.39 }),
];
const initialResults = twoLowSimilarityResults();
const rerankedResults = [...initialResults].reverse();
mockRerankCandidates.mockResolvedValue(rerankedResults);
const repo = {
searchSimilar: vi.fn().mockResolvedValue(initialResults),
} as any;

const runtimeConfig = {
...mockConfig,
crossEncoderModel: 'runtime-cross-encoder',
crossEncoderDtype: 'fp16',
...mockConfig, crossEncoderModel: 'runtime-cross-encoder', crossEncoderDtype: 'fp16',
} as any;

await runSearchPipelineWithTrace(
repo,
null,
'user-1',
'runtime config query',
2,
undefined,
undefined,
{ runtimeConfig },
createVectorRepo(initialResults), null, 'user-1', 'runtime config query', 2,
undefined, undefined, { runtimeConfig },
);

expect(mockRerankCandidates).toHaveBeenCalledWith(
'runtime config query',
initialResults,
{
crossEncoderModel: 'runtime-cross-encoder',
crossEncoderDtype: 'fp16',
},
'runtime config query', initialResults,
{ crossEncoderModel: 'runtime-cross-encoder', crossEncoderDtype: 'fp16' },
);
expect(mockTraceStage).toHaveBeenCalledWith(
'cross-encoder',
rerankedResults,
{
model: 'runtime-cross-encoder',
dtype: 'fp16',
},
'cross-encoder', rerankedResults,
{ model: 'runtime-cross-encoder', dtype: 'fp16' },
);
});

Expand Down