Creating a brain, step by step
Memory serverOfficial Doctor Droid project
Created by Doctor Droid
Published in DrDroidLab/open-index. Publisher page
114 GitHub stars
What it is
Deterministic memory layer for your agents
AutoGineer overview, from the brain’s own files
Open Index is a memory infrastructure tool that enables agents to operate on accurate, structured domain data. It allows users to define a 'brain'—a searchable context graph consisting of doc_types, schemas, entities, and connectors. By providing a standardized way to store, retrieve, and update domain knowledge, it ensures that agents have access to consistent information. The system supports both file-backed storage for human-curated data and index-backed storage for high-volume or machine-generated data, with backends ranging from local SQLite to distributed OpenSearch for multi-agent environments.
How it works
Memory model: Hybrid
AutoGineer overview, from the brain’s own files
Open Index organizes information into a brain directory containing schemas (doc_types) and instances (entities). It uses a hybrid storage model where file-backed entities are tracked in git, while index-backed entities reside in a database. Agents interact with this brain via an MCP server, which provides tools for searching, retrieving, and updating entities. The system includes a CLI for managing the brain, a UI for exploring the knowledge graph, and connectors for automated data ingestion.
| Folder | Role | Files |
|---|---|---|
| .github | agent-config | 1 |
| docs | docs | 28 |
| examples | examples | 71 |
| skills | agent-instructions | 1 |
| tests | tests | 36 |
| docker | folder | 1 |
| open_index | folder | 39 |
The agent writes
- tests/ evidence: README.md
- docs/ evidence: README.md
Requirements
Runtimes: Python >=3.10
Databases: PostgreSQL, SQLite
API keys (by name): OPEN_INDEX_EMBEDDING_API_KEY, OPEN_INDEX_TOKEN
Works with: Claude Code, Claude apps, OpenAI Codex, GitHub Copilot, Cursor, Gemini CLI, Google Antigravity, Kiro, OpenCode, Amp, Roo Code, Cline, JetBrains Junie, Devin (ex-Windsurf)
Set it up
- Not scanned by AutoGineerThe author’s README also describes installing it from a clone of its repository. Read its install section: AutoGineer did not scan that path. https://github.com/DrDroidLab/open-index/blob/8e7ffadfae0052215a1ba4e3504786435dc0b2f6/README.md#getting-set-up
- Not scanned by AutoGineerThe author’s README also describes installing it with Docker Compose. Read its install section: AutoGineer did not scan that path. https://github.com/DrDroidLab/open-index/blob/8e7ffadfae0052215a1ba4e3504786435dc0b2f6/README.md#quickstart
Created by Doctor Droid. Published in DrDroidLab/open-index. Licence: MIT License.
Usage
AutoGineer overview, from the brain’s own files
- Define domain-specific concepts and schemas to structure agent knowledge.
- Use the MCP server to provide agents with read and write access to the brain.
- Automate data ingestion from external sources using custom connectors.
- Visualize and explore the knowledge graph through the built-in UI.
- Scale from local development with SQLite to production environments with OpenSearch.
- “Search for all services owned by the payments-team.”
- The agent queries the brain and returns a list of service entities matching the owner field.
- “Create a new issue entity linked to the checkout service.”
- The agent uses the put_entity tool to store the new issue and establish a relationship edge to the specified service.
Documentation by Doctor Droid
Open Index
Image: tests Image: Join our Discord
Open Index is a tool for building domain specific accurate, structured data that agents can actually operate on — and for keeping that data correct as things change.
You use Open Index to build a brain: a searchable, continuously-improving context graph of your domain. A brain is domain-agnostic — model a support org (product → "has common issue" → issue), a sales pipeline (customer → order), your infrastructure (service → runbook), or anything else. You define the concepts; Open Index stores them, searches them, and draws the map.
A brain is built from four primitives:
doc_type — a concept you want to track and maintain (e.g.
service,customer,issue).doc_schema — the fields stored for a given doc_type.
entity — one instance of a doc_type, stored per its schema. Every entity can link to others via
related_to(the target) +relationship_edge_meaning(free-text edge semantics).connector — an optional source you extract entities from (e.g. an MCP server).
Quickstart
pip install -e '.[all]' # core + explorer UI + MCP server
# Try the bundled example (support brain: products, issues, segments, comments)
open-index index --brain examples/support-brain
open-index ui --brain examples/support-brain # open the Map tab, pick an anchor
# Or start your own brain from scratch
open-index init my-brain
open-index add-doc-type customer --brain my-brain
# ...add entities under my-brain/entities/**/*.json...
open-index index --brain my-brain
open-index ui --brain my-brainPrefer containers, or need a brain several agents share? →
docs/deployment.mdx (docker compose --profile sqlite up).
Commands
| Command | What it does |
|---|---|
open-index init <name> [dir] | Scaffold a new brain directory. |
open-index add-doc-type <name> | Add a doc_type schema stub under doc_types/. |
open-index add-entity <file> | Validate + store an entity JSON file. |
open-index import <file> | Bulk-import entities from JSON / JSONL / CSV. |
open-index index | (Re)load entities/**/*.json into the search index. |
open-index validate | Validate brain.yaml, schemas, and every entity file (use in CI). |
open-index ingest <connector> | Run a connector now to pull entities from an MCP server. |
open-index run [--force] [--loop N] | Run every connector whose schedule is due (wire into cron/CI). |
open-index search <query> [-t doc_type] | Search from the terminal. |
open-index ui | Launch the explorer (How to use / Schema / Explore / Map / Analytics / Jobs). |
open-index mcp [--read-only] | Run the MCP context layer over stdio. Read+write by default; --read-only opts out of writes. |
open-index serve [--port --token --read-only] | Serve the MCP context layer over HTTP for remote agents (bearer-token auth). |
open-index serve --brains <root> | Serve every brain under a directory from one process, each at /<name>/mcp. |
open-index mcp-config [--url --token] | Print the MCP connection block to paste into your agent. |
A context layer for domain-specialized agents
Open Index is designed to sit behind agents specialized for a domain—legal, marketing, customer support, sales, infrastructure, or a domain of your own. The MCP server gives those agents structured context and a validated way to keep that context current:
agent prompt — dynamic domain navigation is published through MCP server instructions so supporting hosts can inject it before the first turn
read —
navigation_guidelines()refreshes those instructions;search_brain()andget_entity()retrieve domain contextwrite —
put_entity()(add/update an entity),create_doc_type()(define a concept)
Read and write is the default MCP mode so a domain agent can both use knowledge
and maintain it. Add --read-only when the agent should consume context without
mutating it. Claude Code is supported as one optional MCP client; open-index init
scaffolds .mcp.json, CLAUDE.md, and an editing skill as conveniences for it.
Portable agent setup skill
skills/setup-open-index/SKILL.md follows the
portable Agent Skills SKILL.md format used by agent runtimes including OpenClaw,
Hermes, and Claude Code. Give or install this skill in the selected runtime when
the agent should set up Open Index itself. It covers installation, domain-brain
initialization, generic MCP wiring, default read/write verification, the
--read-only opt-out, and production guardrails.
A brain on disk
my-brain/
brain.yaml # name + storage/search backend
doc_types/*.yaml # one schema per doc_type (fields, boosts, display color)
entities/**/*.json # entities, with related_to edges
connectors/*.py # optional ingestion scripts (MCP → entities)Storage defaults to SQLite + FTS5 (zero external services). The backend sits
behind a pluggable interface with two implementations: SQLite (default, local/dev)
and OpenSearch (select with search.backend: opensearch — see
Using the brain from a cloud agent).
Where entities live — storage: file | index
Each doc_type declares its source of truth, so curated and machine-generated data don't fight over git:
storage: index(default) — the search DB owns these entities; they are not written to files. Right for connector-pulled, high-volume, or temporal data (hundreds of services, memories, alerts) that would otherwise churn the repo.storage: file— JSON files underentities/<doc_type>/are the source of truth, git-tracked and PR-reviewable. Right for curated, human/agent-authored entities.
open-index index reconciles file-backed types from disk on each run and
leaves index-backed entities (written by connectors/agents) untouched. So
brain.db is durable state for index-backed types — back it up or re-ingest;
it's gitignored by default.
Creating a brain, step by step
open-index init <name> scaffolds the directory below; then you author two kinds
of file — doc_types (schemas) and entities (instances). Sample doc_types:
infra (service, datastore, dashboard, runbook, alert), sales (lead,
deal, account), lending (loan, borrower, application), or personal
(goal, project, person, area, note). Three runnable examples ship in
examples/: support-brain, infra-brain, and personal-brain.
1. Define a doc_type
A doc_type is a concept plus its schema — one YAML file in doc_types/:
# doc_types/service.yaml
doc_type: service
description: A deployed service.
storage: file # file = git source of truth · index = DB-owned (default)
display:
label_field: name
color: "#7c3aed"
schema:
fields:
- { name: name, type: string, search: syntactic, boost: 6 } # weighted 6× in ranking
- { name: description, type: text, search: semantic }
- { name: owner, type: string, search: syntactic }
relationships: # the correlations this type uses — optional but recommended
- { name: "writes to", target_doc_type: datastore }
- { name: "is monitored by", target_doc_type: dashboard }boostsets per-field search weight — a hit in aboost: 6title outranks aboost: 1description hit 6-to-1. Optional; defaults to 1.relationshipsdeclares the edge vocabulary so correlations are discoverable (shown in the UI + navigation guide) and lightly validated (right target type). Optional — entities may still use undeclared meanings.
Create one with open-index add-doc-type service (writes a stub you edit), or ask your agent.
2. Add entities
An entity is one instance. For storage: file types, write one JSON per entity
under entities/<doc_type>/:
// entities/service/checkout.json
{
"doc_type": "service",
"id": "service:checkout",
"name": "Checkout",
"owner": "payments-team",
"related_to": [
{ "target": "datastore:postgres-main", "relationship_edge_meaning": "writes to" },
{ "target": "dashboard:checkout-latency", "relationship_edge_meaning": "is monitored by" }
]
}idmust be<doc_type>:<slug>.related_tois the reserved correlation field present on every entity — it defines the graph edges (target+relationship_edge_meaning). This is how you say "this ticket is about that service" without any graph database.
Then open-index index (loads file-backed entities) and open-index validate.
3. Populate at scale (four ways, one validated store)
Manual / agent — write JSON, or open Claude Code in the folder and let it call
put_entity/create_doc_typeover MCP.Bulk — import a file directly, or let an agent write a batch in one call with
put_entities:open-index import issues.csv --doc-type issue --asserted-by import:jira open-index import export.jsonl --dry-run # validate first, write nothingJSON arrays, JSONL, and CSV all work. Bare slugs are qualified (
checkout→product:checkout), CSV scalars are coerced, and arelated_tocolumn takestarget|meaningpairs separated by;. A bad row is reported and skipped — the rest still land.--asserted-by/--confidenceattribute the whole batch once instead of per row.Connectors —
connectors/*.pypull from an MCP server on aschedule; run withopen-index ingest <name>oropen-index run(cron/CI-friendly).Agent write-back — a Stop hook that records learnings via
put_entity(the "continuously improving" loop).
See Entity Management for guidance on cadence and decay.
4. Explore
open-index ui opens a read-only explorer. The sidebar always shows every doc_type
with its count and storage policy, so the structure is visible without navigating
anywhere. Four tabs: Explore (search + browse + drill into an entity's
relationships), Map (auto-anchored on the most-connected entities — click any
node to expand it), Analytics (what context CLI/MCP/UI clients fetched, and how
often — zero-result searches show what to model next), and Jobs (connectors and
their schedules).
Using Open Index as your agent's context layer
open-index mcp runs an MCP server (stdio) exposing the brain to any MCP client —
read and write by default:
The server publishes dynamic, brain-specific instructions as part of the agent prompt so supporting hosts can navigate the domain before the first tool call.
navigation_guidelines()— refresh that guide after the index/schema changes.search_brain(query, doc_types, limit)·get_entity(id)— read.put_entity(...)·put_entities([...])·create_doc_type(...)— write (validated, honors the storage policy).put_entitieswrites a whole batch in one call and takes a sharedprovenanceblock.
Use open-index mcp --read-only (or open-index serve --read-only) to opt out
when an agent should retrieve domain context but never maintain it.
Local context-fetch analytics
CLI and MCP searches, entity fetches, and navigation-guide reads are recorded in
the user's local state directory (~/.local/state/open-index/), outside the brain
checkout. The Analytics tab shows fetch counts
by client/operation, frequently fetched queries or entity IDs, latency, failures,
zero-result searches, and recent activity. This file stays local and is never
sent to Open Index's creators.
open-index init also includes optional Claude Code conveniences: .mcp.json, a
CLAUDE.md describing durable editing workflows (not runtime navigation), and an
edit-brain skill. They are one client integration, not a requirement for
building legal, marketing, support, or other specialized agents on Open Index.
Using the brain from a cloud agent (production)
📖 Full deployment guide → docs/deployment.mdx — local,
remote (with and without Docker), TLS/proxying, and exactly what to paste into
Claude Code, Claude Desktop, or Cursor.
Getting the connection details
Never hand-assemble the config. Ask for it:
open-index mcp-config --brain ./my-brain # local (stdio)
open-index mcp-config --url brain.acme.internal:8080 --token $OPEN_INDEX_TOKEN
open-index mcp-config --url https://brain.acme.com --token $TOKEN --cli # `claude mcp add …`
open-index mcp-config --brain ./my-brain > .mcp.json # pipes where it belongsopen-index serve prints the same details on startup — including the addresses a
remote client can actually reach. (The bind address it listens on, 0.0.0.0, is
not one of them.) Behind a proxy or tunnel, pass --public-url so what's printed
is what agents should use.
With Docker (recommended for a shared brain)
cp .env.example .env # set OPEN_INDEX_TOKEN + BRAIN_DIR
docker compose --profile sqlite up --build # single writer, no extra services
docker compose --profile opensearch up --build # many writers, incl. the clusterBoth serve http://localhost:8080/mcp. Your brain.yaml is identical either way —
the profile sets OPEN_INDEX_SEARCH_BACKEND, which overrides the file. Add
--profile ui for the explorer on :8501. The brain directory is mounted, not
baked into the image, so doc_types and entities stay in git.
Without Docker
pip install 'open-index[serve]' # add ,opensearch for that backend
open-index index --brain /srv/acme-brain # load file-backed entities first
OPEN_INDEX_TOKEN=… open-index serve --brain /srv/acme-brain --port 8080serve exposes the same read+write tools over streamable HTTP with
bearer-token auth. Without a token the endpoint is unauthenticated — anyone who
can reach the port can write to your brain. Use --read-only for a queryable
endpoint that agents can't mutate.
Choosing a backend
SQLite is single-writer. That, not entity count, is the line: the moment a second agent needs to write, move to OpenSearch. It also gives native per-field boosting, fuzzy (typo-tolerant) search, and k-NN semantic search that scales past SQLite's ~10k-entity brute-force ceiling.
Select it per-environment without touching brain.yaml:
export OPEN_INDEX_SEARCH_BACKEND=opensearch
export OPEN_INDEX_OPENSEARCH_HOSTS=https://opensearch.internal:9200…or commit it, with secrets as ${ENV} refs resolved at connect time:
search:
backend: opensearch
hosts: ["https://opensearch:9200"]
index: open_index_acme # optional; defaults to open_index_<name>
username: "${OPENSEARCH_USER}"
password: "${OPENSEARCH_PASSWORD}"
use_ssl: true
verify_certs: trueThe doc_type/file-entity part comes from git; index-backed data lives only in the
cluster (or brain.db), so give it a persistent home and a backup. Rule of thumb:
local/dev → SQLite; shared endpoint → OpenSearch + serve.
Controlling search
📖 Full configuration reference → docs/guides/search-configuration.mdx —
decision tables for storage: file | index, SQLite vs OpenSearch, and every search knob.
Schema (per field): data type (string/number/boolean/timestamp), processing
(keyword/text/timestamp), and search kind (syntactic = keyword+prefix,
semantic = vector-backed dense search, none = not indexed).
Mark a field search: semantic and the backend automatically embeds it at index time.
Ranking — genuine per-field boosters: each field's boost weights how much a
match there counts, so you tune "title matters more than description" with one number.
For hybrid queries, keyword and semantic scores are blended with search.semantic_weight
(default 0.3 — keyword matches dominate; semantic similarity rescues queries that use
different words than the text). semantic_weight: 0 gives keyword-only behavior; 1.0
gives semantic-only.
Storage defaults to SQLite + FTS5; the OpenSearch backend implements the same
interface with native per-field boosting, fuzzy matching, and k-NN semantic search.
Embedding model — install the [semantic] extra (pip install 'open-index[semantic]')
to enable local embeddings. The default model is BAAI/bge-small-en-v1.5 (384-D). Override
it with search.embedding_model in brain.yaml, or use an OpenAI-compatible API by setting
OPEN_INDEX_EMBEDDING_BASE_URL, OPEN_INDEX_EMBEDDING_API_KEY, OPEN_INDEX_EMBEDDING_MODEL,
and OPEN_INDEX_EMBEDDING_DIM.
Changing the embedding dimension (e.g., switching from the local 384-D model to a 512-D API
provider) requires rebuilding the index: open-index index --reembed on SQLite, or a full
open-index index --reembed on OpenSearch after recreating the index.
Re-embedding — the reserved field embedding stores the per-entity vector. If you enable
semantic search on an existing index, run open-index index --reembed to backfill vectors.
SQLite semantic ceiling — the SQLite backend performs a brute-force cosine scan over the
entities in scope. This is fine up to roughly 10,000 entities; for larger brains, switch to
the OpenSearch backend or a future sqlite-vec integration.
Not yet implemented (declarable seams exist): type-level & temporal boosters.
Contributing
Contributions are welcome — new doc_type examples, connectors, backends, docs fixes, or bug reports all help.
Join the community on Discord to ask questions, share the brains you're modelling, or discuss an idea before you build it. It's the fastest way to get an answer and the best place to sanity-check a bigger change.
Getting set up
git clone https://github.com/DrDroidLab/open-index
cd open-index
pip install -e '.[all]' # core + explorer UI + MCP server
pytest # run the test suiteNote that pytest skips the MCP and UI suites when those extras aren't
installed, so a green run on a partial install doesn't mean much — use [all]
(or at least [ui,mcp]) locally. CI installs them explicitly and fails if they
are missing.
Making a change
Open an issue first for anything non-trivial (new backend, schema change, CLI surface) so we can agree on the shape — or bring it to Discord.
Branch off
main(feat/…,fix/…,docs/…).Add tests under
tests/for behaviour changes, and runpytest.If you touched a brain in
examples/, runopen-index validate --brain examples/<name>so schemas and entities stay consistent.Update the README / the docs under
docs/when you change user-facing behaviour.Open a PR describing what changed and why, and link the issue. CI runs the test suite on Python 3.10 and 3.13 and validates every brain in
examples/.
Good first contributions
A new example brain under
examples/for a domain we don't cover yet.A connector in
connectors/that pulls entities from an MCP server you use.Doc_type schemas for a common vertical (support, infra, sales, lending, personal).
Sharper docs — if something tripped you up while onboarding, that's a bug.
Questions, ideas, or just want to show what you built? → https://discord.gg/AQ3tusPtZn
docs/deployment.mdx
title: Deployment description: Deploy a brain and connect your agent — local, remote, Docker, TLS, and the exact config to paste.
This guide answers three questions, in order:
Which setup do I want? — local vs remote, SQLite vs OpenSearch
How do I run it — local, remote without Docker, remote with Docker
How do I get the MCP details into my agent? — the part that is easy to get wrong
<Note> There is a second, unrelated use of "MCP" in Open Index — *connectors*, which pull data **from** someone else's MCP server into your brain. That's [Connectors](/agents/connectors). </Note>
1. Which setup do I want?
Two independent choices. Pick one from each column.
Where does the brain run?
| Local (stdio) | Remote (HTTP) | |
|---|---|---|
| How the agent connects | It launches open-index mcp itself | It connects to a URL you host |
| Needs a URL or token | No | Yes |
| Who can use it | One agent, on your machine | Any agent that can reach the host |
| Use when | You're the only user; the brain lives in a repo you have checked out | A team, cloud agents, or CI shares one brain |
Which search backend?
| SQLite (default) | OpenSearch | |
|---|---|---|
| External services | None — one brain.db file | An OpenSearch cluster |
| Concurrent writers | One. SQLite is single-writer | Many |
| Semantic search | Brute-force cosine scan, fine to ~10k entities | Native k-NN, scales past that |
| Extras | Native per-field boosting, fuzzy/typo-tolerant search | |
| Use when | Local brains, single agent, trials, read-mostly | Several agents writing, or >10k entities |
Rule of thumb: local + SQLite to start. Move to remote + OpenSearch when a second writer appears — that's the line SQLite can't cross, not entity count.
<Warning> **OpenSearch is only set up for you in the Docker path.** Running it outside Docker means operating a cluster yourself; Open Index will happily connect to one ([config below](#opensearch-without-docker)), but this repo only ships a ready-made cluster in `docker-compose.yml`. </Warning>
Switching backends does not require editing brain.yaml — set
OPEN_INDEX_SEARCH_BACKEND=sqlite|opensearch in the environment and it wins over
the file. That's what the compose profiles do.
2. Local brain (stdio)
Nothing to host. The agent starts the server itself over stdio.
pip install -e '.[all]'
open-index init my-brain
open-index index --brain my-brainopen-index init already writes a .mcp.json into the brain directory, so if you
open Claude Code in that folder, it connects automatically — no further setup.
To connect from somewhere else (a different repo, Claude Desktop, Cursor), get the config block:
open-index mcp-config --brain ./my-brain{
"mcpServers": {
"open-index": {
"command": "open-index",
"args": ["mcp", "--brain", "/absolute/path/to/my-brain"]
}
}
}The path is absolutized deliberately: your agent's working directory is usually not
the brain directory, and a relative --brain . quietly opens the wrong place (or an
empty one). See Connecting your agent for where this
block goes.
3. Remote brain, without Docker
Use this when you have a VM and don't want containers. The brain becomes an HTTP MCP endpoint that any agent can register by URL.
Install and run
# On the host
pip install 'open-index[serve]' # add ,opensearch if using OpenSearch
git clone <your-brain-repo> /srv/acme-brain # your doc_types + entities
open-index index --brain /srv/acme-brain # load file-backed entities
export OPEN_INDEX_TOKEN=$(openssl rand -hex 32)
open-index serve --brain /srv/acme-brain --port 8080serve prints exactly what to connect to:
open-index · brain 'acme' · read+write · search backend: sqlite
listening on 0.0.0.0:8080
on this machine http://127.0.0.1:8080/mcp
from another machine http://10.0.1.42:8080/mcp
auth: Authorization: Bearer 9f3a…******
agent config: open-index mcp-config --url http://10.0.1.42:8080 --token $OPEN_INDEX_TOKEN<Warning> **`0.0.0.0` is not an address.** It's the *bind* address — "listen on every interface". It is never what you paste into an agent. That's why the banner prints the reachable addresses separately. </Warning>
The token is not optional
serve exposes put_entity and create_doc_type. Without a token, anyone who can
reach the port can rewrite your brain. Either set OPEN_INDEX_TOKEN, or pass
--read-only to drop the write tools:
open-index serve --brain /srv/acme-brain --read-only # queryable, not writableA common shape is two endpoints: a read-only one on an open port, and an authenticated read+write one for the agents allowed to author.
Keep it running (systemd)
# /etc/systemd/system/open-index.service
[Unit]
Description=open-index brain (MCP)
After=network-online.target
[Service]
User=openindex
WorkingDirectory=/srv/acme-brain
Environment=OPEN_INDEX_TOKEN=<token>
# Uncomment to use an OpenSearch cluster instead of SQLite:
# Environment=OPEN_INDEX_SEARCH_BACKEND=opensearch
# Environment=OPEN_INDEX_OPENSEARCH_HOSTS=https://opensearch.internal:9200
ExecStartPre=/usr/local/bin/open-index index --brain /srv/acme-brain
ExecStart=/usr/local/bin/open-index serve --brain /srv/acme-brain --port 8080
Restart=on-failure
[Install]
WantedBy=multi-user.targetExecStartPre matters: file-backed entities live in git, not in the index. After a
git pull or a fresh machine, the index is empty until open-index index runs and
the brain answers every query with nothing.
TLS / behind a proxy
serve speaks plain HTTP. For TLS, terminate at nginx/Caddy and forward to it.
Streamable HTTP uses long-lived responses, so disable response buffering:
location /mcp {
proxy_pass http://127.0.0.1:8080/mcp;
proxy_http_version 1.1;
proxy_set_header Connection "";
proxy_set_header Authorization $http_authorization;
proxy_buffering off; # required — SSE/streaming responses
proxy_read_timeout 3600s;
}Then tell serve its public name so the banner and mcp-config print the URL
agents should actually use, rather than the internal one:
open-index serve --brain /srv/acme-brain --public-url https://brain.acme.com/mcpOpenSearch without Docker
Point brain.yaml at your existing cluster. Secrets stay as ${ENV} refs, resolved
when the connection is opened:
search:
backend: opensearch
hosts: ["https://opensearch.internal:9200"]
index: open_index_acme # optional; defaults to open_index_<brain name>
username: "${OPENSEARCH_USER}"
password: "${OPENSEARCH_PASSWORD}"
use_ssl: true
verify_certs: trueOr leave brain.yaml on SQLite and override per-environment:
export OPEN_INDEX_SEARCH_BACKEND=opensearch
export OPEN_INDEX_OPENSEARCH_HOSTS=https://opensearch.internal:92004. Remote brain, with Docker
The shortest path to a shared brain, and the only path where OpenSearch is set up for you.
cp .env.example .env
# Set OPEN_INDEX_TOKEN (openssl rand -hex 32) and BRAIN_DIR (path to your brain).<Tabs> <Tab title="SQLite"> No external services: ```bash docker compose --profile sqlite up --build ``` </Tab> <Tab title="OpenSearch"> Adds a single-node cluster with a persistent volume: ```bash docker compose --profile opensearch up --build ``` </Tab> </Tabs>
Both serve http://localhost:8080/mcp. The only difference is
OPEN_INDEX_SEARCH_BACKEND; your brain.yaml is identical either way, so you can
switch by changing the profile and nothing else.
Add the explorer UI on :8501 alongside either:
docker compose --profile opensearch --profile ui upWhat the container does on start
The brain directory is mounted, not baked in (BRAIN_DIR:/brain) — doc_types
and entities stay in your git repo. On start the entrypoint:
fails fast with a clear message if
/brain/brain.yamlisn't there,waits for OpenSearch to report healthy when that backend is selected,
runs
open-index indexso file-backed entities are loaded (skip withOPEN_INDEX_SKIP_INDEX=1),execs
open-index serve.
Permissions on the mounted brain directory
The container runs as uid 10001 (not root), so a bind-mounted brain directory owned by your user is not writable by it — indexing fails on the first write. Give the container ownership and keep group access for yourself:
sudo chown -R 10001:"$(id -g)" /path/to/my-brain
chmod -R g+rwX /path/to/my-brainYou can still read and edit the files; writes from inside the container land as uid 10001 with your group.
Many brains from one process
serve --brain <dir> runs one brain. For more than a handful, --brains <root>
serves every brain under a directory from a single process, each at /<name>/mcp:
open-index serve --brains /srv/brains --port 8080
# /srv/brains/support/ → /support/mcp
# /srv/brains/sales/ → /sales/mcpThis matters because of what is not duplicated. A process per brain re-loads the Python runtime and a ~250MB resident embedding model each time, so a modest host tops out at a handful. In one process the model is loaded once and a brain costs only its config and doc_types.
Measured on the bundled example brain, SQLite-backed:
| Brains | One process | One process per brain |
|---|---|---|
| 50 | 345 MB | ~13 GB |
| 200 | 373 MB | ~53 GB |
That is 1.8MB of marginal cost per brain, and 200 mount in ~4 seconds.
Each brain keeps its own storage, its own read/write policy and its own token —
OPEN_INDEX_TOKEN_<NAME> gates one brain (OPEN_INDEX_TOKEN_SALES_EU for
sales-eu/), and --token covers any without one. Nothing is shared between brains
except the process and the model.
Two extras come with it: GET / lists every brain with its URL, entity count and
doc_types, and GET /healthz is an unauthenticated probe for a load balancer.
<Note> **Prefer SQLite here.** With OpenSearch, every brain is a separate cluster index and therefore a shard; the working guidance is ~20 shards per GB of heap, so hundreds of brains would hit that ceiling long before RAM. SQLite gives each brain its own file and no shard cost at all. Use OpenSearch for the few brains that genuinely need concurrent writers or >10k entities. </Note>
Running one-off commands
docker compose --profile sqlite run --rm brain-sqlite validate
docker compose --profile sqlite run --rm brain-sqlite search "checkout latency"
docker compose --profile sqlite run --rm brain-sqlite ingest my-connector--brain /brain is added for you.
Plain docker run, no compose
docker build -t open-index .
docker run -p 8080:8080 \
-v "$PWD/my-brain:/brain" \
-e OPEN_INDEX_TOKEN=secret \
open-index serveNotes for real deployments
Persistence. SQLite:
brain.dbis inside your mounted brain dir — back that up. OpenSearch: theopensearch-datanamed volume. Index-backed entities (storage: index) exist only there; they are not in git and are not recreated byopen-index index. Back it up or be able to re-ingest.The OpenSearch cluster here has security disabled (
DISABLE_SECURITY_PLUGIN=true) and binds to loopback. That's fine for a single host where only the brain container talks to it; enable the security plugin and setsearch.username/passwordbefore putting it on a shared network.Behind a proxy, set
OPEN_INDEX_PUBLIC_URLin.envso the printed connection details are the ones agents can actually use.
5. Connecting your agent
You need two things: the URL (remote) or brain path (local), and the
token (remote only). mcp-config assembles both into the right block.
# Local brain
open-index mcp-config --brain ./my-brain
# Remote brain — host:port, or a full URL; /mcp is appended if you omit it
open-index mcp-config --url brain.acme.internal:8080 --token $OPEN_INDEX_TOKEN
# As a `claude mcp add` one-liner instead of JSON
open-index mcp-config --url https://brain.acme.com/mcp --token $TOKEN --cliIt writes to stdout, so it pipes straight where it belongs:
open-index mcp-config --brain ./my-brain > .mcp.jsonWhere the block goes
| Client | Location |
|---|---|
| Claude Code (project) | .mcp.json in the repo root — shared with the team via git |
| Claude Code (user-wide) | claude mcp add … (use --cli to get the exact command) |
| Claude Desktop | claude_desktop_config.json — same mcpServers shape |
| Cursor | .cursor/mcp.json — same mcpServers shape |
| Anything else | Any MCP client that speaks stdio or streamable HTTP |
A remote block looks like this:
{
"mcpServers": {
"open-index": {
"type": "http",
"url": "https://brain.acme.com/mcp",
"headers": { "Authorization": "Bearer <token>" }
}
}
}<Warning> Committing a token into `.mcp.json` puts it in git history. For a shared repo, prefer a per-developer user-scoped entry, or a read-only endpoint with no token for the committed config. </Warning>
Check it works before blaming the agent
# 401 = the server is up and your token is wrong or missing.
# 406/400 = you reached the MCP endpoint (it wants a proper MCP handshake). Good.
curl -i -X POST https://brain.acme.com/mcp \
-H "Authorization: Bearer $OPEN_INDEX_TOKEN" \
-H "Content-Type: application/json" \
-H "Accept: application/json, text/event-stream" \
-d '{"jsonrpc":"2.0","id":"1","method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{},"clientInfo":{"name":"curl","version":"0"}}}'Once connected, the agent should call navigation_guidelines() first — it reports
the doc_types, fields, and relationship vocabulary of your brain.
Troubleshooting
| Symptom | Cause |
|---|---|
| Agent can't connect at all | You used the bind address (0.0.0.0) instead of a reachable one. Re-read the serve banner. |
401 unauthorized | Token missing/wrong. The header must be exactly Authorization: Bearer <token>. |
Connects, but 404 | Missing the /mcp path. mcp-config appends it; hand-written URLs often don't. |
| Connects, but every search is empty | open-index index never ran on this host, so file-backed entities aren't loaded. |
| Tools are read-only unexpectedly | The server was started with --read-only. |
| Writes fail with "unknown doc_type" | Create the doc_type first (create_doc_type), or check navigation_guidelines() for existing ones. |
| Local stdio server opens an empty brain | Relative --brain . resolved against the agent's cwd. Use an absolute path — mcp-config emits one. |
| Hangs/timeouts behind nginx | Response buffering is on. Set proxy_buffering off. |
187 files · 176 not served here
- .dockerignore (not served here)
- .env.example (not served here)
- .github/workflows/tests.yml (not served here)
- .gitignore (not served here)
- ATTRIBUTION.md
- CLAUDE.md
- Dockerfile (not served here)
- LICENSE
- README.md
- docker-compose.yml (not served here)
- docker/entrypoint.sh (not served here)
- docs/README.md
- docs/agents/connectors.mdx (not served here)
- docs/agents/mcp.mdx (not served here)
- docs/concepts.mdx
- docs/deployment.mdx
- docs/docs.json (not served here)
- docs/favicon.svg (not served here)
- docs/guide/conflicts-and-staleness.mdx (not served here)
- docs/guide/context-poisoning.mdx (not served here)
- docs/guide/context-search-accuracy.mdx (not served here)
- docs/guide/determinism.mdx (not served here)
- docs/guide/human-steering.mdx (not served here)
- docs/guide/index.mdx (not served here)
- docs/guide/provenance.mdx (not served here)
- docs/guide/self-learning-loops.mdx (not served here)
- docs/guide/the-harness.mdx (not served here)
- docs/guide/what-are-autonomous-agents.mdx (not served here)
- docs/guides/creating-a-brain.mdx (not served here)
- docs/guides/entity-management.mdx (not served here)
- docs/guides/populating-entities.mdx (not served here)
- docs/guides/search-configuration.mdx (not served here)
- docs/index.mdx
- docs/logo/dark.svg (not served here)
- docs/logo/light.svg (not served here)
- docs/quickstart.mdx
- docs/reference/cli.mdx (not served here)
- docs/use-cases.mdx
- docs/why-open-index.mdx
- examples/cloud-brain/brain.yaml (not served here)
- examples/cloud-brain/connectors/k8s.py (not served here)
- examples/cloud-brain/doc_types/dashboard.yaml (not served here)
- examples/cloud-brain/doc_types/deployment.yaml (not served here)
- examples/cloud-brain/doc_types/known_issue.yaml (not served here)
- examples/cloud-brain/doc_types/metric.yaml (not served here)
- examples/cloud-brain/doc_types/namespace.yaml (not served here)
- examples/cloud-brain/doc_types/node.yaml (not served here)
- examples/cloud-brain/doc_types/runbook.yaml (not served here)
- examples/cloud-brain/doc_types/service.yaml (not served here)
- examples/cloud-brain/entities/dashboard/checkout-latency.json (not served here)
- examples/cloud-brain/entities/dashboard/errors.json (not served here)
- examples/cloud-brain/entities/known_issue/five-xx-on-scaledown.json (not served here)
- examples/cloud-brain/entities/known_issue/latency-spike.json (not served here)
- examples/cloud-brain/entities/metric/error-rate.json (not served here)
- examples/cloud-brain/entities/metric/p99-latency.json (not served here)
- examples/cloud-brain/entities/runbook/five-xx.json (not served here)
- examples/cloud-brain/entities/runbook/high-latency.json (not served here)
- examples/cloud-brain/tools_server.py (not served here)
- examples/infra-brain/.claude/skills/edit-brain/SKILL.md (not served here)
- examples/infra-brain/brain.yaml (not served here)
- examples/infra-brain/connectors/alerts_connector.py (not served here)
- examples/infra-brain/doc_types/alert.yaml (not served here)
- examples/infra-brain/doc_types/dashboard.yaml (not served here)
- examples/infra-brain/doc_types/datastore.yaml (not served here)
- examples/infra-brain/doc_types/runbook.yaml (not served here)
- examples/infra-brain/doc_types/service.yaml (not served here)
- examples/infra-brain/entities/dashboard/checkout-latency.json (not served here)
- examples/infra-brain/entities/dashboard/payments-errors.json (not served here)
- examples/infra-brain/entities/datastore/postgres-main.json (not served here)
- examples/infra-brain/entities/datastore/redis-cache.json (not served here)
- examples/infra-brain/entities/runbook/db-failover.json (not served here)
- examples/infra-brain/entities/runbook/payment-declines.json (not served here)
- examples/infra-brain/entities/service/api-gateway.json (not served here)
- examples/infra-brain/entities/service/checkout.json (not served here)
- examples/infra-brain/entities/service/orders.json (not served here)
- examples/infra-brain/entities/service/payments.json (not served here)
- examples/personal-brain/.claude/skills/edit-brain/SKILL.md (not served here)
- examples/personal-brain/brain.yaml (not served here)
- examples/personal-brain/doc_types/area.yaml (not served here)
- examples/personal-brain/doc_types/goal.yaml (not served here)
- examples/personal-brain/doc_types/note.yaml (not served here)
- examples/personal-brain/doc_types/person.yaml (not served here)
- examples/personal-brain/doc_types/project.yaml (not served here)
- examples/personal-brain/entities/area/career.json (not served here)
- examples/personal-brain/entities/area/family.json (not served here)
- examples/personal-brain/entities/area/health.json (not served here)
- examples/personal-brain/entities/goal/run-marathon.json (not served here)
- examples/personal-brain/entities/goal/ship-side-project.json (not served here)
- examples/personal-brain/entities/goal/weekly-family-time.json (not served here)
- examples/personal-brain/entities/note/week31-reflection.json (not served here)
- examples/personal-brain/entities/person/alex.json (not served here)
- examples/personal-brain/entities/person/sam.json (not served here)
- examples/personal-brain/entities/project/open-index.json (not served here)
- examples/personal-brain/entities/project/training-plan.json (not served here)
- examples/support-brain/.claude/skills/edit-brain/SKILL.md (not served here)
- examples/support-brain/brain.yaml (not served here)
- examples/support-brain/connectors/example_connector.py (not served here)
- examples/support-brain/doc_types/comment.yaml (not served here)
- examples/support-brain/doc_types/issue.yaml (not served here)
- examples/support-brain/doc_types/product.yaml (not served here)
- examples/support-brain/doc_types/user_segment.yaml (not served here)
- examples/support-brain/entities/comment/acme-frustrated.json (not served here)
- examples/support-brain/entities/issue/no-results.json (not served here)
- examples/support-brain/entities/issue/payment-declined.json (not served here)
- examples/support-brain/entities/issue/slow-checkout.json (not served here)
- examples/support-brain/entities/issue/threed-secure-timeout.json (not served here)
- examples/support-brain/entities/product/checkout.json (not served here)
- examples/support-brain/entities/product/search.json (not served here)
- examples/support-brain/entities/user_segment/enterprise.json (not served here)
- examples/support-brain/entities/user_segment/mobile-first.json (not served here)
- open_index/__init__.py (not served here)
- open_index/analytics.py (not served here)
- open_index/brain.py (not served here)
- open_index/bulk.py (not served here)
- open_index/cli.py (not served here)
- open_index/config.py (not served here)
- open_index/connectors/__init__.py (not served here)
- open_index/connectors/base.py (not served here)
- open_index/connectors/mcp_client.py (not served here)
- open_index/connectors/runner.py (not served here)
- open_index/embeddings.py (not served here)
- open_index/graph.py (not served here)
- open_index/mcp_config.py (not served here)
- open_index/mcp_server.py (not served here)
- open_index/models.py (not served here)
- open_index/scaffold.py (not served here)
- open_index/scheduling.py (not served here)
- open_index/schema.py (not served here)
- open_index/storage/__init__.py (not served here)
- open_index/storage/base.py (not served here)
- open_index/storage/opensearch_backend.py (not served here)
- open_index/storage/sqlite_backend.py (not served here)
- open_index/ui/__init__.py (not served here)
- open_index/ui/static/app.css (not served here)
- open_index/ui/static/app.js (not served here)
- open_index/ui/static/map.js (not served here)
- open_index/ui/static/vendor/cytoscape.min.js (not served here)
- open_index/ui/templates/analytics.html (not served here)
- open_index/ui/templates/base.html (not served here)
- open_index/ui/templates/directory.html (not served here)
- open_index/ui/templates/entity.html (not served here)
- open_index/ui/templates/explore.html (not served here)
- open_index/ui/templates/help.html (not served here)
- open_index/ui/templates/jobs.html (not served here)
- open_index/ui/templates/map.html (not served here)
- open_index/ui/templates/missing.html (not served here)
- open_index/ui/templates/schema.html (not served here)
- open_index/ui/view.py (not served here)
- open_index/ui/web.py (not served here)
- pyproject.toml (not served here)
- skills/setup-open-index/SKILL.md (not served here)
- tests/conftest.py (not served here)
- tests/test_agent_skill.py (not served here)
- tests/test_analytics.py (not served here)
- tests/test_analytics_paths.py (not served here)
- tests/test_bulk.py (not served here)
- tests/test_bulk_embeddings.py (not served here)
- tests/test_cli.py (not served here)
- tests/test_cli_serving.py (not served here)
- tests/test_connector.py (not served here)
- tests/test_env_overrides.py (not served here)
- tests/test_graph.py (not served here)
- tests/test_import_cli.py (not served here)
- tests/test_mcp_authoring.py (not served here)
- tests/test_mcp_client.py (not served here)
- tests/test_mcp_config.py (not served here)
- tests/test_mcp_server.py (not served here)
- tests/test_mcp_transport.py (not served here)
- tests/test_models.py (not served here)
- tests/test_multi_brain_serve.py (not served here)
- tests/test_navigation_guide.py (not served here)
- tests/test_opensearch_builders.py (not served here)
- tests/test_opensearch_integration.py (not served here)
- tests/test_provenance.py (not served here)
- tests/test_relationships.py (not served here)
- tests/test_run_and_env.py (not served here)
- tests/test_scheduling.py (not served here)
- tests/test_schema_validation.py (not served here)
- tests/test_search_filters.py (not served here)
- tests/test_search_modes_and_filters.py (not served here)
- tests/test_semantic_search.py (not served here)
- tests/test_storage_policy.py (not served here)
- tests/test_storage_search.py (not served here)
- tests/test_structure.py (not served here)
- tests/test_ui_view.py (not served here)
- tests/test_ui_web.py (not served here)
- tests/test_writes.py (not served here)
Advantages
- Supports both file-based and index-based storage, allowing for a mix of human-curated and machine-generated data. evidence: README.md
- Provides a standardized MCP interface that enables agents to both consume and maintain domain context. evidence: README.md
Limitations
- SQLite backend is limited to single-writer operations, requiring a move to OpenSearch for multi-agent write access. evidence: README.md
Security
Risk level: Low risk
This brain runs
Agent instructions that run or fetch something (7)
examples/infra-brain/.claude/skills/edit-brain/SKILL.md:Run `open-index index` (loads file-backed entities) then `open-index validate`.examples/personal-brain/.claude/skills/edit-brain/SKILL.md:Run `open-index index` (loads file-backed entities) then `open-index validate`.examples/support-brain/.claude/skills/edit-brain/SKILL.md:Run `open-index index` (loads file-backed entities) then `open-index validate`.skills/setup-open-index/SKILL.md:If Open Index is published as a package, install the equivalent `open-index[mcp]` release instead.skills/setup-open-index/SKILL.md:OpenClaw and Hermes may install this `SKILL.md` in their own skills directory while their MCP connection is configured according to that runtime's current documentation.skills/setup-open-index/SKILL.md:Run a safe smoke test using synthetic content:skills/setup-open-index/SKILL.md:Run `open-index validate` after changing file-backed schemas or entities.
- Low risk: network-instructions (skills/setup-open-index/SKILL.md:22)
Attribution and licence
- Created by
- Doctor Droid
- Publisher
- Doctor Droid (Official Doctor Droid project)
- Source
- DrDroidLab/open-index · commit 8e7ffad
- Licence
- MIT License (files served here)
- ATTRIBUTION.md (written by AutoGineer)
- Synced
AutoGineer is not affiliated with or endorsed by Doctor Droid.
Install from source
176 of its files are not served here, so there is no ZIP. DrDroidLab/open-index at 8e7ffad