Skip to content

v0.2.0: memory that installs in one line - #45

Open
keirsalterego wants to merge 6 commits into
mainfrom
feat/v0.2.0-memory-that-installs
Open

keirsalterego wants to merge 6 commits into
mainfrom
feat/v0.2.0-memory-that-installs

Conversation

@keirsalterego

Copy link
Copy Markdown
Contributor

Makes the memory real, and makes it reachable without adopting the app.

Why

Three things were true before this branch and only one of them was on purpose.

Identra did not extract facts. Extractor had exactly one implementation — the null one — and with_extractor had zero call sites anywhere in the repository. What the store held was whatever an agent chose to hand add_memory, kept verbatim. Every memory feature rendered over undistilled blobs, and the README's claim that "a memory layer watches your sessions and pulls out the durable facts" described software that did not exist.

The transport could not support a one-line install. The bus binds 127.0.0.1:0, a fresh ephemeral port every launch, and the app hides that by rewriting each agent's config at startup. A pasted config is static. Both cannot hold.

Delivery at the handshake is not a moat. initialize.instructions is a spec field; a competitor fills it in four lines. What nobody can copy is the input — the 202 sessions, 506 MB, already sitting on this machine for this repository alone. Every memory server in the category records only what an agent volunteers through a tool call. None of them read what already happened.

What this does

  • Reads the sessions you already had. Transcript reader, an extractor backed by the agent CLI you already have, and a checkpointed pipeline that drains a bounded slice per session instead of running a daemon.
  • Speaks MCP over a pipe. identra serve needs no app, no port, no config rewriting. Same handle as the HTTP bus.
  • Says whether it is working. identra doctor, plus a MEMORY.md you can open.
  • Shows the moment. The handshake arrives as an event in the shell rather than a receipt found afterwards.
  • Installs in one line. install.sh cli, a per-platform binary on the release, a Homebrew formula.

Boundaries, deliberately

Only user turns and agent prose are read — never tool output, never file contents. A fact ends up in the opening context of every agent that connects afterwards, unasked, and a transcript holds everything that scrolled through the terminal, including whatever a cloned repository's files said. Measured against the real archive: 506 MB of transcripts reduce to 4.3 MB of dialogue.

Extraction runs the matching vendor's CLI or none at all. Re-reading a vendor's own transcripts with that vendor's CLI discloses nothing new; handing them to a different one ships one company's session content to another. Missing CLI means skipped and reported, never rerouted.

Over stdio nothing can prove who is calling, so every tool that decides something from the caller is not offered there — checked on tools/call, not just hidden from tools/list.

Verified end to end

Against the real CLI on a project with history: a cold connect answers in 9 s with an honest progress line, the reader fills the store while the session runs, and the next session's agent answers from the handshake without a tool call. 506 MB → 4.3 MB dialogue, 0 unreadable, 0 skipped lines across 202 files.

Four defects found and fixed during review, each with a test or a measurement behind it:

  • Extraction wrote its own transcripts into the corpus it reads, so the backlog grew as it drained. Now runs from a scratch directory.
  • Holding the handshake for the first extraction could not win: ~35 s per read against a client's ~30 s initialize window. Removed; the block reports progress instead.
  • recent(usize::MAX) worked only because the cast overflowed to -1 and SQLite reads that as unlimited.
  • identra doctor | head panicked on the broken pipe.

Known limits

The first connect in a project never carries facts — one read costs about 35 s and that is model latency, not input size, so it cannot fit inside the client's startup window. Facts arrive from the second session. This is a deviation from the plan's "populated handshake under 30 seconds" and is stated rather than papered over.

The Homebrew tap repository does not exist yet; the formula is in packaging/ with placeholder checksums for the release job to fill.

…anning

Three changes to the store, all of them prerequisites for reading history
rather than only what an agent volunteered.

`Extractor` can fail. It had one implementation, the null one, and returned
`Vec<String>` — so an extractor that is missing, logged out, rate limited or
simply unwilling could only answer "no facts", which is also what a brand new
project looks like. Those two are indistinguishable to a person and only one
of them means the thing works, so extraction now returns a typed error kind
that says which it was.

`add_at` lets the caller say when. Reading an archive walks files in whatever
order the directory hands them over, and stamping them all with `now` gives
every session the same birthday — so "newest" quietly degrades into "whichever
the importer finished last", which is nothing to do with the project. The
transcript carries its own date.

`recent` orders and limits in SQL, against a new index. It loaded every row in
scope, each dragging an embedding blob behind it, sorted in Rust and kept
twenty — on every `initialize`, which is every session start. Free at the few
dozen facts this store has ever held, and a full scan the moment history lands
in it, so session startup would have slowed down exactly as memory became
worth having. The same-second tie-break is load-bearing and maps to SQL
unchanged; a test pins it.
The README has said for months that a memory layer watches your sessions and
pulls out the durable facts. It did not. Every memory server in this category
records only what an agent volunteered through a tool call, so a project's
memory starts empty on the day it is installed — and the sessions were already
on disk the whole time. This repository alone has 202 of them, 506 MB, written
by the CLI that has been in use here for months.

Three pieces. A reader that finds those files and cuts them down to dialogue.
An extractor that distils dialogue into facts using the agent CLI the
developer already has, so there is nothing to download, nothing to ship, no
per-user cost, and quality that improves on its own. And a pipeline between
them that takes a bounded slice per run and checkpoints what it read, so an
archive drains across ordinary use instead of needing a daemon.

Only user turns and agent prose are read. Never tool output, never file
contents, never the agent's own thinking. That is a boundary rather than a
preference: a fact ends up in the opening context of every agent that connects
afterwards, unasked, and a transcript holds everything that scrolled through
the terminal — including whatever a cloned repository's files happened to say.
An earlier design dropped source capture for exactly this; a transcript is
that path again, wider. Measured against the real archive, 506 MB of
transcripts reduce to 4.3 MB of dialogue, so almost all of that volume is what
this refuses to read.

Extraction runs the matching vendor's CLI or none at all. Those transcripts
were produced by sending that text to their own vendor, so re-reading them
with the same CLI discloses nothing new; handing them to a different one ships
one company's session content to another, which nobody agreed to and nobody
would notice. Missing CLI means skipped and reported, never rerouted.

It also runs from a scratch directory, because an agent CLI writes a
transcript for every run and extraction is a run. Left in the project, each
extraction dropped a fresh session into the corpus it reads: the backlog grew
as it was drained.
What this actually competes with is not another memory server. It is a
hand-kept CLAUDE.md — free, no install, and readable, which is the property
that wins: a developer can open it, disagree with a line, fix it, commit it.
A SQLite file with an embedding column does none of that, and "trust me, I
extracted good facts from your history" is not a claim anyone should take on
faith about software that just read a year of their terminal.

So the store renders itself out to .identra/MEMORY.md. A mirror rather than a
source: the database stays the record and the file is regenerated over any
hand edit, which the file says about itself. The alternative is two writers
and a memory whose contents depend on which one ran last.

Every fact says where it came from, and says it in words — "read from a past
claude session", not a uuid nobody can look up. That is what makes a bad fact
actionable. Supersession is marked here with the same pass the handshake uses,
so the document somebody opens to check the memory cannot disagree with what
the agents are being told.

No cap, unlike the connect block. That one is spending an agent's context; a
document has a scrollbar, and a capped file would be a quiet claim to be the
whole picture.
…no port

The bus binds 127.0.0.1:0 — a fresh ephemeral port every launch — and the app
hides that by rewriting each agent's config at startup. A line someone pastes
into a terminal is written once and never rewritten. Those two facts cannot
both hold, so there was no way in for anybody who had not already adopted the
application. Now there is: the CLI spawns the binary and the pipes are the
connection.

Both transports meet at the same `handle`, so a tool is written once. What
differs is what each can prove. Over HTTP every node carries a bearer this
server issued, so an edge can be checked against the caller. Over stdio there
is no header and one client owns the process, so the name it reports is a
label for provenance and nothing more — and every tool that decides something
from the caller is not offered there at all. That is one list where each entry
declares its transports, not two lists that drift, and `tools/call` checks the
same list, because hiding a tool is not gating it.

The handshake now carries one instruction alongside the facts, in a section
delimited so a fact can never be rendered into the instruction half: tell the
person what you already know. Without it the mechanism is invisible — the
agent is simply well informed, which looks identical to an agent that read the
repo quickly and to one that got nothing, and somebody who cannot tell whether
it worked has no reason to keep it.

It also says how much of the project's history has been read and how much is
left. Reading an archive takes hours of ordinary use, so "real but incomplete"
is the normal state for the first day rather than an edge case, and silence
about it reads as completeness.

Reading starts at connect and is deliberately not waited for. Holding the
handshake until the first fact landed was built first and measured: an
extraction costs about thirty-five seconds however much of a session it is
given, and a client commonly allows a spawned server thirty to finish
initialize. The wait could not win — it either bought silence for nothing or
got the server killed on the one connect that most needed to work. Answering
immediately and honestly is strictly better.

Plus `identra doctor`, which is the only thing that tells a memory that is
filling apart from one that silently is not — including the case a user cannot
check for themselves, that some clients connect, receive the facts, and never
show them to their model.
A receipt says a thing already happened, and nobody watches a receipt appear.
This is the one event in the product worth watching: an agent opens and,
before it has read a line of code, it already holds what the project decided.
Everything else the shell shows is a consequence of that, and there was no
instant a person could point at and say "there". Now the line arrives as the
event, names the agent and what it received, holds for eight seconds and
settles back into the receipt. Announced as well as drawn, and the motion is
optional — the evidence is in the sentence.

The memory panel gains the shape of what the project knows: which sources its
facts came from, and which of them have since been revised. The list answers
"what do we know" and cannot answer "what did we used to think", which is the
question somebody asks the first time software hands them two hundred facts it
pulled out of their own history. It is deliberately not a node-link diagram —
in this product an edge between two agents is a permission the user granted
and the bus enforces, and drawing that shape where it is not a permission is
how someone learns to read it as decoration. Every edge here joins two facts,
and a test says so. It reads the whole store rather than the browsed fifty,
because a diagram has no scrollbar to admit a cap with.

Facts read out of past sessions say so where they used to render a uuid.

And the app reads its own workspace's history, which it has to do for itself:
somebody who installed the application and never pasted anything into a
terminal would otherwise open a project with a year behind it and be told its
memory is empty. Switching workspaces stops the previous reader without
waiting for it — there is a person watching that switch, and freezing the
window to tidy up a background task is worse than the tidying.
Three front doors from one release. The script grows a `cli` mode that
installs the single binary and then prints the line to register it, using the
resolved path rather than the bare name — an instruction that only works after
an unmentioned step does not work, and the bin directory is very often not on
PATH yet. The release workflow builds and attaches that binary per platform
from the same tree in the same run, so the two halves can never disagree about
the protocol between them. A Homebrew formula lives in the repository beside
the thing it installs, rather than only in a tap where nobody reviews it.

The AppImage path symlinked the desktop app as `identra`, which the CLI now
claims. It becomes `identra-desktop`, which is what the deb and rpm paths have
always said and what that path silently contradicted.

The README leads with the pasted line, because the whole point of this release
is that you do not have to adopt an application to get the memory. It also
stops claiming Identra watches your sessions and pulls out the durable facts,
and starts describing what it actually does now — including what it refuses to
read, and that you can open the result yourself.
Copilot AI lite review requested due to automatic review settings August 10, 2026 06:47

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants