From 2373a6fcd38cc04582e2cd703c488bd4873f053b Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Sun, 6 Sep 2026 10:49:38 +0000 Subject: [PATCH 1/2] Honesty c: drop store-identity keys on shaped GQL emit (0.19.5) SHAPE_DROP_KEYS strips hid / _memnet_hid / elementId from GQL emit and jsonl. RSV errors no longer print hid. Nickname id stays off pin_map. Co-authored-by: chouswei --- .cursor/skills/SKILL-GRAPH.md | 2 +- .cursor/skills/memnet-format/SKILL.md | 4 +- .cursor/skills/memnet-reference/SKILL.md | 4 +- AGENTS.md | 2 +- CHANGELOG.md | 9 +- README.md | 4 +- docs/LLM-GUIDE.md | 4 +- docs/README.md | 3 +- docs/ROADMAP.md | 14 +- docs/SHAPE.md | 2 +- docs/grammar/gql-wire-profile.md | 1 + docs/operations/README.md | 1 + docs/operations/honesty-c-wire-audit.md | 32 +++ parts/common/memnet/memnet/__init__.py | 2 +- parts/common/memnet/memnet/gql.py | 7 +- parts/common/memnet/memnet/gql_codec.py | 2 + parts/common/memnet/memnet/mem_store.py | 6 +- parts/common/memnet/memnet/models.py | 4 + parts/common/memnet/memnet/mutate_gate.py | 2 +- .../memnet/memnet/neighbourhood_reserve.py | 11 +- parts/common/memnet/memnet/observable_rank.py | 4 +- .../common/memnet/memnet/pin_map_composer.py | 6 +- project.toml | 2 +- tests/test_honesty_c_wire.py | 205 ++++++++++++++++++ tests/test_neighbourhood_reserve.py | 5 + 25 files changed, 304 insertions(+), 34 deletions(-) create mode 100644 docs/operations/honesty-c-wire-audit.md create mode 100644 tests/test_honesty_c_wire.py diff --git a/.cursor/skills/SKILL-GRAPH.md b/.cursor/skills/SKILL-GRAPH.md index d5a5cef9..8171af61 100644 --- a/.cursor/skills/SKILL-GRAPH.md +++ b/.cursor/skills/SKILL-GRAPH.md @@ -2,7 +2,7 @@ **Audience:** model. Wire SSOT: [memnet-format](memnet-format/SKILL.md) and `docs/grammar/gql-wire-profile.md`. Do **not** treat this file as the session graph. -This folder **vendors** the MemNet stack. Hatch and PyPI are both **0.19.4** (`memnet-llm==0.19.4`). Cue then `pin_map`; `find` if ego unknown. **GQL only.** Package SemVer `a.b.c` (claim / usage-method revision / same-method cut): `docs/ROADMAP.md`. Do not invent 0.20. +This folder **vendors** the MemNet stack. Hatch **0.19.5**; last published PyPI is **`memnet-llm==0.19.4`** until upload. Cue then `pin_map`; `find` if ego unknown. **GQL only.** Package SemVer `a.b.c` (claim / usage-method revision / same-method cut): `docs/ROADMAP.md`. Do not invent 0.20. ## Routing diff --git a/.cursor/skills/memnet-format/SKILL.md b/.cursor/skills/memnet-format/SKILL.md index 00c4b701..d44c116d 100644 --- a/.cursor/skills/memnet-format/SKILL.md +++ b/.cursor/skills/memnet-format/SKILL.md @@ -8,7 +8,7 @@ metadata: pattern: tool-wrapper version: "5.3" domain: data-formats,memnet - product: memnet-llm==0.19.4 + product: memnet-llm==0.19.5 token_guardrails: | - Wire SSOT: docs/grammar/gql-wire-profile.md. This skill is MemNet-specific only. - pin_map from a cue; leftover anchor= is leftover. Do not dump S. @@ -17,7 +17,7 @@ token_guardrails: | # MemNet formats -Pair with [mcp-memnet](../mcp-memnet/SKILL.md). Formal SSOT: `docs/grammar/gql-wire-profile.md`. Hatch **0.19.4**. +Pair with [mcp-memnet](../mcp-memnet/SKILL.md). Formal SSOT: `docs/grammar/gql-wire-profile.md`. Hatch **0.19.5**. **GQL only.** Node / edge / property. Do not teach Layer, Tier A, pipe `@TAG`, or TOON/TRON. leftover `id:'NEW'` / leftover `anchor=` are leftover. diff --git a/.cursor/skills/memnet-reference/SKILL.md b/.cursor/skills/memnet-reference/SKILL.md index f6521e35..2f494d9f 100644 --- a/.cursor/skills/memnet-reference/SKILL.md +++ b/.cursor/skills/memnet-reference/SKILL.md @@ -10,7 +10,7 @@ metadata: pattern: pipeline version: "2.3" domain: memnet - product: "0.19.4" + product: "0.19.5" --- # MemNet product development reference @@ -19,7 +19,7 @@ Repo skill for **building** MemNet in **this** repository. Doctrine SSOT is `doc This checkout **vendors** agent skills under `.cursor/skills/` (core: `memnet-use` + MCP/format/nested/multitask). Load those when **using** MemNet; load **this** skill only when changing the engine, generic MCP, grammar, product SysML, or packaging. -**Product:** Hatch and PyPI are both **0.19.4** (CLI `memnet`; `memnet-llm==0.19.4`). Version map: `docs/ROADMAP.md`. Shape: `docs/SHAPE.md`. **1.0** = 0.5–0.8 claimed (do not tag from this skill). +**Product:** Hatch **0.19.5** (CLI `memnet`). Last published PyPI wheel is **`memnet-llm==0.19.4`** until upload. Version map: `docs/ROADMAP.md`. Shape: `docs/SHAPE.md`. **1.0** = 0.5–0.8 claimed (do not tag from this skill). **`a.b.c` (ROADMAP law).** `a` = claim / generation (`0` = 1.0 unclaimed; `1` = claim of 0.5–0.8). `a` moves only if the contract breaks (Recall=`pin_map` / Commit=`mutate`, GraphElement identity, GQL-only wire). `b` = usage-method revision (new required loop step, new product tool, cue/outline/identity law, new goldfish verb). Historical extras **0.10–0.19** were `b`. N-server (#47) is not a `b`. `c` = same-method cut (honesty, leftover naming, docs/skills, wheel/tag, caps, façade, faster same `cue → pin_map → mutate`). Efficiency on the current loop is **`0.19.c`**, not `0.20`. Do not invent a 0.20 extra. Do not claim 1.0. diff --git a/AGENTS.md b/AGENTS.md index 23c58288..40188612 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -4,7 +4,7 @@ LLM hub for this system repo. Prefer in-repo skills and docs over ad-hoc inventi ## Mission -**MemNet** (Net of Memory) is **mission working memory** — a session graph (GQL **node**/vertex, **edge**/relationship, **property**) **between** LLM call pipelines and data search, not the corpus and not GraphRAG. Agents read a bounded **live pin map** each turn and write in the same **GQL (openCypher-shaped)** family — redefined **Write = display** via shaped subgraph emit ([`docs/grammar/gql-wire-profile.md`](docs/grammar/gql-wire-profile.md)). In-session recall is **serial**: kind/keyword cue, then `pin_map` neighbourhood. Primary read: MCP `pin_map` / CLI `query pin-map`; leftover `query_warm` / `query warm` are leftover aliases. Aims (MN-REQ-00): save wall-clock time and tokens while keeping factual accuracy. Aids **system**, **programme**, **software**, **firmware**, **hardware**, and **documentation**. Transport: **in-process first** (single-agent; TCP fallback). **Multitask** requires TCP serve or streamable-http MCP — see Multitask policy below. This repo is **engine + generic memnet-mcp** only — novel-writer dropped. Repo product **0.19.4** (Hatch SSOT and PyPI **`memnet-llm==0.19.4`**; [#148](https://github.com/chouswei/MemNet/pull/148) nickname-off-`pin_map` honesty `c` is published). **1.0** = 0.5–0.8 claimed (unclaimed). See `README.md`, [`docs/SHAPE.md`](docs/SHAPE.md), and `docs/grammar/`. +**MemNet** (Net of Memory) is **mission working memory** — a session graph (GQL **node**/vertex, **edge**/relationship, **property**) **between** LLM call pipelines and data search, not the corpus and not GraphRAG. Agents read a bounded **live pin map** each turn and write in the same **GQL (openCypher-shaped)** family — redefined **Write = display** via shaped subgraph emit ([`docs/grammar/gql-wire-profile.md`](docs/grammar/gql-wire-profile.md)). In-session recall is **serial**: kind/keyword cue, then `pin_map` neighbourhood. Primary read: MCP `pin_map` / CLI `query pin-map`; leftover `query_warm` / `query warm` are leftover aliases. Aims (MN-REQ-00): save wall-clock time and tokens while keeping factual accuracy. Aids **system**, **programme**, **software**, **firmware**, **hardware**, and **documentation**. Transport: **in-process first** (single-agent; TCP fallback). **Multitask** requires TCP serve or streamable-http MCP — see Multitask policy below. This repo is **engine + generic memnet-mcp** only — novel-writer dropped. Repo product **0.19.5** (Hatch SSOT; last PyPI **`memnet-llm==0.19.4`** until upload; [#148](https://github.com/chouswei/MemNet/pull/148) nickname-off-`pin_map` published; 0.19.5 `SHAPE_DROP_KEYS`). **1.0** = 0.5–0.8 claimed (unclaimed). See `README.md`, [`docs/SHAPE.md`](docs/SHAPE.md), and `docs/grammar/`. ## Where to look diff --git a/CHANGELOG.md b/CHANGELOG.md index 2fd704c2..fad86be4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,12 @@ This project uses Semantic Versioning as **interpreted for MemNet**: package `a. ## [Unreleased] +## [0.19.5] - 2026-09-06 + +### Changed +- **Honesty `c` — store-identity keys off shaped emit** — Audit after [#148](https://github.com/chouswei/MemNet/pull/148): nickname `id` was already off `pin_map`, but `DROP_KEYS` was not a real strip. `hid` / `_memnet_hid` / `elementId` on `Record.fields` could still appear on GQL emit. SSOT is `SHAPE_DROP_KEYS` in `memnet.models`, applied in `_emit_props` (mutate ack and shaped present) and jsonl. Nickname `id` stays off **shaped read** only. RSV product errors no longer print hid. Ranking excludes `elementId` as well as hid / nickname. Package **0.19.5** patch on **0.19**. Numbered extras **0.10–0.19** unchanged. No 0.20 extra. `operatorCount` stays 2. Last published PyPI wheel remains **`memnet-llm==0.19.4`** until upload. Do not dump \(S\). Do not invent `rag_query`. **1.0** stays unclaimed. Surfaces: [`docs/operations/honesty-c-wire-audit.md`](docs/operations/honesty-c-wire-audit.md). +- **Package identity 0.19.5** — Hatch / `project.toml` / `memnet.__version__` honesty cut on **0.19**. Hatch **0.19.5**; PyPI last **0.19.4** until upload. + ## [0.19.4] - 2026-09-04 ### Changed @@ -684,7 +690,8 @@ Initial public release. - Caps are configurable via `MEMNET_MAX_*` env vars. - Sessions live in process memory only. On `serve` restart, all sessions are gone unless saved via `session save`. -[Unreleased]: https://github.com/chouswei/MemNet/compare/v0.19.4...HEAD +[Unreleased]: https://github.com/chouswei/MemNet/compare/v0.19.5...HEAD +[0.19.5]: https://github.com/chouswei/MemNet/compare/v0.19.4...v0.19.5 [0.19.4]: https://github.com/chouswei/MemNet/compare/v0.19.3...v0.19.4 [0.19.0]: https://github.com/chouswei/MemNet/compare/v0.9.0...v0.19.0 [0.9.0]: https://github.com/chouswei/MemNet/compare/v0.8.0...v0.9.0 diff --git a/README.md b/README.md index 3aa1fd42..9ad7cffe 100644 --- a/README.md +++ b/README.md @@ -6,13 +6,13 @@ MemNet sits **between** LLM call pipelines and data search (MN-REQ-00). Corpus l This repo ships the engine + generic MCP only. **Product shape:** [`docs/SHAPE.md`](docs/SHAPE.md). **Pinned role:** working set of **a few technical documents** (atoms and locators, not PDF bytes) plus live `TSK`/`USR`/`MOD`, re-read fast. Tens of MiB typical; **hundreds of MiB still in role**; gigabytes = RAG/cabinet. -Package **`memnet-llm`** (CLI **`memnet`**). Python ≥ 3.11. Hatch and PyPI are both **0.19.4** (`memnet-llm==0.19.4`; [#148](https://github.com/chouswei/MemNet/pull/148) nickname-off-`pin_map` honesty `c`). **1.0** stays unclaimed. SemVer meaning (`a.b.c`): [`docs/ROADMAP.md`](docs/ROADMAP.md). +Package **`memnet-llm`** (CLI **`memnet`**). Python ≥ 3.11. Hatch **0.19.5**; last published PyPI wheel is **`memnet-llm==0.19.4`** until upload ([#148](https://github.com/chouswei/MemNet/pull/148) nickname-off-`pin_map`; 0.19.5 `SHAPE_DROP_KEYS`). **1.0** stays unclaimed. SemVer meaning (`a.b.c`): [`docs/ROADMAP.md`](docs/ROADMAP.md). ## Install + quick CLI ```bash pip install memnet-llm -# or pin: pip install memnet-llm==0.19.4 +# or pin published: pip install memnet-llm==0.19.4 # optional extras (drivers only — not AgensGraph/Neo4j servers): # pip install 'memnet-llm[mcp]' # pip install 'memnet-llm[agensgraph]' diff --git a/docs/LLM-GUIDE.md b/docs/LLM-GUIDE.md index 4ae770b5..30975547 100644 --- a/docs/LLM-GUIDE.md +++ b/docs/LLM-GUIDE.md @@ -1,6 +1,6 @@ # MemNet — Agent Playbook (for LLMs) -**Class:** developers — MemNet engine / MCP / GQL wire / agent operating doctrine. Index: [`docs/README.md`](README.md). Product shape: [`SHAPE.md`](SHAPE.md). **Product 0.19.4.** Hatch and PyPI are both **`memnet-llm==0.19.4`**. **1.0** = 0.5–0.8 claimed (unclaimed). SemVer `a.b.c`: [`ROADMAP.md`](ROADMAP.md). +**Class:** developers — MemNet engine / MCP / GQL wire / agent operating doctrine. Index: [`docs/README.md`](README.md). Product shape: [`SHAPE.md`](SHAPE.md). **Product 0.19.5.** Hatch **0.19.5**; last published PyPI is **`memnet-llm==0.19.4`** until upload. **1.0** = 0.5–0.8 claimed (unclaimed). SemVer `a.b.c`: [`ROADMAP.md`](ROADMAP.md). **Dialect teach = GQL only** — [`grammar/gql-wire-profile.md`](grammar/gql-wire-profile.md). ADR: [`adr/ADR-001-gql-agent-wire.md`](adr/ADR-001-gql-agent-wire.md). **M2 shipped:** engine/MCP accept openCypher-shaped GQL and emit shaped `pin_map`. Do **not** teach Layer / Tier A / `@TAG` pipe as agent wire. @@ -66,7 +66,7 @@ Formal wire: [`grammar/gql-wire-profile.md`](grammar/gql-wire-profile.md). | Mode | When | Setup | |------|------|-------| -| **MCP in-process** | Cursor / local agents (**primary**) | Register `memnet-mcp` in `.cursor/mcp.json`; extra `[mcp]`. `pip install 'memnet-llm[mcp]'` (Hatch + PyPI **0.19.4**). **No** `memnet serve` | +| **MCP in-process** | Cursor / local agents (**primary**) | Register `memnet-mcp` in `.cursor/mcp.json`; extra `[mcp]`. `pip install 'memnet-llm[mcp]'` (Hatch **0.19.5**; PyPI last **0.19.4**). **No** `memnet serve` | | **CLI + serve** | Scripts, TCP shared process | Terminal 1: `memnet serve`; Terminal 2: CLI with `MEMNET_SESSION` | | **MCP streamable-http** | Remote shared graph | `memnet-mcp --transport streamable-http` on `:18766/mcp` | diff --git a/docs/README.md b/docs/README.md index 27f8c8aa..ee4a0b7b 100644 --- a/docs/README.md +++ b/docs/README.md @@ -21,7 +21,7 @@ Do **not** teach Layer / Tier A. **1.0** = claim of 0.5–0.8 (unclaimed). | Doc | Role | |-----|------| | [`SHAPE.md`](SHAPE.md) | Product shape from the problem (0.8 teach) | -| [`ROADMAP.md`](ROADMAP.md) | **SemVer SSOT** — locked `a.b.c`; extras 0.10–0.19 in Hatch **0.19.4** | +| [`ROADMAP.md`](ROADMAP.md) | **SemVer SSOT** — locked `a.b.c`; extras 0.10–0.19 in Hatch **0.19.5** | | [`adr/ADR-001-gql-agent-wire.md`](adr/ADR-001-gql-agent-wire.md) | Accepted: GQL wire; **no Layer** | ## Wire — `grammar/` @@ -61,6 +61,7 @@ Multitask MUST for this product. Index: [`operations/README.md`](operations/READ | Doc | Role | |-----|------| | [`operations/multi-agent-sessions.md`](operations/multi-agent-sessions.md) | Multitask operating model (as-is 0.8; RSV + Path-B ingest shipped; full ACL modes to-be) | +| [`operations/honesty-c-wire-audit.md`](operations/honesty-c-wire-audit.md) | 0.19.5 hid / nickname emit audit | Product skill: [`.cursor/skills/memnet-reference/`](../.cursor/skills/memnet-reference/). SysML trail: MN-REQ-12 → [`sysml-models/outputs/multitask-case-study.md`](../sysml-models/outputs/multitask-case-study.md). diff --git a/docs/ROADMAP.md b/docs/ROADMAP.md index 543b2858..fab2f85d 100644 --- a/docs/ROADMAP.md +++ b/docs/ROADMAP.md @@ -4,9 +4,9 @@ **Audience:** product developers. Dialect teach = **GQL** ([`grammar/gql-wire-profile.md`](grammar/gql-wire-profile.md)). Product shape: [`SHAPE.md`](SHAPE.md). British English. -**Package now:** Hatch and PyPI are both **0.19.4** (`memnet-llm==0.19.4`). Numbered extras **0.10–0.19** are in this package (unchanged). [#148](https://github.com/chouswei/MemNet/pull/148) nickname-off-`pin_map` honesty `c` is published. GraphGlot parse front is on master (#109 @ 73a63c9b). Extra **0.14** claims `liveNeo4jClaimed=true`. **1.0** is still unclaimed (0.5–0.8). +**Package now:** Hatch **0.19.5** (`memnet.__version__`). Last published PyPI wheel is **`memnet-llm==0.19.4`** until upload. Numbered extras **0.10–0.19** are in this package (unchanged). [#148](https://github.com/chouswei/MemNet/pull/148) nickname-off-`pin_map` is published; **0.19.5** honesty `c` applies `SHAPE_DROP_KEYS` on GQL emit. GraphGlot parse front is on master (#109 @ 73a63c9b). Extra **0.14** claims `liveNeo4jClaimed=true`. **1.0** is still unclaimed (0.5–0.8). -**Last updated:** 2026-09-04 (`a.b.c` law locked; package **0.19.4** honesty `c` — nickname `id` off `pin_map` emit; extras **0.10–0.19** unchanged; Hatch + PyPI **0.19.4**; ranking honesty [#147](https://github.com/chouswei/MemNet/pull/147); do not claim **1.0**; do not invent a **0.20** extra). +**Last updated:** 2026-09-06 (`a.b.c` law locked; package **0.19.5** honesty `c` — store-identity keys off shaped emit; extras **0.10–0.19** unchanged; Hatch **0.19.5**; PyPI last **0.19.4** until upload; ranking honesty [#147](https://github.com/chouswei/MemNet/pull/147); do not claim **1.0**; do not invent a **0.20** extra). Patch notes: [`../CHANGELOG.md`](../CHANGELOG.md). CHANGELOG still follows Keep a Changelog. This file is how MemNet **interprets** SemVer — not a silent switch to npm-strict major=breaking for 0.x extras. @@ -39,13 +39,13 @@ One concern per `b`. Do **not** fuse two usage-method changes into one `b`. Skip Same usage method. Honesty, leftover naming, docs / skills, wheel / tag, caps, façade, **faster same goldfish loop** (`cue → pin_map → mutate` with no new agent step). -**0.19.1–0.19.4** were this (`session_close` + `sessions|n/max` + cap 1024 + ranking + nickname-off-`pin_map` shipped as `c` because the loop stayed the same). +**0.19.1–0.19.5** were this (`session_close` + `sessions|n/max` + cap 1024 + ranking + nickname-off-`pin_map` + `SHAPE_DROP_KEYS` shipped as `c` because the loop stayed the same). Pure efficiency / speed on the current loop is **`0.19.c`**, not `0.20`. ### Consequences -- Package stays **0.19.4** until a later cut. Extras **0.10–0.19** stay the owns table below. **1.0** stays unclaimed. +- Package stays **0.19.5** until a later cut. Extras **0.10–0.19** stay the owns table below. **1.0** stays unclaimed. - **1.0 does not wait** on 0.19.c, efficiency, HostSearch, Peak_L, catalog Snap, N-server, or GraphGlot. - A new cabinet adapter does **not** move `a`. Hosted Agens as a product service, first-class `PORT`, and full ACL modes / `session_token` stay Later / unnumbered until a cut exists. @@ -99,8 +99,8 @@ Handoff = **session id** (+ cue / write scope). Peers **re-`pin_map`** from labe | **0.7.0** | Live AgensGraph hydrate/flush; `liveCabinetClaimed=true`. Server not vendored. Fake + skip unless `MEMNET_AGENSGRAPH_URL` | **Shipped** (`v0.7.0`) | | **0.8.0** | GQL-only **teach** + product **shape for people** (`SHAPE.md`, playbook, application-note contract, Multitask honesty). Docs only. **No** engine cut. Cabinet stays claimed | **Shipped** (`v0.8.0`) | | **0.9.0** | Neo4j `DurableStoreAdapter` client (`memnet-llm[neo4j]`); factory both-URL rule; [`cabinet/neo4j-buffer.md`](cabinet/neo4j-buffer.md). Live round-trip claimed later as extra **0.14**. Cabinet extra, **not** a 1.0 gate | **Shipped** (`v0.9.0` era; extras later packaged as 0.19.0) | -| **0.10–0.19** | Numbered extras (table below). Each row is one `b` (usage-method revision). Same pattern as 0.9: **not** 1.0 gates | **Packaged** (Hatch + PyPI **0.19.4**; extras first shipped as 0.19.0) | -| **1.0.0** | **Claim** of **0.5 + 0.6 + 0.7 + 0.8**. Shape mature for people. Not GraphRAG. Not cabinet-only. Not a new engine. This is `a=1` | **Claim when coordinator tags** — package / PyPI **0.19.4** does not claim 1.0 | +| **0.10–0.19** | Numbered extras (table below). Each row is one `b` (usage-method revision). Same pattern as 0.9: **not** 1.0 gates | **Packaged** (Hatch **0.19.5**; PyPI last **0.19.4** until upload; extras first shipped as 0.19.0) | +| **1.0.0** | **Claim** of **0.5 + 0.6 + 0.7 + 0.8**. Shape mature for people. Not GraphRAG. Not cabinet-only. Not a new engine. This is `a=1` | **Claim when coordinator tags** — package **0.19.5** does not claim 1.0 | | **Later** | Grammar Open / hosted product / leftover ACL; N-server research (#47). GraphGlot parse-front is **shipped**. If **1.0 tags first**, remaining extras become **1.1, 1.2, …** with the same owns (`b` after the claim) | **Out** of 1.0 | **1.0 MAY ship from 0.9** (claim only). **0.10+ MAY ship before 1.0** as extras (`b` on `a=0`). Do not wait for the other. User-pack GQL rewrite is **sibling** (`chouswei/cursor-user-skills`), not this repo. @@ -135,7 +135,7 @@ Do **not** treat leftover 0.9 identity as a live-Neo4j claim. Do **not** claim * ## Numbered extras (0.10–0.19) -One concern per **`b`** (usage-method revision). Dependency order. **In package 0.19.0** (git tag by coordinator). Skip a `b` only if the coordinator writes the skip in CHANGELOG; do not fuse two usage-method changes into one `b`. Cuts on the same method are **`c`** (0.19.1–0.19.4). There is **no** 0.20 extra row. +One concern per **`b`** (usage-method revision). Dependency order. **In package 0.19.0** (git tag by coordinator). Skip a `b` only if the coordinator writes the skip in CHANGELOG; do not fuse two usage-method changes into one `b`. Cuts on the same method are **`c`** (0.19.1–0.19.5). There is **no** 0.20 extra row. | Version | Owns | Depends on | MUST NOT | |---------|------|------------|----------| diff --git a/docs/SHAPE.md b/docs/SHAPE.md index 584d7ff2..49b03138 100644 --- a/docs/SHAPE.md +++ b/docs/SHAPE.md @@ -74,7 +74,7 @@ Do not call MemNet a “shaped RAG” or a “shaped Cypher proxy”. **0.8.0** is this shape **taught for people** in-repo: this file, GQL-only playbook, application-note contract, Multitask honesty (RSV + Path-B ingest shipped; full ACL modes still to-be). -**1.0.0** is **0.5 + 0.6 + 0.7 + 0.8** claimed — the shape is mature for people: one GQL dialect, goldfish `pin_map`, gated mutate, cue-then-shape (including find when there is no ego), optional **proven** cabinet so \(S\) can outlive a process. Not GraphRAG. Not cabinet-only. Map and **`a.b.c` law:** [`ROADMAP.md`](ROADMAP.md). **Honest install:** Hatch and PyPI are both **0.19.4** (`memnet-llm==0.19.4`; extras 0.10–0.19; Neo4j client from 0.9; live claimed as extra **0.14**; [#148](https://github.com/chouswei/MemNet/pull/148) nickname-off-`pin_map` honesty `c` is published). Optional extras `[mcp]`, `[agensgraph]`, `[neo4j]` (drivers only). **1.0** stays unclaimed. +**1.0.0** is **0.5 + 0.6 + 0.7 + 0.8** claimed — the shape is mature for people: one GQL dialect, goldfish `pin_map`, gated mutate, cue-then-shape (including find when there is no ego), optional **proven** cabinet so \(S\) can outlive a process. Not GraphRAG. Not cabinet-only. Map and **`a.b.c` law:** [`ROADMAP.md`](ROADMAP.md). **Honest install:** Hatch **0.19.5**; last published PyPI wheel is **`memnet-llm==0.19.4`** until upload (extras 0.10–0.19; Neo4j client from 0.9; live claimed as extra **0.14**; [#148](https://github.com/chouswei/MemNet/pull/148) nickname-off-`pin_map` is published; 0.19.5 `SHAPE_DROP_KEYS`). Optional extras `[mcp]`, `[agensgraph]`, `[neo4j]` (drivers only). **1.0** stays unclaimed. --- diff --git a/docs/grammar/gql-wire-profile.md b/docs/grammar/gql-wire-profile.md index 52eaaab1..05d3491a 100644 --- a/docs/grammar/gql-wire-profile.md +++ b/docs/grammar/gql-wire-profile.md @@ -245,6 +245,7 @@ Shaped subgraph = ordered openCypher-family lines (or isomorphic structured grap - Include the seed neighbourhood and in-budget neighbours only (depth / view / max_rows). - **MUST NOT** emit nickname `{id:'…'}` on nodes or edges. Match the next mutate by labels + observable properties. Cue / find MAY still accept a nickname the agent already holds. +- **MUST NOT** emit store-identity keys `hid` / `_memnet_hid` / `elementId` (`SHAPE_DROP_KEYS`). Cabinet MERGE may keep `_memnet_hid` off this wire. - Hide recyclable / out-of-budget neighbours (MN-REQ-04). - Engine-law / control preamble rows **MAY** prepend when authorised — still not a binding table. - Ranking already excludes nickname `id` from the rank key. This cut also drops it from the composed text. diff --git a/docs/operations/README.md b/docs/operations/README.md index 104db0fb..f221c07c 100644 --- a/docs/operations/README.md +++ b/docs/operations/README.md @@ -5,5 +5,6 @@ Agent operating doctrine for this product (not domain recipes). | Doc | Role | |-----|------| | [`multi-agent-sessions.md`](multi-agent-sessions.md) | Multitask MUST — shared session, TCP/HTTP, parent settle | +| [`honesty-c-wire-audit.md`](honesty-c-wire-audit.md) | 0.19.5 honesty `c` — hid / nickname emit audit | Application pattern for `modelbasedPrj-*`: [`../application-notes/system/llm-system-dev-multitask.md`](../application-notes/system/llm-system-dev-multitask.md). Index: [`../README.md`](../README.md). diff --git a/docs/operations/honesty-c-wire-audit.md b/docs/operations/honesty-c-wire-audit.md new file mode 100644 index 00000000..1a411d37 --- /dev/null +++ b/docs/operations/honesty-c-wire-audit.md @@ -0,0 +1,32 @@ +# Honesty `c` wire audit (0.19.5) + +**Date:** 2026-09-06. **Package:** Hatch **0.19.5** (same-method cut on **0.19**). Last published PyPI wheel remains **`memnet-llm==0.19.4`** until upload. Numbered extras **0.10–0.19** unchanged. No usage-method `b`. No claim `a`. No 0.20. No 1.0. No `rag_query`. + +This note records the hot-path audit after [#148](https://github.com/chouswei/MemNet/pull/148) (nickname off `pin_map`) and [#147](https://github.com/chouswei/MemNet/pull/147) (observable rank). Chat is not SSOT. + +## Surfaces checked + +| Surface | Result | +|---------|--------| +| `PinMapComposer` / CLI `query pin-map` / leftover `query warm` | Nickname `id` already off (0.19.4). Store-identity keys now dropped by `SHAPE_DROP_KEYS` even if present in `Record.fields`. | +| Outline, CueConflict, `query find` / MCP `find` | Same composer / `emit_gql` / `record_to_gql_line` path. PASS after drop. | +| `export_pin_map` / CLI `export pin-map` / MCP `export_pin_map` | Body is composer text. PASS. | +| MCP `pin_map` JSON envelope | `stdout` is CLI pin-map. PASS (regression in `tests/test_honesty_c_wire.py`). | +| Mutate / GQL ack | `emit_item(..., as_mutate=True)` may echo nickname `id` the agent wrote. Hid / `_elN` / `_memnet_hid` / `elementId` stripped via `_emit_props`. | +| jsonl (`MemStore.to_jsonl_rows`) | `Record.hid` already `exclude=True`. Field copies of `SHAPE_DROP_KEYS` now popped. | +| Rank / offer order | `RANK_EXCLUDE_KEYS` = `{id, src, dist}` ∪ `SHAPE_DROP_KEYS`. Seed-first in `view=shell` still prefers the cued element, not peer hid. | +| RSV product errors | Were leaking hid (`id {_elN} already held…`). Now `anchor=` + `llm_id` only. Pin-map `## Reserves` already omitted held ids. | +| Cabinet `_memnet_hid` MERGE | Internal only. Tests in `tests/test_durable_store.py` still require it on adapter Cypher. | + +## Allowed leftovers (not this cut) + +- Cue / find / `match_nickname` **lookup** by nickname the agent already holds. +- leftover CLI `query walk` `@WALK:` hops still use endpoint **hids** (leftover hop debug, not goldfish). leftover `read list` / `query context` pipe `@TAG` may show nickname `id` as the first field. +- leftover `add`/`update` façades; leftover `--anchor`. +- Snapshot files (`# memnet-snapshot-v1`) are operator save/load, not `pin_map`. + +## Cut in 0.19.5 + +`#148` documented `DROP_KEYS={id,hid}` but `wire.py` had no strip and `_emit_props` would print `hid` / `_memnet_hid` / `elementId` if those keys sat on `Record.fields` (cabinet leftover / poison). SSOT is now `memnet.models.SHAPE_DROP_KEYS`, applied on GQL emit and jsonl. Nickname `id` stays off **shaped read** only (`include_nickname=False`). + +Regression: `tests/test_honesty_c_wire.py`. diff --git a/parts/common/memnet/memnet/__init__.py b/parts/common/memnet/memnet/__init__.py index c6349ec2..424e0072 100644 --- a/parts/common/memnet/memnet/__init__.py +++ b/parts/common/memnet/memnet/__init__.py @@ -1,3 +1,3 @@ """MemNet — mission working memory for LLM agents (session graph + pin_map).""" -__version__ = "0.19.4" +__version__ = "0.19.5" diff --git a/parts/common/memnet/memnet/gql.py b/parts/common/memnet/memnet/gql.py index 29e8a9ab..d870c441 100644 --- a/parts/common/memnet/memnet/gql.py +++ b/parts/common/memnet/memnet/gql.py @@ -19,6 +19,7 @@ gate_programs, parse_program, ) +from memnet.models import SHAPE_DROP_KEYS from memnet.tier_a import Document, EdgeRec, Field, NodeRec, Op, Section _IDENT = r"[A-Za-z_][A-Za-z0-9_]*" @@ -899,6 +900,8 @@ def _emit_py(obj: Any) -> str: def _emit_props(props: dict[str, str], *, omit_empty: bool = True) -> str: parts: list[str] = [] for k, v in props.items(): + if k in SHAPE_DROP_KEYS: + continue if omit_empty and v == "": continue parts.append(f"{k}: {_emit_value(v)}") @@ -917,7 +920,7 @@ def emit_node_shaped( if include_nickname and rid: props["id"] = rid for k, v in fields.items(): - if k == "id": + if k == "id" or k in SHAPE_DROP_KEYS: continue if k == "recycle" and v == "persistent": continue @@ -958,7 +961,7 @@ def emit_edge_shaped( if store_key in fields and fields[store_key]: rel_props[wire_key] = fields[store_key] for k, v in fields.items(): - if k in ( + if k in SHAPE_DROP_KEYS or k in ( "id", "src", "dist", diff --git a/parts/common/memnet/memnet/gql_codec.py b/parts/common/memnet/memnet/gql_codec.py index 440e8acd..f98dbbdf 100644 --- a/parts/common/memnet/memnet/gql_codec.py +++ b/parts/common/memnet/memnet/gql_codec.py @@ -24,6 +24,7 @@ round_trip_ok, soft_validate, ) +from memnet.models import SHAPE_DROP_KEYS __all__ = [ "GqlCodec", @@ -39,6 +40,7 @@ "emit_edge_shaped", "emit_item", "emit_node_shaped", + "SHAPE_DROP_KEYS", "looks_like_gql", "looks_like_legacy_layer_or_tier_a", "parse", diff --git a/parts/common/memnet/memnet/mem_store.py b/parts/common/memnet/memnet/mem_store.py index 1d0faaea..d111808f 100644 --- a/parts/common/memnet/memnet/mem_store.py +++ b/parts/common/memnet/memnet/mem_store.py @@ -9,7 +9,7 @@ from memnet.config import DEFAULT_QUERY_DEPTH, DEFAULT_QUERY_MAX_ROWS, Caps from memnet.exceptions import MemNetError from memnet.filter import record_matches -from memnet.models import Record, TagMap, new_hid +from memnet.models import Record, SHAPE_DROP_KEYS, TagMap, new_hid from memnet.observable_rank import node_rank_key, ranked from memnet.output import emit_wrn @@ -696,6 +696,10 @@ def to_jsonl_rows(self) -> list[dict]: if rec: dump = rec.model_dump() dump.pop("hid", None) + fields = dump.get("fields") + if isinstance(fields, dict): + for key in SHAPE_DROP_KEYS: + fields.pop(key, None) rows.append(dump) return rows diff --git a/parts/common/memnet/memnet/models.py b/parts/common/memnet/memnet/models.py index 50fdfb23..2a870d38 100644 --- a/parts/common/memnet/memnet/models.py +++ b/parts/common/memnet/memnet/models.py @@ -10,6 +10,10 @@ # Hidden store handle (elementId-style). Off the agent wire. Not a nickname. _HID_SEQ = itertools.count(1) +# Keys that MUST NOT appear on product GQL / jsonl emit. Cabinet MERGE may +# keep ``_memnet_hid`` in-process. Nickname ``id`` is dropped on pin_map only. +SHAPE_DROP_KEYS = frozenset({"hid", "_memnet_hid", "elementId"}) + def new_hid() -> str: return f"_el{next(_HID_SEQ)}" diff --git a/parts/common/memnet/memnet/mutate_gate.py b/parts/common/memnet/memnet/mutate_gate.py index 24f025e5..9ff79042 100644 --- a/parts/common/memnet/memnet/mutate_gate.py +++ b/parts/common/memnet/memnet/mutate_gate.py @@ -968,7 +968,7 @@ def _commit_records( and (old is None or old.fields.get("status") != "settled") ): warnings.append( - f"mission_settled|{rec.id or rec.hid}|next read use query pin-map from cue" + f"mission_settled|{rec.id or rec.tag}|next read use query pin-map from cue" ) self.ss.mark_written() except MemNetError: diff --git a/parts/common/memnet/memnet/neighbourhood_reserve.py b/parts/common/memnet/memnet/neighbourhood_reserve.py index 7e1fdbbd..8194ca03 100644 --- a/parts/common/memnet/memnet/neighbourhood_reserve.py +++ b/parts/common/memnet/memnet/neighbourhood_reserve.py @@ -140,10 +140,11 @@ def reserve( new_ids = collect_neighbourhood_ids(store, anchor, depth) conflict = table.ids_held_by_others(holder, new_ids) if conflict: - iid, lease = next(iter(conflict.items())) + lease = next(iter(conflict.values())) raise MemNetError( "reserve_conflict", - f"id {iid} already held by llm_id={lease.llm_id}", + f"neighbourhood already held by llm_id={lease.llm_id} " + f"anchor={lease.anchor}", ) # Same llm_id: union existing leases that overlap / deepen, refresh TTL. @@ -266,12 +267,14 @@ def check_mutate_ids( if not holder: raise MemNetError( "no_llm_id", - f"llm_id required to mutate reserved id {iid}", + f"llm_id required to mutate reserved neighbourhood " + f"anchor={lease.anchor}", ) if holder != lease.llm_id: raise MemNetError( "reserved", - f"id {iid} held by llm_id={lease.llm_id} until={_iso(lease.until)} " + f"neighbourhood held by llm_id={lease.llm_id} " + f"anchor={lease.anchor} until={_iso(lease.until)} " f"(caller llm_id={holder})", ) diff --git a/parts/common/memnet/memnet/observable_rank.py b/parts/common/memnet/memnet/observable_rank.py index 5f235ca2..eceb7dfc 100644 --- a/parts/common/memnet/memnet/observable_rank.py +++ b/parts/common/memnet/memnet/observable_rank.py @@ -13,11 +13,11 @@ from collections.abc import Callable, Iterable -from memnet.models import Record +from memnet.models import Record, SHAPE_DROP_KEYS # Nickname ``id`` and internal endpoint tokens stay off the rank key. # ``src`` / ``dist`` on EDG are hid (or leftover nick) handles, not payload. -RANK_EXCLUDE_KEYS = frozenset({"id", "src", "dist", "hid", "_memnet_hid"}) +RANK_EXCLUDE_KEYS = frozenset({"id", "src", "dist"}) | SHAPE_DROP_KEYS ResolveFn = Callable[[str], Record | None] diff --git a/parts/common/memnet/memnet/pin_map_composer.py b/parts/common/memnet/memnet/pin_map_composer.py index 325e51bc..4f769882 100644 --- a/parts/common/memnet/memnet/pin_map_composer.py +++ b/parts/common/memnet/memnet/pin_map_composer.py @@ -1,8 +1,10 @@ """PinMapComposer / PinMapShapedRead — live pin map as shaped GQL subgraph. Emits openCypher-family node and relationship lines (gql-wire-profile §5). -Nickname property ``id`` stays off this emit (same effect as DROP_KEYS={id,hid} -on the wire text). Cue / find / match_nickname may still look up a nickname. +Nickname property ``id`` stays off this emit (``include_nickname=False``). +Store-identity keys (``hid`` / ``_memnet_hid`` / ``elementId``) are dropped by +``gql.SHAPE_DROP_KEYS`` on every GQL emit. Cue / find / match_nickname may +still look up a nickname. Optional ``view=`` grain: ``shell`` / ``interior`` taught; ``flowchart`` / ``parts`` / ``statechart`` accepted with soft shell caps. diff --git a/project.toml b/project.toml index 747ee0b5..b7d98dab 100644 --- a/project.toml +++ b/project.toml @@ -1,7 +1,7 @@ [project] name = "memnet" repo = "MemNet" -version = "0.19.4" +version = "0.19.5" description = "Mission working memory for LLM agents: session graph + pin_map, not a RAG corpus" # No pcba-libs pins — software-only system (see LAYOUT.md). diff --git a/tests/test_honesty_c_wire.py b/tests/test_honesty_c_wire.py new file mode 100644 index 00000000..3dfc4faa --- /dev/null +++ b/tests/test_honesty_c_wire.py @@ -0,0 +1,205 @@ +"""Honesty c: store-identity keys stay off product shaped emit.""" + +from __future__ import annotations + +import asyncio +import json +import re + +from typer.testing import CliRunner + +from memnet.cli import app +from memnet.config import examples_dir +from memnet.gql import emit_node_shaped +from memnet.models import SHAPE_DROP_KEYS, Record +from memnet.mutate_gate import MutateGate +from memnet.observable_rank import node_rank_key +from memnet.pin_map_composer import PinMapComposer +from memnet.pin_map_export import export_pin_map +from memnet.session import get_session, open_session + +runner = CliRunner() +_CODING_MAP = examples_dir() / "schema.coding.example.txt" +_ID_PROP = re.compile(r"(?:\{|,)\s*id\s*:") + + +def _open() -> str: + r = runner.invoke(app, ["session", "open", "--map-file", str(_CODING_MAP)]) + assert r.exit_code == 0, r.stderr + return r.stdout.strip().split("|")[0].replace("@SESSION: ", "") + + +def _assert_clean_shape(text: str) -> None: + assert "_el" not in text + assert "_memnet_hid" not in text + assert "elementId" not in text + assert not _ID_PROP.search(text) + for key in SHAPE_DROP_KEYS: + assert f"{key}:" not in text + + +def _poison(rec: Record) -> None: + rec.fields["hid"] = rec.hid + rec.fields["_memnet_hid"] = rec.hid + rec.fields["elementId"] = "4:cabinet" + rec.fields["id"] = "NICK_POISON" + + +def test_shape_drop_keys_constant(): + assert SHAPE_DROP_KEYS == frozenset({"hid", "_memnet_hid", "elementId"}) + + +def test_emit_node_shaped_drops_store_identity_even_with_nickname(): + line = emit_node_shaped( + "TSK", + "NICK", + { + "goal": "visible", + "hid": "_el9", + "_memnet_hid": "cab", + "elementId": "4:x", + }, + include_nickname=True, + ) + assert "goal: 'visible'" in line + assert "id: 'NICK'" in line + _assert_clean_shape(line.replace("id: 'NICK'", "")) + assert "_el9" not in line + assert "4:x" not in line + + +def test_rank_key_excludes_element_id(): + a = Record(tag="TSK", fields={"goal": "same", "elementId": "4:aaa"}) + b = Record(tag="TSK", fields={"goal": "same", "elementId": "4:zzz"}) + a.hid = "_el99" + b.hid = "_el1" + assert node_rank_key(a) == node_rank_key(b) + assert "elementId" not in str(node_rank_key(a)) + assert "4:aaa" not in str(node_rank_key(a)) + + +def test_poisoned_fields_off_pin_map_find_export_jsonl(memnet_temp): + del memnet_temp + ss = open_session(map_file=str(_CODING_MAP)) + MutateGate(ss).apply( + ["CREATE (:TSK {goal: 'poison-cut', status: 'in_progress'})"], + mode="add", + ) + rec = [r for r in ss.store.list_records("TSK") if r.fields.get("goal") == "poison-cut"][0] + _poison(rec) + + _rows, mapped = PinMapComposer(ss).compose( + anchor=None, + kind="TSK", + locators=[("goal", "poison-cut")], + ) + _assert_clean_shape(mapped) + assert "poison-cut" in mapped + assert "NICK_POISON" not in mapped + + exported = export_pin_map(ss, kind="TSK", locators=[("goal", "poison-cut")]) + _assert_clean_shape(exported.body) + assert exported.body == mapped + + find = runner.invoke( + app, + [ + "query", + "find", + "--kind", + "TSK", + "--locator", + "goal=poison-cut", + "--limit", + "4", + "--session", + ss.meta.session_id, + ], + ) + assert find.exit_code == 0, find.stderr + _assert_clean_shape(find.stdout) + + conflict_ss = open_session(map_file=str(_CODING_MAP)) + MutateGate(conflict_ss).apply( + [ + "CREATE (:TSK {goal: 'same-goal', status: 'open'})", + "CREATE (:TSK {goal: 'same-goal', status: 'open'})", + ], + mode="add", + ) + for row in conflict_ss.store.list_records("TSK"): + _poison(row) + _crows, conflict = PinMapComposer(conflict_ss).compose(anchor=None, kind="TSK") + assert "CueConflict" in conflict + _assert_clean_shape(conflict) + assert "NICK_POISON" not in conflict + + dump = ss.store.to_jsonl_rows() + blob = json.dumps(dump) + assert "_memnet_hid" not in blob + assert "elementId" not in blob + assert all("hid" not in row for row in dump) + + +def test_mutate_ack_and_cli_pin_map_hide_hid(memnet_temp): + sid = _open() + create = runner.invoke( + app, + ["mutate", "--stdin", "--session", sid], + input="CREATE (:TSK {goal: 'ack-hid', status: 'in_progress'})\n", + ) + assert create.exit_code == 0, create.stderr + _assert_clean_shape(create.stdout) + pin = runner.invoke( + app, + [ + "query", + "pin-map", + "--kind", + "TSK", + "--locator", + "goal=ack-hid", + "--session", + sid, + ], + ) + assert pin.exit_code == 0, pin.stderr + _assert_clean_shape(pin.stdout) + outlined = runner.invoke(app, ["query", "pin-map", "--session", sid]) + assert outlined.exit_code == 0, outlined.stderr + _assert_clean_shape(outlined.stdout) + + +def test_mcp_pin_map_and_find_drop_identity_keys(memnet_temp, monkeypatch): + monkeypatch.setenv("MEMNET_TEST_INLINE", "1") + from memnet_mcp.server import find, pin_map, session_open + + open_raw = asyncio.run( + session_open(map_lines=_CODING_MAP.read_text(encoding="utf-8").strip().splitlines()) + ) + payload = json.loads(open_raw) + assert payload["exit_code"] == 0 + sid = payload["session_id"] + monkeypatch.setenv("MEMNET_SESSION", sid) + add = runner.invoke( + app, + ["mutate", "--stdin", "--session", sid], + input="CREATE (:TSK {id: 'TSK_mcp', goal: 'mcp-wire', status: 'open'})\n", + ) + assert add.exit_code == 0, add.stderr + rec = get_session(sid).store.match_nickname("TSK_mcp")[0] + _poison(rec) + + pin_raw = asyncio.run( + pin_map(kind="TSK", locators=["goal=mcp-wire"], session=sid), + ) + pin_payload = json.loads(pin_raw) + assert pin_payload["exit_code"] == 0, pin_payload + _assert_clean_shape(pin_payload["stdout"]) + assert "TSK_mcp" not in pin_payload["stdout"] + assert "mcp-wire" in pin_payload["stdout"] + + find_raw = asyncio.run(find(limit=4, kind="TSK", locators=["goal=mcp-wire"], session=sid)) + find_payload = json.loads(find_raw) + assert find_payload["exit_code"] == 0, find_payload + _assert_clean_shape(find_payload["stdout"]) diff --git a/tests/test_neighbourhood_reserve.py b/tests/test_neighbourhood_reserve.py index f0f6c5d8..3b954196 100644 --- a/tests/test_neighbourhood_reserve.py +++ b/tests/test_neighbourhood_reserve.py @@ -85,10 +85,13 @@ def test_reserve_blocks_foreign_mutate(memnet_temp, schema_file): with pytest.raises(MemNetError) as ei: MutateGate(ss).apply([_PATCH], mode="update", llm_id="coder_b") assert ei.value.code == "reserved" + assert "_el" not in ei.value.message + assert "_memnet_hid" not in ei.value.message with pytest.raises(MemNetError) as ei2: MutateGate(ss).apply([_PATCH], mode="update") assert ei2.value.code == "no_llm_id" + assert "_el" not in ei2.value.message MutateGate(ss).apply([_PATCH], mode="update", llm_id="coder_a") assert ss.store.get("PLR01").fields["wealth"] == "9" @@ -119,6 +122,8 @@ def test_reserve_conflict_on_overlap(memnet_temp, schema_file): now=t0, ) assert ei.value.code == "reserve_conflict" + assert "_el" not in ei.value.message + assert "PLR01" in ei.value.message def test_extend_and_release(memnet_temp, schema_file): From 1bdaeaef08617df1c358a32b9290d885c9be8e1c Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Sun, 6 Sep 2026 10:51:40 +0000 Subject: [PATCH 2/2] Fix ruff import order and RSV error wrapping Keep SHAPE_DROP_KEYS import isort-clean so CI format check passes. Co-authored-by: chouswei --- parts/common/memnet/memnet/mem_store.py | 2 +- parts/common/memnet/memnet/neighbourhood_reserve.py | 6 ++---- parts/common/memnet/memnet/observable_rank.py | 2 +- 3 files changed, 4 insertions(+), 6 deletions(-) diff --git a/parts/common/memnet/memnet/mem_store.py b/parts/common/memnet/memnet/mem_store.py index d111808f..21166070 100644 --- a/parts/common/memnet/memnet/mem_store.py +++ b/parts/common/memnet/memnet/mem_store.py @@ -9,7 +9,7 @@ from memnet.config import DEFAULT_QUERY_DEPTH, DEFAULT_QUERY_MAX_ROWS, Caps from memnet.exceptions import MemNetError from memnet.filter import record_matches -from memnet.models import Record, SHAPE_DROP_KEYS, TagMap, new_hid +from memnet.models import SHAPE_DROP_KEYS, Record, TagMap, new_hid from memnet.observable_rank import node_rank_key, ranked from memnet.output import emit_wrn diff --git a/parts/common/memnet/memnet/neighbourhood_reserve.py b/parts/common/memnet/memnet/neighbourhood_reserve.py index 8194ca03..b4e11c22 100644 --- a/parts/common/memnet/memnet/neighbourhood_reserve.py +++ b/parts/common/memnet/memnet/neighbourhood_reserve.py @@ -143,8 +143,7 @@ def reserve( lease = next(iter(conflict.values())) raise MemNetError( "reserve_conflict", - f"neighbourhood already held by llm_id={lease.llm_id} " - f"anchor={lease.anchor}", + f"neighbourhood already held by llm_id={lease.llm_id} anchor={lease.anchor}", ) # Same llm_id: union existing leases that overlap / deepen, refresh TTL. @@ -267,8 +266,7 @@ def check_mutate_ids( if not holder: raise MemNetError( "no_llm_id", - f"llm_id required to mutate reserved neighbourhood " - f"anchor={lease.anchor}", + f"llm_id required to mutate reserved neighbourhood anchor={lease.anchor}", ) if holder != lease.llm_id: raise MemNetError( diff --git a/parts/common/memnet/memnet/observable_rank.py b/parts/common/memnet/memnet/observable_rank.py index eceb7dfc..afdc2c82 100644 --- a/parts/common/memnet/memnet/observable_rank.py +++ b/parts/common/memnet/memnet/observable_rank.py @@ -13,7 +13,7 @@ from collections.abc import Callable, Iterable -from memnet.models import Record, SHAPE_DROP_KEYS +from memnet.models import SHAPE_DROP_KEYS, Record # Nickname ``id`` and internal endpoint tokens stay off the rank key. # ``src`` / ``dist`` on EDG are hid (or leftover nick) handles, not payload.