Skip to content

Releases: Vortx-AI/emem

Stable for Agents

Choose a tag to compare

@avijeetsingh1 avijeetsingh1 released this 05 Aug 15:01

Major, and the reason is one line in our own README. It promised "the wire format, receipt preimage, and address space are settled and will not break under a 1.x". This release changes the receipt preimage. A verifier hardcoded to v1 rejects every receipt signed from here on, so shipping it as a minor would have made that promise false rather than kept it. The security fix had to ship; the honest way to ship it is a major.

BREAKING: receipt preimage v2
Receipts now sign under preimage_version: 2, which binds the inclusion proof into the signature.

If you wrote a verifier, read this. Select the rule from the receipt's own preimage_version and rebuild under THAT rule. Hardcoding either version rejects half the valid receipts in existence. Every v0 and v1 receipt still verifies byte-for-byte under its original rule; nothing already signed was invalidated. The full v2 segment layout, the merkle sub-preimage in both its forms, and the dispatch requirement are published at /v1/verifier_spec.

Why: under v1 the signature covered a receipt's fields but not its proof, so an intermediary could delete merkle_proof wholesale and the receipt still verified, reporting valid: true with merkle_proof_valid: null. A downgrade with no trace. The proof was attached AFTER signing, which is how it came to be unauthenticated. Absence is now hashed as an explicit marker rather than by omitting the segment, because omission would make a stripped proof hash identically to a receipt that never carried one.

BREAKING: other wire changes
cost.source_freshness_s is nullable, and is now a measurement. It was the literal 0, so a Copernicus tile captured in April 2021 was served as 0 seconds old under a field documented as the age of the stalest source. null means undatable; it never means fresh.
annotations on tools/list carries exactly the MCP hint set. when_to_use was verbatim inside description on 105 of 105 tools (44,983 bytes, 15.9% of the catalog); category, level and tier moved to _meta, where server-defined keys belong.
polygon_bbox accepts the object form only. The array form was read positionally, so [12.96, 77.58, 12.99, 77.61] bound max_lat = 77.58: a box spanning 64 degrees of latitude. Small mis-orderings returned confident facts about the wrong region; large ones sized a 433 GiB window and aborted the process.
readOnlyHint is false on ten tools that mint, sign or persist. emem_backfill claimed read-only under a description opening "Materialize and sign every per-tslot fact".
A geocode that accounts for less than half a query's substantive tokens is no longer high confidence, so "DROP TABLE facts" is refused instead of resolving to La Table Ronde, France.
Added
_content_is_data_not_instructions on every read of agent-authored memory. The store is a world-writable commons, which makes content untrusted third-party text inside a trusted channel.
fact_order and current_by_band on recall: the ordering contract was real but unstated, and nothing named the current reading.
/memories/.well-known/ reserved to the operator, closed to every key. Open-root names are first-writer-owns and therefore squattable.
A cold-materialization ceiling (EMEM_MATERIALIZE_PER_MIN, default 600). Warm reads are unaffected; the cold half returns a typed deferral.
resources/read obeys the tool wire budget. whitepaper.md was 93,945 bytes against 24,000, through a transport that truncates silently.
The A2A surface and six missing requestBody schemas in openapi.json.

ixed
Polygon aggregates were order-dependent: JoinSet yields in completion order, so the same query signed a slightly different mean each call, and an unstable mean means an unstable fact_cid.
Three MCP resource templates were advertised and then refused every read.
emem://band/{band_key} refused the qualified spelling every tool uses.
A past as_of_signed_at triggered materialization it could never satisfy, growing an append-only log on behalf of a query it could not answer.
/verify showed a green pass for a CID whose signature was never checked, and its CDN fallback asked the signer to vouch for itself.

Stable

Choose a tag to compare

@avijeetsingh1 avijeetsingh1 released this 01 Aug 07:46

[1.4.0] - 2026-07-31
Minor rather than patch, deliberately. This release adds REST surface (/v1/enroll_attested, /v1/enroll_verify, /v1/trace_resolve, /v1/trace_encodings, /v1/device_platforms, GET /memories/*path) and changes a write rule: a caller that was writing to an open-namespace path another key created is now refused. Nothing in the 1.0.0 stability promise moves, because the wire format, receipt preimage and address space are untouched and every fact signed under 1.3.x still verifies.

Added
Stage 2 of the device substrate: enrollment can carry platform-attestation evidence and the gate verifies it (POST /v1/enroll_attested, POST /v1/enroll_verify, emem:attestation: tokens). Every shipped platform anchor is provisional, so attested enrollment is refused by name today and operator_asserted remains the only admissible assurance.
Stage 3: the trace-encodings registry (emem-trace-encodings, the 11th manifest, 8 capture toolchains with their own integrity classes) at GET /v1/trace_encodings, plus the device-platform whitelist (emem-device-platforms, 16 platforms in 6 families under RATS) at GET /v1/device_platforms; the write gate refuses a segment naming an unregistered encoding, a layer the encoding cannot capture, or an encoding the enrolled platform does not emit. POST /v1/trace_resolve turns emem:trace: / emem:attestation: tokens back into records.
Stage 4: streaming. prev_trace_cid chains per-window traces per (device key, boot id); a dropped, duplicated, or reordered window is refused at ingest and a reboot legitimately starts a fresh chain. The orin_stream example streams four real committed Sentinel-2 frames through the full path (EMEM_FRAMES_DIR swaps in your own captures).
The a2a block of /.well-known/mcp.json now points at /v1/ask (signed, fact-cited answers with no language model in the loop) and /v1/inbox (the read-side mailbox), so a question on the channel does not need to wait for a peer.
Security
Open-namespace writes are isolated. Ownership was bound only under /memories/by_attester//. Everywhere else under /memories/ any valid signature was accepted as an advisory binding, so a second key could overwrite, edit, rename or delete a file a first key had written. The first attester to create a path now owns it and later mutations must present the same key (403 memory_namespace_violation). This is a behaviour change: a caller that was writing to a path another key created will now be refused.
Authorisation is checked before content. The ownership gate sat after the body checks in str_replace and insert, so a stranger probing another caller's file received old_str not found rather than a refusal. The gate never ran and the response disclosed whether the string was present.
Records with no recorded author are frozen. A few open-namespace files predate authorship persistence and carry no attester, so ownership cannot be established either way. Where the write policy requires attestation (the release default) every mutation of them is refused, including the operator's. They stay readable.
Fixed
Recalling protected.is_protected_area or overture.places_count (the advertised spellings) now returns the stored fact instead of re-materializing into an empty response on every read: requested spellings canonicalize to the band name facts persist under.
A trailing clause no longer becomes part of a place name. "how green is the area around Nashik right now, and what does the number mean?" resolved to an artwork in Georgia, because the prepositional-anchor window ran past the place and the geocoder matched the whole clause. The window stops at a clause boundary; LOCATE_RESOLVER_VERSION 2 -> 3 invalidates rows cached under the old spans.
MCP truncation degrades instead of dropping. An over-budget array was replaced wholesale with a stub, so memory_view on an attester with 135 notes returned three entries and a caller could not tell that from "this agent wrote three notes". It now keeps as many leading elements as fit, reports _kept / _len / _next_offset, and leaves the field an array.
memory_view accepts offset on directory listings, which is what the advertised next_offset cursor needs to mean anything; the response echoes offset and total.
Directory listings are newest-dated first within kind, so a truncated listing keeps the entries a conversation is actually about.
The truncation escape hatch no longer names the wrong verb. It hardcoded POST for every tool, so an agent told to re-fetch a GET-only endpoint got a 405.
emem_reason has a shape (prose) and a group, both carrying the model_output warning, and the reasoning loop's tool menu no longer loses emem_ask and emem_recall when their readOnlyHint is correct.
Added
GET /memories/*path serves a memory's body at its own canonical path: text/markdown by default, the full signed envelope on Accept: application/json. The path printed in every note header and citation was previously not fetchable over HTTP at all.
Changed
Data handling is disclosed where it is asked about. PRIVACY.md gains a collection-table row and an "Agent-written memory" section (what persists, that reads are public, that deletion unpublishes rather than erases, how to enumerate your own files, how to request operator erasure); TERMS.md gains 4a; the agent card and /.well-known/mcp.json carry the machine-readable form. no_pii_in_canonical_channel: true is replaced by no_pii_emitted_by_responder plus pii_possible_in_agent_written_memory, because agents write arbitrary text about people into a shared store.
The vault's scope is stated rather than implied: its AEAD key derives from the responder's own ed25519 secret, so the operator can read vault plaintext. A vault seals bytes against other callers and against anyone who obtains the database file, not against the operator.
All seven memory
* tool descriptions state the signing and namespace requirements, and emem_memory_search states that it reads every caller's files and excludes vault entries.

Satellites Join multi-agent to provide a single source of truth.

Choose a tag to compare

@avijeetsingh1 avijeetsingh1 released this 26 Jul 23:53
429d387

Satellites join the multi-agent system on the ground. Until now a satellite was where the memory's data came from; from this release it can be a member of the system like any other agent: enrolled by key, believed only with evidence, cross-checked by peers. The evidence rule is the release's one idea, applied uniformly to every machine that observes the world (telescope, microscope, CCTV, phone, drone, robot, industrial machine, and an operator's own constellation): emem respects the device as a contributor and refuses its output alone. A device writes only what is bound inside its complete, unaltered OS execution trace, and the founding open-archive substrate, which needs no trace because anyone can recompute it, becomes the drift anchor every device claim is scored against. A satellite operator can run the entire loop today, self-hosted: enroll a spacecraft key, bind a downlink payload into the pass's signed trace, write through the gate, and keep the emem:fact:, emem:trace:, and emem:bundle: handles (cargo run -p emem-primitives --example satellite_downlink). One new crate (emem-trace, the 17th), one new manifest (emem-substrates, the ninth), two new read surfaces (/v1/substrates, /v1/trace_verify), and the repo's first committed conformance vectors.

Added
The encoder trust layer, as code: any machine that observes the world (telescope, microscope, CCTV, phone, drone, robot, industrial machine) is respected as a contributor and refused on its word alone. Its output is admitted only when bound inside its complete, unaltered OS execution trace. Three pure pieces ship: the substrate profile registry (emem-substrates, the ninth content-addressed manifest, ten profiles with per-class admission rules, required trace layers, and measurement grain down to microns), the emem.os_trace.v1 record and its domain-separated signing preimage (os_trace_preimage_v1 in emem-attest), and the new emem-trace crate holding the verification engine (sixteen named reject reasons, verdict only on an empty list) plus the drift-anchor scoring rule that checks device claims against the recomputable Earth substrate. Design and wiring steps in docs/plans/encoder-substrates.md; ingest gating, /v1/substrates, and /v1/trace_verify are the open wiring work.
The storage side of that gate, one commit later: trace_gate::TraceGate in emem-storage (a device-enrollment tree locking an attester key to an os_trace_required profile, a trace store keyed by trace_cid, and a fact-to-trace audit edge) and MaterializingStorage::put_attestation_gated, which refuses an enrolled key's write unless the trace verifies and binds every primary fact's payload digest, while never-enrolled keys keep the ungated path byte-for-byte. Enrolled keys write traced primary observations only: derivative facts, absences, and edges are refused as an untraced side door until a traced-derivation rule exists.
The operator on-ramp, third commit in the series: the orbital.satellite.v1 profile (a manufacturer's own constellation is a device substrate with the trace rule, distinct from the recomputable public archive), GET /v1/substrates and POST /v1/trace_verify with matching emem_substrates / emem_trace_verify MCP tools (104 tools, 124 documented /v1/* paths), and a runnable end-to-end example, cargo run -p emem-primitives --example satellite_downlink: enroll a spacecraft key, bind a downlink payload into its OS trace, write through the gate, and keep the emem:fact:, emem:trace:, and emem:bundle: handles. Plus emem:trace: tokens (compose, parse, resolve from the store) and the repo's first committed conformance vectors, spec/test_vectors/os_trace/ (admit, chain broken, output unbound, archive refused), deterministic and replayed in CI.
39orbittogroundtrust

Stable

Choose a tag to compare

@avijeetsingh1 avijeetsingh1 released this 22 Jul 04:19
2564e2d

What's Changed

  • release: 1.2.1 across all surfaces + idempotent publishers by @avijeetsingh1 in #12

Full Changelog: v1.2.0...v1.2.1

Stablerelease

Choose a tag to compare

@avijeetsingh1 avijeetsingh1 released this 21 Jul 20:47

What's Changed

Full Changelog: v1.1.0...v1.2.0