Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
785b87d
feat: add chroma_host/port/ssl config properties with env var overrides
cypromis Apr 8, 2026
cf40e78
fix: raise ValueError on invalid MEMPALACE_CHROMA_PORT env var
cypromis Apr 8, 2026
659fd89
feat: add palace_db factory module (get_client / get_collection)
cypromis Apr 8, 2026
25539cd
refactor: route all ChromaDB instantiation through palace_db factory
cypromis Apr 8, 2026
4137388
refactor: move palace_db imports to module level in mcp_server and cli
cypromis Apr 8, 2026
a44d5c0
feat: add 'mempalace remote status' command showing local/remote mode
cypromis Apr 8, 2026
7ef3b40
docs: add Remote Mode section to README (Docker compose, config, env …
cypromis Apr 8, 2026
214127b
fix: use palace_db.get_collection() in layers.py; note graph tools ar…
cypromis Apr 8, 2026
1f2f909
refactor: consolidate DEFAULT_COLLECTION constant, cache HttpClient, …
cypromis Apr 8, 2026
b2451ff
fix: resolve ruff lint errors (unused f-string, unused import, unused…
cypromis Apr 8, 2026
7ebf9cc
style: apply ruff format to config.py
cypromis Apr 8, 2026
94be736
docs: add palace_db.py to module guide
cypromis Apr 8, 2026
36e837f
docs: pin Docker image to chromadb 0.6.3 to match client version
cypromis Apr 8, 2026
f8b9391
docs: explain chromadb version pin and 1.0 incompatibility
cypromis Apr 8, 2026
57623b0
docs: document multi-user support scope and ID collision risks
cypromis Apr 8, 2026
b74c0f4
docs: clarify v1 scope as single-dev multi-workstation; note multi-us…
cypromis Apr 8, 2026
2093acb
fix: replace hardcoded cwd path in test with dynamic Path(__file__) r…
cypromis Apr 8, 2026
f7116ef
feat: introduce palace_db.py as central ChromaDB factory
cypromis Apr 9, 2026
ce5de75
fix: migrate palace_graph.py from hardcoded PersistentClient to palac…
cypromis Apr 9, 2026
b5c07ad
fix: remove dead cache globals and fix MD5 usedforsecurity in mcp_server
cypromis Apr 9, 2026
44f0797
fix: guard unknown remote subcommands with non-zero exit in cli.py
cypromis Apr 9, 2026
cd11355
fix: replace dead _reset_mcp_cache fixture with real cache teardown
cypromis Apr 9, 2026
636edc0
test: add clear_caches, unknown-subcommand, and palace_graph routing …
cypromis Apr 9, 2026
c321ae4
ci: fix non-existent actions/checkout@v6 and setup-python@v6 pins
cypromis Apr 9, 2026
d2f58a9
ci: add loop guard to bump workflow against self-triggered commits
cypromis Apr 9, 2026
1492332
docs: add security warning for unauthenticated ChromaDB port in README
cypromis Apr 9, 2026
87c68b1
bench: add factory latency benchmarks for palace_db hotspots
cypromis Apr 9, 2026
7df8b55
chore: add TODO.md and update CLAUDE.md scope
cypromis Apr 9, 2026
72a384f
docs: add development workflow, commit rules, and upstream sync to CL…
cypromis Apr 9, 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
3 changes: 2 additions & 1 deletion .github/workflows/bump-plugin-version.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,12 @@ on:

jobs:
bump-version:
if: "!startsWith(github.event.head_commit.message, 'chore: bump version')"
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v4

- name: Bump patch version
run: |
Expand Down
16 changes: 8 additions & 8 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@ jobs:
matrix:
python-version: ["3.9", "3.11", "3.13"]
steps:
- uses: actions/checkout@v6
- uses: actions/setup-python@v6
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
- run: pip install -e ".[dev]"
Expand All @@ -23,8 +23,8 @@ jobs:
test-windows:
runs-on: windows-latest
steps:
- uses: actions/checkout@v6
- uses: actions/setup-python@v6
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: "3.9"
- run: pip install -e ".[dev]"
Expand All @@ -33,17 +33,17 @@ jobs:
test-macos:
runs-on: macos-latest
steps:
- uses: actions/checkout@v6
- uses: actions/setup-python@v6
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: "3.9"
- run: pip install -e ".[dev]"
- run: python -m pytest tests/ -v --ignore=tests/benchmarks --cov=mempalace --cov-report=term-missing --cov-fail-under=85
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: actions/setup-python@v6
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: "3.11"
- run: pip install "ruff>=0.4.0,<0.5"
Expand Down
191 changes: 191 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,191 @@
# MemPalace — Remote ChromaDB Fork

## Goal

Add optional remote ChromaDB support via `HttpClient`, so a single
ChromaDB instance running on a server (e.g. Docker on an always-on machine)
can be shared by multiple workstations or users. Local `PersistentClient`
behaviour must remain the default — zero breaking changes for existing users.

## Background

Currently every module that touches ChromaDB hardcodes:

```python
chromadb.PersistentClient(path=palace_path)
```

This means the palace is always local. There is no way to point MemPalace
at a remote ChromaDB server, which makes multi-workstation and multi-user
setups impossible without fragile file-sync hacks.

The fix is straightforward: introduce a factory function that returns either
`PersistentClient` or `HttpClient` depending on configuration, and replace
all direct `PersistentClient(...)` calls with that factory.

## Task

### 1. Add remote config keys to `mempalace/config.py`

Add to `MempalaceConfig` (alongside existing keys):

```python
chroma_host: str | None # e.g. "192.168.1.10" or hostname
chroma_port: int # default 8000
chroma_ssl: bool # default False
```

Read from `~/.mempalace/config.json`:

```json
{
"chroma_host": "m1mini.local",
"chroma_port": 8000,
"chroma_ssl": false
}
```

If `chroma_host` is absent or null, behaviour is local (current default).

Also support env var overrides (higher priority than config file):
- `MEMPALACE_CHROMA_HOST`
- `MEMPALACE_CHROMA_PORT`
- `MEMPALACE_CHROMA_SSL`

### 2. Create `mempalace/palace_db.py` (new file)

Central factory — all ChromaDB access must go through this:

```python
def get_client(palace_path: str = None) -> chromadb.ClientAPI:
"""
Returns HttpClient if remote config present, PersistentClient otherwise.
palace_path is ignored in remote mode (server manages its own storage).
"""

def get_collection(palace_path: str = None, name: str = "mempalace_drawers"):
"""
Returns the named collection from whichever client is active.
Creates the collection if it does not exist.
"""
```

This file does not exist yet upstream (PR #25 proposed it but was closed).

### 3. Replace all direct ChromaDB instantiation

Find every occurrence of `chromadb.PersistentClient(` in the codebase and
replace with a call to `palace_db.get_client()` or `palace_db.get_collection()`.

Files known to contain direct instantiation (verify with grep):
- `mempalace/convo_miner.py`
- `mempalace/miner.py`
- `mempalace/searcher.py`
- `mempalace/layers.py`
- `mempalace/mcp_server.py`

Use grep to confirm the full list before editing — do not assume.

### 4. Update `mempalace/cli.py`

Add a `mempalace remote` status command:

```
mempalace remote status
```

Output should show whether remote or local mode is active, and if remote,
confirm connectivity to the ChromaDB server (attempt a `.heartbeat()` call).

### 5. Update `README.md`

Add a "Remote Mode" section after the Configuration section covering:
- When to use remote mode (multi-workstation, multi-user)
- How to run ChromaDB as a Docker container (provide the compose snippet)
- The three config keys / env vars
- A note that `palace_path` is ignored in remote mode

Docker compose snippet to include:

```yaml
services:
chromadb:
image: chromadb/chroma:latest
ports:
- "8000:8000"
volumes:
- ./chromadb_data:/chroma/chroma
environment:
- ANONYMIZED_TELEMETRY=False
```

### 6. Tests

Add `tests/test_palace_db.py` covering:
- `get_client()` returns `PersistentClient` when no host configured
- `get_client()` returns `HttpClient` when host configured (mock chromadb)
- Env var overrides take priority over config file
- `get_collection()` creates collection if absent

## Constraints

- **Do not break existing behaviour.** No host configured = identical
behaviour to current upstream. All existing tests must still pass.
- **Do not add new required dependencies.** `chromadb` already ships
`HttpClient` — no extra packages needed.
- **Do not touch AAAK, knowledge_graph.py, or dialect.py.**
Those are out of scope.
`palace_graph.py` has been migrated to use `palace_db` (no longer excluded).
- **Keep the PR focused.** This is a single-concern change: remote client
support. Resist the temptation to refactor anything else while in there.
- Follow existing code style — no type annotation style changes, no formatter
changes unless the file already uses one.

## Development Workflow

```bash
# Run tests
uv run python -m pytest tests/ -q --ignore=tests/benchmarks

# Lint and format (run before every commit)
uv run ruff check --fix <changed files>
uv run ruff format <changed files>

# Sync upstream changes
git fetch upstream # upstream = git@github.com:milla-jovovich/mempalace.git
git merge upstream/main
# Conflicts in version.py / pyproject.toml / .claude-plugin/*.json / .codex-plugin/plugin.json
# are always version-number-only — resolve with:
git checkout --theirs <conflicted files> && git add <conflicted files>
```

## Commit Rules

- One commit per logical change (feat, fix, test, ci, docs, bench, chore)
- Run ruff check + format on staged files before every commit — no exceptions
- `tests/benchmarks/` = upstream's benchmark suite (do not edit); `benchmarks/` = our scripts

## Verification

After implementation, verify manually:

```bash
# Local mode (default) — must work identically to upstream
mempalace init /tmp/test-palace
mempalace mine /tmp/test-palace
mempalace search "test"

# Remote mode — requires a running ChromaDB container
docker run -p 8000:8000 chromadb/chroma:latest
MEMPALACE_CHROMA_HOST=localhost mempalace remote status
MEMPALACE_CHROMA_HOST=localhost mempalace mine /tmp/test-palace
MEMPALACE_CHROMA_HOST=localhost mempalace search "test"
```

## PR intent

This fork is intended to be contributed back upstream once the architecture
stabilises. Keep the diff minimal and the commit history clean (one logical
commit per step above, or squash to a single clean commit before PR).
The upstream maintainer has indicated preference for small,
single-concern PRs.
115 changes: 115 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -623,6 +623,121 @@ Plain text. Becomes Layer 0 — loaded every session.

---

## Remote Mode

By default MemPalace stores vectors locally using ChromaDB's `PersistentClient`.
Remote mode points it at a shared ChromaDB server — useful for multi-workstation
or multi-user setups where you want a single, always-on palace.

### When to use remote mode

- You work across multiple machines and want shared memory.
- Multiple users share one palace (e.g. a team AI assistant).
- You want to offload embeddings to a dedicated server.

### v1 scope and known limitations

This release targets **one developer across multiple workstations**. All clients
share a single `mempalace_drawers` collection with no per-user namespacing or
authentication.

| Scenario | Works? | Notes |
|---|---|---|
| 1 dev, multiple workstations | ✅ Perfect fit | Same person's memories sync across machines |
| Multiple devs, shared team palace | ✅ Works | All memories pooled together — intentional for a shared assistant |
| Multiple devs, each wanting isolated memory | ❌ Not in this version | Planned for a future release |

**ID collision risks when sharing a server between multiple users:**

- **`mine` / `convo-mine`** — drawer IDs are hashed from `source_file + chunk_index`. Two users with the same file path (e.g. both have `/home/user/notes.md`) will overwrite each other's entries silently.
- **Diary entries** — IDs include a second-resolution timestamp. Two users writing a diary entry within the same second produce the same ID; the second write silently overwrites the first.
- **Manual `add_drawer`** — content-addressed, so identical content from two users deduplicates cleanly. Different content in the same wing/room coexists fine.

For single-dev multi-workstation use these collisions are harmless (re-mining
the same file is idempotent). Per-user isolation — collection namespacing and
optional authentication — is planned for a follow-up PR.

### Running ChromaDB with Docker

```yaml
# docker-compose.yml
services:
chromadb:
image: chromadb/chroma:0.6.3
ports:
- "8000:8000"
volumes:
- ./chromadb_data:/chroma/chroma
environment:
- ANONYMIZED_TELEMETRY=False
```

```bash
docker compose up -d
```

> **Security:** ChromaDB 0.6.x has no authentication. The container above
> binds to all interfaces on port 8000 by default — anyone who can reach
> that port has unrestricted read/write access to your memories.
> Restrict access using one of:
>
> - Bind to localhost only (`ports: "127.0.0.1:8000:8000"`) and use SSH
> port-forwarding from other machines (`ssh -L 8000:localhost:8000 host`).
> - Place the container on a private network or behind a VPN.
> - Add a firewall rule that whitelists only your workstation IPs.
>
> Do not expose port 8000 to the public internet.

> **Version note:** The image is pinned to `0.6.3` to match the `chromadb` client
> version that MemPalace depends on. ChromaDB 1.0 introduced breaking changes to
> the collection configuration API (a `_type` field the 0.6.x client does not
> understand), so mixing versions causes `KeyError: '_type'` errors when creating
> or opening collections. Do not use `chromadb/chroma:latest` until MemPalace
> upgrades its client dependency.

### Configuring MemPalace for remote mode

Add to `~/.mempalace/config.json`:

```json
{
"chroma_host": "m1mini.local",
"chroma_port": 8000,
"chroma_ssl": false
}
```

Or use environment variables (higher priority than the config file):

```bash
export MEMPALACE_CHROMA_HOST=m1mini.local
export MEMPALACE_CHROMA_PORT=8000
export MEMPALACE_CHROMA_SSL=false
```

| Key / Env Var | Default | Description |
|---|---|---|
| `chroma_host` / `MEMPALACE_CHROMA_HOST` | _(none)_ | Hostname or IP. Absence means local mode. |
| `chroma_port` / `MEMPALACE_CHROMA_PORT` | `8000` | TCP port of the ChromaDB server. |
| `chroma_ssl` / `MEMPALACE_CHROMA_SSL` | `false` | Set to `true` for HTTPS. |

> **Note:** `palace_path` is ignored in remote mode. The server manages its own storage.

> **Note:** Graph traversal MCP tools (`mempalace_traverse`, `mempalace_find_tunnels`,
> `mempalace_graph_stats`) currently operate in local mode only, even when remote
> mode is configured. All other tools respect the remote configuration.

### Verify connectivity

```bash
mempalace remote status
```

This prints the active mode and — in remote mode — attempts a `.heartbeat()` call
to confirm the server is reachable.

---

## File Reference

| File | What |
Expand Down
11 changes: 11 additions & 0 deletions TODO.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# TODO

## Performance

- [ ] Cache `MempalaceConfig()` in `palace_db.py` — currently re-instantiated on every `get_client()` call, causing config file + env var I/O on every DB operation. Acceptable for CLI use; measurable overhead for long-running MCP server. Cache at module level or lazily on first call. Trade-off: env var changes won't be picked up without a server restart.

## Remote ChromaDB (follow-up to PR #294)

- [x] Migrate `palace_graph.py` to use `palace_db.get_collection()` — done.
- [ ] Per-user collection namespacing — multiple users sharing a remote instance currently share a single `mempalace_drawers` collection. Add optional namespace/prefix so each user gets isolated memory.
- [ ] Optional authentication support for remote ChromaDB (token-based).
Loading