Skip to content

Commit fdcd0b4

Browse files
jpheinclaude
andcommitted
feat(cli): add why + tunnels fast-path daemon commands (slice of #191)
Two more verbs join the daemon-fast-path family (tags / overlap / list / move / stats / cypher / graph): mempalace why <drawer_id> and mempalace tunnels [--wing W] [--passive]. mempalace why composes three read-only daemon calls into a single explain-this-drawer report — no searcher.py changes, pure orchestration. mempalace_get_drawer for location + tags + content snippet; a read-only Cypher hop over the drawer's :MENTIONS-Entity edges (drawer ID sanitized through sanitize_kg_value and inlined as a Cypher literal since /cypher takes no params); mempalace_search keyed on the drawer's own first non-blank paragraph for nearest semantic neighbors (drawer itself filtered out). A 500 on the entities hop (AGE not configured on a chroma-only backend) degrades to an empty MENTIONS block — the report still renders. The minimum-viable debugging lens for retrieval calibration work. mempalace tunnels wraps the existing mempalace_list_tunnels MCP tool — default returns explicit (agent-wired) tunnels only; --passive opts in to the inferred passive overlap per issue #75; --wing W filters to tunnels touching one wing. 36 new tests follow the cmd_tags/cmd_overlap mock-urlopen pattern: three-block report rendering, self-drawer filtered from neighbors, JSON envelope shapes, drawer-id-as-literal Cypher composition, /cypher 500 degrades cleanly, --neighbors/--entities limits, missing/whitespace drawer_id rejection, daemon-down/unreachable/drawer-not-found/search-down exit codes, kind column in the tunnels table, --passive opts in mixed kinds, --wing/--passive forwarding to mempalace_list_tunnels arguments, inner-error envelopes, argparse wiring incl. negative-limit rejection. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent db413bb commit fdcd0b4

8 files changed

Lines changed: 1556 additions & 152 deletions

File tree

FORK_CHANGELOG.md

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,56 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
2424
### Added
2525

2626

27+
- **mempalace why + tunnels — explain a drawer + inventory cross-wing tunnels (slice of #191)** ([`PLACEHOLDER`](https://github.com/techempower-org/mempalace/commit/PLACEHOLDER))
28+
Two more verbs join the daemon-fast-path family started by
29+
``tags`` / ``overlap`` / ``list`` / ``move`` / ``stats`` /
30+
``cypher`` / ``graph``. Same shape: daemon-only, ``--format=table``
31+
(default) or ``--format=json`` / ``--json``, sibling failure-mode
32+
exit codes (1 for daemon-down or unreachable, 2 for input
33+
validation or inner-error envelopes).
34+
35+
``mempalace why <drawer_id>`` answers "why would this drawer
36+
surface?" by composing three read-only daemon calls into one
37+
report — no ``searcher.py`` changes, pure orchestration over
38+
existing read paths. (1) ``mempalace_get_drawer`` for the wing,
39+
room, tag set, and content snippet. (2) A read-only Cypher hop
40+
against AGE for the drawer's ``:MENTIONS``-Entity edges, top-N
41+
by mention count (``--entities``, default 10) — the drawer ID
42+
is sanitized through ``sanitize_kg_value`` and inlined as a
43+
Cypher literal because the daemon's ``/cypher`` endpoint accepts
44+
only ``{cypher, graph}``. (3) ``mempalace_search`` keyed on the
45+
drawer's own first non-blank paragraph for the nearest semantic
46+
neighbors (``--neighbors``, default 5); the drawer itself is
47+
filtered out (self-distance 0). A 500 on the entities hop (AGE
48+
not configured on a chroma-only backend) degrades to an empty
49+
MENTIONS block — the report still renders. The minimum-viable
50+
"debugging lens" for retrieval calibration work: see *where* a
51+
drawer lives, *what* it links to in the KG, and *what siblings*
52+
it sits next to in vector space — all without standing up a
53+
notebook.
54+
55+
``mempalace tunnels [--wing W] [--passive]`` wraps the existing
56+
``mempalace_list_tunnels`` MCP tool. Default returns explicit
57+
tunnels only (the agent-wired records at
58+
``~/.mempalace/tunnels.json``); ``--passive`` opts in to the
59+
inferred passive overlap (rooms appearing in 2+ wings,
60+
computed from ``graph_stats`` per issue #75's
61+
explicit/passive merge). ``--wing W`` filters to tunnels
62+
touching one wing. The daemon already had this read path
63+
exposed via MCP; the missing piece was the CLI surface —
64+
previously you had to call ``stats`` and grep, or hit the
65+
MCP endpoint directly with curl.
66+
67+
Slice of #191 (Polished CLI experience). No upstream PR yet;
68+
both verbs depend on daemon-side state (``mempalace_list_tags``,
69+
``mempalace_list_tunnels``, ``/cypher``, ``mempalace_search``)
70+
that upstream doesn't have, so they ship fork-only until either
71+
the daemon merges or there's a generic local-palace adapter.
72+
73+
*Tests:* 36 — tests/test_cli_why.py (three-block report with location/MENTIONS/NEIGHBORS rendering, self-drawer filtered from neighbors, JSON envelope shape, get_drawer + /cypher + mempalace_search composition with drawer-id-as-literal Cypher, /cypher 500 degrades to empty entities not failure, --neighbors/--entities limits, missing/whitespace drawer_id rejection, daemon-down/unreachable + drawer-not-found + search-down exit codes, argparse wiring incl. --neighbors negative rejection); tests/test_cli_tunnels.py (table with kind column, empty payload, --wing scope label in header, --passive opts in mixed kinds, JSON pass-through, --wing/--passive forwarding to mempalace_list_tunnels arguments, daemon-down + inner-error exit codes, argparse wiring)
74+
*Files:* `mempalace/cli.py`, `tests/test_cli_why.py`, `tests/test_cli_tunnels.py`
75+
76+
2777
- **RRF vs convex-blend rerank — A/B measurement on our corpus (#162)** ([`ea5d567`](https://github.com/techempower-org/mempalace/commit/ea5d567))
2878
Closes the measurement promised by #162 (the harness landed
2979
in #247 — this PR runs it). Adds a self-contained ``--mine-corpus``

README.md

Lines changed: 77 additions & 76 deletions
Large diffs are not rendered by default.

docs/fork-changes.yaml

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,63 @@
2424

2525
entries:
2626

27+
- id: cli-why-and-tunnels-fast-path
28+
date: 2026-05-28
29+
bucket: Added
30+
commit: PLACEHOLDER
31+
area: CLI
32+
summary: "mempalace why + tunnels — explain a drawer + inventory cross-wing tunnels (slice of #191)"
33+
tests: "36 — tests/test_cli_why.py (three-block report with location/MENTIONS/NEIGHBORS rendering, self-drawer filtered from neighbors, JSON envelope shape, get_drawer + /cypher + mempalace_search composition with drawer-id-as-literal Cypher, /cypher 500 degrades to empty entities not failure, --neighbors/--entities limits, missing/whitespace drawer_id rejection, daemon-down/unreachable + drawer-not-found + search-down exit codes, argparse wiring incl. --neighbors negative rejection); tests/test_cli_tunnels.py (table with kind column, empty payload, --wing scope label in header, --passive opts in mixed kinds, JSON pass-through, --wing/--passive forwarding to mempalace_list_tunnels arguments, daemon-down + inner-error exit codes, argparse wiring)"
34+
files:
35+
- mempalace/cli.py
36+
- tests/test_cli_why.py
37+
- tests/test_cli_tunnels.py
38+
body: |
39+
Two more verbs join the daemon-fast-path family started by
40+
``tags`` / ``overlap`` / ``list`` / ``move`` / ``stats`` /
41+
``cypher`` / ``graph``. Same shape: daemon-only, ``--format=table``
42+
(default) or ``--format=json`` / ``--json``, sibling failure-mode
43+
exit codes (1 for daemon-down or unreachable, 2 for input
44+
validation or inner-error envelopes).
45+
46+
``mempalace why <drawer_id>`` answers "why would this drawer
47+
surface?" by composing three read-only daemon calls into one
48+
report — no ``searcher.py`` changes, pure orchestration over
49+
existing read paths. (1) ``mempalace_get_drawer`` for the wing,
50+
room, tag set, and content snippet. (2) A read-only Cypher hop
51+
against AGE for the drawer's ``:MENTIONS``-Entity edges, top-N
52+
by mention count (``--entities``, default 10) — the drawer ID
53+
is sanitized through ``sanitize_kg_value`` and inlined as a
54+
Cypher literal because the daemon's ``/cypher`` endpoint accepts
55+
only ``{cypher, graph}``. (3) ``mempalace_search`` keyed on the
56+
drawer's own first non-blank paragraph for the nearest semantic
57+
neighbors (``--neighbors``, default 5); the drawer itself is
58+
filtered out (self-distance 0). A 500 on the entities hop (AGE
59+
not configured on a chroma-only backend) degrades to an empty
60+
MENTIONS block — the report still renders. The minimum-viable
61+
"debugging lens" for retrieval calibration work: see *where* a
62+
drawer lives, *what* it links to in the KG, and *what siblings*
63+
it sits next to in vector space — all without standing up a
64+
notebook.
65+
66+
``mempalace tunnels [--wing W] [--passive]`` wraps the existing
67+
``mempalace_list_tunnels`` MCP tool. Default returns explicit
68+
tunnels only (the agent-wired records at
69+
``~/.mempalace/tunnels.json``); ``--passive`` opts in to the
70+
inferred passive overlap (rooms appearing in 2+ wings,
71+
computed from ``graph_stats`` per issue #75's
72+
explicit/passive merge). ``--wing W`` filters to tunnels
73+
touching one wing. The daemon already had this read path
74+
exposed via MCP; the missing piece was the CLI surface —
75+
previously you had to call ``stats`` and grep, or hit the
76+
MCP endpoint directly with curl.
77+
78+
Slice of #191 (Polished CLI experience). No upstream PR yet;
79+
both verbs depend on daemon-side state (``mempalace_list_tags``,
80+
``mempalace_list_tunnels``, ``/cypher``, ``mempalace_search``)
81+
that upstream doesn't have, so they ship fork-only until either
82+
the daemon merges or there's a generic local-palace adapter.
83+
2784
- id: cli-tags-and-overlap-fast-path
2885
date: 2026-05-28
2986
bucket: Added

0 commit comments

Comments
 (0)