𧬠Companion Memory
Your life, secured locally. A fully private, Personal Digital Twin database and lifelogging engine that ensures your digital history belongs entirely to you.
Status: Companion Memory is in closed preview. Join the waitlistΒ for early access.
Overview
Companion Memory is the intelligence core of the Companion ecosystem. It runs entirely on your local hardware and acts as a unified personal knowledge base, automatically aggregating data signals from your devices, browser, wearables, and integrated workflows into a single private timeline β your Personal Digital Twin.
Unlike cloud-based memory products, your data never leaves your hardware. The server exposes a local API that Companion Agents query to provide contextually aware suggestions, recall, and guidance.
Key Features
- Automatic ingestion β captures data from browser extensions, desktop applet, wearables, and import tools with no manual tagging
- Unified Explorer β one searchable view of your whole history across List, Gallery, Map, Graph, and Timeline
- Combinable search & filters β free-text + semantic search combined with who / where / when / how filters, sort, and tags
- Saved searches β name and reuse any search you build, and share a read-only link with someone else on your server
- Agent context (MCP + REST) β structured endpoints for Companion Agents to query memory with temporal and contextual filters
- Private by design β zero telemetry, zero cloud sync unless explicitly configured
- Modular connectors β extensible data source plugins for custom integrations
Use Cases
- Recall anything youβve seen, read, or done β without relying on cloud services
- Give your local AI agents rich, personal context to improve suggestions and task completion
- Build a searchable personal archive of life events, documents, browser history, and media
- Maintain a continuous data log from wearables (Mentra Live, Omi, Mentra) for health and memory analysis
- Power the Spatial Time Machine and Photogram with structured memory data
Architecture
The server ships as a Docker container managed by Companion Hub. It exposes:
- A REST API for data ingestion and retrieval
- An MCP (Model Context Protocol) server for direct agent integration
- A local web UI for browsing your timeline and configuring connectors
Setup
1. Install via Companion Hub
Open Companion Hub, navigate to the App Store, and search for Companion Memory. Click Install.
2. Configure data sources
App hostnames use your configured local domain β .localhost is the default. If one doesnβt resolve on your setup, see Finding Your Appβs URL.
After installation, open the Companion Memory web UI at
http://companion-memory.localhost. Navigate to Settings β Connectors and
enable the sources you want to capture from:
- Browser (requires the Browser Extension)
- Desktop capture (install CI-Applet, sign in to this CI-Server, and optionally add watched folders for file edits)
- Wearables (configure in each wearableβs settings page)
- Manual import (via Import Tools)
3. Connect Companion Agents
In the Hub dashboard, open Agents β Context Sources and add your Companion Memory endpoint. Agents will automatically query it when generating responses.
4. Configure retention policy (optional)
Navigate to Settings β Retention to set data retention windows, storage limits, and automatic pruning rules.
Using the web UI
Most of the time youβll work with your memory through the local web UI at http://companion-memory.localhost. The whole experience is built around one screen β the Explorer at /memory β that you reshape with search, filters, and views.
The Explorer
The Explorer answers one question β βshow me memories that match thisβ β in five different ways:
| View | Best for |
|---|---|
| List | Chronological reading: everything that matches, grouped by day |
| Gallery | Photos and videos with a cross-result lightbox |
| Map | Located events on a base map with a synced timeline panel |
| Graph | The relationships between entities (people, places, things) |
| Timeline | A denser, scrubbable version of List for skimming long ranges |
A Companion βΊ Memory βΊ {view} breadcrumb at the top hosts the view switcher, with a global search box and a left navigation rail down the side; everything below is the active view. The URL holds the full state of your search (?search=β¦&view=map&from=β¦), so any result is a shareable link to someone else on your server.
The default List view groups everything that matches by day (and a No date bucket), each entry tagged with a type icon β person, concept, event, place, question, or file:
The Graph view (the Knowledge Graph) draws the relationships between entities β people, concepts, events, and places β as an interactive node graph. Filter chips toggle node and link types (concept, event, person, place; attended, knows, located_at, related_to), and the 2D/3D switch, neighbor cap, and hop-depth slider control how much of the graph is drawn around the anchored node:
The Gallery view tiles every entity as a card labelled with its type (Person, Concept, Place, Item, Question, Content, Email, SMS) β photo/video events show real thumbnails, and text-only entities show their summary.
The Map view pairs a base map with a synced timeline panel on the left; clusters expand as you zoom, and the Search events in this area button restricts results to the current viewport.
Photo/video events show real thumbnails in List/Gallery/Map; clicking one opens a cross-result lightbox. The same media is preserved when you share the search β see Sharing below.
Search & filters
The search box at the top performs a free-text search across everything youβve ingested. Switching on the Semantic toggle re-ranks results by meaning rather than keyword match.
When you want to be more precise, the Advanced toggle opens a filter drawer with five combinable dimensions:
- Who β people involved (e.g. βAlex Chen, Mariaβ)
- Where β places, or βLocated onlyβ to limit the map to placed events
- When β a date range with optional times
- How β event types (visited, messaged, called, browsed, β¦)
- Source β connectors the event came from (gmail, imessage, browser, β¦)
Plus Sort (Default / Relevance / Newest / Oldest / Name AβZ / ZβA) and Tags. Every filter you add appears as a removable chip above the results so you can see exactly what your search is doing β and tear it back down one chip at a time.
Saved searches
Any search you build can be saved so you can come back to it. In the Explorer toolbar, click Save search β the dialog stores the current query, entity types, tags, and all your active filters (who/where/when/how/sort/semantic/located), so the saved search round-trips the full search.
Saved searches live at /saved-searches. Each card shows:
- A filter-count chip (
β N) β hover to see which dimensions are active (Who, Where, When, How, Source, Semantic) - A Shared badge if the search is currently link-shared
- Edit / Share / Delete actions
Clicking a card opens the Explorer with that search restored β every filter, the right sort, the right view.
Editing a saved search
Click the pencil on any card to open the Edit modal. Below the usual name / description / query / entity types / tags, a Filters section exposes a full editor for the secondary dimensions:
- Who / Where / How / Source β removable chips with a free-text add input
- From / To β date pickers
- Sort β a preset dropdown
- Located only / Semantic β toggles
Editing here keeps the saved search in sync everywhere it runs β including a shared link.
Sharing a saved search
A saved search can be turned into a read-only capability link that someone else on your server can open.
Open the share modal from a cardβs share button β or build a search, click Save search, and choose Save & share to do both in one step.
Toggle Link shared to mint an unguessable token. The modal then exposes:
- A share link (
/shared/<token>) with a copy button - Email / Message / Facebook / LinkedIn quick-share buttons
- An iframe embed snippet to drop the search results into another page
The recipient signs into your server and opens the link to a focused, read-only Explorer (List / Gallery / Map) β no edit affordances, no global search, no escape to other parts of the app. The search runs as you (the owner) so the link surfaces only whatβs already in your saved search; if theyβre signed out when they click, the OIDC login flow brings them back to the same share link automatically.
Capability tokens, signed-in only. A share link grants access only to that one saved search. Toggling sharing off β then on again β rotates the token; the old link stops working. Any viewer still has to be a signed-in user on your server: shares are not anonymous.
Assistant chat
The Chat section turns your memory into a conversational surface: ask a question in natural language and a locally-served model answers grounded in your own entities, citing the people, events, and concepts it drew from. A left rail keeps your recent threads, and the model picker in the header selects which local model answers.
Feed
The Feed is the home surface: an Actions carousel where Companion asks you a question or surfaces something that needs your input, and an Insights & Summaries stream the server generates as it learns from your memory.
Mobile
The Explorer is responsive, and a shared search opens on a phone too β the view tabs render inline (the global header collapses), and the map uses a touch-optimised mobile layer.
API Access
Companion Memory exposes a local REST and GraphQL API. Requests authenticate with an API key that you create in the web UI under Settings β API Keys β the full key is shown once at creation, and you send it as an x-api-key header.
REST
# Ingest events β up to 1000 per request, deduplicated on the provenance
# triple (source_type, source_system, source_id); source_type defaults to
# "capture" when omitted, so re-POSTing this body counts as a duplicate
curl -X POST http://companion-memory.localhost/api/v1/events \
-H "x-api-key: $CI_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"events": [
{
"source_system": "my-app",
"source_id": "meeting-2026-04-01",
"kind": "note",
"occurred_at": "2026-04-01T10:00:00Z",
"title": "Meeting with team",
"payload": {}
}
]
}'Searching is a GraphQL operation rather than a REST route β see below.
GraphQL
The graph of entities, events, files, people, places, and summaries is available through a single GraphQL endpoint at /api/graphql:
# Recent events
curl -X POST http://companion-memory.localhost/api/graphql \
-H "x-api-key: $CI_API_KEY" \
-H "Content-Type: application/json" \
-d '{"query": "{ events(first: 5) { edges { node { id title startTime } } } }"}'
# Search β results are a union, so select per type with inline fragments
curl -X POST http://companion-memory.localhost/api/graphql \
-H "x-api-key: $CI_API_KEY" \
-H "Content-Type: application/json" \
-d '{"query": "{ search(input: {query: \"your query\", first: 5}) { totalCount edges { node { __typename ... on EventType { id title startTime } } } } }"}'API Playground (GraphiQL)
Companion Memory ships the GraphiQLΒ IDE so you can explore the schema and run queries interactively β no external tooling required.
- Create an API key β in the web UI, open Settings β API Keys, click οΌ, name the key, and copy it (itβs shown only once).
- Open the playground β go to Settings β GraphiQL. It opens the IDE in a new tab (served at
/api/graphiql). - Authenticate β paste your key into the API key field in the top bar and click Save. The status turns to
key activeand the key is stored in your browser and attached (x-api-key) to every request. Use Clear to remove it. - Explore & run β click Docs (top-right) to browse the schema, write a query in the editor, and press the βΆ button to run it. Results appear on the right.
# Your 20 most recent events
query RecentEvents {
events(first: 20) {
edges {
node {
id
title
startTime
}
}
pageInfo {
hasNextPage
endCursor
}
}
}List fields use cursor pagination (first/after), so pass the endCursor from pageInfo back as after to page through results. The playground follows GraphiQLβs own light/dark toggle:
Agent Integration (MCP)
Connecting an agent you already run? Connect Companion Memory is the full guide β scopes, capability, per-client config, and troubleshooting by symptom. This section is the short version.
Companion Memory speaks the Model Context Protocol over Streamable HTTP at /api/mcp, so any
MCP-capable client β Claude Code, Cursor, OpenCode, OpenClaw, Hermes β can use your memory as a tool
source. Authenticate with the same API key. Companion Memory accepts it either as an x-api-key
header or as Authorization: Bearer β prefer Authorization: Bearer, since Companion Hub accepts
only that form and one pattern then covers both servers.
{
"mcpServers": {
"companion-memory": {
"type": "http",
"url": "http://companion-memory.localhost/api/mcp",
"headers": {
"Authorization": "Bearer <your API key>"
}
}
}
}Keep the "type": "http" line. Clients that infer the transport from the URL tolerate its absence,
but Claude Code does not β it skips an entry that has a url and no type, and the server simply
never appears.
Key scopes
Scopes decide both whether you connect and which tools you see:
| Key | Connects | Tools advertised |
|---|---|---|
| No scopes (full access) | Yes | All β GraphQL-derived and native intents |
mcp + intents | Yes | Native intents only |
mcp alone | Yes | None |
Without mcp | 403 | β |
A key holding mcp but not intents is the trap: it authenticates, the session opens, and the tool
list comes back empty β which most agents surface as βthis server has no toolsβ rather than as a
permissions problem. Give a scope-limited key both mcp and intents.
A key with no scopes at all also fixes the empty tool list, but it is a
full-access credential β it reaches GraphQL, bulk ingest, and files, and
nothing checks its capability on those surfaces. Prefer mcp + intents
unless you specifically need GraphQL. See the scope
trap.
GraphQL-derived tools are never advertised to a scope-limited key, whatever its scopes; they require
a full-access key. Requests carrying no key at all are rejected with 401.
Troubleshooting
Ingestion pipeline not running Check the container logs in Companion Hub under the Companion Memory app. Ensure the source connector has the correct API key or token configured.
Search returning no results Indexing runs asynchronously after ingestion. If data was just added, wait a few minutes and try again. If the problem persists, trigger a manual re-index from Settings β Maintenance β Re-index.
High disk usage Navigate to Settings β Storage to see a breakdown by data source. Reduce retention periods or exclude high-volume sources to reclaim space.
Agent not using memory
Verify the MCP endpoint responds from within the Hub network. An unauthenticated request should return 401 β that confirms routing and auth are both live:
curl -s -o /dev/null -w '%{http_code}\n' -X POST http://companion-memory.localhost/api/mcp \
-H "Content-Type: application/json" \
-H "Accept: application/json, text/event-stream" \
-d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{}}'Read the code you get back:
405β the URL is wrong. The MCP server is at/api/mcp, not/mcp.401with a key attached β the key is unknown or revoked. Mint a new one under Settings β API Keys.403β the key is valid but lacks themcpscope.
If the endpoint answers correctly and the agent still ignores memory, check in this order:
- The agent reports the server as connected but empty. The key is missing the
intentsscope β see Key scopes. This is the most common cause. - Claude Code shows no such server at all. Its entry is missing
"type": "http". - Otherwise, check the agentβs context source configuration.
Shared link returns βThis shared search isnβt availableβ
The token may have been revoked. Sharing is one of Not shared / Link shared; turning sharing off β then on again β issues a new token, and the old link stops working. Ask the owner to share again; their share modal shows the current link. The link also requires the viewer to be a signed-in user on the same server (shares are signed-in-only, not anonymous).
Shared gallery is blank
The ownerβs photo/video event needs to have its media file ingested too (not just the timeline event), and the shared search must include event and file entity types. Open the search via the ownerβs Edit modal and confirm the entity types and filters.