Skip to content

Use it from where you already are

You do not have to change how you work. Message it like a person, add it to the assistant you already talk to, or drive it from a terminal. It is the same memory behind all of them.

Message it, like a person

In the assistant you already use

On your own machine

Send it things as they happen — a photo of a letter, a voice note in the car, a line you want to remember. Nothing to install.

  1. Sign up, then open Dashboard → Connections → Telegram. It gives you a personal link.
  2. Tap the link. Telegram opens the bot with your code already in it — press Start and the chat is joined to your account. Nothing to copy or paste.
  3. Send or forward anything. Text, photos, PDFs, voice notes. Photos and documents are read, not just filed.
  4. Type /machines to see the computers you have connected, or /get ~/notes/rent.pdf to ask one of them for a file.

Everything Telegram reads, and what it does not

Telegram is where things go IN. To ask questions, use it from your assistant or the dashboard — the bot confirms what it saved, it does not answer from your memory.

boiler service booked for the 14th, engineer is Tom

Saved.

[photo] insurance renewal letter

Read it — 2 pages. Saved.

/machines

macbook: connected

Message PersistMemory…

Documentation

Everything it does, and what it refuses to do.

PersistMemory captures material from the places you already work, extracts what it meant, and hands it back months later. Connect a source, point an assistant at the MCP server, or call the API yourself — and once it is running, it can reach your own machine for a file you never uploaded.

What happens between handing something over and being able to search it.

Four stages, all of them asynchronous. Knowing the order is what makes the timing of the API make sense.

  1. 01

    Something arrives

    A connector lists what changed, fetches it, and hands the bytes to the pipeline. A PDF from Drive and a PDF attached to an email take the same path from there.

  2. 02

    It is read, not stored

    The text is normalized, then a model extracts candidate claims from it: decisions, tasks, commitments, preferences, facts, and the people involved.

  3. 03

    It meets what you already know

    Entity resolution, deduplication and conflict detection run against the existing store. One piece of material may produce one memory, several, or none.

  4. 04

    It keeps its history

    A memory that changes is revised into a new version and the old one is superseded, not overwritten. Search hides superseded facts by default, because a stale answer presented as current is the worst thing retrieval can do.

Reading an answer

Why an answer sometimes says NO LONGER TRUE

Nothing is overwritten here. When something you believed changes, the new memory supersedes the old one — the old one keeps its evidence, its sources and the dates it was true between, and it moves into a section of its own. That section is headed, in those words, so a model cannot skim past it and report last year’s answer as this year’s.

Relevant memory

The ledger runs on Postgres with pgvector

current since 2026-03-11 · decision · confirmed by you

No longer true — this WAS true and has since changed

The ledger runs on DynamoDB

superseded true 2025-09-02 to 2026-03-11 · replaced by the above

Struck, dated, and kept — not deleted. Both rows are one lineage.

It is a feature, and here is what it buys you

“I used to use AWS” does not stop having been true because it stopped being current. A store that erases the old claim can answer what you think now and nothing about what you thought in June — which is most of what people actually ask a memory.

Current and historical are never interleaved

Ranked together by similarity, a superseded memory sits among live ones with only a date to tell them apart — and dates are exactly what a model skims. So they are rendered as two sections with that heading between them, and the heading says what it means rather than labelling it archive and hoping.

You have to ask for the history

Search returns current memories by default. Ask for the rest with includeHistorical over HTTP, or includeHistory from an assistant. Ask what was believed at a moment with asOf, and read one memory’s whole lineage with memory_timeline.

A superseded memory cannot become current again. Becoming true once more takes a new memory superseding the one that replaced it — which is what actually happened, and keeps the record readable in order. Editing the old row back to life would silently rewrite a history other memories already cite.

A contradiction the evidence cannot settle is a different thing, and it is not hidden either: it is rendered first, under CONTRADICTIONS IN THE RECORD, because it is the one part of an answer that must not be skimmed. Supersession says something changed. A conflict says two sources disagree and nobody has decided.

Three things that surprise people

remember does not return a memory

It answers 202 with a job id. Extraction and consolidation run afterwards, so nothing is searchable the instant the call returns. Poll the job if you need to know when; the terminal success state is completed.

Search degrades rather than fails

With embeddings unavailable it falls back to deterministic retrieval and still answers. Read diagnostics.degraded before telling a user the system knows nothing. It may merely be looking with one eye.

Conflicts are surfaced, not settled

When two memories contradict each other and nothing in the evidence decides it, the pair is held for you. A resolution you record outranks everything the system inferred.

One credential, and it is shown once.

Every request carries a bearer token: a pm_live_ key or a session JWT. Keys are minted only while you are signed in on the website, because a key that can mint another key makes revoking the first one pointless.

The response to a mint is the only place the key ever appears. Only its hash is stored, so a client that does not capture it there has lost it.

SDK reference
# Mint a key. Session only, so this runs from the browser,
# not from a script holding another key.
POST /keys
{ "name": "laptop", "scopes": ["read", "write"], "expiresInDays": 90 }

# Then every call carries it.
GET /api/v1/memories?type=decision&limit=50
Authorization: Bearer pm_live_...

# /v1 is the same router, kept for clients that predate the prefix.
GET /v1/memories

Connect a source, or let an assistant do the writing.