Skip to content

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

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

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

Conversation

@rrader26

Copy link
Copy Markdown
Contributor

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

New: mm.memory.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

On both MemMesh and AsyncMemMesh. There was no graph surface before, so the
structural half of memory — the entities and typed edges extraction builds —
was unreachable from Python.

st = mm.memory.graph.stats()
print(st["entityCount"], st["edgeCount"], st["memoriesWithEdges"])

sarah, = mm.memory.graph.list_entities(search="Sarah", limit=1)
chain = mm.memory.graph.traverse(sarah["id"], hops=2, predicates=["member_of", "led_by"])

Read-only on purpose: entities and edges are written by extraction during
observe(), and exposing the manual create/retire routes would invite
hand-maintained graphs — the work the engine exists to do.

Use stats(), not len(list_entities()), 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.

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 on
anything written through observe().

They are provenance, not a tenancy boundary — search filters
chatIdentityId IS NULL OR = $1, permissive by design so project-wide memories
stay visible. Documented as such.

Compatibility

Additive only. The fields are omitted rather than sent as null, so an existing
call site produces a byte-identical request — covered by
test_observe_text_omits_identity_when_unset.

Verification

  • 136 tests pass (126 existing + 10 new in tests/test_graph.py), covering
    request shaping for all five graph methods, filter omission, and sync/async
    parity.
  • Verified live against app.memmesh.ai: all five methods return real data
    — 12,142 entities, 287,698 edges, 184,737 memories with edges.

Remaining parity gaps

Go, Rust, and .NET have the same two gaps and are being brought up separately.
Go is furthest behind — it has no raw-text observe at all.

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

New `mm.memory.graph` — stats, list_entities, get_entity, list_edges,
traverse, on both the sync and async clients. There was no graph surface
before, so the structural half of memory was unreachable from Python.

Read-only on purpose: entities and edges are written by extraction during
observe(), and exposing the manual create/retire routes would invite
hand-maintained graphs, which is the work the engine exists to do.

Prefer graph.stats() over len(list_entities()) for any size question — the
list routes page, so their length is the page size, not the total. Against
a real project that is 1000 vs 12142.

observe() now forwards user_id / agent_id / session_id. The server route
has always accepted them; the SDK was dropping them, so provenance never
arrived. Omitted rather than sent as null, so an existing call site
produces byte-identical requests.

They are provenance, NOT a tenancy boundary: search filters
`chatIdentityId IS NULL OR = $1`, permissive by design.

Verified live against app.memmesh.ai: all five graph methods return real
data (12142 entities / 287698 edges). 136 tests pass, 10 new.
list_edges / traverse / get_entity().edges return the server's
GraphTraversalEdge — subject and object are hydrated entity dicts, not
ids, plus a hop counter. There is no subjectId on the wire.

Python returns plain dicts so this never failed at runtime, but the type
alias and docs described a shape the API does not produce. The Rust port
did fail, which is how it was found.
@rrader26
rrader26 merged commit ab6868d 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