A ANLAAgent-Native Lossless Archive 中文
The second thing this turned out to be

An agent that remembers its own history, exactly

An archive that preserves bytes exactly and expands any part of itself on demand is also a description of what a model needs from its own context. The same package carries a context layer, reachable over MCP: remember losslessly, address semantically, and get the record itself back — not a summary of it.

The loop, and what each step has to hold up

ToolThe claim it has to hold up
context_captureEvery byte of the transcript, or a refusal. A limit that would drop the front of a conversation is an error, not a quiet truncation.
context_segmentAn index family over the turns — and the archive is byte-identical before and after, which is checked rather than asserted.
context_segment_exportThe views to embed, with the identity that must come back with them, and a sample that names which part of the record it covered.
context_attach_vectorsVectors into the auxiliary plane: a sidecar beside the archive, never a record inside it.
context_addressA question in, (turn, start_byte, end_byte) out, with the turn's digest re-checked against what the index was built from.
The idea the design rests on

A segment is an index, never a stored fragment

From Neo.K's 同一性微積分: 切割 = 索引 — a cut adds a perspective and leaves the object whole. So a segment is (turn, start_byte, end_byte) in the auxiliary plane, several schemes coexist over one record, re-cutting rewrites nothing, and a segmenter is allowed to be wrong.

Ontological layer

The turn, stored whole in the preservation plane, untouched by any cutting. Its digest is the same before indexing, after two different schemes, and after every retrieval.

Presentation layer

Segments as raw byte offsets into that turn. A better segmenter later produces a new index family; the record is untouched, so choosing between schemes is a measurement rather than a migration.

Measured on this repository's own development transcript

What it costs and what it returns

Every figure below was produced by bench/context_bench.py into a JSON file this page is generated from, stamped with the revision and the corpus digest it was measured against.

Measured at 2026-08-15 05:12 UTC · 71a719f 38c5455779cbe268 changepoint-v1

The record

6,581 turns
16.68 MiB → 11.00 MiB (66%)

lossless — every byte of the transcript is in the archive

The index

61,458 segments
median 249 B · coverage 1.0000

no byte of any turn is unreachable through the index, and none is covered twice · preservation digest unchanged through indexing

The vector plane

61,458 × 768

sizeload
JSON array of decimals932.74 MiB28.6 s
float32 behind a JSON header182.95 MiB0.13 s

5.1× smaller · 221× faster to load

The search

183 ms with NumPy
13 s pure Python

NumPy is optional and the preservation plane never needs it. The pure-Python path refuses only when its own projection passes a stated 30-second budget, and it quotes the projection so you can disagree with the estimate rather than with a constant.

Over the wire

2.54 s median for a whole addressed query

5/5 of them returned digest-verified exact bytes. A 64-wide query against a 768-wide corpus → INCOMPARABLE: dimensions differs — 64 against 768

Does segmenting actually help?

Twelve labelled queries, one pinned corpus

Ground truth is located by exact search for a distinctive anchor string — and the question is then written to avoid that anchor entirely. So the label comes from a match the retriever never sees, and the query is exactly the case lexical search cannot answer.

Model: text-embedding-3-small · 768d 6,581 turns · 38c5455779cbe268 12 labelled queries
SchemeSegments p95R@1R@5MRR Median rank
whole-turn-v1 (baseline)6,581+0.4430.170.420.2807.5
structural-v118,814+0.3560.500.580.5452
sized-900-v1 (control)23,036+0.3610.580.750.6561
changepoint-v161,458+0.2190.751.000.8471

The same twelve queries, on a model that runs on this machine

nomic-embed-text through Ollama — 137M parameters, Apache-2.0, 768 dimensions, which is the width this vector plane already used. Identical ground: the same corpus digest, the same queries, the same schemes, the same budget. Hosted → local.

ollama:nomic-embed-text:latest768d38c5455779cbe268
SchemeR@1MRRp95
whole-turn-v10.17 → 0.420.280 → 0.569+0.443 → +0.500
structural-v10.50 → 0.580.545 → 0.704+0.356 → +0.351
sized-900-v10.58 → 0.500.656 → 0.631+0.361 → +0.315
changepoint-v10.75 → 0.750.847 → 0.861+0.219 → +0.203
▸ Where it counts they are level, and the local model can do one thing the hosted one cannot. On the winning scheme both reach Recall@1 0.75 and Recall@5 1.00, with the local model marginally ahead on MRR and on crowding. It also pins its own weights: Ollama reports a content digest, so a query is comparable with a corpus only if the weights are the same bytes. A hosted model is a name, and a name can be re-pointed without changing. Two things follow that the hosted run alone could not show — segmentation's benefit is a joint property of the unit and the model (4.5× hosted, 1.8× local, same destination), and the p95 gate is refuted a second time: at whole-turn the local model is more crowded and retrieves much better, so the quantity the gate measures moved opposite to the one it existed to predict.
▸ The control beat the scheme it controls for. Cutting every ~900 bytes did better than reading the document's own headings, paragraph breaks and code fences. So that structure was not carrying the information, and the scheme that parses it earned nothing over arithmetic. What works is cutting where the vocabulary changes — the only one of the three that looks at content.
The stated p95 gate failed on every row, including the winner. It wanted centred random-pair p95 below +0.15, calibrated against a baseline of +0.238 measured on a third of this corpus — where the baseline is now +0.443. The winning scheme halves the crowding, which is what the gate was reaching for, and the gate as written still failed. It is reported failed here and in the JSON, because a threshold re-read after the fact to mean whatever the result supports is not a threshold.
relation edges

The graph, and what it turned out to be

Typed edges the record states outright — a reply's parent, a tool call matched to its result, the same file named twice. Each carries a kind and its evidence, never a score. Measured against two controls: random pairs, and merely adjacent turns, which is what conversation order gives for free.

9,050 edges ollama:nomic-embed-text:latest · 768d 6,581 turns · 38c5455779cbe268
relationn mean cosinevs adjacency already adjacent
random (floor)7,999+0.00740.04×
adjacent (free baseline)6,580+0.18501.00×
mentions-path2,069+0.47232.55×23.8%
mentions-path (gap>1)1,576+0.44732.42×
tool-result-of1,737+0.27851.51×90.6%
tool-result-of (gap>1)164+0.13240.72×
replies-to5,244+0.19741.07×90.6%
replies-to (gap>1)495+0.08870.48×
Two of the three are adjacency with a label. 90.6% of replies-to edges connect turns that are already next to each other, and the tenth that do not score below plain ordering — +0.089 against adjacency's +0.185. tool-result-of has the same shape. Only mentions-path survives the split: 76% long-range, reaching 5,103 turns apart, and still 2.42× adjacency with its adjacent edges removed. Of 9,050 edges, 2,035 link a pair that ordering does not already put side by side.
▸ On retrieval it is smaller than the instrument resolves. A graph bonus over the twelve labelled queries moved 3 of 12 — 2 up, 1 down — for MRR +0.056, where one query is 0.083. The control at β = 0 reproduces the published baseline exactly, so the harness is wired right and the answer is simply that twelve queries cannot resolve this. It is two-sided: at one setting the bonus pushed a correct answer out of the top five.
Three refusals

The load-bearing part is what it declines to answer

01

Identity before similarity

Two 768-wide vectors from different models — or one model over two different preprocessings — compare to a confident, meaningless number, and nothing downstream can tell. Model, revision, dimensions, projection version and segmentation scheme must all agree, or the answer is INCOMPARABLE rather than a value. Width is not identity.

02

A capture that would not be lossless

A byte limit that would drop the front of a transcript is refused. Taken deliberately, the result is reported as partial and names the byte range it dropped — because every downstream claim would otherwise be stated over a record the caller believes is whole.

03

A search over part of the record

A vectorised corpus covering a tenth of the index still returns its nearest hit, and that answer is indistinguishable from a complete search unless the share is stated. It is stated, on every call.

Run it

23 tools over stdio. Nothing here computes an embedding: the vectors come from whatever model the agent already has, and the identity travels with them so a local or browser model can replace the one used here without anything silently comparing across the two.

pip install "mcp>=1.10,<2"
python tools/mcp/anla_mcp.py

python bench/context_bench.py <transcript.jsonl>
python bench/segment_retrieval.py <transcript.jsonl>

The retrieval table needs an embedding model; everything else on this page runs offline. Source access is private · Source access is private