Files
timmy-home/scripts/mempalace_ezra_integration.py
Alexander Whitestone 5c8ba43dbf
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
feat: add Ezra MemPalace operator bundle (#570)
2026-04-21 07:22:18 -04:00

302 lines
9.8 KiB
Python

#!/usr/bin/env python3
"""Prepare an executable MemPalace v3.0.0 integration bundle for Ezra's Hermes home."""
from __future__ import annotations
import argparse
import json
from pathlib import Path
PACKAGE_SPEC = "mempalace==3.0.0"
DEFAULT_HERMES_HOME = "~/.hermes/"
DEFAULT_SESSIONS_DIR = "~/.hermes/sessions/"
DEFAULT_PALACE_PATH = "~/.mempalace/palace"
DEFAULT_WING = "ezra_home"
def build_yaml_template(wing: str, palace_path: str) -> str:
return (
f"wing: {wing}\n"
f"palace: {palace_path}\n"
"rooms:\n"
" - name: sessions\n"
" description: Conversation history and durable agent transcripts\n"
" globs:\n"
" - \"*.json\"\n"
" - \"*.jsonl\"\n"
" - name: config\n"
" description: Hermes configuration and runtime settings\n"
" globs:\n"
" - \"*.yaml\"\n"
" - \"*.yml\"\n"
" - \"*.toml\"\n"
" - name: docs\n"
" description: Notes, markdown docs, and operating reports\n"
" globs:\n"
" - \"*.md\"\n"
" - \"*.txt\"\n"
"people: []\n"
"projects: []\n"
)
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)
sessions_dir = overrides.get("sessions_dir", DEFAULT_SESSIONS_DIR)
palace_path = overrides.get("palace_path", DEFAULT_PALACE_PATH)
wing = overrides.get("wing", DEFAULT_WING)
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,
"sessions_dir": sessions_dir,
"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}",
"mine_sessions_command": f"echo \"\" | mempalace mine {sessions_dir} --mode convos",
"search_command": 'mempalace search "your common queries"',
"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.",
"Pipe empty stdin into mining commands (`echo \"\" | ...`) to avoid the entity-detector stdin hang on larger directories.",
"First mine downloads the ChromaDB embedding model cache (~79MB).",
"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
def render_markdown(plan: dict) -> str:
gotchas = "\n".join(f"- {item}" for item in plan["gotchas"])
return f"""# MemPalace v3.0.0 — Ezra Integration Packet
This packet turns issue #570 into an executable, reviewable integration plan for Ezra's Hermes home.
It is a repo-side scaffold: no live Ezra host changes are claimed in this artifact.
## Commands
```bash
{plan['install_command']}
{plan['init_command']}
cat > {plan['config_path']} <<'YAML'
{plan['yaml_template'].rstrip()}
YAML
{plan['mine_home_command']}
{plan['mine_sessions_command']}
{plan['search_command']}
{plan['wake_up_command']}
{plan['mcp_command']}
```
## Manual config template
```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
{gotchas}
## Report back to #568
After live execution on Ezra's actual environment, post back to #568 with:
- install result
- mine duration and corpus size
- 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
This repo artifact does **not** prove live installation on Ezra's host. It makes the work reproducible and testable so the next pass can execute it without guesswork.
"""
def main() -> None:
parser = argparse.ArgumentParser(description="Prepare the MemPalace Ezra integration packet")
parser.add_argument("--hermes-home", default=DEFAULT_HERMES_HOME)
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()
plan = build_plan(
{
"hermes_home": args.hermes_home,
"sessions_dir": args.sessions_dir,
"palace_path": args.palace_path,
"wing": args.wing,
}
)
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}")
elif not args.bundle_dir:
print(rendered)
if __name__ == "__main__":
main()