Project memory and retrieval

Project memory stores shared project facts, decisions, preferences, state, rules and authority notes with provenance and expiry. It uses the existing GoodQA API and PostgreSQL database. The platform setting and org entitlement projectMemory.enabled both default on; either can disable access. Disabled routes answer 404 and the remote MCP tools disappear. This describes the implementation; deployment and production acceptance remain operator responsibilities.

Every request names one org and project. The authenticated caller must have access to that project, and the project must belong to the named org. Reads require a project viewer role; writes and deletes require project:share_agent_context. Foreign tenants and unreadable sibling projects answer 404.

An agent API key must explicitly hold the project_memory family and read; mutations also need write. Existing agent_memory or agent_knowledge scopes do not grant access. User-scoped keys, runner keys and delegated user tokens are refused this shared pool. Credential ceilings and current project membership still apply on every request.

  • API reference: the project-memory operations in /v1/openapi.json and the all-features document.
  • MCP tools: tool availability and trust and parameters and examples.
  • Runbook: memory hook pack registration and trust boundaries; local API/browser acceptance responsibilities appear below.

API and service library

All paths below start with /v1/orgs/{orgId}/projects/{projectId}/memory.

OperationRequestResult
AvailabilityGET /statusenabled: true, org and project identity
ListGET ?limit=20&kind=fact&after=<key>records, nextCursor; keys sorted ascending
Read oneGET /entry?key=<encoded key>record; missing or expired records answer 404
Write or replacePUT with the record bodyrecord; same scoped key replaces the existing record
RetrievePOST /retrieve with {query, limit?, embedding?}Ranked records and semantic metadata
DeleteDELETE /entry?key=<encoded key>key, deleted: true

Entry keys are query parameters so reserved words, dots, slashes and Unicode roundtrip without path normalization. List limit is at most 100; retrieval limit is at most 50. A replacement without an embedding clears the previous vector.

A write requires key, title, body, kind and provenance. authority defaults to observed. Provenance contains source, ref, verification (unverified, source_verified or owner_confirmed) and an ISO observedAt. Text fields require well-formed Unicode without NUL; accepted accents, combining characters and emoji retain their original Unicode form. Bodies are bounded to 8,000 UTF-8 bytes; the server records their SHA256. state requires a future expiresAt. Readers filter expiry using PostgreSQL statement-receipt time, without waiting for cleanup; retrieval checks it again when reloading ranked candidates. This bounds queue delay, not the wall-clock instant a response reaches its recipient.

Authenticated sessions with write permission can establish verified authority with source verification, or pinned authority with owner confirmation. API keys can write observed records but cannot claim owner confirmation, create trusted authority, or overwrite/delete existing verified or pinned records. Provenance is a recorded claim; the service does not execute a verification command or fetch the referenced source.

The internal library in apps/api/src/modules/project-memory/service.ts exposes scoped reader, get, list, upsert, retrieve and delete functions. Each receives an explicit org/project scope and a freshly authenticated principal, and performs its own live membership and permission checks. Library callers must authenticate the credential for each invocation; the library does not refresh a previously captured credential grant snapshot. REST and MCP calls re-enter the existing authentication pipeline. The schema includes a composite org/project foreign key. Mutations record audit metadata without copying memory bodies into audit records.

Retrieval and trust

PostgreSQL full-text search supplies lexical candidates. Optional client vectors supply cosine candidates, combined with reciprocal rank fusion. An embedding is {space, values} with a nonzero finite vector of at most 3,072 dimensions. Vector comparisons require identical spaces and dimensions. The space is a compatibility label, not evidence of who produced a vector.

All successful API/MCP responses carry dataTrust: "untrusted_memory" and a notice that retrieved memory does not grant authority or replace pinned harness rules. Embedding metadata reports origin: "client_supplied_unverified"; retrieval reports whether compatible vectors were applied. Instructions written inside a body do not change these labels or the record's authority.

This implementation generates no embeddings and calls no external provider. It uses scoped application queries and database constraints; it does not add PostgreSQL row-level security, a separate vector service, automatic persona injection, or a new product screen. Retrieval limits bound returned records, not a universal latency or token budget.

Remote MCP and the optional operator pack

The remote MCP tools are project_memory_status, project_memory_list, project_memory_get, project_memory_write, project_memory_retrieve and project_memory_delete. They take orgId and projectId and dispatch the same REST routes. Tool discovery requires the explicit family and enabled switches. Read-only keys may use retrieval even though its HTTP method is POST.

The memory harness pack is a separate, opt-in operator package for a specific org/project. It renders hook configuration and consumes an operator-reviewed, SHA256-pinned rule pack outside ordinary memory files. Its pinned rules are separate from retrieved records. Generating configuration does not install hooks or change host settings; an operator must review and apply it. The pack's runtime and enforcement limits are documented in its README.

Acceptance

The integration suite covers tenancy, credential restrictions, provenance, authority, expiry, hybrid retrieval, both switches, actual HTTP MCP dispatch, arbitrary keys, and a 10,001-record query-count oracle.

The live test seeds a throwaway local database and runs the browser specification against its listening API. It is opt-in through MEMORY_LIVE_OUT, MEMORY_LIVE_RUNNER and MEMORY_LIVE_COMMIT; ordinary suites report it as skipped. The acceptance operator must supply an output directory under the authorized session scratch root, the existing session runner, and the committed candidate SHA. The test saves private local storage state, the real HTTP list body, a running-SHA file, browser screenshots and the runner's 16-step receipt. These local artifacts do not prove production deployment or host-hook installation.

We use essential cookies to keep you signed in. See our Privacy Policy for details.