--- description: >- Optional `Memory` trait backend that delegates to a locally-running agentmemory REST server, for users who self-host agentmemory across Claude Code, Cursor, Codex, OpenCode, and OpenHuman. icon: database --- # agentmemory backend OpenHuman's default `Memory` trait backend is `sqlite` — the unified store documented in [Memory Trees](memory-tree.md). For users who already self-host [agentmemory](https://github.com/rohitg00/agentmemory) — typically because they want a single durable memory shared across Claude Code, Cursor, Codex, OpenCode, and OpenHuman — OpenHuman exposes an opt-in backend that proxies every trait call through agentmemory's REST surface. Selecting `backend = "agentmemory"` skips OpenHuman's SQLite + embedder path entirely. agentmemory owns the storage, embedding, and retrieval layers. OpenHuman becomes a thin REST client. ## When to use this Use the agentmemory backend if: - You already run `npx -y @agentmemory/agentmemory` for one or more coding agents and want OpenHuman to share the same durable store. - You want hybrid BM25 + vector + graph retrieval without provisioning a separate embedder on the OpenHuman side. - You prefer agentmemory's lifecycle (consolidation, retention scoring, auto-forget, graph extraction) over OpenHuman's unified store. Keep the default `sqlite` backend if: - You want self-contained, single-process operation with no external daemon dependency. - You rely on OpenHuman-specific Memory Tree features (chunking, sealing, summary trees) that operate on top of the SQLite store. The Memory Tree pipeline is unaffected by the trait backend — it operates on the host's document store, orthogonally — but the agentmemory backend is most valuable when you've already standardised on agentmemory across other agents. ## Quick start 1. **Install + start agentmemory** (one terminal): ```bash npx -y @agentmemory/agentmemory ``` Defaults to `http://localhost:3111` (REST) + `ws://localhost:49134` (engine). First boot generates an HMAC secret at `~/.agentmemory/.hmac` and prints it once. 2. **Point OpenHuman at it** in your `config.toml`: ```toml [memory] backend = "agentmemory" # Defaults below — set only when overriding. # agentmemory_url = "http://localhost:3111" # agentmemory_secret = "" # HMAC bearer token, optional # agentmemory_timeout_ms = 5000 ``` 3. **Restart OpenHuman**. The factory short-circuits the SQLite path and logs `[memory::factory] using agentmemory backend at `. That's it. Existing OpenHuman call sites (`store`, `recall`, `get`, `list`, `forget`, `namespace_summaries`, `count`, `health_check`) work unchanged. ## Config keys | Field | Default | Purpose | |---|---|---| | `agentmemory_url` | `http://localhost:3111` | Base URL for the agentmemory REST server | | `agentmemory_secret` | _none_ | Optional HMAC bearer token. Sent as `Authorization: Bearer ` | | `agentmemory_timeout_ms` | `5000` | Per-request reqwest timeout | When `backend == "agentmemory"`, the following existing `MemoryConfig` fields are **ignored** — agentmemory owns its own embedding stack via `~/.agentmemory/.env`: - `embedding_provider` - `embedding_model` - `embedding_dimensions` - `sqlite_open_timeout_secs` Setting them on this path is a no-op. The local-AI Ollama health-gate also doesn't run on this path — agentmemory's daemon manages its own embedder lifecycle. ## Field mapping OpenHuman's `MemoryEntry` ↔ agentmemory wire row: | OpenHuman field | agentmemory field | Notes | |---|---|---| | `namespace` | `project` | Defaults to `"default"` when empty | | `key` | `title` | | | `content` | `content` | | | `id` | `id` | agentmemory-generated (`mem_`) | | `category: Core` | `type: "fact"` | | | `category: Daily` | `type: "conversation"` | | | `category: Conversation` | `type: "conversation"` | | | `category: Custom(s)` | `type: "fact"` + `concepts: [s]` | Custom tag rolled into the concepts array so it remains queryable | | `session_id` | `sessionIds: [...]` | OpenHuman exposes a single id; agentmemory persists an array | | `timestamp` | `updatedAt` (RFC3339) | Falls back to `createdAt` if `updatedAt` is absent | | `score` (recall hits only) | smart-search `score` | Populated on `recall` responses, `None` on `get` / `list` | agentmemory carries additional fields — `concepts` (auto-extracted), `files` (path tags), `strength` (retention score), `version`, `supersedes` (the lifecycle chain) — that this backend leaves at defaults. They're internal to agentmemory's lifecycle layer and don't need to round-trip through OpenHuman's trait. ## Trait method → endpoint | `Memory` method | agentmemory REST | Notes | |---|---|---| | `store` | `POST /agentmemory/remember` | `{project, title, content, type, concepts, sessionIds}` | | `recall` | `POST /agentmemory/smart-search` | Hybrid BM25 + vector + graph | | `get` | `POST /agentmemory/smart-search` | + client-side exact-title filter | | `list` | `GET /agentmemory/memories?latest=true&project=` | | | `forget` | `get(ns, key)` → `POST /agentmemory/forget` | Two-step: resolve id then forget | | `namespace_summaries` | `GET /agentmemory/projects` | Returns `[{name, count, lastUpdated}]` | | `count` | `GET /agentmemory/health` | Reads `memories` field | | `health_check` | `GET /agentmemory/livez` | | `RecallOpts.category`, `RecallOpts.session_id`, and `RecallOpts.min_score` are applied as **client-side filters** on the smart-search response. agentmemory's REST surface doesn't expose them as server-side filters today. For very large recall windows (limit > 100) prefer issuing a tighter query string to reduce server-side work over relying on client-side post-filtering. ## Security When `agentmemory_secret` is set, the client honours agentmemory's v0.9.12 plaintext-bearer guard contract: - **Loopback hosts** (`localhost`, `127.0.0.1`, `::1`) over `http://` — allowed. Local dev path. - **`https://`** to any host — allowed. - **Plaintext HTTP to a non-loopback host** — emits a one-time stderr warning at construction time. The bearer is observable on the wire. - **`AGENTMEMORY_REQUIRE_HTTPS=1`** (process env, ASCII-case-insensitive matches `1` or `true`) — escalates the warning into a hard refusal at client construction. The backend fails to start rather than leak the bearer once. Production deploys should set `AGENTMEMORY_REQUIRE_HTTPS=1` so a misconfigured TLS terminator fails loud rather than silently leaking. The plaintext-bearer guard mirrors the integration plugin guards in agentmemory's [PR #315](https://github.com/rohitg00/agentmemory/pull/315) so an operator who's seen the warning on Hermes / OpenClaw / pi will recognise the same message on OpenHuman. ## Failure modes | Failure | Backend behaviour | |---|---| | Daemon unreachable at startup | `from_config` succeeds (URL parses), but `health_check()` returns false on first call. Trait methods bubble up `reqwest` transport errors | | Network timeout | `anyhow::Error` per trait contract; surfaces to caller | | 4xx / 5xx response | `anyhow::Error` with status + body snippet | | Bearer over plaintext non-loopback (no env) | One-time stderr warning, request proceeds | | Bearer over plaintext non-loopback + `AGENTMEMORY_REQUIRE_HTTPS=1` | Hard refusal at construction time | | Empty `agentmemory_url` | Hard refusal at construction time with hint to leave it unset for the default | | Invalid URL syntax | Hard refusal at construction time with the parser error | **No automatic fallback to SQLite.** If the daemon is down at boot, the backend surfaces the transport error loudly. Operators flip back to `backend = "sqlite"` in `config.toml` to recover. Rationale: a silent SQLite fallback would hide a misconfigured daemon — "private, simple, predictable" wins over "magically tolerant". ## Performance notes The backend is a thin REST proxy — it adds one HTTP round-trip per trait call. Practical implications: - `store` and `forget` are single-RTT. - `recall`, `get`, `list` are single-RTT. - `forget` against an unknown key is two-RTT (the implicit `get` lookup + a no-op confirmation). Caller can short-circuit this by checking the return value of a prior `list`. - agentmemory's REST is `127.0.0.1` by default — same-host latency is sub-millisecond. Over a managed deploy with HTTPS termination, expect ~10–30ms per RTT. - The default per-request timeout is 5 seconds. Bump `agentmemory_timeout_ms` if you're seeing intermittent timeouts on cold-start of the iii engine; agentmemory's first-request latency after a long idle can stretch toward 3–5s depending on persistence state. ## Migration: from SQLite to agentmemory There's no in-place migration today. The recommended path: 1. Export your existing memories from the SQLite store via OpenHuman's existing export RPC (or by direct SQL). 2. Walk the export and POST each row to `/agentmemory/remember` with the same `project` + `title` + `content`. agentmemory will assign new ids; the OpenHuman side picks them up on first `list`. 3. Set `backend = "agentmemory"` and restart. A dedicated bulk import path is filed as a follow-up. ## Implementation reference In-tree files: - [`store/agentmemory/mod.rs`](https://github.com/tinyhumansai/openhuman/tree/main/src/openhuman/memory/store/agentmemory/mod.rs) — module surface - [`store/agentmemory/backend.rs`](https://github.com/tinyhumansai/openhuman/tree/main/src/openhuman/memory/store/agentmemory/backend.rs) — `impl Memory for AgentMemoryBackend` - [`store/agentmemory/client.rs`](https://github.com/tinyhumansai/openhuman/tree/main/src/openhuman/memory/store/agentmemory/client.rs) — reqwest wrapper + plaintext-bearer guard - [`store/agentmemory/mapping.rs`](https://github.com/tinyhumansai/openhuman/tree/main/src/openhuman/memory/store/agentmemory/mapping.rs) — `MemoryEntry` ↔ agentmemory JSON - [`tests/agentmemory_backend.rs`](https://github.com/tinyhumansai/openhuman/tree/main/tests/agentmemory_backend.rs) — 12 axum-mock integration tests Related upstream: - agentmemory repo — - agentmemory REST contract — `~/.agentmemory/.env` keys + endpoint list in the agentmemory README - v0.9.12 plaintext-bearer guard — agentmemory PR #315