Metadata-Version: 2.5
Name: octop-memory-server
Version: 0.1.0
Summary: Standalone multi-tenant server for octop-memory: /mcp/ MCP service + /dashboard visual management, API-Key auth, per-key physical data isolation
Project-URL: Homepage, https://github.com/TencentCloud/octop-memory
Project-URL: octop-memory upstream, https://github.com/TencentCloud/octop-memory
Author: openbot-coder
License-Expression: MIT
License-File: LICENSE
Keywords: agent,dashboard,fastapi,llm,long-term-memory,mcp,memory,model-context-protocol,multi-tenant,octop-memory
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Requires-Python: >=3.12
Requires-Dist: fastapi>=0.110
Requires-Dist: fastmcp>=2.0
Requires-Dist: octop-memory>=1.0.0
Requires-Dist: uvicorn>=0.29
Description-Content-Type: text/markdown

# octop-memory-server

[![PyPI](https://img.shields.io/pypi/v/octop-memory-server)](https://pypi.org/project/octop-memory-server/)
[![Python](https://img.shields.io/pypi/pyversions/octop-memory-server)](https://pypi.org/project/octop-memory-server/)
[![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE)
[![upstream: octop-memory](https://img.shields.io/badge/upstream-octop--memory-2456d6)](https://github.com/TencentCloud/octop-memory)

**English** | [简体中文](README.zh-CN.md)

A **batteries-included, multi-tenant HTTP server** wrapping
[octop-memory](https://github.com/TencentCloud/octop-memory) (Tencent Cloud's
open-source long-term memory system for AI agents). One command gives you:

- **`/mcp/`** — an MCP service (streamable HTTP) exposing 6 memory tools to agents
- **`/dashboard`** — a visual management console: browse / search / save memories
- **API key authentication** — both entry points are protected by `X-API-Key`
- **Physical isolation** — each API key maps to its own tenant directory and its
  own SQLite database file; tenants never touch each other's data

```
                       ┌────────────────────────────────────────────┐
  Agent (MCP client)──▶│ /mcp/        X-API-Key ──▶ tenant=alice   │
  Browser (console)──▶ │ /dashboard   X-API-Key ──▶ tenant=alice   │──┐
  Another agent    ──▶ │ /mcp/        X-API-Key ──▶ tenant=bob     │  │
                       └────────────────────────────────────────────┘  │
                                                                       ▼
                              data/tenants/
                              ├── alice/memory.sqlite   ← alice only
                              └── bob/memory.sqlite     ← bob only
```

---

## 1. Quick Start

### 1.1 Install

```bash
pip install octop-memory-server        # Python ≥ 3.12; dependencies (incl. octop-memory) pulled automatically
```

From source (development):

```bash
git clone <this-repo> octop-memory-server && cd octop-memory-server
pip install -e .
```

### 1.2 Configure API keys

Copy `api_keys.json.example` to `api_keys.json` and replace the keys:

```json
{
  "keys": {
    "oag-paste-a-random-long-string-alice": { "tenant": "alice" },
    "oag-another-random-string-bob": {
      "tenant": "bob",
      "llm": { "base_url": "http://localhost:11434/v1", "model": "qwen3:8b" }
    }
  }
}
```

Generate a random key: `python -c "import secrets; print('oag-' + secrets.token_urlsafe(24))"`

**Edits to this file take effect immediately (hot reload) — no restart needed.**

### 1.3 Start (CLI)

```bash
octop-memory-server                              # defaults to 127.0.0.1:8600
octop-memory-server --host 0.0.0.0 --port 8600   # serve externally
octop-memory-server --data-dir /srv/mem --keys-file /srv/mem/api_keys.json
octop-memory-server --llm-endpoint http://localhost:11434/v1 --llm-model qwen3:8b
octop-memory-server --reload                     # dev hot reload
octop-memory-server --version / --help
```

| Option | Default | Description |
|---|---|---|
| `--host` | `127.0.0.1` | Listen address |
| `--port` | `8600` | Listen port |
| `--data-dir` | `./data` | Root directory for tenant data |
| `--keys-file` | `./api_keys.json` | Path to the key manifest |
| `--llm-endpoint` / `--llm-model` | from env | Global default LLM (per-key config wins) |

Prefer plain uvicorn? `uvicorn octop_memory_server.app:app --host 0.0.0.0 --port 8600`.

- Dashboard: `http://localhost:8600/dashboard` (paste your API key on first open)
- MCP endpoint: `http://localhost:8600/mcp/`
- Health check (no auth): `http://localhost:8600/health`

---

## 2. Physical Isolation Design

Each API key maps 1:1 to a `tenant`, isolated at three layers:

| Layer | Mechanism | Notes |
|---|---|---|
| **Directory** | `data/tenants/<tenant>/` | Per-tenant directory; set OS permissions and back up independently |
| **File** | Own `memory.sqlite` | SQLite WAL mode — zero sharing, zero lock contention between tenants |
| **In-database** | namespace `t_<tenant>` | Table prefixes differ even if a db file were misconfigured — belt and suspenders |

**Hard security guarantees (enforced in code):**

1. The db path and namespace are resolved **server-side only** from the API key;
   no REST/MCP endpoint ever accepts them from the client;
2. Dashboard list queries use **read-only connections** (`file:...?mode=ro`);
3. Table names are built solely from the normalized namespace
   (`re.sub(r"[^a-z0-9_]", "_", ns)`), ruling out SQL injection;
4. Keys are compared with `hmac.compare_digest` (timing-safe); logs only record
   a 12-char SHA-256 fingerprint of each key.

> ⚠️ `api_keys.json` currently stores keys in plain text. On multi-admin machines,
> consider storing SHA-256 hashes instead (`KeyEntry` already has fingerprint
> plumbing; the change to `ApiKeyStore.reload_if_needed` is under ten lines).

---

## 3. `/mcp/` — MCP Service

### 3.1 Client setup

MCP client configuration (e.g. the mcp.json of WorkBuddy / Claude Desktop / Cursor):

```json
{
  "mcpServers": {
    "octop-memory": {
      "url": "http://your-host:8600/mcp/",
      "headers": { "X-API-Key": "oag-xxxx-alice" }
    }
  }
}
```

Issue one key per agent / user and you get "agents can't see each other's
memories" for free.

### 3.2 Tools

| Tool | Description | Needs LLM |
|---|---|---|
| `memory_save(content, topic?)` | Explicitly save a long-term memory (fact / preference / decision) | No |
| `memory_recall(query, thread_id?, limit?)` | Recall and return a **markdown block ready to inject into a prompt** | No |
| `memory_search(query, max_results?, corpus?)` | Multi-source search (incl. the raw conversation layer), structured hits | No |
| `memory_get(path)` | Read source by virtual path: `atom/<id>.md`, `raw/<date>/<id>.md`, `page/<entity>.md` | No |
| `memory_capture(user?, assistant?, session_id?)` | Capture one conversation turn as L0 raw events (privacy filtering applies) | No |
| `memory_extract(session_id)` | Distill a session's raw events into long-term memory (L0→L1→L2→L3) | **Yes** |

### 3.3 Recommended agent-side usage

In the agent loop: call `memory_capture` (non-blocking) at the end of each turn,
`memory_extract` on session end or when idle, and `memory_recall` while
assembling the system prompt. **No capture, no raw material for extract.**

---

## 4. `/dashboard` — Visual Management Console

A dependency-free single-page app (`static/index.html`). Paste your API key on
first open (stored in localStorage); it reconnects automatically afterwards.

| Tab | What it shows |
|---|---|
| Overview | Tenant info, db size, per-layer counts (L0/L1/L2/entities/digests/journal), recent-memory timeline |
| Atoms | Atom list with keyword search, deprecated filter, verbatim quotes, confidence/importance |
| Candidates | Review view (filter by promoted / needs_review / conflict / duplicate) |
| Raw events | L0 RawEvent browser, filterable by session |
| Tools | Manual memory save / recall test (preview the injected block) / session extract (needs LLM) |

REST API (all require `X-API-Key`, prefix `/dashboard/api`):

```
GET  /tenant                          tenant & db info
GET  /overview                        stats + recent memories
GET  /atoms?q&include_deprecated&limit&offset
GET  /atoms/{id}                      atom detail (source excerpt)
GET  /candidates?status&limit&offset
GET  /raw_events?session_id&event_type&limit&offset
POST /memory       {"content","topic"}            save
POST /recall       {"query","limit","thread_id"}  recall test
POST /extract      {"session_id"}                 session extract (needs LLM, else 409)
```

> Review actions (candidate promote/reject, gc, db slim) are **deliberately not
> exposed** to the dashboard/MCP — they are destructive or decision-making
> operations. Use the upstream CLI on the host when needed
> (`octop-memory --db data/tenants/<tenant>/memory.sqlite --namespace t_<tenant> candidate ...`).

---

## 5. LLM Configuration (optional)

| Where | Priority | Scope |
|---|---|---|
| Per-key `llm` field in `api_keys.json` | High | A single tenant |
| Env vars `OCTOP_LLM_ENDPOINT` / `OCTOP_LLM_MODEL` / `OCTOP_LLM_MODEL_HEAVY` | Low | All tenants without their own config |

Without an LLM: save / recall / search / get / capture all work; only
`extract` (automatic distillation) and page regeneration are unavailable.
The endpoint can be any **OpenAI-compatible API** (OpenAI / Ollama / vLLM / etc.).

Other environment variables:

| Variable | Default | Description |
|---|---|---|
| `OCTOP_GW_DATA_DIR` | `./data` | Root directory for tenant data |
| `OCTOP_GW_KEYS_FILE` | `./api_keys.json` | Path to the key manifest |
| `OCTOP_GW_HOST` | `octop-memory-server` | Host label written into RawEvents |

---

## 6. Deployment Notes

1. **Process**: single uvicorn process, `--workers 1` (SQLite has a single-writer
   model; for multi-worker writes switch to the PostgreSQL backend — see
   octop-memory's `postgres` extra).
2. **HTTPS**: API keys travel in plain headers; put the service behind a TLS
   reverse proxy (nginx/caddy) for anything public-facing.
3. **Backup**: just back up the `data/tenants/` directory, or use the upstream
   `octop-memory export` / `portable` (`.hmpkg`) for cross-host migration.
4. **Maintenance**: run gc / consolidate / page regeneration via the upstream CLI
   on a scheduler during idle windows (aligns with upstream ADR-027):

```bash
octop-memory --db data/tenants/alice/memory.sqlite --namespace t_alice gc
octop-memory --db data/tenants/alice/memory.sqlite --namespace t_alice consolidate
```

5. **Monitoring**: probe `/health`; every tenant operation is logged with its
   tenant id and can be aggregated per directory.

---

## 7. Known Limitations

- Upstream octop-memory 1.0.0 is Alpha: the PostgreSQL backend and vector search
  (chroma/qdrant) are **not verified with real integrations**; this server uses
  the SQLite backend only (also the cleanest form of isolation).
- MCP's `memory_extract` is synchronous; distilling a long session may exceed
  MCP client timeouts. Prefer running extraction from an external scheduler via
  the upstream CLI (`candidate extract`); keep MCP for reads/writes.
- The dashboard shows candidates read-only; approve/reject actions require
  upstream promotion internals — extend `dashboard_api.py` as needed.

---

## 8. Testing

```bash
pip install -e .
python smoke_test.py     # 15 end-to-end checks: 401 auth, MCP handshake, cross-tenant isolation, dashboard REST
```

---

## 9. License & Acknowledgements

- This project is released under the [MIT License](LICENSE).
- The core capability comes from Tencent Cloud's open-source
  [octop-memory](https://github.com/TencentCloud/octop-memory) (MIT License).
  This project is a serving wrapper only (multi-tenant HTTP server / MCP /
  Dashboard) and does not modify upstream code. Report upstream issues to the
  original repository.
