Compare commits

...

2 Commits

Author SHA1 Message Date
Alexander Whitestone
5c8ba43dbf feat: add Ezra MemPalace operator bundle (#570)
Some checks failed
Self-Healing Smoke / self-healing-smoke (pull_request) Failing after 31s
Agent PR Gate / gate (pull_request) Failing after 1m2s
Smoke Test / smoke (pull_request) Failing after 28s
Agent PR Gate / report (pull_request) Successful in 19s
2026-04-21 07:22:18 -04:00
Alexander Whitestone
cf461ec99f wip: extend Ezra MemPalace regression coverage (#570) 2026-04-21 07:20:25 -04:00
3 changed files with 255 additions and 2 deletions

View File

@@ -64,11 +64,95 @@ people: []
projects: []
```
## Native MCP config snippet
```yaml
mcp_servers:
mempalace:
command: python
args:
- -m
- mempalace.mcp_server
```
## Session start wake-up hook
Drop this into Ezra's session start wrapper (or source it before starting Hermes) so the wake-up context is refreshed automatically.
```bash
#!/usr/bin/env bash
set -euo pipefail
if command -v mempalace >/dev/null 2>&1; then
mkdir -p "~/.hermes/wakeups"
mempalace wake-up > "~/.hermes/wakeups/ezra_home.txt"
export HERMES_MEMPALACE_WAKEUP_FILE="~/.hermes/wakeups/ezra_home.txt"
printf '[MemPalace] wake-up context refreshed: %s\n' "$HERMES_MEMPALACE_WAKEUP_FILE"
fi
```
## Metrics reply for #568
Use this as the ready-to-fill comment body after the live Ezra run:
```md
# Metrics reply for #568
Refs #570.
## Ezra live run
- package: mempalace==3.0.0
- hermes home: ~/.hermes/
- sessions dir: ~/.hermes/sessions/
- palace path: ~/.mempalace/palace
- wake-up file: ~/.hermes/wakeups/ezra_home.txt
## Results to fill in
- install result: [pass/fail + note]
- init result: [pass/fail + note]
- mine home duration: [seconds]
- mine sessions duration: [seconds]
- corpus size after mining: [drawers/rooms]
- query 1: [query] -> [top result]
- query 2: [query] -> [top result]
- query 3: [query] -> [top result]
- wake-up context token count: [tokens]
- MCP wiring succeeded: [yes/no]
- session-start hook enabled: [yes/no]
## Commands actually used
```bash
pip install mempalace==3.0.0
mempalace init ~/.hermes/ --yes
echo "" | mempalace mine ~/.hermes/
echo "" | mempalace mine ~/.hermes/sessions/ --mode convos
mempalace search "your common queries"
mempalace wake-up
hermes mcp add mempalace -- python -m mempalace.mcp_server
```
```
## Operator-ready support bundle
Generate copy-ready files for Ezra's host with:
```bash
python3 scripts/mempalace_ezra_integration.py --bundle-dir /tmp/ezra-mempalace-bundle
```
That bundle writes:
- `mempalace.yaml`
- `hermes-mcp-mempalace.yaml`
- `session-start-mempalace.sh`
- `issue-568-comment-template.md`
## Why this shape
- `wing: ezra_home` matches the issue's Ezra-specific integration target.
- `rooms` split the mined material into sessions, config, and docs to keep retrieval interpretable.
- Mining commands pipe empty stdin to avoid the interactive entity-detector hang noted in the evaluation.
- `mcp_servers:` gives the native-MCP equivalent of `hermes mcp add ...`, so the operator can choose either path.
- `HERMES_MEMPALACE_WAKEUP_FILE` makes the wake-up context explicit and reusable from the session-start boundary.
## Gotchas
@@ -86,6 +170,7 @@ After live execution on Ezra's actual environment, post back to #568 with:
- 2-3 real search queries + retrieved results
- wake-up context token count
- whether MCP wiring succeeded
- whether the session-start hook exported `HERMES_MEMPALACE_WAKEUP_FILE`
## Honest scope boundary

View File

@@ -1,5 +1,7 @@
#!/usr/bin/env python3
"""Prepare a MemPalace v3.0.0 integration packet for Ezra's Hermes home."""
"""Prepare an executable MemPalace v3.0.0 integration bundle for Ezra's Hermes home."""
from __future__ import annotations
import argparse
import json
@@ -38,6 +40,91 @@ def build_yaml_template(wing: str, palace_path: str) -> str:
)
def build_mcp_config_snippet() -> str:
return (
"mcp_servers:\n"
" mempalace:\n"
" command: python\n"
" args:\n"
" - -m\n"
" - mempalace.mcp_server\n"
)
def build_session_start_hook(wakeup_file: str) -> str:
wakeup_path = wakeup_file.rstrip()
wakeup_dir = wakeup_path.rsplit("/", 1)[0]
return (
"#!/usr/bin/env bash\n"
"set -euo pipefail\n\n"
"if command -v mempalace >/dev/null 2>&1; then\n"
f" mkdir -p \"{wakeup_dir}\"\n"
f" mempalace wake-up > \"{wakeup_path}\"\n"
f" export HERMES_MEMPALACE_WAKEUP_FILE=\"{wakeup_path}\"\n"
" printf '[MemPalace] wake-up context refreshed: %s\\n' \"$HERMES_MEMPALACE_WAKEUP_FILE\"\n"
"fi\n"
)
def build_report_back_template(plan: dict) -> str:
return f"""# Metrics reply for #568
Refs #570.
## Ezra live run
- package: {plan['package_spec']}
- hermes home: {plan['hermes_home']}
- sessions dir: {plan['sessions_dir']}
- palace path: {plan['palace_path']}
- wake-up file: {plan['wakeup_file']}
## Results to fill in
- install result: [pass/fail + note]
- init result: [pass/fail + note]
- mine home duration: [seconds]
- mine sessions duration: [seconds]
- corpus size after mining: [drawers/rooms]
- query 1: [query] -> [top result]
- query 2: [query] -> [top result]
- query 3: [query] -> [top result]
- wake-up context token count: [tokens]
- MCP wiring succeeded: [yes/no]
- session-start hook enabled: [yes/no]
## Commands actually used
```bash
{plan['install_command']}
{plan['init_command']}
{plan['mine_home_command']}
{plan['mine_sessions_command']}
{plan['search_command']}
{plan['wake_up_command']}
{plan['mcp_command']}
```
"""
def build_bundle_files(plan: dict) -> dict[str, str]:
return {
"mempalace.yaml": plan["yaml_template"],
"hermes-mcp-mempalace.yaml": plan["mcp_config_snippet"],
"session-start-mempalace.sh": plan["session_start_hook"],
"issue-568-comment-template.md": plan["report_back_template"],
}
def write_bundle_files(bundle_dir: str | Path, plan: dict) -> list[Path]:
output_dir = Path(bundle_dir).expanduser()
output_dir.mkdir(parents=True, exist_ok=True)
written: list[Path] = []
for relative_path, content in build_bundle_files(plan).items():
path = output_dir / relative_path
path.parent.mkdir(parents=True, exist_ok=True)
path.write_text(content, encoding="utf-8")
written.append(path)
return written
def build_plan(overrides: dict | None = None) -> dict:
overrides = overrides or {}
hermes_home = overrides.get("hermes_home", DEFAULT_HERMES_HOME)
@@ -47,6 +134,11 @@ def build_plan(overrides: dict | None = None) -> dict:
yaml_template = build_yaml_template(wing=wing, palace_path=palace_path)
config_home = hermes_home[:-1] if hermes_home.endswith("/") else hermes_home
wakeup_dir = overrides.get("wakeup_dir", f"{config_home}/wakeups")
wakeup_file = f"{wakeup_dir.rstrip('/')}/{wing}.txt"
mcp_config_snippet = build_mcp_config_snippet()
session_start_hook = build_session_start_hook(wakeup_file)
plan = {
"package_spec": PACKAGE_SPEC,
"hermes_home": hermes_home,
@@ -54,6 +146,8 @@ def build_plan(overrides: dict | None = None) -> dict:
"palace_path": palace_path,
"wing": wing,
"config_path": f"{config_home}/mempalace.yaml",
"wakeup_dir": wakeup_dir,
"wakeup_file": wakeup_file,
"install_command": f"pip install {PACKAGE_SPEC}",
"init_command": f"mempalace init {hermes_home} --yes",
"mine_home_command": f"echo \"\" | mempalace mine {hermes_home}",
@@ -62,6 +156,8 @@ def build_plan(overrides: dict | None = None) -> dict:
"wake_up_command": "mempalace wake-up",
"mcp_command": "hermes mcp add mempalace -- python -m mempalace.mcp_server",
"yaml_template": yaml_template,
"mcp_config_snippet": mcp_config_snippet,
"session_start_hook": session_start_hook,
"gotchas": [
"`mempalace init` is still interactive in room approval flow; write mempalace.yaml manually if the init output stalls.",
"The yaml key is `wing:` not `wings:`. Using the wrong key causes mine/setup failures.",
@@ -70,6 +166,7 @@ def build_plan(overrides: dict | None = None) -> dict:
"Report Ezra's before/after metrics back to issue #568 after live installation and retrieval tests.",
],
}
plan["report_back_template"] = build_report_back_template(plan)
return plan
@@ -101,11 +198,49 @@ YAML
{plan['yaml_template'].rstrip()}
```
## Native MCP config snippet
```yaml
{plan['mcp_config_snippet'].rstrip()}
```
## Session start wake-up hook
Drop this into Ezra's session start wrapper (or source it before starting Hermes) so the wake-up context is refreshed automatically.
```bash
{plan['session_start_hook'].rstrip()}
```
## Metrics reply for #568
Use this as the ready-to-fill comment body after the live Ezra run:
```md
{plan['report_back_template'].rstrip()}
```
## Operator-ready support bundle
Generate copy-ready files for Ezra's host with:
```bash
python3 scripts/mempalace_ezra_integration.py --bundle-dir /tmp/ezra-mempalace-bundle
```
That bundle writes:
- `mempalace.yaml`
- `hermes-mcp-mempalace.yaml`
- `session-start-mempalace.sh`
- `issue-568-comment-template.md`
## Why this shape
- `wing: {plan['wing']}` matches the issue's Ezra-specific integration target.
- `rooms` split the mined material into sessions, config, and docs to keep retrieval interpretable.
- Mining commands pipe empty stdin to avoid the interactive entity-detector hang noted in the evaluation.
- `mcp_servers:` gives the native-MCP equivalent of `hermes mcp add ...`, so the operator can choose either path.
- `HERMES_MEMPALACE_WAKEUP_FILE` makes the wake-up context explicit and reusable from the session-start boundary.
## Gotchas
@@ -119,6 +254,7 @@ After live execution on Ezra's actual environment, post back to #568 with:
- 2-3 real search queries + retrieved results
- wake-up context token count
- whether MCP wiring succeeded
- whether the session-start hook exported `HERMES_MEMPALACE_WAKEUP_FILE`
## Honest scope boundary
@@ -132,6 +268,7 @@ def main() -> None:
parser.add_argument("--sessions-dir", default=DEFAULT_SESSIONS_DIR)
parser.add_argument("--palace-path", default=DEFAULT_PALACE_PATH)
parser.add_argument("--wing", default=DEFAULT_WING)
parser.add_argument("--bundle-dir", default=None)
parser.add_argument("--output", default=None)
parser.add_argument("--json", action="store_true")
args = parser.parse_args()
@@ -146,12 +283,17 @@ def main() -> None:
)
rendered = json.dumps(plan, indent=2) if args.json else render_markdown(plan)
if args.bundle_dir:
written = write_bundle_files(args.bundle_dir, plan)
for path in written:
print(f"Wrote bundle file: {path}")
if args.output:
output_path = Path(args.output).expanduser()
output_path.parent.mkdir(parents=True, exist_ok=True)
output_path.write_text(rendered, encoding="utf-8")
print(f"MemPalace integration packet written to {output_path}")
else:
elif not args.bundle_dir:
print(rendered)

View File

@@ -32,6 +32,10 @@ class TestMempalaceEzraIntegration(unittest.TestCase):
self.assertIn('wing:', plan["yaml_template"])
self.assertTrue(any('stdin' in item.lower() for item in plan["gotchas"]))
self.assertTrue(any('wing:' in item for item in plan["gotchas"]))
self.assertIn('mcp_servers:', plan["mcp_config_snippet"])
self.assertIn('export HERMES_MEMPALACE_WAKEUP_FILE=', plan["session_start_hook"])
self.assertIn('#570', plan["report_back_template"])
self.assertIn('#568', plan["report_back_template"])
def test_build_plan_accepts_path_and_wing_overrides(self):
mod = load_module(SCRIPT_PATH, "mempalace_ezra_integration")
@@ -47,6 +51,25 @@ class TestMempalaceEzraIntegration(unittest.TestCase):
self.assertIn('/root/wizards/ezra/home', plan["mine_home_command"])
self.assertIn('/root/wizards/ezra/home/sessions', plan["mine_sessions_command"])
self.assertIn('wing: ezra_archive', plan["yaml_template"])
self.assertIn('ezra_archive', plan["session_start_hook"])
def test_build_bundle_files_emits_operator_ready_support_files(self):
mod = load_module(SCRIPT_PATH, "mempalace_ezra_integration")
bundle = mod.build_bundle_files(mod.build_plan({}))
self.assertEqual(
set(bundle),
{
"mempalace.yaml",
"hermes-mcp-mempalace.yaml",
"session-start-mempalace.sh",
"issue-568-comment-template.md",
},
)
self.assertIn('wing: ezra_home', bundle["mempalace.yaml"])
self.assertIn('mcp_servers:', bundle["hermes-mcp-mempalace.yaml"])
self.assertIn('HERMES_MEMPALACE_WAKEUP_FILE', bundle["session-start-mempalace.sh"])
self.assertIn('Metrics reply for #568', bundle["issue-568-comment-template.md"])
def test_repo_contains_mem_palace_ezra_doc(self):
self.assertTrue(DOC_PATH.exists(), "missing committed MemPalace Ezra integration doc")
@@ -59,6 +82,9 @@ class TestMempalaceEzraIntegration(unittest.TestCase):
"mempalace wake-up",
"hermes mcp add mempalace -- python -m mempalace.mcp_server",
"Report back to #568",
"mcp_servers:",
"HERMES_MEMPALACE_WAKEUP_FILE",
"Metrics reply for #568",
]
for snippet in required:
self.assertIn(snippet, text)