Context Platform
Documentation

Version Latest

Team memory

Shared decisions, conventions, notes and glossary entries a team edits together, merged by the server, and served to AI tools only after a person reviews and accepts the exact text.

Team memory holds what a team has decided and why: decisions, conventions, notes and glossary entries. People edit an entry together from the Editor, the Agents app or the CLI, offline-capable and without conflicts. Once a person accepts an entry, its text feeds retrieval, so the team's AI tools know it.

Team memory has no console page yet; the first-party apps use it through the API below.

Entries

An entry belongs to a Context space (the space is the team). Entries cannot be created in personal spaces.

FieldMeaning
kinddecision, convention, note or glossary
titleOne line, 1 to 200 characters
statusdraft, accepted, superseded or archived
linksUp to 50 links to a work item (https URL), a source or another entry of the same space
supersedes, supersededByThe entry this one replaces, and the one that replaced it
classificationThe entry's own value, which may only tighten the space's

Status moves draft to accepted, then to superseded or archived; a draft or superseded entry can be archived. An accepted entry can be accepted again as a new revision. Superseded and archived entries are read-only. Only accepted entries are retrievable.

How editing works

The text is a Yjs document (one Y.Text named content, plain strings), which editors such as CodeMirror, Monaco and ProseMirror bind to. Clients send Yjs updates; the server merges them itself and derives the plain text. No client ever supplies the text that retrieval sees.

  • Updates must be causally complete: a client that is behind sends its changes against the server's state vector. An update that refers to something the server lacks is 409 missing_dependency with the server's state vector.
  • Each update has an updateId (8 to 64 characters of A-Za-z0-9_-), so a retry is harmless: the same bytes answer 200 with duplicate: true, other bytes 409 update_id_reused.
  • Only plain text is supported. Formatting, embeds, maps, nested types and subdocuments are refused as 422 unsupported_update.
  • Each Yjs client id is bound to the first author who uses it in an entry; another author using it gets 403 client_id_owned.
  • The edit log is append-only and keeps every update's author and time. Old update bytes are compacted away after KEYSTONE_CONTEXT_MEMORY_COMPACT_AFTER updates; the merged state keeps everything.

Client protocol

  1. GET /api/v1/context/memory/entries/{id} and apply snapshot to a Y.Doc (root getText("content")).
  2. On every local update, post it with a fresh updateId.
  3. On 409 missing_dependency, post the diff against details.stateVector.
  4. To catch up, poll GET .../updates?sinceSeq=<last seen> (apply snapshot when it is returned instead) or send your state vector.
  5. Use a fresh Yjs client id per editing session.

Review and accept

Edits to an accepted entry do not change what retrieval serves: they are pending changes until a person accepts again.

POST /api/v1/context/memory/entries/{id}/accept
{"documentSha256": "<the hash the reviewer was shown>"}
  • documentSha256 is the SHA-256 of the document that would be indexed (title, kind, supersedes, links and text), returned by GET on the entry. If the entry changed since, the answer is 409 document_changed with the current hash.
  • Separation of duties. The creator of the entry, and anyone who changed it since its last accepted revision, cannot accept it (403 context.memory.self_accept). Another member accepts.
  • Accept needs a signed-in person; an API key gets 403 context.person_required.
  • DLP runs at accept at the entry's level: findings are redacted at redact, and drop or block_source refuses the accept (422 dlp_blocked).
  • Accepting an entry that has supersedes pointing at an accepted entry supersedes that entry in the same transaction.

Whether the reviewer actually reads the text is a process question; the hash ensures the text accepted is the text reviewed.

Retrieval

Accepting creates (or refreshes) a source of kind memory for the entry, named Memory: <title>, which indexes the accepted revision only. Drafts are never retrievable. Superseded and archived entries leave retrieval in the transaction that retires them. Chunks carry metadata.memory (entryId, revision, kind, title, acceptedAt), and attribution records list the memory entries and revisions served. Memory sources cannot be created, deleted or reclassified through the source API (409 memory_managed).

Routes

Base path /api/v1/context/memory. Bytes are base64.

MethodPathWhat
GET/entries?spaceId=&status=&kind=&q=&limit=&cursor=List or search a space's entries; spaceId required; snippets pass DLP
POST/entriesCreate an empty draft: {spaceId, kind, title, links?, supersedes?, classification?}
GET/entries/{id}The entry, its DLP-gated text, projectionSha256, documentSha256, stateVector, snapshot and limits
PATCH/entries/{id}Change title, kind, links, supersedes or classification, with optional ifMetaVersion
POST/entries/{id}/updates{updateId, update}: one Yjs update
GET/entries/{id}/updates?sinceSeq=N or ?stateVector=: updates, a snapshot or a diff
POST/entries/{id}/accept{documentSha256}
POST/entries/{id}/supersede{supersededBy}: an accepted entry of the same space
POST/entries/{id}/archiveRetire an entry
GET/entries/{id}/historyStatus events and edits, with authors and times

Reads need context:space:read. Every change needs context:space:write and read access to the space: a write grant alone never lets someone edit what they cannot read. Organisation deny rules apply. Every change is audited (context.memory.create, patch, update, accept, supersede, archive); audit entries never contain the text.

The collaboration policy's team_memory switch gates create, patch, update and accept (403 context.collab_policy.capability_disabled). Reading, superseding and archiving stay possible when it is off, so an organisation can always clean up.

Limits

VariableDefaultLimit
KEYSTONE_CONTEXT_MEMORY_MAX_UPDATE_BYTES65536One update
KEYSTONE_CONTEXT_MEMORY_MAX_TEXT_BYTES65536An entry's text (UTF-8)
KEYSTONE_CONTEXT_MEMORY_MAX_UNITS262144UTF-16 units of a document, live and deleted
KEYSTONE_CONTEXT_MEMORY_MAX_UPDATES_PER_ENTRY50000Rows of an entry's edit log
KEYSTONE_CONTEXT_MEMORY_UPDATES_PER_MINUTE240Updates one author may add to one entry per minute (five times that across entries)
KEYSTONE_CONTEXT_MEMORY_ENTRIES_PER_SPACE5000Entries (not archived) in a space
KEYSTONE_CONTEXT_MEMORY_COMPACT_AFTER256Retained update payloads that trigger compaction

An entry that reaches its unit cap accepts deletions only; the team continues in a new entry that supersedes it. An invalid value is a configuration error.

What a malicious editor cannot do

An editor with write access can write anything within the caps, including misleading text, and can delete other people's text. They cannot make their text reach AI tools without another person accepting the exact document, skip DLP, lower the classification below the space's, move an entry to another space, take over another author's editing session, or read or write another organisation's or space's entries. Deletions and edits stay in the history with their authors. The raw edit log is readable by everyone who may read the space; space membership, not DLP, is the control for that.

Last reviewed 17 October 2026View as Markdown

Team memory · Docs · Neural Inverse