Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
81 commits
Select commit Hold shift + click to select a range
c924d07
fix(status): paginate metadata fetch to support large palaces
vnguyen-lexipol Apr 14, 2026
e083cd6
Create test_claude_plugin_hook_wrappers.py
fatkobra Apr 16, 2026
5fe0c1c
Update mempal-stop-hook.sh
fatkobra Apr 16, 2026
be9214a
Update mempal-precompact-hook.sh
fatkobra Apr 16, 2026
1dc55a7
test: make Claude plugin wrapper tests portable on Windows
fatkobra Apr 16, 2026
683e940
feat(i18n): add Traditional + Simplified Chinese entity detection
lmanchu Apr 16, 2026
84e2aa1
perf: graph cache with write-invalidation in build_graph()
jphein Apr 12, 2026
1657a79
fix: clarify cache docs, skip caching empty graphs
jphein Apr 12, 2026
8adf35a
fix: add threading lock to graph cache, expand docstring
jphein Apr 12, 2026
c88b8a2
style: fix ruff format for test_entity_detector.py
lmanchu Apr 16, 2026
7006a6b
feat(i18n): add entity detection to German locale
mvalentsev Apr 18, 2026
e17f219
feat(i18n): add entity detection to Spanish locale
mvalentsev Apr 18, 2026
118cbe4
feat(i18n): add entity detection to French locale
mvalentsev Apr 18, 2026
5189e0d
test(i18n): add entity section smoke tests and schema invariants
mvalentsev Apr 18, 2026
9149456
fix(hooks): honor silent_save when stop_hook_active is set
jphein Apr 18, 2026
6a3a5c7
fix(hooks): write hook JSON to real stdout, bypassing mcp_server redi…
jphein Apr 18, 2026
5deb815
fix(hooks): address Copilot review feedback on #1021
jphein Apr 18, 2026
1531a25
test: add missing import os in test_hooks_cli
jphein Apr 18, 2026
2183d86
style(hooks): ruff format hooks_cli.py and test_hooks_cli.py
jphein Apr 19, 2026
3004ac4
fix(miner): port None-metadata guard into paginated status loop
vnguyen-lexipol Apr 19, 2026
5d2da04
Merge remote-tracking branch 'upstream/develop' into fix/status-pagin…
vnguyen-lexipol Apr 19, 2026
0b316d4
test: normalize wrapper script path for bash on Windows
fatkobra Apr 19, 2026
2629ae5
fix(hooks): default silent_guard=True — config-read failure must not …
jphein Apr 19, 2026
d657626
style: ruff format — collapse AttributeError log call to single line
jphein Apr 19, 2026
54c314d
feat(i18n): add Belarusian
itfarrier Apr 19, 2026
2a5914b
Merge pull request #945 from lmanchu/feat/zh-entity-detection
igorls Apr 21, 2026
6d42f61
Merge pull request #1001 from mvalentsev/feat/i18n-de-es-fr-entity
igorls Apr 21, 2026
1a180cd
Merge pull request #1051 from itfarrier/feat/i18n-belarusian
igorls Apr 21, 2026
4fb0ee5
Merge pull request #942 from fatkobra/fix-hooks-resolve-claude-plugin
igorls Apr 21, 2026
9f5b8f5
fix: add mempalace-mcp console entry point for pipx/uv compatibility
messelink Apr 9, 2026
be89e49
fix: use mempalace CLI in hook scripts instead of python3 -m
messelink Apr 9, 2026
67a0677
fix: use mempalace CLI in top-level hook scripts
messelink Apr 16, 2026
982d421
fix: update mempalace mcp command to use mempalace-mcp entry point
messelink Apr 16, 2026
9e53228
test: update test_cli assertions for mempalace-mcp entry point
messelink Apr 16, 2026
5522d34
Merge pull request #340 from messelink/fix/mcp-pipx-compat
igorls Apr 21, 2026
48eb627
fix(hooks): MEMPAL_PYTHON override for .sh hooks' internal python3 calls
igorls Apr 13, 2026
1b00f93
Merge pull request #833 from MemPalace/fix/hooks-python-resolution
igorls Apr 21, 2026
74e9cbc
feat: deterministic hook saves — zero data loss via silent Python API
jphein Apr 21, 2026
810f9a5
Merge pull request #851 from vnguyen-lexipol/fix/status-paginate-larg…
bensig Apr 22, 2026
02aafc0
Merge pull request #1021 from jphein/upstream-fix/silent-save-visibility
bensig Apr 22, 2026
23ee2a0
Merge pull request #673 from jphein/feat/deterministic-hook-save
bensig Apr 22, 2026
9b35d9f
Merge pull request #661 from jphein/perf/graph-cache
bensig Apr 22, 2026
4f00390
feat(website): apply Crystal Lattice brand — fonts, palette, hero copy
bensig Apr 23, 2026
818b7f4
Merge pull request #1118 from MemPalace/ben/crystal-lattice-brand
bensig Apr 23, 2026
df3ee28
fix: add wing param to diary_write/diary_read, derive from transcript…
jphein Apr 23, 2026
9947ad0
fix: treat empty string as no filter in mempalace_search wing/room (#…
KunalG67 Apr 23, 2026
102372b
release: v3.3.3
bensig Apr 23, 2026
4f799af
release(3.3.3): bump README version badge
bensig Apr 23, 2026
6d252a0
Merge pull request #1144 from MemPalace/chore/release-3.3.3-prep
igorls Apr 24, 2026
d158375
fix(hooks): derive project wing from non-macOS transcript paths (#1145)
igorls Apr 24, 2026
1fd16da
fix(mcp): diary_read(wing='') spans all wings for agent (#1145)
igorls Apr 24, 2026
6fcfd34
docs(changelog): log #1145 fixes in 3.3.3 section
igorls Apr 24, 2026
6aebf45
fix(entity): reduce noise in regex-based detection
igorls Apr 24, 2026
9e7fa1c
feat(init): scan manifests and git authors for real entity signal
igorls Apr 24, 2026
14d7444
fix(deps): add tomli fallback for Python <3.11
igorls Apr 24, 2026
c7bd2cd
feat(convo): parse Claude Code conversation dirs into project entities
igorls Apr 24, 2026
df6c7d0
feat(llm): pluggable provider abstraction for entity refinement
igorls Apr 24, 2026
10a743d
feat(llm): interactive entity refinement with batching and cancellation
igorls Apr 24, 2026
36a8f21
feat(init): wire --llm flag and convo_scanner into discover_entities
igorls Apr 24, 2026
70d4c54
fix(project-scanner): address review feedback
Copilot Apr 24, 2026
851ebeb
test(project-scanner): tighten git helper env handling
Copilot Apr 24, 2026
ec9084f
refactor(project-scanner): tidy manifest priority helpers
Copilot Apr 24, 2026
d4cc367
test(project-scanner): harden git helper execution
Copilot Apr 24, 2026
9486d8b
test(project-scanner): make gitdir fixtures portable
Copilot Apr 24, 2026
035fe6d
fix(llm): tighter refinement — word boundaries, JSON extraction, auth…
igorls Apr 24, 2026
b150d33
fix(mine): skip generated entities file
igorls Apr 24, 2026
4631d6a
feat(init): wire confirmed entities into the miner's known-entities r…
igorls Apr 24, 2026
1b1854e
fix(init): address registry review feedback
Copilot Apr 24, 2026
8ac98f0
Merge pull request #1147 from MemPalace/fix/3.3.3-followups
bensig Apr 24, 2026
bcd0791
fix(security): normalize MEMPALACE_PALACE_PATH env var with abspath+e…
arnoldwender Apr 24, 2026
02a88b0
test(config): make palace_path tests portable across POSIX and Windows
arnoldwender Apr 24, 2026
ae1c52e
test(config): drop tilde-absence assertion for Windows 8.3 compatibility
arnoldwender Apr 24, 2026
a851c7a
Merge pull request #1148 from MemPalace/feat/project-scanner-entity-d…
igorls Apr 24, 2026
61d6c3c
Merge pull request #1157 from MemPalace/feat/wire-entities-to-miner
igorls Apr 24, 2026
19ce58c
chore: rescue merged stacked PRs #1150 and #1157 into develop
igorls Apr 24, 2026
55c83e9
fix(init): case-insensitive project dedup across manifest and convo s…
igorls Apr 24, 2026
8a6ebbe
Merge pull request #1175 from MemPalace/chore/rescue-stacked-prs-into…
igorls Apr 24, 2026
f246d25
Merge pull request #1166 from arnoldwender/fix/security-palace-path-e…
igorls Apr 24, 2026
431e42a
docs(changelog): document init entity-detection overhaul in 3.3.3
igorls Apr 24, 2026
174ecaf
Update CHANGELOG.md
igorls Apr 24, 2026
7a75791
Merge pull request #1176 from MemPalace/docs/changelog-3.3.3-init-ove…
igorls Apr 24, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 1 addition & 5 deletions .claude-plugin/.mcp.json
Original file line number Diff line number Diff line change
@@ -1,9 +1,5 @@
{
"mempalace": {
"command": "python3",
"args": [
"-m",
"mempalace.mcp_server"
]
"command": "mempalace-mcp"
}
}
23 changes: 21 additions & 2 deletions .claude-plugin/hooks/mempal-precompact-hook.sh
Original file line number Diff line number Diff line change
@@ -1,5 +1,24 @@
#!/bin/bash
# MemPalace PreCompact Hook — thin wrapper calling Python CLI
# All logic lives in mempalace.hooks_cli for cross-harness extensibility
INPUT=$(cat)
echo "$INPUT" | python3 -m mempalace hook run --hook precompact --harness claude-code
run_mempalace_hook() {
if command -v mempalace >/dev/null 2>&1; then
mempalace hook run "$@"
return $?
fi

if command -v python3 >/dev/null 2>&1 && python3 -c "import mempalace" >/dev/null 2>&1; then
python3 -m mempalace hook run "$@"
return $?
fi

if command -v python >/dev/null 2>&1 && python -c "import mempalace" >/dev/null 2>&1; then
python -m mempalace hook run "$@"
return $?
fi

echo "MemPalace hook error: could not find a runnable mempalace command or module" >&2
return 1
}

run_mempalace_hook --hook precompact --harness claude-code
23 changes: 21 additions & 2 deletions .claude-plugin/hooks/mempal-stop-hook.sh
Original file line number Diff line number Diff line change
@@ -1,5 +1,24 @@
#!/bin/bash
# MemPalace Stop Hook — thin wrapper calling Python CLI
# All logic lives in mempalace.hooks_cli for cross-harness extensibility
INPUT=$(cat)
echo "$INPUT" | python3 -m mempalace hook run --hook stop --harness claude-code
run_mempalace_hook() {
if command -v mempalace >/dev/null 2>&1; then
mempalace hook run "$@"
return $?
fi

if command -v python3 >/dev/null 2>&1 && python3 -c "import mempalace" >/dev/null 2>&1; then
python3 -m mempalace hook run "$@"
return $?
fi

if command -v python >/dev/null 2>&1 && python -c "import mempalace" >/dev/null 2>&1; then
python -m mempalace hook run "$@"
return $?
fi

echo "MemPalace hook error: could not find a runnable mempalace command or module" >&2
return 1
}

run_mempalace_hook --hook stop --harness claude-code
2 changes: 1 addition & 1 deletion .claude-plugin/marketplace.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
"name": "mempalace",
"source": "./.claude-plugin",
"description": "AI memory system — mine projects and conversations into a searchable palace. 19 MCP tools, auto-save hooks, guided setup.",
"version": "3.3.2",
"version": "3.3.3",
"author": {
"name": "milla-jovovich"
}
Expand Down
8 changes: 2 additions & 6 deletions .claude-plugin/plugin.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "mempalace",
"version": "3.3.2",
"version": "3.3.3",
"description": "Give your AI a memory — mine projects and conversations into a searchable palace. 19 MCP tools, auto-save hooks, and guided setup.",
"author": {
"name": "milla-jovovich"
Expand All @@ -9,11 +9,7 @@
"commands": [],
"mcpServers": {
"mempalace": {
"command": "python3",
"args": [
"-m",
"mempalace.mcp_server"
]
"command": "mempalace-mcp"
}
},
"keywords": [
Expand Down
2 changes: 1 addition & 1 deletion .codex-plugin/hooks/mempal-hook.sh
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ set -euo pipefail
HOOK_NAME="${1:?Usage: mempal-hook.sh <hook-name>}"
INPUT_FILE=$(mktemp) || { echo "Failed to create temp file" >&2; exit 1; }
cat > "$INPUT_FILE"
cat "$INPUT_FILE" | python3 -m mempalace hook run --hook "$HOOK_NAME" --harness codex
cat "$INPUT_FILE" | mempalace hook run --hook "$HOOK_NAME" --harness codex
EXIT_CODE=$?
rm -f "$INPUT_FILE" 2>/dev/null
exit $EXIT_CODE
8 changes: 2 additions & 6 deletions .codex-plugin/plugin.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "mempalace",
"version": "3.3.2",
"version": "3.3.3",
"description": "Give your AI a memory — mine projects and conversations into a searchable palace. 19 MCP tools, auto-save hooks, and guided setup.",
"author": {
"name": "milla-jovovich"
Expand All @@ -21,11 +21,7 @@
"hooks": "./hooks.json",
"mcpServers": {
"mempalace": {
"command": "python3",
"args": [
"-m",
"mempalace.mcp_server"
]
"command": "mempalace-mcp"
}
},
"interface": {
Expand Down
38 changes: 38 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,44 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),

---

## [3.3.3] — 2026-04-23

### Bug Fixes

- **Install regression** — `mempalace-mcp` console script is now declared in `pyproject.toml` alongside `.claude-plugin/plugin.json`'s reference to it. In v3.3.2 the two drifted apart (plugin.json shipped the new `"command": "mempalace-mcp"` form before the matching entry point landed), so every fresh `pip install mempalace==3.3.2` produced a Claude Code plugin config pointing at a binary that wasn't installed. (#1093, #340)
- Restore silent-save visibility after the Claude Code 2.1.114 client regression — production transcript saves were failing silently until this PR. (#1021)
- Paginate `status`-path metadata fetches so large palaces don't trip SQLite variable limits. (#851)
- Resolve the Claude plugin hook runner across platform / plugin-dir variations; previously broke on Windows and some macOS layouts. (#942)
- Real `python3` resolution for `.sh` hooks with a `MEMPAL_PYTHON` override path. (#833)
- Add optional `wing` parameter to `tool_diary_write` / `tool_diary_read` and derive per-project wing from the Claude Code transcript path when writing from the stop hook — diary entries from different projects no longer collapse into a shared default wing. (#659)
- Treat empty string as "no filter" in `mempalace_search` `wing`/`room`; LLM agents that default to filling every optional parameter with `""` no longer get bounced with `must be a non-empty string`. (#1097, #1084)
- Broaden `_wing_from_transcript_path` to handle Claude Code project folders without a `-Projects-` segment (e.g. `~/dev/<parent>/<project>`, `~/code/<project>`). The project name is now derived from the final dash-separated token of the encoded folder, so Linux users with code outside `~/Projects/` get per-project diary scoping instead of falling through to `wing_sessions`. (#1145, follow-up to #659)
- `mempalace_diary_read(wing="")` now returns diary entries from every wing this agent has written to, matching the #1097 "empty-string as no filter" pattern. Previously defaulted to `wing_<agent>`, siloing entries that hooks wrote to project-derived wings. (#1145)
- `mempalace mine` now skips the generated `entities.json` file so its contents aren't re-ingested as project content. (#1175)

### Improvements

- **Deterministic hook saves.** Save hook now uses a silent Python API path, so successive hook invocations produce reproducible results and zero data loss on the hot path. (#673)
- **Graph cache with write-invalidation** inside `build_graph()` — warm-path calls no longer rebuild the palace-graph per request. (#661)
- **`mempalace init` entity detection overhaul.** Canonical project names now come from package manifests (`package.json`, `pyproject.toml`, `Cargo.toml`, `go.mod`) and real people come from git commit authors, rather than being inferred from prose. Includes union-find dedup across name/email aliases, bot filtering that keeps `@users.noreply.github.com` humans, and automatic "mine" flagging by contribution share. (#1148)
- **Regex detector accuracy.** CamelCase extraction so `MemPalace`, `ChromaDB`, `OpenAI` aren't fragmented; tighter versioned/hyphenated pattern kills `context-manager` / `multi-word` false positives; dialogue `^NAME:\s` requires ≥2 hits so `Created: <date>` metadata stops classifying field names as people; expanded stopwords for common English participles and descriptors; high-pronoun signal classifies as person rather than dumping to uncertain. (#1148)
- **Init → miner wire-up.** Confirmed entities merge into `~/.mempalace/known_entities.json` on init, which the miner reads to tag drawer metadata for entity-filtered search. Previously init's output was not consumed by the miner; the per-project `entities.json` is kept as an audit trail. (#1157)
- **Case-insensitive project dedup** across manifest, git, and convo sources so casing variants of the same project name collapse into one review entry. (#1175)

### Added

- i18n: Belarusian translation. (#1051)
- i18n: entity detection for German, Spanish, and French locales. (#1001)
- i18n: Traditional + Simplified Chinese entity detection. (#945)
- **`mempalace init --llm`**: optional LLM-assisted entity classification. Defaults to local Ollama (zero-API); also supports any OpenAI-compatible endpoint (LM Studio, llama.cpp server, vLLM, OpenRouter, etc.) and the Anthropic Messages API. Runs interactively with a progress indicator; Ctrl-C cancels cleanly and returns partial results. Useful for prose-heavy folders where the regex detector struggles (diaries, transcripts, research notes). Opt-in only — default init path remains zero-API. (#1150)
- **Claude Code conversation scanner.** `~/.claude/projects/<slug>/` directories now contribute project entities using each session's authoritative `cwd` metadata, avoiding slug-decoding ambiguity. (#1150)

### Known — deferred to v3.3.4

- HNSW parallel-insert SIGSEGV when `hnsw:num_threads` is unset on collection creation (#974) — fix in-flight as #976, awaiting rebase against develop.

---

## [3.3.2] — 2026-04-19

### Bug Fixes
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -177,7 +177,7 @@ PRs welcome. See [CONTRIBUTING.md](CONTRIBUTING.md).
MIT — see [LICENSE](LICENSE).

<!-- Link Definitions -->
[version-shield]: https://img.shields.io/badge/version-3.3.2-4dc9f6?style=flat-square&labelColor=0a0e14
[version-shield]: https://img.shields.io/badge/version-3.3.3-4dc9f6?style=flat-square&labelColor=0a0e14
[release-link]: https://github.com/MemPalace/mempalace/releases
[python-shield]: https://img.shields.io/badge/python-3.9+-7dd8f8?style=flat-square&labelColor=0a0e14&logo=python&logoColor=7dd8f8
[python-link]: https://www.python.org/
Expand Down
25 changes: 24 additions & 1 deletion examples/HOOKS_TUTORIAL.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,4 +25,27 @@ Add this to your configuration file to enable automatic background saving:
}
]
}
}
}
```

### 3. What changed (v3.1.0+)

Both hooks now have **two-layer capture**:

1. **Auto-mine**: Before blocking the AI, the hook runs the normalizer on the JSONL transcript and upserts chunks directly into the palace. This captures raw tool output (Bash results, search findings, build errors) that the AI would otherwise summarize away.

2. **Updated reason messages**: The block reason now explicitly tells the AI to save tool output verbatim — not just topics and decisions.

### 4. Backfill past conversations (one-time)

The hooks capture conversations going forward, but you probably have months of past sessions. Run this once to mine them all:

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

### 5. Configuration

- **`SAVE_INTERVAL=15`** — How many human messages between saves
- **`MEMPALACE_PYTHON`** — Python interpreter with mempalace + chromadb. Auto-detects: env var → repo venv → system python3
- **`MEMPAL_DIR`** — Optional directory for auto-ingest via `mempalace mine`
4 changes: 2 additions & 2 deletions examples/mcp_setup.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,13 @@
Run the MCP server:

```bash
python -m mempalace.mcp_server
mempalace-mcp
```

Or add it to Claude Code:

```bash
claude mcp add mempalace -- python -m mempalace.mcp_server
claude mcp add mempalace -- mempalace-mcp
```

## Available Tools
Expand Down
67 changes: 53 additions & 14 deletions hooks/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,10 @@ These hook scripts make MemPalace save automatically. No manual "save" commands

| Hook | When It Fires | What Happens |
|------|--------------|-------------|
| **Save Hook** | Every 15 human messages | Blocks the AI, tells it to save key topics/decisions/quotes to the palace |
| **PreCompact Hook** | Right before context compaction | Emergency save — forces the AI to save EVERYTHING before losing context |
| **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 |

The AI does the actual filing — it knows the conversation context, so it classifies memories into the right wings/halls/closets. The hooks just tell it WHEN to save.
**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

Expand Down Expand Up @@ -68,6 +68,7 @@ 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. Set to a conversations directory to auto-run `mempalace mine <dir>` on each save trigger. Leave blank (default) to let the AI handle saving via the block reason message.
- **`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.

### mempalace CLI

Expand All @@ -91,15 +92,19 @@ User sends message → AI responds → Claude Code fires Stop hook
┌─── < 15 since last save ──→ echo "{}" (let AI stop)
└─── ≥ 15 since last save ──→ {"decision": "block", "reason": "save..."}
AI saves to palace
AI tries to stop again
stop_hook_active = true
Hook sees flag → echo "{}" (let it through)
└─── ≥ 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.
Expand All @@ -109,14 +114,18 @@ The `stop_hook_active` flag prevents infinite loops: block once → AI saves →
```
Context window getting full → Claude Code fires PreCompact
Hook ALWAYS blocks
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.
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

Expand All @@ -137,6 +146,36 @@ Example output:

**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:

```bash
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:

```bash
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 50K–200K drawers.

For Codex CLI sessions:
```bash
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.
11 changes: 9 additions & 2 deletions hooks/mempal_precompact_hook.sh
Original file line number Diff line number Diff line change
Expand Up @@ -54,18 +54,25 @@ mkdir -p "$STATE_DIR"
# Leave empty to skip auto-ingest (AI handles saving via the block reason).
MEMPAL_DIR=""

# Resolve the Python interpreter. Same contract as mempal_save_hook.sh:
# MEMPAL_PYTHON (explicit override) → $(command -v python3) → bare python3.
MEMPAL_PYTHON_BIN="${MEMPAL_PYTHON:-}"
if [ -z "$MEMPAL_PYTHON_BIN" ] || [ ! -x "$MEMPAL_PYTHON_BIN" ]; then
MEMPAL_PYTHON_BIN="$(command -v python3 2>/dev/null || echo python3)"
fi

# Read JSON input from stdin
INPUT=$(cat)

SESSION_ID=$(echo "$INPUT" | python3 -c "import sys,json; print(json.load(sys.stdin).get('session_id','unknown'))" 2>/dev/null)
SESSION_ID=$(echo "$INPUT" | "$MEMPAL_PYTHON_BIN" -c "import sys,json; print(json.load(sys.stdin).get('session_id','unknown'))" 2>/dev/null)

echo "[$(date '+%H:%M:%S')] PRE-COMPACT triggered for session $SESSION_ID" >> "$STATE_DIR/hook.log"

# Optional: run mempalace ingest synchronously so memories land before compaction
if [ -n "$MEMPAL_DIR" ] && [ -d "$MEMPAL_DIR" ]; then
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
REPO_DIR="$(dirname "$SCRIPT_DIR")"
python3 -m mempalace mine "$MEMPAL_DIR" >> "$STATE_DIR/hook.log" 2>&1
mempalace mine "$MEMPAL_DIR" >> "$STATE_DIR/hook.log" 2>&1
fi

# Silent: return empty JSON to not block. "decision": "allow" is invalid —
Expand Down
Loading
Loading