JSPM

@mahin14m/project-context-memory

0.1.5
  • ESM via JSPM
  • ES Module Entrypoint
  • Export Map
  • Keywords
  • License
  • Repository URL
  • TypeScript Types
  • README
  • Created
  • Published
  • Downloads 6
  • Score
    100M100P100Q78092F
  • License MIT

Long-term project memory for AI agents backed by PostgreSQL and pgvector.

Package Exports

  • @mahin14m/project-context-memory
  • @mahin14m/project-context-memory/package.json

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-memory

Quick 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_entries
  • memory_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 logs
  • search(input) retrieves the top ranked summary memories
  • setup() initializes the schema eagerly
  • close() closes the owned pg pool

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:

  • projectName
  • featureName
  • taskType
  • tags
  • sourceThreadId
  • limit
  • minScore

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:

  • id
  • project_name
  • feature_name
  • task_type
  • summary
  • decision
  • outcome
  • tags
  • file_paths
  • source_thread_id
  • created_at
  • updated_at
  • embedding

memory_raw_logs

Secondary raw interaction table:

  • id
  • memory_entry_id
  • prompt
  • response
  • analysis
  • created_at

Raw Logs and Summaries

  • summary is 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.ts
  • codex-agent-starter/
  • custom-embedding-provider.ts
  • langgraph-integration.ts
  • mcp-memory-middleware.ts
  • project-scoped-retrieval.ts
  • store-with-raw-logs.ts

Development

npm run typecheck
npm run lint
npm test
npm run build

Publish Checklist

npm run clean
npm run typecheck
npm run lint
npm test
npm run build
npm pack --dry-run

Check that the tarball only contains built output plus README.md and LICENSE.

License

MIT