@mahin14m/project-context-memory
v0.1.4
Published
Long-term project memory for AI agents backed by PostgreSQL and pgvector.
Maintainers
Readme
@mahin14m/project-context-memory
Long-term project memory for AI agents with PostgreSQL as the source of truth and pgvector for semantic retrieval.
The package is library-first. Summaries are the main retrieval unit. Raw prompt, response, and analysis logs are stored separately so you can keep provenance without polluting semantic search.
Installation
npm install @mahin14m/project-context-memoryQuick Start
import {
BaseEmbeddingProvider,
ProjectContextMemory
} from "@mahin14m/project-context-memory";
class DemoEmbeddingProvider extends BaseEmbeddingProvider {
constructor() {
super({
model: "text-embedding-3-small",
dimensions: 1536
});
}
embedDocuments(texts: string[]): Promise<number[][]> {
return Promise.resolve(
texts.map((text) =>
Array.from({ length: 1536 }, (_, index) => (text.length + index) % 17 / 100)
)
);
}
embedQuery(text: string): Promise<number[]> {
return Promise.resolve(
Array.from({ length: 1536 }, (_, index) => (text.length + index) % 17 / 100)
);
}
}
const memory = new ProjectContextMemory({
postgresUrl: process.env.DATABASE_URL!,
embeddingProvider: new DemoEmbeddingProvider()
});
await memory.store({
projectName: "billing-system",
featureName: "subscription-renewal",
taskType: "analysis",
summary: "Renewal flow validates trial expiration before invoice generation.",
decision: "Validate before invoice creation.",
outcome: "Prevents invalid renewals.",
tags: ["billing", "renewal", "validation"]
});
const results = await memory.search({
query: "How did we handle renewal validation before invoice creation?",
projectName: "billing-system",
limit: 5
});
console.log(results);
await memory.close();PostgreSQL Setup
Create a PostgreSQL database and make sure the application user can create extensions and tables in the target schema.
CREATE DATABASE project_memory;Use DATABASE_URL in standard PostgreSQL form:
export DATABASE_URL="postgres://app_user:app_password@localhost:5432/project_memory"pgvector Setup
The package enables pgvector during setup(), but PostgreSQL must have the extension available.
CREATE EXTENSION IF NOT EXISTS vector;On first use, the library creates:
memory_entriesmemory_raw_logs- metadata tables needed to track embedding dimensions safely
It also adds indexes for project_name, feature_name, task_type, created_at, source_thread_id, tag overlap, and raw-log lookup.
Public API
ProjectContextMemory
Main entrypoint for npm consumers.
new ProjectContextMemory({
postgresUrl,
embeddingProvider,
embedding: {
model: "text-embedding-3-small",
dimensions: 1536
},
schemaName: "public",
autoMigrate: true
});Primary methods:
store(input)stores a summary memory and optional raw logssearch(input)retrieves the top ranked summary memoriessetup()initializes the schema eagerlyclose()closes the ownedpgpool
BaseEmbeddingProvider
Extend BaseEmbeddingProvider to plug in your own embedding backend.
import { BaseEmbeddingProvider } from "@mahin14m/project-context-memory";
class MyEmbeddingProvider extends BaseEmbeddingProvider {
constructor() {
super({
model: "internal-embedding-model",
dimensions: 1536
});
}
embedDocuments(texts: string[]): Promise<number[][]> {
return Promise.resolve(texts.map(() => Array.from({ length: 1536 }, () => 0.01)));
}
embedQuery(text: string): Promise<number[]> {
return Promise.resolve(Array.from({ length: 1536 }, () => text.length / 1000));
}
}MemoryRetriever
search() uses pgvector similarity and supports:
projectNamefeatureNametaskTypetagssourceThreadIdlimitminScore
Ranking combines:
- vector similarity
- exact project boost
- exact feature boost
- exact task-type boost
- recency boost from
updated_at
Memory Model
memory_entries
Primary semantic retrieval table:
idproject_namefeature_nametask_typesummarydecisionoutcometagsfile_pathssource_thread_idcreated_atupdated_atembedding
memory_raw_logs
Secondary raw interaction table:
idmemory_entry_idpromptresponseanalysiscreated_at
Raw Logs and Summaries
summaryis the canonical retrieval text and the only field embedded by default- raw logs are stored separately in
memory_raw_logs - duplicate memories are merged by normalized
project_name + feature_name + task_type + summary
Vector Index Guidance
For larger datasets, add an approximate vector index after the table has enough rows:
CREATE INDEX IF NOT EXISTS memory_entries_embedding_ivfflat_idx
ON public.memory_entries
USING ivfflat (embedding vector_cosine_ops)
WITH (lists = 100);
ANALYZE public.memory_entries;For read-heavy workloads on newer pgvector versions, consider hnsw with vector_cosine_ops.
LangGraph Example
The package does not depend on LangGraph directly. Use the helpers inside your node functions.
import {
ProjectContextMemory,
retrieveRelevantMemories,
saveInteractionAsMemory
} from "@mahin14m/project-context-memory";
async function retrieveMemoryNode(state: {
question: string;
projectName: string;
featureName: string;
threadId: string;
}, memory: ProjectContextMemory) {
const memories = await retrieveRelevantMemories(memory, {
query: state.question,
projectName: state.projectName,
featureName: state.featureName,
sourceThreadId: state.threadId,
limit: 5
});
return {
...state,
memories
};
}
async function persistInteractionNode(state: {
question: string;
answer: string;
projectName: string;
featureName: string;
threadId: string;
}, memory: ProjectContextMemory) {
await saveInteractionAsMemory(memory, {
projectName: state.projectName,
featureName: state.featureName,
taskType: "analysis",
summary: `${state.question} -> ${state.answer}`,
sourceThreadId: state.threadId,
prompt: state.question,
response: state.answer
});
return state;
}MCP Middleware Helper
If you are building an MCP server, you can run retrieval, prompt injection, generation, and persistence in one call.
import {
ProjectContextMemory,
runMemoryBackedTurn
} from "@mahin14m/project-context-memory";
async function askWithMemory(memory: ProjectContextMemory, input: {
projectName: string;
featureName: string;
threadId: string;
question: string;
}) {
return runMemoryBackedTurn(
memory,
{
query: input.question,
projectName: input.projectName,
featureName: input.featureName,
taskType: "analysis",
sourceThreadId: input.threadId,
tags: ["mcp", "assistant-turn"]
},
{
generate: async ({ prompt, memories }) => {
const response = memories.length > 0
? "Answered with prior context."
: "Answered without prior context.";
return {
response,
summary: `Answered: ${input.question}`,
analysis: `Prompt length=${prompt.length}`
};
}
}
);
}runMemoryBackedTurn returns:
prompt(what was sent to your model)memoryContext(formatted retrieval context)memories(raw retrieval rows)generated(your model output)writeResult(stored memory entry and optional raw log)
Examples
Consumer-facing examples live in examples/:
basic-store-search.tscodex-agent-starter/custom-embedding-provider.tslanggraph-integration.tsmcp-memory-middleware.tsproject-scoped-retrieval.tsstore-with-raw-logs.ts
Development
npm run typecheck
npm run lint
npm test
npm run buildPublish Checklist
npm run clean
npm run typecheck
npm run lint
npm test
npm run build
npm pack --dry-runCheck that the tarball only contains built output plus README.md and LICENSE.
License
MIT
