Repository navigation
v0.2.0: memory that installs in one line - #45
Open
keirsalterego wants to merge 6 commits into
Open
keirsalterego wants to merge 6 commits into
keirsalterego wants to merge 6 commits into
Conversation
…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.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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.
Extractorhad exactly one implementation — the null one — andwith_extractorhad zero call sites anywhere in the repository. What the store held was whatever an agent chose to handadd_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.instructionsis 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
identra serveneeds no app, no port, no config rewriting. Samehandleas the HTTP bus.identra doctor, plus aMEMORY.mdyou can open.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 fromtools/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 MBdialogue, 0 unreadable, 0 skipped lines across 202 files.Four defects found and fixed during review, each with a test or a measurement behind it:
recent(usize::MAX)worked only because the cast overflowed to-1and SQLite reads that as unlimited.identra doctor | headpanicked 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.