mempalace/hooks
undeadindustries bf156fb010 feat: add Antigravity IDE support (plugin, MCP, skill, hooks, docs, tests)
Adds first-class integration with Google's Antigravity IDE
(https://antigravity.google/) as a third sibling to the existing
Claude Code and Codex hook integrations. Strictly additive — no
existing files in main are restructured.

What ships
----------

* `.antigravity-plugin/` — verified-minimal plugin package:
  * `plugin.json` with `{"name": "mempalace"}` (no fabricated fields)
  * `mcp_config.json` registering the `mempalace-mcp` stdio server
  * `hooks.json.tmpl` templated with `__PLUGIN_DIR__` substitution
  * `skills/mempalace/SKILL.md` (real file — no symlinks)
* `hooks/antigravity/`:
  * `lib/common.sh` — shared bash 3.2.57-compatible helpers with
    sentinel-guarded camelCase JSON parser, antigravity_*-namespaced
    state files, every existing kill switch, `MEMPAL_SAVE_INTERVAL >= 1`
    floor (no /0), and fail-open emitters
  * `mempal_save_hook_antigravity.sh` — Stop event handler:
    increments per-conversation counter, defers when fullyIdle=False
    or terminationReason=error, validates transcriptPath against
    `..` traversal, spawns `mempalace mine --mode convos` in a
    detached subprocess with a per-conversation pending marker,
    ALWAYS emits `{}` (never `{"decision":"continue"}` — that would
    force an infinite agent loop)
  * `mempal_wake_hook_antigravity.sh` — PreInvocation handler gated
    to invocationNum==1 with an atomic mkdir loop guard, runs
    `mempalace wake-up` with a 500ms hard timeout, emits verbatim
    output as `{"injectSteps":[{"ephemeralMessage":"..."}]}` or
    `{}` on any failure
  * `install.sh` — idempotent installer with cmp-gated copies,
    `__PLUGIN_DIR__` substitution, relative path absolutization,
    `--dry-run`, and basename-guarded `--uninstall` (refuses to
    wipe a directory whose basename isn't `mempalace`)
  * `INVESTIGATION.md` — verbatim quotes + URLs + dates from the
    five official Antigravity doc pages, recording every surface
    shipped and every surface deliberately omitted
    (PreCompact equivalent, slash-commands, rules/, plugin
    permissions field — the latter is third-party fabrication)
  * `STDIN_SHAPE.md` — exact stdin/stdout contract per event with
    worked examples
  * `README.md` — local hook docs + troubleshooting
* `examples/antigravity/{hooks.json,mcp_config.json,README.md}` —
  standalone configs for users who don't want the full installer
* `website/guide/antigravity.md` + sidebar entry — VitePress guide
* Updates to `README.md`, `CHANGELOG.md` (Unreleased), `hooks/README.md`

Tests (56 new, all passing)
---------------------------

* `tests/test_antigravity_plugin_manifest.py` (11 tests) — schema
  contract on the in-repo `.antigravity-plugin/` directory, including
  guards against re-introducing the fabricated `permissions` field
  and against any symlink leak.
* `tests/test_antigravity_hooks_shell.py` (31 tests) — invokes the
  bash hooks via subprocess with synthetic camelCase stdin, asserts
  `{}` on every failure path, kill-switch coverage (env vars +
  config.json + palace nuke), divide-by-zero floor, transcript
  traversal rejection, namespacing, wing inference, and the hard
  refusal to ever emit `decision=continue` from the Stop hook.
* `tests/test_antigravity_hooks_install.py` (14 tests) — `--dry-run`
  side-effect-free, real install layout, executable bits preserved,
  byte-identical idempotent re-runs (md5 + filecmp), basename-match
  uninstall safety, refusal when plugin.json is missing or names a
  different plugin, relative path absolutization. Skipped on Windows.

Verification
------------

* `uv run pytest tests/ --ignore=tests/benchmarks -v` → 2314 passed,
  3 skipped (Windows), 1 unrelated warning
* `uv run ruff check .` → all checks passed
* `uv run ruff format --check .` → 139 files already formatted
* `bash -n` clean on common.sh, both hook scripts, install.sh
* Local install at `~/.gemini/config/plugins/mempalace/` verified end-
  to-end: layout correct, paths absolutized in hooks.json, both hooks
  fire with realistic camelCase JSON in <1s, wing inference picks
  `wing_mempalace` from workspacePaths[0], state files all
  `antigravity_*`-namespaced, second `install.sh` run produces
  byte-identical output (md5 snapshots match), uninstall removes
  only the mempalace plugin and leaves all 6 sibling Google plugins
  untouched.

Constraints honoured
--------------------

bash 3.2.57 (no mapfile / readarray / declare -A / `${var^^}`),
verbatim guarantee on all wake injections, hooks <500ms / startup
injection <100ms target (kill-switch path returns in <1.5s in CI),
zero new runtime dependencies, no telemetry, no external API,
strictly additive (existing Claude/Codex hooks unchanged).

Refs: hooks/antigravity/INVESTIGATION.md for the full audit.
2026-05-27 19:19:17 +10:00
..
antigravity feat: add Antigravity IDE support (plugin, MCP, skill, hooks, docs, tests) 2026-05-27 19:19:17 +10:00
README.md feat: add Antigravity IDE support (plugin, MCP, skill, hooks, docs, tests) 2026-05-27 19:19:17 +10:00
mempal_precompact_hook.sh fix: address review — enrich block reasons, clean up tests 2026-05-23 12:05:34 -04:00
mempal_save_hook.sh fix: address review — enrich block reasons, clean up tests 2026-05-23 12:05:34 -04:00

README.md

MemPalace Hooks — Auto-Save for Terminal AI Tools

These hook scripts make MemPalace save automatically. No manual "save" commands needed.

If you are trying to protect existing Claude Code transcripts immediately, use the short checklist first: website/guide/claude-code-retention.md. It covers hook wiring, JSONL backup, and one-time backfill.

What They Do

Hook When It Fires What Happens
Save Hook Every 15 human messages Auto-mines transcript (tool output included), then blocks the AI to save topics/decisions/quotes
PreCompact Hook Right before context compaction Auto-mines transcript, then emergency save — forces the AI to save EVERYTHING before losing context

Two-layer capture: Hooks auto-mine the JSONL transcript directly into the palace (capturing raw tool output — Bash results, search findings, build errors). They also block the AI with a reason message telling it to save verbatim tool output and key context. Belt and suspenders — tool output gets stored even if the AI summarizes instead of quoting.

Install — Claude Code

Add to .claude/settings.local.json:

{
  "hooks": {
    "Stop": [{
      "matcher": "*",
      "hooks": [{
        "type": "command",
        "command": "/absolute/path/to/hooks/mempal_save_hook.sh",
        "timeout": 30
      }]
    }],
    "PreCompact": [{
      "hooks": [{
        "type": "command",
        "command": "/absolute/path/to/hooks/mempal_precompact_hook.sh",
        "timeout": 30
      }]
    }]
  }
}

Make them executable:

chmod +x hooks/mempal_save_hook.sh hooks/mempal_precompact_hook.sh

Install — Antigravity (Google)

The Antigravity integration lives in its own subdirectory because the wire format (camelCase JSON, injectSteps[] output) and event names (Stop, PreInvocation) are Antigravity-specific. Use the dedicated installer:

bash hooks/antigravity/install.sh

This installs to ~/.gemini/config/plugins/mempalace/, registers the MCP server, ships the mempalace skill, and wires the Stop + PreInvocation hooks. See hooks/antigravity/README.md for the full guide and hooks/antigravity/INVESTIGATION.md for the source-of-truth audit of which Antigravity surfaces the integration uses.

Install — Codex CLI (OpenAI)

Add to .codex/hooks.json:

{
  "Stop": [{
    "type": "command",
    "command": "/absolute/path/to/hooks/mempal_save_hook.sh",
    "timeout": 30
  }],
  "PreCompact": [{
    "type": "command",
    "command": "/absolute/path/to/hooks/mempal_precompact_hook.sh",
    "timeout": 30
  }]
}

Configuration

Edit mempal_save_hook.sh to change:

  • SAVE_INTERVAL=15 — How many human messages between saves. Lower = more frequent saves, higher = less interruption.
  • STATE_DIR — Where hook state is stored (defaults to ~/.mempalace/hook_state/)
  • MEMPAL_DIR — Optional project directory (code, notes, docs) to also mine on each save trigger, with --mode projects. The hook ALWAYS mines the active conversation transcript automatically with --mode convosMEMPAL_DIR is purely additive, never an override. Leave blank if you don't want to ingest project files.
  • MEMPALACE_PYTHON — Optional env var. Python interpreter with mempalace + chromadb installed. Auto-detects: MEMPALACE_PYTHON env var → repo venv/bin/python3 → system python3. Set this if your venv is in a non-standard location.

Disabling Auto-Save (Silent Mode)

To keep hooks installed but disable auto-save blocking entirely, set hooks.auto_save to false in your config:

Option 1 — config file (~/.mempalace/config.json):

{
  "hooks": {
    "auto_save": false
  }
}

Option 2 — environment variable:

export MEMPALACE_HOOKS_AUTO_SAVE=false

When disabled, both the stop hook and precompact hook pass through without blocking. You can still save manually with mempalace mine <dir> --mode convos.

mempalace CLI

The relevant commands are:

mempalace mine <dir>               # Mine all files in a directory
mempalace mine <dir> --mode convos # Mine conversation transcripts only

The hooks resolve the repo root automatically from their own path, so they work regardless of where you install the repo.

How It Works (Technical)

Save Hook (Stop event)

User sends message → AI responds → Claude Code fires Stop hook
                                            ↓
                                    Hook counts human messages in JSONL transcript
                                            ↓
                              ┌─── < 15 since last save ──→ echo "{}" (let AI stop)
                              │
                              └─── ≥ 15 since last save
                                            ↓
                                    Auto-mine transcript → palace (tool output captured)
                                            ↓
                                    {"decision": "block", "reason": "save tool output verbatim..."}
                                            ↓
                                    AI saves to palace (topics, decisions, quotes)
                                            ↓
                                    AI tries to stop again
                                            ↓
                                    stop_hook_active = true
                                            ↓
                                    Hook sees flag → echo "{}" (let it through)

The stop_hook_active flag prevents infinite loops: block once → AI saves → tries to stop → flag is true → we let it through.

PreCompact Hook

Context window getting full → Claude Code fires PreCompact
                                        ↓
                                Find transcript (from input or session_id lookup)
                                        ↓
                                Auto-mine transcript → palace (tool output captured)
                                        ↓
                                {"decision": "block", "reason": "save tool output verbatim..."}
                                        ↓
                                AI saves everything
                                        ↓
                                Compaction proceeds

No counting needed — compaction always warrants a save. The auto-mine captures raw tool output before the AI gets a chance to summarize it away.

Debugging

Check the hook log:

cat ~/.mempalace/hook_state/hook.log

Example output:

[14:30:15] Session abc123: 12 exchanges, 12 since last save
[14:35:22] Session abc123: 15 exchanges, 15 since last save
[14:35:22] TRIGGERING SAVE at exchange 15
[14:40:01] Session abc123: 18 exchanges, 3 since last save

Known Limitations

Hooks require session restart after install. Claude Code loads hooks from settings.json at session start only. If you run mempalace init or manually edit hook config mid-session, the hooks won't fire until you restart Claude Code. This is a Claude Code limitation.

MEMPAL_PYTHON override for the hook's internal Python calls. The save hook parses its JSON input and counts transcript messages with python3. When the harness is launched from a GUI on macOS — open -a, Spotlight, the dock — its PATH is the minimal /usr/bin:/bin:/usr/sbin:/sbin inherited from launchd, not your shell PATH. If python3 isn't on that PATH, those internal calls fail and the hook can't count exchanges.

Point the hook at any Python 3 interpreter to fix it:

export MEMPAL_PYTHON="/usr/bin/python3"                   # system Python is fine
export MEMPAL_PYTHON="$HOME/.venvs/mempalace/bin/python"  # or your venv

Resolution priority: $MEMPAL_PYTHON (if set and executable) → $(command -v python3) → bare python3. The interpreter only needs json and sys from the standard library — mempalace itself does not need to be installed in it.

Note: the mempalace mine auto-ingest runs via the mempalace CLI, so that command also needs to be on the hook's PATH. Installing with pipx install mempalace or uv tool install mempalace puts it on a stable global location; otherwise extend the hook environment's PATH to include your venv's bin/.

Backfill Past Conversations

The hooks only capture conversations going forward. To mine past Claude Code sessions into your palace, run a one-time backfill:

mempalace mine ~/.claude/projects/ --mode convos

This scans all JSONL transcripts from previous sessions and files them into the conversations wing. On a typical developer machine with months of history, this can yield 50K200K drawers.

For Codex CLI sessions:

mempalace mine ~/.codex/sessions/ --mode convos

This only needs to be done once — after that, the hooks auto-mine each session as you go.

Cost

Zero extra tokens. The hooks notify the AI that saves happened in the background — the AI doesn't need to write anything in the chat. All filing is handled automatically. Previous versions asked the AI to write diary entries and drawer content in the chat window, which cost ~$1/session in retransmitted tokens.