From 0ee1ec00303da6e4ed64da9caea955340138c5d0 Mon Sep 17 00:00:00 2001 From: Cristina Poncela Cubeiro <140309543+cristinaponcela@users.noreply.github.com> Date: Mon, 27 Jul 2026 16:12:43 +0200 Subject: [PATCH] rename: may not be index --- .../agent/src/harness/session/repo-utils.ts | 6 ++-- .../src/harness/session/search-backend.ts | 25 ++++++++++++++ .../agent/src/harness/session/search-index.ts | 32 ----------------- packages/agent/src/harness/session/session.ts | 20 +++++------ packages/agent/src/harness/types.ts | 18 +++++----- packages/agent/src/index.ts | 2 +- .../agent/test/harness/sqlite-node.test.ts | 20 +++++------ .../storage/sqlite-node/src/sqlite/index.ts | 2 +- .../storage/sqlite-node/src/sqlite/repo.ts | 34 +++++++++---------- .../{search-index.ts => search-backend.ts} | 14 ++++---- .../storage/sqlite-node/src/sqlite/types.ts | 6 ++-- 11 files changed, 86 insertions(+), 93 deletions(-) create mode 100644 packages/agent/src/harness/session/search-backend.ts delete mode 100644 packages/agent/src/harness/session/search-index.ts rename packages/storage/sqlite-node/src/sqlite/{search-index.ts => search-backend.ts} (85%) diff --git a/packages/agent/src/harness/session/repo-utils.ts b/packages/agent/src/harness/session/repo-utils.ts index 546c8449d..471b5d988 100644 --- a/packages/agent/src/harness/session/repo-utils.ts +++ b/packages/agent/src/harness/session/repo-utils.ts @@ -5,7 +5,7 @@ import { SessionError, type SessionMetadata, type SessionSearchHit, - type SessionSearchIndexWriter, + type SessionSearchWriter, type SessionStorage, type SessionTreeEntry, } from "../types.ts"; @@ -21,9 +21,9 @@ export function createTimestamp(): string { export function toSession( storage: SessionStorage, - searchIndexWriter?: SessionSearchIndexWriter, + searchWriter?: SessionSearchWriter, ): Session { - return new Session(storage, {}, searchIndexWriter); + return new Session(storage, {}, searchWriter); } export function findSessionEntryMatches( diff --git a/packages/agent/src/harness/session/search-backend.ts b/packages/agent/src/harness/session/search-backend.ts new file mode 100644 index 000000000..4726d3e34 --- /dev/null +++ b/packages/agent/src/harness/session/search-backend.ts @@ -0,0 +1,25 @@ +import type { SessionMetadata, SessionSearchRecord, SessionSearchWriter, SessionTreeEntry } from "../types.ts"; + +export function toSessionSearchRecord( + metadata: TMetadata, + entry: SessionTreeEntry, +): SessionSearchRecord { + return { metadata, entry }; +} + +/** Delivers each lifecycle event to its targets, like Go's io.MultiWriter. */ +export class FanoutSessionSearchWriter implements SessionSearchWriter { + private readonly writers: readonly SessionSearchWriter[]; + + constructor(...writers: SessionSearchWriter[]) { + this.writers = writers; + } + + async upsert(record: SessionSearchRecord): Promise { + for (const writer of this.writers) await writer.upsert(record); + } + + async remove(record: SessionSearchRecord): Promise { + for (const writer of this.writers) await writer.remove(record); + } +} diff --git a/packages/agent/src/harness/session/search-index.ts b/packages/agent/src/harness/session/search-index.ts deleted file mode 100644 index 14e1ba4e6..000000000 --- a/packages/agent/src/harness/session/search-index.ts +++ /dev/null @@ -1,32 +0,0 @@ -import type { - SessionMetadata, - SessionSearchIndexRecord, - SessionSearchIndexWriter, - SessionTreeEntry, -} from "../types.ts"; - -export function toSessionSearchIndexRecord( - metadata: TMetadata, - entry: SessionTreeEntry, -): SessionSearchIndexRecord { - return { metadata, entry }; -} - -/** Delivers each lifecycle event to its targets, like Go's io.MultiWriter. */ -export class MultiSessionSearchIndexWriter - implements SessionSearchIndexWriter -{ - private readonly writers: readonly SessionSearchIndexWriter[]; - - constructor(...writers: SessionSearchIndexWriter[]) { - this.writers = writers; - } - - async write(record: SessionSearchIndexRecord): Promise { - for (const writer of this.writers) await writer.write(record); - } - - async remove(record: SessionSearchIndexRecord): Promise { - for (const writer of this.writers) await writer.remove(record); - } -} diff --git a/packages/agent/src/harness/session/session.ts b/packages/agent/src/harness/session/session.ts index 330b8166f..1c08845ef 100644 --- a/packages/agent/src/harness/session/session.ts +++ b/packages/agent/src/harness/session/session.ts @@ -14,14 +14,14 @@ import type { SessionEntryCursorOptions, SessionInfoEntry, SessionMetadata, - SessionSearchIndexWriter, + SessionSearchWriter, SessionStats, SessionStorage, SessionTreeEntry, ThinkingLevelChangeEntry, } from "../types.ts"; import { SessionError } from "../types.ts"; -import { toSessionSearchIndexRecord } from "./search-index.ts"; +import { toSessionSearchRecord } from "./search-backend.ts"; export type ContextEntryTransform = (entries: readonly SessionTreeEntry[]) => readonly SessionTreeEntry[]; @@ -152,16 +152,16 @@ export function buildSessionContext( export class Session { private storage: SessionStorage; private contextBuildOptions: SessionContextBuildOptions; - private searchIndexWriter: SessionSearchIndexWriter | undefined; + private searchWriter: SessionSearchWriter | undefined; constructor( storage: SessionStorage, contextBuildOptions: SessionContextBuildOptions = {}, - searchIndexWriter?: SessionSearchIndexWriter, + searchWriter?: SessionSearchWriter, ) { this.storage = storage; this.contextBuildOptions = contextBuildOptions; - this.searchIndexWriter = searchIndexWriter; + this.searchWriter = searchWriter; } getMetadata(): Promise { @@ -219,15 +219,15 @@ export class Session { return this.storage.getSessionName(); } - private async writeSearchIndexRecord(entry: SessionTreeEntry): Promise { - if (this.searchIndexWriter) { - await this.searchIndexWriter.write(toSessionSearchIndexRecord(await this.storage.getMetadata(), entry)); + private async upsertSearchRecord(entry: SessionTreeEntry): Promise { + if (this.searchWriter) { + await this.searchWriter.upsert(toSessionSearchRecord(await this.storage.getMetadata(), entry)); } } private async appendTypedEntry(entry: TEntry): Promise { await this.storage.appendEntry(entry); - await this.writeSearchIndexRecord(entry); + await this.upsertSearchRecord(entry); return entry.id; } @@ -358,7 +358,7 @@ export class Session { throw new SessionError("not_found", `Entry ${entryId} not found`); } const leafEntry = await this.storage.setLeafId(entryId); - await this.writeSearchIndexRecord(leafEntry); + await this.upsertSearchRecord(leafEntry); if (!summary) return undefined; return this.appendTypedEntry({ type: "branch_summary", diff --git a/packages/agent/src/harness/types.ts b/packages/agent/src/harness/types.ts index b4c3162e9..40e1c3743 100644 --- a/packages/agent/src/harness/types.ts +++ b/packages/agent/src/harness/types.ts @@ -532,21 +532,21 @@ export interface SessionSearchHit { +/** A persisted session entry delivered to a search backend. */ +export interface SessionSearchRecord { metadata: TMetadata; entry: SessionTreeEntry; } -/** Receives persisted-entry lifecycle events for search indexing. */ -export interface SessionSearchIndexWriter { - write(record: SessionSearchIndexRecord): Promise; - remove(record: SessionSearchIndexRecord): Promise; +/** Receives persisted-entry lifecycle events for a search backend. */ +export interface SessionSearchWriter { + upsert(record: SessionSearchRecord): Promise; + remove(record: SessionSearchRecord): Promise; } -/** A search index that can both receive session entries and query them. */ -export interface SessionSearchIndex - extends SessionSearchIndexWriter { +/** A search backend that can receive session entries and query them. */ +export interface SessionSearchBackend + extends SessionSearchWriter { search(options: SessionSearchOptions): Promise[]>; } diff --git a/packages/agent/src/index.ts b/packages/agent/src/index.ts index d6bd648e8..f7f3c23ac 100644 --- a/packages/agent/src/index.ts +++ b/packages/agent/src/index.ts @@ -34,7 +34,7 @@ export * from "./harness/session/jsonl-storage.ts"; export * from "./harness/session/memory-repo.ts"; export * from "./harness/session/memory-storage.ts"; export * from "./harness/session/repo-utils.ts"; -export * from "./harness/session/search-index.ts"; +export * from "./harness/session/search-backend.ts"; export * from "./harness/session/session.ts"; export * from "./harness/skills.ts"; export * from "./harness/system-prompt.ts"; diff --git a/packages/agent/test/harness/sqlite-node.test.ts b/packages/agent/test/harness/sqlite-node.test.ts index a94ad61b5..c1649b2d5 100644 --- a/packages/agent/test/harness/sqlite-node.test.ts +++ b/packages/agent/test/harness/sqlite-node.test.ts @@ -7,10 +7,10 @@ import { } from "../../../storage/sqlite-node/src/index.ts"; import { NodeExecutionEnv } from "../../src/harness/env/nodejs.ts"; import type { + SessionSearchBackend, SessionSearchHit, - SessionSearchIndex, - SessionSearchIndexRecord, SessionSearchOptions, + SessionSearchRecord, } from "../../src/harness/types.ts"; import { createTempDir, createUserMessage } from "./session-test-utils.ts"; @@ -30,14 +30,14 @@ describe("sqlite-node adapter", () => { } }); - it("delegates lifecycle events and queries to an injected search index", async () => { + it("delegates lifecycle events and queries to an injected search backend", async () => { const root = createTempDir(); - const writes: SessionSearchIndexRecord[] = []; - const removals: SessionSearchIndexRecord[] = []; + const upserts: SessionSearchRecord[] = []; + const removals: SessionSearchRecord[] = []; const searches: SessionSearchOptions[] = []; - const index: SessionSearchIndex = { - async write(record) { - writes.push(record); + const index: SessionSearchBackend = { + async upsert(record) { + upserts.push(record); }, async remove(record) { removals.push(record); @@ -51,13 +51,13 @@ describe("sqlite-node adapter", () => { env: new NodeExecutionEnv({ cwd: root }), sqlite: createNodeSqliteFactory(), databasePath: join(root, "sessions.sqlite"), - searchIndexFactory: () => index, + searchBackendFactory: () => index, }); const session = await repo.create({ cwd: root, id: "session-1" }); const metadata = await session.getMetadata(); const entryId = await session.appendMessage(createUserMessage("indexed remotely")); - expect(writes).toEqual([expect.objectContaining({ metadata, entry: expect.objectContaining({ id: entryId }) })]); + expect(upserts).toEqual([expect.objectContaining({ metadata, entry: expect.objectContaining({ id: entryId }) })]); await expect(repo.search({ text: "indexed remotely" })).resolves.toEqual([]); expect(searches).toEqual([{ text: "indexed remotely" }]); await repo.delete(metadata); diff --git a/packages/storage/sqlite-node/src/sqlite/index.ts b/packages/storage/sqlite-node/src/sqlite/index.ts index 583bb49ef..330150c7c 100644 --- a/packages/storage/sqlite-node/src/sqlite/index.ts +++ b/packages/storage/sqlite-node/src/sqlite/index.ts @@ -1,5 +1,5 @@ export * from "./migrations.ts"; export * from "./repo.ts"; -export * from "./search-index.ts"; +export * from "./search-backend.ts"; export * from "./storage/index.ts"; export * from "./types.ts"; diff --git a/packages/storage/sqlite-node/src/sqlite/repo.ts b/packages/storage/sqlite-node/src/sqlite/repo.ts index 4a2140ec7..bd5d9ecc3 100644 --- a/packages/storage/sqlite-node/src/sqlite/repo.ts +++ b/packages/storage/sqlite-node/src/sqlite/repo.ts @@ -11,10 +11,10 @@ import { getFileSystemResultOrThrow, SessionError, toSession, - toSessionSearchIndexRecord, + toSessionSearchRecord, } from "@earendil-works/pi-agent-core"; import { applyMigrations } from "./migrations.ts"; -import { SqliteSessionSearchIndex } from "./search-index.ts"; +import { SqliteSessionSearchBackend } from "./search-backend.ts"; import { SqliteSessionStorage } from "./storage/index.ts"; import { decodeEntry, type SessionEntryRow } from "./storage/session-entries.ts"; import { rowToMetadata, type SessionRow } from "./storage/sessions.ts"; @@ -26,7 +26,7 @@ import type { SqliteSessionMetadata, SqliteSessionRepoApi, SqliteSessionRepoEnv, - SqliteSessionSearchIndexFactory, + SqliteSessionSearchBackendFactory, } from "./types.ts"; function getParentPath(path: string): string { @@ -54,20 +54,20 @@ export class SqliteSessionRepo implements SqliteSessionRepoApi { private readonly env: SqliteSessionRepoEnv; private readonly sqlite: SqliteDatabaseFactory; private readonly databasePathInput: string; - private readonly searchIndexFactory: SqliteSessionSearchIndexFactory; + private readonly searchBackendFactory: SqliteSessionSearchBackendFactory; private databasePath: string | undefined; constructor(options: { env: SqliteSessionRepoEnv; sqlite: SqliteDatabaseFactory; databasePath: string; - searchIndexFactory?: SqliteSessionSearchIndexFactory; + searchBackendFactory?: SqliteSessionSearchBackendFactory; }) { this.env = options.env; this.sqlite = options.sqlite; this.databasePathInput = options.databasePath; - this.searchIndexFactory = - options.searchIndexFactory ?? (({ db, databasePath }) => new SqliteSessionSearchIndex(db, databasePath)); + this.searchBackendFactory = + options.searchBackendFactory ?? (({ db, databasePath }) => new SqliteSessionSearchBackend(db, databasePath)); } private async getDatabasePath(): Promise { @@ -80,8 +80,8 @@ export class SqliteSessionRepo implements SqliteSessionRepoApi { return this.databasePath; } - private async createSearchIndex(db: SqliteDatabase) { - return this.searchIndexFactory({ db, databasePath: await this.getDatabasePath() }); + private async createSearchBackend(db: SqliteDatabase) { + return this.searchBackendFactory({ db, databasePath: await this.getDatabasePath() }); } private async ensureDatabaseDir(): Promise { @@ -116,7 +116,7 @@ export class SqliteSessionRepo implements SqliteSessionRepoApi { parentSessionId: options.parentSessionId, metadata: options.metadata, }); - return toSession(storage, await this.createSearchIndex(db)); + return toSession(storage, await this.createSearchBackend(db)); } catch (error) { await db.close(); throw error; @@ -132,7 +132,7 @@ export class SqliteSessionRepo implements SqliteSessionRepoApi { const db = await this.openDatabase(); try { const storage = await SqliteSessionStorage.open(db, metadata); - return toSession(storage, await this.createSearchIndex(db)); + return toSession(storage, await this.createSearchBackend(db)); } catch (error) { await db.close(); throw error; @@ -168,7 +168,7 @@ export class SqliteSessionRepo implements SqliteSessionRepoApi { if (!getFileSystemResultOrThrow(await this.env.exists(path), `Failed to check database ${path}`)) return []; const db = await this.openDatabase(); try { - return await (await this.createSearchIndex(db)).search(options); + return await (await this.createSearchBackend(db)).search(options); } finally { await db.close(); } @@ -178,14 +178,14 @@ export class SqliteSessionRepo implements SqliteSessionRepoApi { const db = await this.openDatabase(); try { await db.transaction(async () => { - const searchIndex = await this.createSearchIndex(db); + const searchBackend = await this.createSearchBackend(db); const entries = await db .prepare( "SELECT rowid, session_id, id, entry_seq, parent_id, type, timestamp, payload FROM session_entries WHERE session_id = ?", ) .all(metadata.id); for (const row of entries) { - await searchIndex.remove(toSessionSearchIndexRecord(metadata, decodeEntry(row))); + await searchBackend.remove(toSessionSearchRecord(metadata, decodeEntry(row))); } await db.prepare("DELETE FROM branch_entries WHERE session_id = ?").run(metadata.id); await db.prepare("DELETE FROM session_entries WHERE session_id = ?").run(metadata.id); @@ -222,13 +222,13 @@ export class SqliteSessionRepo implements SqliteSessionRepoApi { parentSessionId: options.parentSessionId ?? sourceMetadata.id, metadata: options.metadata ?? sourceMetadata.metadata, }); - const searchIndex = await this.createSearchIndex(db); + const searchBackend = await this.createSearchBackend(db); const metadata = await storage.getMetadata(); for (const entry of forkedEntries) { await storage.appendEntry(entry); - await searchIndex.write(toSessionSearchIndexRecord(metadata, entry)); + await searchBackend.upsert(toSessionSearchRecord(metadata, entry)); } - return toSession(storage, searchIndex); + return toSession(storage, searchBackend); } catch (error) { await db.close(); throw error; diff --git a/packages/storage/sqlite-node/src/sqlite/search-index.ts b/packages/storage/sqlite-node/src/sqlite/search-backend.ts similarity index 85% rename from packages/storage/sqlite-node/src/sqlite/search-index.ts rename to packages/storage/sqlite-node/src/sqlite/search-backend.ts index 3a78c2eb7..b30599de6 100644 --- a/packages/storage/sqlite-node/src/sqlite/search-index.ts +++ b/packages/storage/sqlite-node/src/sqlite/search-backend.ts @@ -1,15 +1,15 @@ import type { + SessionSearchBackend, SessionSearchHit, - SessionSearchIndex, - SessionSearchIndexRecord, SessionSearchOptions, + SessionSearchRecord, } from "@earendil-works/pi-agent-core"; import { SessionError } from "@earendil-works/pi-agent-core"; import { rowToMetadata, type SessionRow } from "./storage/sessions.ts"; import type { SqliteDatabase, SqliteSessionMetadata } from "./types.ts"; -/** SQLite-backed search index for persisted session entries. */ -export class SqliteSessionSearchIndex implements SessionSearchIndex { +/** SQLite-backed search backend for persisted session entries. */ +export class SqliteSessionSearchBackend implements SessionSearchBackend { private readonly db: SqliteDatabase; private readonly databasePath: string; @@ -18,19 +18,19 @@ export class SqliteSessionSearchIndex implements SessionSearchIndex): Promise { + async upsert(record: SessionSearchRecord): Promise { const row = await this.db .prepare("SELECT rowid FROM session_entries WHERE session_id = ? AND id = ?") .get<{ rowid: number }>(record.metadata.id, record.entry.id); if (!row) { - throw new SessionError("not_found", `Entry ${record.entry.id} not found for search indexing`); + throw new SessionError("not_found", `Entry ${record.entry.id} not found for search persistence`); } await this.db .prepare("INSERT INTO session_entry_search_fts (rowid, search_text) VALUES (?, ?)") .run(row.rowid, JSON.stringify(record.entry)); } - async remove(record: SessionSearchIndexRecord): Promise { + async remove(record: SessionSearchRecord): Promise { const row = await this.db .prepare("SELECT rowid FROM session_entries WHERE session_id = ? AND id = ?") .get<{ rowid: number }>(record.metadata.id, record.entry.id); diff --git a/packages/storage/sqlite-node/src/sqlite/types.ts b/packages/storage/sqlite-node/src/sqlite/types.ts index 1cf56ba1b..4d1bb4dd3 100644 --- a/packages/storage/sqlite-node/src/sqlite/types.ts +++ b/packages/storage/sqlite-node/src/sqlite/types.ts @@ -3,7 +3,7 @@ import type { SessionCreateOptions, SessionMetadata, SessionRepo, - SessionSearchIndex, + SessionSearchBackend, } from "@earendil-works/pi-agent-core"; /** Result of a prepared SQLite statement execution. */ @@ -60,7 +60,7 @@ export interface SqliteSessionRepoApi export type SqliteSessionRepoEnv = Pick; -export type SqliteSessionSearchIndexFactory = (options: { +export type SqliteSessionSearchBackendFactory = (options: { db: SqliteDatabase; databasePath: string; -}) => SessionSearchIndex; +}) => SessionSearchBackend;