Skip to content

feat: knowledge-graph resource + observe identity provenance - #5

Merged
rrader26 merged 1 commit into
mainfrom
feat/graph-resource-and-observe-identity
Aug 19, 2026
Merged

feat: knowledge-graph resource + observe identity provenance#5
rrader26 merged 1 commit into
mainfrom
feat/graph-resource-and-observe-identity

Conversation

@rrader26

Copy link
Copy Markdown
Contributor

Rust parity with @memmesh/sdk v0.9.0 — see
thinkfleet-memory-sdk#21.

New: mm.graph()

Method Endpoint
stats() GET /admin/memory/graph/stats
list_entities(..) GET /admin/memory/entities
get_entity(id, ..) GET /admin/memory/entities/:id
list_edges(..) GET /admin/memory/graph/edges
traverse(id, ..) POST /admin/memory/graph/traverse

There was no graph surface before, so the structural half of memory — the
entities and typed edges extraction builds — was unreachable from Rust.

Use stats(), not list_entities(..).len() for any "how big is it"
question. The list routes page, so their length is the page size. Against a real
project that is the difference between reporting 1,000 and the actual 12,142.

Edges are hydrated, and modelling them wrong is a hard failure

list_edges, traverse, and get_entity().edges return the server's
GraphTraversalEdgesubject and object are full entities, not ids,
plus a hop counter. There is no subjectId / objectId / brainId on the
wire.

I found this by running the port against production, where strict decoding blew
up with missing field 'subjectId'. The same mistake was live in the TypeScript
PR, where structural typing hid it — the call compiled and handed back objects
whose declared fields were all undefined at runtime. Fixed in both.

The raw memory_edge row shape is deliberately not modelled: no read route
returns it, and a type nothing returns is a trap.

hop is 0 from list_edges (no seed) and 1-indexed from traverse.

Query encoding

Filter values are percent-encoded. An unescaped & in a search string would
otherwise truncate the query server-side and quietly return the wrong page —
covered by with_query_encodes_values.

observe forwards identity

user_id / agent_id / session_id now reach the server. The route has always
accepted them; the SDK was dropping them, so provenance never arrived.
Inserted only when set, so an existing call site sends an identical request
(observe_text_omits_identity_when_unset).

Provenance, not a tenancy boundary — search filters
chatIdentityId IS NULL OR = $1, permissive by design.

Verification

  • 113 tests pass (9 new), cargo clippy --all-targets clean.
  • Verified live against app.memmesh.ai via examples/graph_live.rs:
    12,142 entities / 287,698 edges, decoding real edges as
    NVIDIA CORP -[reported_metric]-> Cost of Revenue and the literal-object case
    NVIDIA CORP -[ticker_symbol]-> NVDA.

Remaining parity gaps

.NET and Go are next. Go is furthest behind — no raw-text observe at all.

Parity with @memmesh/sdk v0.9.0 (thinkfleet-memory-sdk#21).

New `mm.graph()` — stats, list_entities, get_entity, list_edges, traverse.
There was no graph surface before, so the structural half of memory was
unreachable from Rust.

Edges deserialize as `GraphTraversalEdge`, the shape the read routes
actually return: `subject` and `object` are hydrated entities, not ids,
plus a `hop` counter. Modelling the raw `memory_edge` row instead fails
against production with `missing field 'subjectId'` — found by running
this against the live API, and fixed in the TS SDK too.

`hop` is 0 from list_edges (no seed) and 1-indexed from traverse.

Query values are percent-encoded. An unescaped `&` in a `search` filter
would otherwise truncate it server-side and quietly return the wrong page.

Observe gains user_id / agent_id / session_id. The server route has always
accepted them; the SDK was dropping them, so provenance never arrived.
Inserted only when set, so existing call sites send identical requests.
They are provenance, NOT a tenancy boundary.

Verified live against app.memmesh.ai — 12142 entities / 287698 edges, and
edges decode as `NVIDIA CORP -[reported_metric]-> Cost of Revenue`.
113 tests pass (9 new), clippy clean.
@rrader26
rrader26 merged commit c63535d into main Aug 19, 2026
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