# Firekeep documentation

Provision Firekeep, connect your MCP clients, and run the self-hosted stack day
to day.

## Architecture

Firekeep is two halves. A **server** you run once, and a **client kit**
installed on each member's machine that wires your agent runtimes into it.

### Server services

| Service | Port | Purpose |
|---|---|---|
| Cortex | `8100` API, `8080` MCP | Long-term memory — semantic and graph recall |
| Bridge | `8070` | Session context that survives compression and crashes |
| Sentinel | `8060` | Environment observer — containers, git, file activity |
| Relay | `8050` | Agent-to-agent coordination, leases, tasks, messages |
| Dashboard | `8040` | Web UI over all of the above |

Backed by three datastores in the same stack — **Neo4j** (knowledge graph),
**Qdrant** (vector embeddings) and **Redis** (cache, queues, streams) — plus
**Ollama** for local model inference.

### Client kit

Installs to `~/.firekeep` and renders configuration for whichever agent runtimes
you use — Claude Code, Codex, Kiro and OpenCode are configured together (Claude Desktop too, when the app is installed), and any
other MCP client can be added with `firekeep install --runtime generic`. It
provides the MCP gateway your agent talks to, plus lifecycle automation where
each runtime's hook surface allows it — a generic client exposes no hooks, so it
gets the MCP tools and instructions on connect but not the hook lifecycle. The
day-to-day section below names the current differences explicitly.

**Dexes run client-side.** A dex has to be local to what it indexes, so Symdex
ships as a stdio MCP server on your machine rather than in the server stack, and
Docdex reads your folders from there too. Which ones run is a registry on that
machine — see [Dexes and documents](#dexes-and-documents). The Decision Board is
client-side as well: it opens an ephemeral loopback browser surface and returns
submitted answers to the waiting agent.

## Capability map

Firekeep joins durable memory, working context, code intelligence and agent
coordination. The boundaries below are part of the product: it records what
agents explicitly send and automates only the lifecycle events each client
exposes.

| Capability | Customer outcome | Current boundary |
|---|---|---|
| **Cortex memory** | Store decisions, incidents and proven fixes, then retrieve project-scoped evidence through semantic and relationship recall within a context budget. | Memory is explicitly written and is not a truth-verification system; retrieved sources remain inspectable. |
| **Bridge sessions** | Keep an explicit shadow of plans, decisions, progress and file notes so work can survive compression, interruption or a crash. | Direct session tools work everywhere; automatic capture and completion depend on the client's lifecycle hooks. |
| **Relay** | Coordinate agent identities with tasks, channels, direct messages, presence and fenced file or task leases. | Presence is best-effort and leases still require clients to cooperate with the protocol. |
| **Corpus & Knowledge** | Make an ingested document searchable immediately, then draft detected procedures into skills for human review. | Drafting requires a configured generation model. Without one, the source remains searchable but no procedure drafts are produced. |
| **Decision Board** | Collect answers to several agent-supplied questions in one browser board, alongside retrieved evidence and optional visual comparisons. | Retrieved evidence is deterministic; drafted suggestions need a generation backend that can answer inside the 30 s budget and are skipped on a CPU-only host, where the board still returns your questions and their evidence. Headless clients receive a text fallback. Answers return to the agent but do not execute actions or automatically become memory. |
| **Replay** | Inspect Firekeep-mediated lifecycle, tool, memory and policy events, open the exact or nearest saved context and narrow likely contributors to a failure. | Replay is an inspectable Firekeep trace, not a screen, keystroke or complete client recorder; contributor ranking is not causal proof. |
| **Sentinel** | Follow commits, selected file activity, custom operational events and optional container state. | Watch paths and repositories must be selected; Docker observation is deliberately off until an operator enables access. |
| **Symdex** | Index local symbols and relationships so agents can request focused caller, impact and architecture context instead of opening every file. | The index stays on the workstation; code context requested by an agent enters that agent's model context. Its tools appear once the dex is registered. |
| **Docdex** | Extract the document folders a person chooses — `.md`, `.txt`, `.pdf`, `.docx`, `.html`, `.eml` and conversation-shaped `.json` — into recall, private to that member by default even on a shared Keep. | Folder selection is human-only; no agent tool for it exists. No OCR, no file watching and no cloud sources; background sync needs a runtime with a session-start hook. |
| **Vault, policy & dashboard** | Keep credentials out of memory, mediate supported edits and inspect memories, sessions, skills, events, coordination and operations in one UI. | Vault values are encrypted at rest and returned in plaintext to authorized readers. Policy enforcement varies by client, as documented below. |
| **FirekeepScope** | Run shared, server-backed clarification screens that can block for an answer or stay open while an agent continues. | Headless or MCP-only agents call the `scope_*` tools; answers are human actions through the dashboard Scope tab or the REST answer endpoint. |
| **Evals & patterns** | Review trace-derived session health, recall use and prediction calibration, and surface recurring tool sequences, hotspots or failure modes. | Metrics are operational proxies, not answer-accuracy scores. Pattern observations are not proof; automated validation and experiments are off by default. |

## Symdex local code intelligence

Symdex parses your working tree locally with tree-sitter and gives supported
agents focused codebase context through MCP. It is the first
[dex](https://firekeep.ai/dexes.html) — a domain index that gives your Keep
understanding of one domain. Its wheel installs with the client kit; whether its
tools appear is a registration, and on a fresh machine that is one command:
`firekeep dex add symdex`. A machine that already had Symdex keeps it across the
update. See [Dexes and documents](#dexes-and-documents).

The built-in parsers cover Python, JavaScript, TypeScript, Go, Rust, Java, PHP,
C, C#, Ruby, Kotlin and Swift. Depending on the language's syntax, the index
captures functions, classes, methods, types, constants, routes and their
relationships. Thirty tools are available by default for symbol lookup, search,
callers, architecture, impact and related context; eight heavier analytics tools
are optional.

Symdex can serve symbol-level context when that is sufficient and include
broader file context when an edit needs it. Firekeep's code-and-memory tools can
also cross-reference the current code with decisions, fixes and change history
recalled from earlier sessions.

**Local indexing does not mean model-local processing.** The index stays on the
workstation and is not uploaded to the Firekeep server. Tool output requested by
an agent becomes part of that agent's working context, so it may be sent to
whichever model provider you configured for that client.

### Targeted symbol benchmark

The repeatable benchmark compares the stable response from a verified Symdex
symbol lookup with reading that symbol's entire containing file. It uses one
pinned public repository per supported language and 30 deterministic,
file-balanced functions, methods, classes or types from each repository. Test,
fixture, example and benchmark paths are excluded. No generation model or
subjective judge is involved.

| Language | Reference project | Sampled files | Mean estimated-token reduction | Exact Git-source checks |
|---|---|---|---|---|
| Python | Click | 17 | 82.01% lower | 30/30 |
| JavaScript | Express | 6 | 83.13% lower | 30/30 |
| TypeScript | p-queue | 5 | 60.39% lower | 30/30 |
| Go | Cobra | 18 | 80.47% lower | 30/30 |
| Rust | clap | 30 | 69.64% lower | 30/30 |
| Java | Gson | 30 | 46.40% lower | 30/30 |
| PHP | Guzzle | 30 | 21.74% lower | 30/30 |
| C | jq | 29 | 67.35% lower | 30/30 |
| C# | Humanizer | 30 | 0.55% lower | 30/30 |
| Ruby | Rack | 30 | 32.95% lower | 30/30 |
| Kotlin | Moshi | 30 | 57.12% lower | 30/30 |
| Swift | Swift Argument Parser | 30 | 56.27% lower | 30/30 |
| **Balanced result** | 12 projects | **285** | **54.83% lower** | **360/360** |

The primary result is the mean of all 360 per-lookup reductions, with equal
samples per language and file-balanced selection within each project. Token
counts use the `cl100k_base` encoding; `o200k_base` produces a similar 54.54%
result. Every indexed file and returned source was also checked byte-for-byte
against its pinned Git blob, verifying the requested code was returned exactly.
The median reduction was 78.42%; 58 of 360 lookups used more tokens because the
serialized tool response can exceed an already small source file. The benchmark
measures direct known-symbol retrieval against a whole-file baseline.

### Mixed-task Click benchmark

A separate archived benchmark asked 20 comprehension, navigation and
modification questions about Click, with three generated answers per question
and an LLM judge. Across the 20 unique context comparisons, Symdex consumed
23.31% fewer total context tokens. Its judged-accuracy point estimate was 4.42
versus 4.45 for raw-file context.

| Workload | Unique context comparisons | Total context-token result | Judged accuracy: Symdex / raw |
|---|---|---|---|
| All questions | 20 | 23.31% fewer | 4.42 / 4.45 |
| Comprehension | 7 | 47.67% fewer | 4.62 / 4.67 |
| Navigation | 7 | 39.75% fewer | 4.43 / 4.43 |
| Modification | 6 | 25.30% more | 4.17 / 4.22 |

Judged accuracy was comparable across both conditions. Modification questions
consumed more context because the tested builder combined raw files with
structural analysis.

### Working-state restore benchmark

When an agent's context window is compacted, it restores its working state from
the session server. That restore returns the whole session document — plan,
decisions, file knowledge, progress and scratchpad — and still does by default.
An agent may instead hand back an opaque cursor and receive only what changed
since it last looked. This benchmark measures the difference between those two
responses. No generation model or subjective judge is involved; token counts use
the cl100k_base encoding.

| Scope | Full restore | Delta restore | Token reduction |
|---|---|---|---|
| 26 real sessions, aggregate | 16,725 | 10,075 | 39.8% lower |
| The 7 sessions above 1,000 tokens | not recorded separately | not recorded separately | 50.7% lower |
| Weakest single session | 991 | 983 | 0.8% lower |
| Strongest single session | 470 | 153 | 67.4% lower |

**This is a per-restore reduction, not a per-request one.** A delta is returned
when an agent asks to restore its working state, which happens after a context
compaction rather than on every turn.

Per-session reductions ranged from 0.8% to 67.4%, and the variation is
structural. Scratchpad entries are stored without per-entry timestamps, so a
delta always returns them in full; the same applies to proactively recalled
memories, which are replaced wholesale rather than appended. Scratchpad alone
accounted for 14.1% of all measured session tokens. Of the reduction, 80% comes
from filtering decision, progress and file entries and 20% from omitting an
unchanged plan.

The dataset is 26 real sessions from one operator on one machine. A separate
check against a live deployment on 2026-08-01 reduced one session's restore from
12,122 to 4,977 characters.

The delta is opt-in. An agent that never sends a cursor receives byte-identical
output to before, and six independent conditions — an absent, unparsable,
wrong-session, stale, unreadable or unanchored cursor — each return the complete
document rather than a partial one. Where the mechanism can be wrong, it returns
too much rather than too little. A restore that omits content states what it
withheld and how to retrieve it, so an agent reading a delta cannot mistake an
omission for an absence.

## Requirements

### Server

- **Docker** and **Docker Compose v2** with `linux/amd64` container support —
  use an x86-64 Linux server or VPS, or **Docker Desktop** with amd64 support on
  Windows or Mac (on Windows, run setup under WSL2 or Git Bash)
- **2 x86-64 CPU cores minimum**
- **Git**
- **16 GB RAM recommended** for the default stack
- **No open ports required** — a default install binds to `127.0.0.1`

**The current server release targets linux/amd64.** Run it directly on an x86-64
Linux host, or through **Docker Desktop** with amd64 container support on Windows
or Mac. On Windows the setup runs under WSL2 or Git Bash (Docker Desktop's own
Linux backend). The Linux x86-64 install path has automated smoke coverage on
relevant main-branch changes and nightly; Windows and macOS are hand-checked on
Docker Desktop. See [Installing Firekeep](#installing-firekeep).

**Size RAM first.** 8 GB is the practical floor, and only with a small embedding
model (`EMBEDDING_MODEL=granite-embedding:30m`, `EMBEDDING_DIM=384`). Below
that, containers are OOM-killed *while HTTP health checks still pass* — a
failure mode that looks like anything except running out of memory.

On **Docker Desktop** (Windows or macOS) the stack runs inside a VM given a
fraction of host RAM by default — a 16 GB machine hands the stack roughly half
that and drops it onto the floor above. Raise Docker Desktop → Settings →
Resources → Memory to at least 16 GB before you set one up.

### Client

- **Linux** — CI cold-installs the published one-liner on seven distros every
  run: Ubuntu, Debian, Alpine, Fedora, Rocky, Arch and openSUSE. glibc and musl
  both work; Alpine is there to prove the musl path rather than to pad the list.
- **Windows** — PowerShell. CI runs the client suite and executes `install.ps1`
  on a Windows runner.
- **macOS** — supported by the same bootstrap and its toolchain is built for it,
  but it is **not** in the CI matrix: our lab runs in Docker, which cannot host
  macOS. We test it by hand, not on every commit.
- Nothing preinstalled — the bootstrap fetches its own pinned toolchain
- Installing from a source checkout instead needs a system `python3 >= 3.10`

## Licensing

Firekeep is one product: one client, one server, one capability set. There are
no editions and no feature gates — the tier answers who governs the Keep, not
which features you get.

The source is available under the [Business Source License
1.1](https://firekeep.ai/LICENSE.txt). Self-hosted internal use is free for
individuals and teams while Firekeep is in early access — any number of members,
with unlimited agents, terminals and devices. Production use outside that grant
requires a commercial licence. Each release converts to Apache-2.0 four years
after publication.

There are no licence keys and nothing to activate. Every install is the complete
product; the licence terms are the only boundary.

The terms also carry a published expiry: each release's licence converts to
Apache-2.0 four years after that release first ships. What you deploy today has
a dated path to a permissive licence built into it.

**Personal mode is a session state, not a licence.** `firekeep personal`
temporarily makes Firekeep dormant for private work. It has nothing to do with
licensing.

## Installing Firekeep

One bootstrap command per platform. It asks two required questions — who you
are, and where your server is — then offers a skippable prompt for another MCP
client. Standing up the server is one of the answers, not a separate procedure.
There is no registry account and no licence token.

macOS / Linux:

```
$ curl -fsSL https://firekeep.ai/latest/install | sh
```

Windows (PowerShell):

```
> irm https://firekeep.ai/latest/install.ps1 | iex
```

The bootstrap brings its own pinned Python toolchain and checksum-verifies
everything it downloads before running it. Nothing has to be installed first —
no system Python, no Node.

**Or let your agent do it.** If you already work with a coding agent, it can run
the install for you. In Claude Code:

```
/plugin marketplace add kapella-hub/FirekeepHQ
```

Then ask it to install Firekeep. The skill file itself is the portable
[Agent Skills](https://agentskills.io) format, so it works in any agent that
reads them — only the directory differs. For **Codex** (including the Codex
surface inside the ChatGPT desktop app, which shares the same host
configuration):

```
mkdir -p ~/.codex/skills/install-firekeep && curl -fsSL https://firekeep.ai/install-firekeep/SKILL.md -o ~/.codex/skills/install-firekeep/SKILL.md
```

For **Claude Code** without the plugin, swap `~/.codex/skills` for
`~/.claude/skills`. For anything else, drop the same file wherever that agent
reads skills from.

**Agents with no skills mechanism** — Cursor, Gemini CLI, and ordinary ChatGPT
conversations among them — have nothing to drop the file into. Install with the
one-liner above and connect them the normal way: MCP for Cursor and other MCP
clients (`firekeep install --runtime generic`), and for ChatGPT chats the Secure
MCP Tunnel recipe.

The skill drives the same commands documented on this page and finishes by
running `firekeep doctor`, so you see the real result rather than a claim of
success.

**It installs the client only.** The install path it uses cannot provision a
server — that is a property of the installer, not a promise about the agent.
Standing up Neo4j, Qdrant, Redis and Ollama is a separate command the skill is
instructed to confirm with you first, and asking is the one part that rests on
the agent honoring an instruction rather than on the code refusing.

### Three shapes, one command

Firekeep is a **server** you run once and a **client kit** on each member's
machine. The same one-liner installs either or both; your answer to its server
question decides which. Skim this, then read the walkthrough below.

| Shape | Run it on | What you do | What you get |
|---|---|---|---|
| **Both** (one machine) | x86-64 Linux, or Docker Desktop with amd64 support on Windows/Mac | The one-liner → answer **1** (*Set one up on this machine*) | Client *and* server, with this box already enrolled against it. The default when Docker is present. |
| **Client only** | Any Mac, Windows or Linux machine | The one-liner → answer **2** (join code), **3** (address + key) or **4** (not yet) | Just the kit, pointed at a server your team already runs — or at nothing yet, finished later. |
| **Server only** | x86-64 Linux, or Docker Desktop with amd64 support on Windows/Mac | `firekeep init --no-self-enroll`, or `bash install.sh` from a checkout or bundle | The full stack, with *no* machine enrolled — a build host, CI runner or golden image. Invite laptops afterwards. |

**Both** and **Server only** are the same install with one difference — whether
the box that built the server also becomes a client of it. Current server images
target `linux/amd64`, so use an x86-64 Linux host or Docker Desktop with amd64
support on Windows or Mac (on Windows, run setup under WSL2 or Git Bash). The
detail for each answer follows.

### The two required questions

What you actually see:

```
Agent identity (attributes every memory, session, and replay event) [alex]:

Where is your Firekeep server?
  1  Set one up on this machine  (installs the server here with Docker)
  2  I have a join code       (Dashboard -> Devices, or from a teammate)
  3  It is already running    (you know its address and have a key)
  4  Not yet                  (finish the client; `firekeep doctor` will
                               tell you how to finish the job later)
Choose [1]:
```

After these required questions, the installer offers one skippable prompt to add
another MCP client and optionally manage its rules file. The four shipped
adapters do not depend on that prompt.

The identity default is your OS username. It is kept as a question because the
username is a good default and a poor answer — on a server it is often `root`,
and every memory, session and replay event this machine writes is attributed to
whatever you say here.

| Answer | What happens |
|---|---|
| **1** — set one up | Runs `firekeep init` for you: provisions the whole stack with Docker on this machine, then enrolls the machine against it. The default when Docker is present. |
| **2** — join code | Asks you to paste the code and redeems it. The default when Docker is not present. |
| **3** — already running | Asks for the server address and an API key. |
| **4** — not yet | Finishes the client and stops. `firekeep doctor` then tells you how to finish the job whenever you are ready. |

**Which answer is yours.** The machine that will host the **server** answers
**1** — use an x86-64 Linux VPS or office box, or Docker Desktop with amd64
support on Windows or Mac (on Windows, run setup under WSL2 or Git Bash).

A laptop that should be a **client** of a server someone else runs answers **2**
or **3** instead. The client kit runs on Linux, Windows and macOS — see
[Requirements](#requirements) for what continuous testing covers.

### Answer 1 — the server provisions itself

It asks nothing further. The host address is detected (`ip route get`, falling
back to `127.0.0.1`) and the Neo4j password is generated. Both are overridable
for the cases that need it — a NAT'd host or floating IP, a restored backup, a
policy-managed secret — with `--ip` and `--neo4j-password`, or the
`FIREKEEP_VPS_IP` and `FIREKEEP_NEO4J_PASSWORD` environment variables for
unattended provisioning.

It downloads and checksum-verifies a source-free deployment bundle, pulls the
public Firekeep images and starts the stack. When the stack is up, **the machine
enrolls itself** through a join code minted locally over loopback, so `firekeep
doctor` reports a connected server straight away — no dashboard, no SSH tunnel,
no copied key. `firekeep init --no-self-enroll` opts out, which is what CI runs
and what a golden image wants.

### Server only — the stack, with nobody enrolled

A build host, a CI runner or a golden image should stand the stack up but not
become a client itself. That is the same provisioning as answer **1** minus the
self-enrollment:

```
$ firekeep init --no-self-enroll
```

From a source checkout or an unpacked release bundle, the direct equivalent is
`bash install.sh` — add `--pull` to run the published images instead of building
from source. Either way the stack comes up unenrolled; there is no client on
this box and nothing is attributed to it.

Enroll machines against it afterwards exactly as below: hand each one the
printed `FIREKEEP_JOIN=…` one-liner, or mint a code on the server with
`deploy/firekeep-admin invite --agent <name>`. See [Connecting a
device](#connecting-a-device).

### Adding a second machine

The server prints a ready-to-paste command for the next machine. Run it there:

```
$ curl -fsSL https://firekeep.ai/latest/install | FIREKEEP_JOIN=fk_join_… sh
```

Same one command, with the code supplied up front, so that machine is not asked
where the server is. A stock server answers only on `127.0.0.1`, so the code
carries an SSH route and `firekeep join` opens the tunnel itself using the SSH
access you already have. The code is single-use and expires.

For every machine after that, mint another on the server with
`deploy/firekeep-admin invite --agent <name>`, or use **Dashboard → Devices →
Add device**.

### The dashboard, and the two credentials

The dashboard is at `http://127.0.0.1:8040/` on the server itself, and over the
same tunnel from a machine that joined through a code. The browser asks for a
login: username `admin`, with the password the installer wrote once to
`dashboard/.htpasswd.cred` on the server at mode `0600` — read it once, then
delete the file.

That is a **separate credential from the admin API key**. The admin key is an
`nxs_` token you send as an `X-API-Key` header to the Cortex API; the dashboard
login will not accept it, and the dashboard password will not authenticate an
API call. Join codes issued from the dashboard are single-use and valid for 24
hours.

### While the models download

The install does not block on the ~3.3 GB embedding-model pull. The command
returns as soon as the stack is up and the pull continues behind it. Until it
finishes, memory writes return `status="partial"`: **stored and queued for
backfill, but not yet findable by search**. `firekeep doctor` shows an
`embeddings` warning row saying exactly that, and `docker compose logs -f
ollama-pull` shows progress. Use `bash install.sh --wait-for-models` if you
would rather the install block until the pull is done.

**Pinning and TLS deployment.** Use `firekeep init --version vX.Y.Z` to install
a specific published server release. `firekeep init --office` enables the
bundled TLS reverse-proxy deployment. Both provision the same complete product.

Only the four Firekeep service images are published. Neo4j, Redis, Qdrant,
Ollama and the nginx that serves the dashboard are pulled by your own Docker
daemon from their upstream registries — Firekeep never redistributes them.

### As a developer — build from source

From a checkout of the repository you have been granted access to:

```
$ firekeep init --server-dir .
```

A source checkout builds the Firekeep services locally. Add `--pull` only when
deliberately testing the published-image path.

### What the server installer does

- Checks for Docker and Compose v2, installing Docker if absent
- **Asks nothing** — it detects the host address and generates the Neo4j
  password, writing a validated `.env` at mode `600`
- Generates a vault encryption key
- Mints API keys and **prints an admin key exactly once**
- Pulls public images or builds source images, then starts the complete stack
- Enrolls the machine it is running on, and prints a ready-to-paste command for
  the next one

**Keep the admin key.** It is printed once and never stored. You do not need it
to get started — this machine enrolls itself and the next one joins with a
printed code — but it is the key that administers devices and workspace
membership later. On a long-running server it simply does not exist any more,
and if you lose it, key minting must be done on the server itself, where a shell
already implies total control.

## Your first authenticated call

A fresh install is closed by default in two independent ways.

| Default | Value | Effect |
|---|---|---|
| `AUTH_ENABLED` | `true` | Protected API routes require a key |
| `BIND_ADDR` | `127.0.0.1` | Ports answer only on the host itself |

Both are deliberate. A fresh install answers only on the host itself and
requires a key on every protected route — widening either is an explicit
operator decision. On the server:

```
# Pre-auth paths answer keyless — this is how you tell "up" from "gated"
$ curl -fsS http://127.0.0.1:8100/health
$ curl -fsS http://127.0.0.1:8100/version

# A real route without a key → 401
$ curl -s -o /dev/null -w '%{http_code}\n' http://127.0.0.1:8100/memory/stats

# ...and with one → 200
$ curl -fsS -H "X-API-Key: $KEY" http://127.0.0.1:8100/memory/stats
```

**401 and "connection refused" are not faults.** If you are following an older
walkthrough, both are the security defaults doing their job. `/health` and
`/version` stay keyless precisely so you can distinguish a service that is down
from one that is merely gated.

## Server configuration

Everything lives in `.env` beside the compose file. The settings that actually
change behaviour:

| Variable | Default | What it controls |
|---|---|---|
| `BIND_ADDR` | `127.0.0.1` | Which interface ports publish on. Widening this exposes the stack. |
| `AUTH_ENABLED` | `true` | Per-key auth and scopes on protected API surfaces. |
| `IMAGE_TAG` | `dev` | Published version to pull. `dev` is never published. |
| `EMBEDDING_MODEL` | `mxbai-embed-large` | Embedding model. Changing it requires re-embedding, and `EMBEDDING_DIM` must match. |
| `EMBEDDING_DIM` | — | Vector width. Changing it requires a collection rebuild. |
| `VAULT_KEY` | generated | Encrypts stored secrets at rest. |
| `NEO4J_PASSWORD` | generated | Graph credential. Embedded in the data volume. Set it yourself at install time with `--neo4j-password` or `FIREKEEP_NEO4J_PASSWORD` when restoring a backup. |
| `VPS_IP` | detected | The address the stack advertises. Detected with `ip route get`, falling back to `127.0.0.1`; override with `--ip` or `FIREKEEP_VPS_IP`. |

**Two settings you cannot change casually.** `EMBEDDING_MODEL` and
`EMBEDDING_DIM` are load-bearing. Changing the model without re-embedding leaves
old memories unsearchable; changing the dimension without rebuilding the
collection breaks recall outright. They are independent of the generation model
— conflating the two forces an avoidable rebuild.

## The client kit

The same one command from [Installing Firekeep](#installing-firekeep), seen from
the client side: what the bootstrap does on a bare machine, and how to re-run
parts of it. No system Python or Node required.

macOS / Linux:

```
$ curl -fsSL https://firekeep.ai/latest/install | sh
```

Windows (PowerShell):

```
> irm https://firekeep.ai/latest/install.ps1 | iex
```

### What the bootstrap does

1. **Resolves the version** and fetches that version's checksum manifest once.
2. **Verifies before use.** Both a mirrored toolchain and the client wheel are
   checksum-verified against that manifest before either is executed. The wheel
   is fetched to a local file and installed by path — never by URL, which does
   no hash checking, and never by name.
3. **Installs the kit** into `~/.firekeep` with its own standalone Python.
4. **Asks two required questions** — your agent identity, and where your server
   is — then offers a skippable prompt for another MCP client. The [install
   walkthrough](#installing-firekeep) shows the menu and what each answer does.
5. **Acts on the answer**: provisions a server here, redeems a join code,
   records an existing server, or stops and leaves the rest to `firekeep
   doctor`.
6. **Renders every shipped adapter** for Claude Code, Codex, Kiro and OpenCode — Claude Desktop too, when the app is installed —
   and, with `firekeep install --runtime generic`, prints a paste-in snippet for
   any other MCP client.
7. **Installs both dex wheels**, Symdex and Docdex, checksum-verified like
   everything else. Turning one on is separate and is yours: `firekeep dex add
   symdex`. An existing install keeps whatever it already had — see [Dexes and
   documents](#dexes-and-documents).

**Skipping the questions.** A machine invited by a server gets its command with
the code already in it — `curl -fsSL https://firekeep.ai/latest/install |
FIREKEEP_JOIN=fk_join_… sh` — so it is never asked where the server is.

### Device credentials

However a machine joins, the client creates its credential locally, redeems the
code once and writes a single server connection. Device credentials default to
90 days; `firekeep doctor` warns during the final 14 days, and a new join code
rotates the device onto a fresh credential.

### From a source checkout

```
$ cd client && ./install          # .\install.ps1 on Windows
$ firekeep install --runtime claude   # re-render one runtime
```

`./install` installs the kit and renders adapters. `firekeep install` run from
that environment **re-renders adapters only** — it skips the package install,
because the code it would install is the code already running.

Use `firekeep install --runtime <name>` only to repair or re-render one adapter.
A normal install prepares all four supported clients together. `firekeep install
--runtime generic --agents-md <path>` adds any other MCP client: it prints the
gateway's MCP-server JSON snippet to paste into that client, and — when
`--agents-md` is given — manages a marker-delimited instruction block in that
rules file. The install wizard also offers this as one skippable question ("Also
use an MCP client we do not ship an adapter for? Paste its rules/AGENTS.md path,
or Enter to skip"). The generic tier gets the MCP tools and instructions on
connect, not the hook lifecycle.

## Firekeep Studio

Firekeep Studio is one desktop workspace for Codex, Claude Code, Kiro CLI and
Grok. Choose the primary agent, bring in fresh review, and run evidence-backed
Missions without switching apps.

### Download Studio

| Platform | Installer | Build |
|---|---|---|
| Windows | [Download the latest stable Windows installer](https://firekeep.ai/latest/studio/windows) | x64 Setup `.exe` |
| macOS | [Download the latest stable universal Mac installer](https://firekeep.ai/latest/studio/macos) | Apple Silicon + Intel `.dmg` |

[Latest release notes](https://firekeep.ai/latest/studio/release) ·
[Published checksums](https://firekeep.ai/latest/studio/checksums) ·
[Studio source](https://github.com/kapella-hub/FirekeepHQ/tree/main/studio)

On Windows, run the per-user setup wizard. On macOS, open the DMG, move
Firekeep Studio to Applications, then launch it from Applications.

**First-launch confirmation.** Your operating system may ask you to confirm this
preview. Only continue after downloading from the official links above and
checking the published SHA-256 value. On Windows, open **More info → Run anyway**
if SmartScreen appears. On macOS, open **System Settings → Privacy & Security →
Open Anyway** if the app is held.

- Windows: `Get-FileHash "$env:USERPROFILE\Downloads\Firekeep-Studio-*-Setup.exe" -Algorithm SHA256`
- macOS: `shasum -a 256 ~/Downloads/Firekeep-Studio-*-universal.dmg`

Match the result to the filename in the [latest published checksums](https://firekeep.ai/latest/studio/checksums).

Studio brings the agents you already use. Install and sign in to Codex, Claude
Code or Kiro CLI before selecting it in Studio. Grok connects directly with an
xAI API key protected by your operating system.

### First run

1. **Choose a workspace.** Select the project folder every runtime should use.
   Studio remembers the choice locally; Missions require an explicit workspace.
2. **Choose the primary agent.** Pick Codex, Claude Code, Kiro CLI or Grok. The
   primary leads the conversation until you switch it.
3. **Connect the provider when needed.** Open **Runtime Center** from the primary
   picker. It shows account, connection, review and Keep status for each runtime.
4. **Check readiness, then work normally.** Type `/doctor`, resolve any setup item
   it names, and send your first prompt. Type `/` for live command completion.

### Useful Studio commands

| Command | Use it for |
|---|---|
| `/help` | Open the complete command inventory |
| `/doctor` | Check installed runtimes and provider sign-in |
| `/workspace choose` | Pick the project folder passed to every runtime |
| `/use codex` | Switch the primary agent; replace `codex` with another available runtime |
| `/reviewer add claude` | Add a fresh, read-only reviewer |
| `/reviewer mode after-turn` | Run configured reviewers after each primary turn |
| `/mission new "goal"` | Create a bounded Mission in the current session |
| `/mission run` | Run the Mission, its checks and any bounded repair |
| `/mission report` | Inspect the evidence and stored result |
| `/update status` | Inspect Studio's separate desktop release channel |

For a verified Mission, give it a clear goal, add the project's real acceptance
command, then run it:

```
/mission new "Ship the feature"
/mission check add "npm test" --name tests --timeout 10m
/mission run
```

Studio confirms the workspace, permission mode, repair limit, token guard and
commands before execution. Checks drive bounded repair, reviewers stay
independent, and you make the final call.

**Connect Studio to your Keep.** Studio and the Client Kit are separate. Add the
Client Kit when you want supported runtimes to receive Firekeep memory, briefings
and hooks. Grok runs through its direct provider connection; Runtime Center
shows the Keep status of every runtime.

Use `/update status` or `/update check` at any time. Windows verifies and
downloads ready updates in the background, then offers **Restart to update**.
On macOS, Studio opens the latest universal DMG for a manual replacement.

## Connecting a device

Every path here is the same mechanism: a single-use join code. It identifies the
server and the enrollment ticket; the client chooses its own credential and
stores one connection. What differs is only where the code comes from.

| Situation | Path |
|---|---|
| The server's own machine | Nothing to do — it enrolled itself over loopback during the install. |
| A machine with no kit yet | Run the install one-liner the server printed, which carries the code — see [Adding a second machine](#adding-a-second-machine). |
| A machine with the kit | `firekeep join fk_join_…` with a code from **Dashboard → Devices → Add device** or `deploy/firekeep-admin invite`. |
| You have SSH to the box | `firekeep connect user@host` — the operator shortcut below. |

```
$ firekeep join fk_join_…
```

**The credential never crosses the enrollment exchange.** The client generates
the secret and sends only its SHA-256 hash while redeeming the single-use code.
Later authenticated calls necessarily send the plaintext API key, so remote
connections still require an SSH tunnel, private network or HTTPS.

### SSH shortcut for operators

An operator who already has shell access can use `firekeep connect`. It issues a
single-use code over SSH and then calls the same join path locally.

**It attaches to a server; it does not create one.** `firekeep connect` expects
Firekeep to be installed already on the far end and stops with *no Firekeep
install found on the server* if it is not. To stand a server up on another box,
install Firekeep *on that box* and answer **1**; then connect to it.

```
$ firekeep connect root@203.0.113.10 --remote-dir ~/.firekeep/server
```

**Loopback is the default.** A stock server answers only on `127.0.0.1`. The SSH
shortcut establishes a tunnel and reuses it on later runs. Tailscale, WireGuard
or your own HTTPS reverse proxy are also valid; Firekeep does not require a
network vendor.

Restart your agent session afterwards so it picks up the newly registered MCP
servers.

## CLI reference

| Command | What it does |
|---|---|
| `firekeep init` | Provision or update a local/self-hosted Firekeep server, and enroll this machine against it. Answering **1** at install runs this for you. `--no-self-enroll` skips the enrollment (CI, golden images); `--ip` and `--neo4j-password` override what it would otherwise detect and generate; `--version` pins a release and `--office` selects the TLS deployment |
| `firekeep join <code>` | Enroll this machine through a single-use join code |
| `firekeep install` | Install from a source checkout, or re-render adapters in an installed kit |
| `firekeep connect <target>` | Operator SSH shortcut against a server that is **already installed** on the far end: mints a join code over SSH, opens a reusable tunnel and enrolls this machine |
| `firekeep login <url>` | Reserved for hosted OAuth sign-in when a server supports it |
| `firekeep doctor [--report]` | Preflight: health, versions, key ACLs, CA expiry, permissions. `firekeep status` is an alias (client ≥ 0.1.40). `--report` additionally sends an anonymous, redacted summary — see below |
| `firekeep dex [list\|add\|remove]` | Which [dexes](https://firekeep.ai/dexes.html) this machine has turned on — see [Dexes and documents](#dexes-and-documents) |
| `firekeep docdex [list\|add\|sync\|remove]` | The document folders Docdex may read, and their sync state |
| `firekeep personal [on\|off\|status]` | Toggle personal mode — dormant, nothing recorded |
| `firekeep update` | Update the kit. `--check` reports only; `--to` pins or rolls back |
| `firekeep night-shift` | Distil queued sessions into memory and draft skills via a local model |
| `firekeep version` | Print the client version |

### Start with doctor

When anything is wrong, this is the first command to run — it checks the whole
chain rather than one link.

```
$ firekeep doctor
```

`firekeep doctor --report` additionally sends an **anonymous** summary to
firekeep.ai — the name and pass/warn/fail status of each check, plus your
client version. Nothing else: not the messages doctor just printed, not a
file path or hostname, not an IP address. This is off unless you type the
flag — plain `firekeep doctor` never leaves your machine. See
[the privacy notice](https://firekeep.ai/privacy.html) for exactly what is
logged.

#### A client with no server

If you answered **4** at install, or the server never came up, doctor leads with
the routing row rather than four identical connection errors:

```
[FAIL] server: This machine has a Firekeep client but no server to talk to.
       Pick the one that describes you:
      • Run the server on THIS machine:      firekeep init
      • Join a server your team already has: firekeep join <code>
        (get a code from Dashboard -> Devices -> Add device)
      • Set one up over SSH on another box:  firekeep connect <user@host>
      The four rows below are the same fact, once per service.
```

On a machine that *does* have a server configured, the same row names the host
that is not answering instead. Note that the third suggestion attaches to a
server that is already installed on that box — see [Connecting a
device](#connecting-a-device).

#### Embeddings still warming

A `warn` row on a freshly installed server is expected and needs no action — it
is the model pull finishing:

```
[WARN] embeddings: still warming — memories you write now are STORED and
       queued for backfill, but not searchable until this finishes.
       Nothing to do; watch it with: docker compose logs -f ollama-pull
```

## Dexes and documents

A [dex](https://firekeep.ai/dexes.html) is a domain index that gives your Keep
understanding of one domain. Three ship today — Symdex for code, Docdex for
documents, Maildex for email — and a registry decides which of them runs on this
machine. Since client 1.2.0 a fresh install registers Symdex and Docdex out of
the box; Maildex registers itself when you connect a mailbox.

### `firekeep dex` — what is turned on

| Command | What it does |
|---|---|
| `firekeep dex list` | Every dex this client knows, with its state: `registered`, `available` (wheel present, not registered), or `not installed`. The default when no action is given |
| `firekeep dex add <name>` | Register `symdex`, `docdex` or `maildex` — though `maildex` is registered automatically by `firekeep maildex add`. Refused if the wheel is not present, rather than written and failing silently next session |
| `firekeep dex remove <name>` | Unregister it. The wheel stays installed; `add` brings it straight back |

Registration gates **activity, not installation**. All three wheels arrive with every
release and are checksum-verified by the installer; `add` downloads nothing and
`remove` uninstalls nothing. The only thing that changes is a line in
`~/.firekeep/dexes.json`, which decides whether the gateway mounts the dex's
tools and whether its background work runs. Both commands are idempotent, and
both take effect on the **next** agent session — the gateway reads the registry
once, at startup.

**An update never removes what an install already had.** A machine that had
Symdex before the registry existed is grandfathered: it keeps working across the
update with no action and no extra install question. Since client 1.2.0 a fresh
machine seeds the registry with `symdex` and `docdex`, so both are on out of the
box — and because an update never touches an existing `~/.firekeep/dexes.json`,
a `firekeep dex remove` sticks across every later release. The one thing the
doctor row reports as a warning is a dex registered whose wheel has gone
missing, because that is a backend that will fail to start.

### `firekeep docdex` — the folders it may read

| Command | What it does |
|---|---|
| `firekeep docdex list` | Sources with their counts, staleness, failures and pending deletes. The default when no action is given |
| `firekeep docdex add <path>` | Add a folder — **private to you by default, even on a shared Keep**. `--shared` makes that one folder visible to your workspace. Refuses a path that is not a folder, or one already added |
| `firekeep docdex sync` | Scan every source now. `--source <id>` does just one |
| `firekeep docdex remove <id>` | Delete the source and its replicas in the Keep. **The folder on disk is never touched** |

Docdex extracts `.md`, `.txt`, `.pdf`, `.docx`, `.html`/`.htm`, `.eml` and
conversation-shaped `.json` (AI-chat exports with role-labeled turns); anything
else is counted as skipped. The extracted text is ingested into your Keep's corpus and surfaces
through ordinary `memory_recall` in your own sessions — there is no new recall
surface and no MCP server. Deleting a file locally deletes its replica on the
next sync, retried until the server confirms it.

These four commands work whether or not the dex is registered: registration
gates the background sync and the doctor row, never a person's ability to say
which of their own folders the Keep may read. There is deliberately **no
agent-callable tool** for choosing a folder — that tool is absent, not guarded.

#### When a sync happens

With `docdex` registered and at least one source, a stale source is picked up in
the background at session start on runtimes that expose a hook for it. It is
detached from the session, so a cold scan never delays a session opening.

| Runtime | Background sync |
|---|---|
| Claude Code, Kiro, OpenCode | Automatic when stale |
| Codex, any other MCP client | None — no hook surface. Run `firekeep docdex sync` |

Opt out with `FIREKEEP_NO_AUTO_SYNC=1` or `[docdex] auto_sync = false` in
`~/.firekeep/config`. Personal mode suspends syncing entirely — the trigger does
not fire, and a run already in flight stops between batches.

#### Caps

All env-overridable; an unparseable value falls back to the documented default.

| Variable | Default | On breach |
|---|---|---|
| `FIREKEEP_DOCDEX_MAX_FILES` | 5000 per source | The source is refused until narrowed. Nothing is written — a folder half-indexed would look synced and be wrong forever |
| `FIREKEEP_DOCDEX_MAX_FILE_MB` | 25 | File skipped and counted. Skipped is not deleted: it keeps any replica it already has |
| `FIREKEEP_DOCDEX_MAX_EXTRACT_KB` | 400 | Extracted text truncated at the cap, flagged, and shown by `list` |
| `FIREKEEP_DOCDEX_SYNC_INTERVAL_HOURS` | 6 | Staleness threshold for the background trigger |
| `FIREKEEP_DOCDEX_INGEST_TIMEOUT_SECONDS` | 180 | Per-document upload budget — the server embeds synchronously, so large documents need real time. A timeout aborts the sync honestly ("timed out", never a false "unreachable"); what landed is kept and the rest retries next sync |

**What Docdex does not do.** There is **no OCR** — a scanned PDF yields no text,
recorded as an honest zero rather than retried as a failure. There is **no live
file watching**: syncs are session-start-triggered or manual. There are **no
cloud sources** (Drive, Notion and the like). Visibility is per folder, never per
file. And removing a document removes its replica, not a separate memory an
agent may have written after reading it.

Default excludes cover dot-directories, `node_modules`, `__pycache__` and secret
patterns such as `.env` and `*.pem` — a net for mistakes, not a security
boundary. Do not point Docdex at a folder containing secrets.

### `firekeep maildex` — mail, read-only

Maildex connects a mailbox over IMAP and indexes recent mail into the same
recall — **always private to you**, even on a shared Keep; there is no
`--shared` for mail. `firekeep maildex add imap.example.com you@example.com`
prompts for an app password and registers the dex automatically;
`firekeep maildex sync`, `list` and `remove` work like their Docdex
counterparts.

Read-only is structural, not promised: every mailbox open is IMAP `EXAMINE` and
every fetch a `PEEK` — the wheel contains no send, move, flag or delete
capability anywhere, and a build-time guard fails if one is ever added. The app
password is stored in the server's vault under your identity — never on the
client's disk — and is deleted when you remove the account. Defaults: INBOX and
Sent, the last 90 days of mail, then incremental syncs.

**What Maildex does not do yet.** Mail you delete at your provider **stays in
the corpus** until you `remove` and re-`add` the mailbox — provider-side
deletions are not mirrored, and `maildex list` restates this every run.
Attachments are not read (their names are noted). OAuth sign-in is deliberately
absent; an app password you can revoke in one click is the current trade.

All three dexes report to `firekeep doctor`, read from disk with no server call, so
the rows are still there when the server is the thing that is broken:

```
dexes         ok    symdex, docdex (registered)
docdex        ok    2 sources · last sync 3h ago · 0 pending deletes · 0 failures
```

`last sync` reports the **stalest** source, not the freshest: a row saying "just
now" because one folder of five synced would hide the four that did not.

## Day to day

**The Keep volunteers (client 1.0.3).** On Claude Code, every user prompt is
checked against team memory and up to three genuinely relevant, not-yet-seen
memories are pushed into the model's context as a `[firekeep recall]` note —
usually none. Kiro runs the same check, but delivery over its `userPromptSubmit`
channel has not been measured — treat it as unverified there. Off-switch:
`FIREKEEP_NO_RECALL_PUSH=1`. Pushed recalls carry
`trigger: prompt-hook` in replay, so the compliance measurement can slice
pushed from deliberate recall.

And since server v1.2.0, declaring a session goal answers back: the response to
`ctx_start_session` carries a *prior art* block — up to three team memories
about similar past work, plus what other agents are building right now — so
"build X" surfaces "the team already built X" before a line is written. It
rides the tool response itself, so it reaches every runtime, hooks or not;
checks are marked `trigger: prior-art` in replay.

The core MCP capabilities work through every shipped adapter — and through any
other MCP client, installed with `firekeep install --runtime generic`. Lifecycle
automation varies because each client exposes different hook surfaces; a
generic client exposes none, so it gets the MCP tools and instructions on
connect but not the hook lifecycle.

### Automation by client

| Client | Automatic surface | Current boundary |
|---|---|---|
| **Claude Code** | The richest hook coverage: start briefing and presence, prompt polling, pre-compaction capture, pre/post edit mediation, final snapshots and true session-end deregistration. | Its pre-edit hook can hard-block a denied edit; service failures still degrade according to the documented hook policy. |
| **Kiro** | Agent-spawn and prompt hooks provide briefing, presence, inbox polling and snapshots; pre/post tool hooks record mediated actions. | There is no true session-end or `PreCompact` event. On validated Kiro 2.12.1, the pre-edit hook fires but is advisory rather than a hard block. |
| **OpenCode** | A local plugin bridges the lifecycle and edit events OpenCode exposes; supported edits can be hard-blocked and session deletion drives final capture. | Briefing and inbox output appear in the console, not model context. Its lifecycle surface is narrower than Claude's and has no pre-compaction event. |
| **Codex** | MCP servers and repository guidance expose the full direct-call tool surface. | Codex has no Firekeep hooks: recall, session updates, inbox polling and policy calls are manual. Presence, heartbeat and snapshots require starting the optional sidecar yourself. |
| **Claude Desktop** (the consumer chat app) | Auto-detected at install (when the app's config directory exists) and mounted into `claude_desktop_config.json` by its own adapter — the full MCP tool surface plus the on-connect protocol. The first non-coding host with a bespoke adapter: a decision made chatting in Claude Desktop is recallable in your coding agent the next morning, and vice versa. Restart the app after install. | The app exposes no hooks, so nothing hook-driven exists: no auto-briefing, no pre-edit gate, no stop→learn, no background dex sync (run `firekeep docdex sync` yourself). Presence requires the optional sidecar. Only the `firekeep` entry in its config is ever written or removed; the rest of the file is never touched. |
| **ChatGPT** (via OpenAI's Secure MCP Tunnel) | A server-side recipe (`deploy/chatgpt-tunnel/` in the repo): a tunnel client on the Keep host makes **outbound-only** HTTPS to OpenAI and forwards ChatGPT's MCP requests to the Firekeep gateway running a curated 12-tool chat surface — recall, learn, feedback, skills and sessions, with vault, corpus, relay and backup tools excluded at the gateway's routing layer (invisible *and* uncallable). The Keep stays private: no public port ever opens. | Requires your own OpenAI Platform tunnel and ChatGPT developer mode; requests transit OpenAI's control plane — a disclosed third party in the request path. No hooks. Every call is attributed `runtime: chatgpt` in replay, so chat-authored memories stay auditable as a class. (ChatGPT desktop's local MCP setting belongs to its Codex surface, which reads the shared Codex config and gets Firekeep the ordinary way.) |
| **Any other MCP client** (Cursor, Windsurf, Gemini CLI, …) | The full MCP tool surface, plus the cognitive-protocol instructions the gateway delivers on connect. Installed via `firekeep install --runtime generic`, which prints a paste-in MCP-server JSON snippet and can manage a marker-delimited instruction block in an `AGENTS.md`-style rules file (`--agents-md <path>`). | No hooks at all, because a generic client exposes none Firekeep can wire: no auto-briefing at session start, no blocking pre-edit gate, no stop→learn and no pre-compaction checkpoint. As with Codex, presence and heartbeat require starting the optional sidecar yourself. The MCP tools and shared memory are identical to every other client; what is absent is the lifecycle automation, not the memory. |

These differences affect convenience, not access to the underlying MCP
capabilities. Every supported client can call the same server tools when its
agent chooses to do so.

### What your agent calls directly

Firekeep exposes its server capabilities through one local gateway, with Symdex
and Decision Board running client-side. The tools that matter in ordinary work
include:

| Area | Tools | Use |
|---|---|---|
| Memory | `memory_recall` / `memory_learn` / `memory_feedback` | Find prior evidence, record a fix or decision, and report whether recalled knowledge actually held up when acted on — the feedback feeds ranking. |
| Skills | `skill_recall` / `skill_create` | Find or author a reusable playbook; document-derived drafts stay out of recall until reviewed. |
| Knowledge | `corpus_ingest` / `knowledge_ingest` / `knowledge_ingest_url` | Make pasted content or a bounded public documentation crawl searchable; the Knowledge front door also queues reviewable procedure drafts. |
| Sessions | `ctx_start_session` / `ctx_update` / `ctx_get_shadow` / `ctx_resume_session` | Persist explicit working state across compression, interruption and crashes. |
| Relay leases | `relay_lease` / `relay_heartbeat` / `relay_release` | Coordinate ownership of a file or task with a renewable fenced lease. |
| Relay work | `relay_task_post` / `relay_task_list` / `relay_task_update` | Create, assign and advance work that another agent identity can pick up. |
| Relay messages | `relay_post` / `relay_read` / `relay_send_dm` / `relay_get_dm` | Post to a catch-up channel or exchange pollable direct messages; `relay_who_is_online` reports best-effort presence. |
| Decision Board | `decision_board` / `decision_board_check` | Open a multi-question clarification board and wait for the human's submitted answers. |
| FirekeepScope | `scope_start` / `scope_ask` / `scope_post` / `scope_check` | Run gating or asynchronous clarification screens through Relay and the dashboard. |
| Replay | `replay_timeline` / `replay_inspect` / `replay_context_at` / `replay_narrow` | Move from a recorded timeline to event details, nearby context and likely contributors to a failure. |
| Sentinel | `sentinel_get_events` / `sentinel_get_health` / `sentinel_push_event` | Read configured environment activity, check collector health or add a custom operational event. |
| Vault | `vault_list` / `vault_retrieve` | Discover metadata and explicitly retrieve an authorized secret without putting it in memory. |
| Vault administration | `vault_store` / `vault_delete` | Create or irreversibly remove entries; these operations require an admin-scoped credential. |
| Evals | `eval_session` / `eval_summary` | Inspect trace-derived operational metrics for one completed session or recent sessions. |

**The habit that pays.** Recall before starting and learn after finishing.
Relevant recorded decisions and fixes can save rediscovery, and the value
compounds as later agents retrieve evidence from earlier work in the same
project.

### The knowledge lifecycle, on autopilot

The knowledge base maintains itself without deciding anything on its own. What
runs automatically: recorded session outcomes and explicit useful or not-useful
feedback can nudge recall ranking, though current outcome discrimination is
limited; sessions whose agent crashed or walked away are closed out after three
days of silence so failures count instead of vanishing; close restatements
supersede what they restate; stale knowledge ages into a recoverable archive.

What is deliberately *proposed, never auto-applied*: when two unconfirmed
memories contradict each other, neither is dropped — both stay recallable,
visibly marked as contested, until a human picks a winner or rules that both are
true in their own contexts. Draft skills, stale skills, procedure proposals and
contested pairs all queue in one review inbox on the dashboard's **Autopilot**
tab, next to a digest of what changed in the last week. A memory's full evidence
— provenance, usage, confirmations, feedback, outcome efficacy, disputes and
lineage — is one read away, so every ranking has a visible why.

The instruction layer is measured too. The same tab's **Living Instructions**
table scores each instruction Firekeep gives your agents — recall before
answering, record as you go, declare consequential actions — against what
sessions actually did, computed deterministically from the replay record with
the caveat built into the response: compliance measures behavior, not whether
the behavior helped. Agent-drafted instruction rewrites under human approval,
validated by A/B across sessions, are roadmap — not shipped.

### The dashboard

Served on port `8040`. Because the stack binds to loopback, reach it through the
tunnel `connect` established — at `http://127.0.0.1:8040/`, not the server's own
address.

## Governance, trust & private documents

Three capabilities that landed recently. Each is scoped honestly here — what it
enforces, what it only reports, and what is still being built.

### Enforced Runbooks

A skill is advice text until something watches whether its steps happen. Give a
skill's steps **command matchers** and it becomes a **runbook** — and a human,
never an agent, can arm it with an enforcement mode. Agents may *propose* a
runbook; only a human with an admin key *arms* it, on a route the ordinary
skill-edit path cannot reach.

The three human-armed enforcement modes are `advise` → `require_ack` → `block`:

- **advise** — a matched command is annotated, and proceeds.
- **require_ack** — it is challenged, and proceeds only after an audited
  acknowledgement with a recorded reason.
- **block** — it is stopped.

**A command counts only when it actually ran and succeeded.** Evidence commits
in two phases: permission time writes a pending record, and only a matching exit
status of zero commits it. A deploy that crashed never scores as a completed
one.

**Block fails closed.** The client honours an `allow` only when the server's
answer carries the marker it appends on a genuinely evaluated path — so a
reachable but broken server reads as a block, not as an authenticated allow.
Every lookup and permit is scoped to the verified workspace; the self-reported
`agent_id` is a label, never the boundary.

Every enforcement event an operator would triage — a block fired, a challenge
acknowledged with its reason, a matched command that ran and failed — lands in a
per-workspace **deviation ledger**, surfaced on the dashboard's runbook cards
and the Autopilot inbox. It stores the command's hash, never its text.

**Honest scope.** Opt-in (`PROCEDURE_ENABLED`), and it needs client 0.1.44+.
Full enforcement lands only on runtimes with a blocking hook — Claude Code
today; Kiro observes but does not stop the command. Command matching catches a
*well-meaning* agent's mistake; it is not adversary-proof, and a machine's owner
can always remove the hooks. This gates agents, not humans.

We are proving it on our own deployments first — observed under `advise`, then
`require_ack`, then `block` — and will not call it finished until block mode has
stopped a real mistake.

### The Trust Ledger

Agents already declare consequential actions through the gateway: what they are
about to do, how sure they are, and afterwards what actually happened. The Trust
Ledger turns that stream into a per-agent **employment record** — how much each
identity declared, how often it reconciled the outcome, how well its stated
predictions matched reality, and how many of its own actions it later marked
failed.

Trust Ledger · 30-day window — reports · never gates:

| Agent | Declared | Reconciled | Calibration | Reversals | Sessions |
|---|---|---|---|---|---|
| alex-laptop | 214 | 96% | 0.11 ↓ (improving) | 3 | 28 |
| ci-runner | 41 | 100% | — (not enough signal) | 0 | 2 |

Illustrative — not a live capture. Components only; there is deliberately no
single trust score.

**It reports; it never gates.** There is no headline trust number, on purpose —
a composite gets treated as a gate, and a self-reported identity is the wrong
thing to gate autonomy on. Turning this into *earned* autonomy is a separate,
later piece of work; round one is the honest measurement, on the dashboard's
Autopilot tab beside the compliance table.

**Read the components for what they are — behaviour, not competence.**
Calibration scores whether an agent's *stated* prediction criteria matched what
was observed — not whether its decisions were good. An action with no stated
criteria scores a perfect match even when it failed. A lower score is better, so
an improving agent trends down.

**Declared actions only.** The ledger sees what an agent declared through the
gateway, and only what the replay log captured of it — a floor on a floor. An
agent that declares nothing is unmeasured, not trusted, and a metric a truncated
read would bias is returned as unknown rather than as a guessed number.

### Member-private documents

Memory on a shared server is a team asset — but not everything you want an agent
to recall should be. Firekeep's document memory carries a **per-folder
visibility** setting, and the default is **private to you, even on a shared
Keep**. Private content surfaces through ordinary `memory_recall` in your own
sessions and nowhere else.

The enforcing half — **server-side tenancy — is shipped.** One shared filter
runs at every member-facing exit: a private chunk never appears in another
member's recall, and a private source's *name* never appears in another member's
listings or dashboard. Each document's chunks carry their own identity, so
deleting your copy of some text can never remove a teammate's identical copy,
and a half-finished re-ingest never exposes a partial document — recall only
ever sees a committed generation.

**Both halves are shipped.** The **server-side** visibility filter, per-source
identity, committed-generation recall and scoped credentials are built and
tested. So is the **client**: `firekeep docdex` points at folders and ingests
`.md`, `.txt`, `.pdf`, `.docx`, `.html`/`.htm`, `.eml` and conversation-shaped
`.json`. The commands, caps and sync behaviour are in
[Dexes and documents](#dexes-and-documents).

**Private from other members — not encrypted from the operator.** The filter is
a tenancy boundary between people who share a server, not cryptography. Content
is visible to agents acting as you, and anyone who can read the server's vector
store directly can read everything. Indexed document text is always treated as
*evidence*, never as instructions to the agent.

## Personal mode

An escape hatch that makes Firekeep dormant for work you do not want it to
record.

```
$ firekeep personal toggle    # or: on | off | status
```

Hooks, the presence sidecar and Decision Board re-read the state and stop
communicating immediately. An MCP shim that is already running evaluates hard
bypass only when it starts, so restart the active agent after changing Personal
mode. A restarted shim exposes zero Firekeep tools while the mode is on; restart
again after turning it off to restore them.

**How it clears.** Claude Code and OpenCode wire a true session-end or
session-deletion event to clear the marker. Kiro has no true session-end event,
and Codex has no hooks, so turn it off with `firekeep personal off` or rely on
the 12-hour TTL backstop. `firekeep doctor` reports a warning whenever it is
active.

For a whole session that is personal from the start, set `FIREKEEP_BYPASS=1`
before launching your agent.

## Operations

### Updating

```
$ firekeep init                  # server: current release
$ firekeep init --version vX.Y.Z # server: pin or roll back
$ firekeep update                # client: latest release
$ firekeep update --check        # report only
$ firekeep update --to X.Y.Z     # client: pin or roll back
```

**Updates never ask you to close your sessions.** Since client 0.1.35, each
release installs side-by-side and a link flip selects it — the environment your
open sessions run from is never touched. Running sessions keep working on the
old version; anything started after the update gets the new one, and `firekeep
update --to` back to the previous release is an instant flip. Old versions are
cleaned up automatically once nothing is using them.

### What version am I running?

Every service answers `/version` without a key and without touching its
dependencies, so it responds even when the things behind it are down.

```
$ curl -fsS http://127.0.0.1:8100/version
```

### Health

```
$ cd ~/.firekeep/server
$ docker compose ps
$ docker compose logs -f cortex-api
$ docker stats --no-stream
```

**Healthy is not the same as working.** Compose health checks are TCP-only. A
container can report healthy while the service inside it cannot reach Redis or
Qdrant — which is exactly what an out-of-memory kill looks like from the
outside. Check `/health` bodies, not just container state.

### Removing Firekeep

```
$ firekeep uninstall            # remove the client kit from this machine
$ firekeep uninstall --server   # also tear down the server + ALL its data
```

`firekeep uninstall` removes only what the kit installed: the Firekeep blocks in
each runtime's config (your own settings are left intact), the `firekeep`
launcher and its PATH entry, and `~/.firekeep`. It asks first; `--yes` skips the
prompt. It never touches a server you set up — memory, sessions and the vault
survive.

**`--server` deletes everything the server stored.** `--server` additionally
runs `docker compose down -v` on the stack this machine provisioned, which
removes the Neo4j, Qdrant and Redis volumes — every memory, session, replay
trace and vault secret, permanently. It is gated behind a separate data-loss
confirmation of its own, so a plain uninstall (or `--yes`) can never trigger it
by accident. Back up first with `deploy/backup.sh` if you might want the data
again. On a server-only box, the bundle's `uninstall.sh` does the same teardown.

## Backup & restore

The rule this feature exists to satisfy: **you should never discover your
backup story during the disaster.** The Keep backs itself up automatically;
your job is one command to keep an off-box copy, and one runbook if the
worst happens.

### Automatic nightly backups

Since server v1.0.3 the Keep backs itself up: a **cold snapshot nightly at
04:30 server time** (neo4j, qdrant and redis stop for the 1–3 minutes of
archiving — a copy of a live store restores without error and is wrong), with
per-file checksums in a manifest, **retention of 7 nightly + 4 weekly**, and
the server's `.env` included so a bare-metal restore recovers vault secrets
too. Model weights are excluded — `docker compose up -d` re-pulls them. The
off-box copy is a pull to a machine you control (client 1.0.2):

```
$ firekeep backup link      # once: paste a deployment ADMIN key
$ firekeep backup pull      # download newest backup, verify every sha256
$ firekeep backup status    # ages, counts, policy — and this machine's last pull
$ firekeep backup list      # one line per backup, indexed and unindexed
$ firekeep backup restore   # prints the guided host-side restore steps
```

**The archive is a secret, and downloads are admin-only — permanently.** A
backup holds every member's private documents and the server's `.env` with
`VAULT_KEY`. `pull` requires a deployment admin key (`link` verifies it
against the live server before storing anything), member keys get `403` by
design, and no member-grantable backup scope will ever exist. Off-box
freshness equals your last pull — `firekeep doctor` shows both the newest
server backup (warns past 36 hours) and when this machine last pulled.

Disaster runbook, retention details and the manual tools:
[docs/guides/backup-and-restore.md](https://github.com/kapella-hub/FirekeepHQ/blob/main/docs/guides/backup-and-restore.md).
Short version: fresh host → `install.sh` → copy the pulled backup over →
`deploy/restore.sh` → `docker compose up -d`.

### Restoring

Restore runs on the server host with the stack stopped — `firekeep backup
restore` prints these steps with your machine's real paths filled in. On a
brand-new host, run the installer first, then restore into it:

```
# fresh host only: install the server first
$ bash install.sh
# copy the pulled backup up from your machine
$ scp -r ~/FirekeepBackups/firekeep-backup-STAMP root@server:/opt/firekeep/backups/
# restore with the stack down, then start it
$ docker compose down
$ bash deploy/restore.sh backups/firekeep-backup-STAMP
$ docker compose up -d   # ollama-pull re-fetches model weights once
```

`restore.sh` restores the archived `.env` when present — with an explicit
confirmation, never a silent overwrite — and `firekeep doctor` from any
enrolled machine confirms the Keep is healthy afterwards.

### Manual backups and recovery caveats

The nightly job wraps the shipped `deploy/backup.sh`, which you can also run
by hand; it quiesces Neo4j, Qdrant and Redis before their files are copied. A
manual run archives the four base volumes (including Ollama's model weights)
and — unlike the nightly job — does **not** copy `.env`.

```
$ bash deploy/backup.sh ~/firekeep-backups
# restore only with the stack stopped; the script asks for confirmation
$ docker compose down
$ bash deploy/restore.sh ~/firekeep-backups/firekeep-backup-…
```

**A manual volume archive is not a complete recovery set.** For manual
`backup.sh` runs, separately protect the original `.env` as a secret: it
contains the `VAULT_KEY` required to decrypt stored secrets, the Neo4j
password, internal credentials and stable workspace/member IDs. (Nightly
archives include it — that is why they are treated as secrets themselves.)
An office-mode deployment must additionally back up its `caddy_data` volume,
which holds the internal CA private key and is in **neither** the nightly nor
the manual volume set. Restore those identities with the data; generating
replacements can make the restored vault unreadable, invalidate clients or
change workspace ownership.

The admin JSONL export covers memories and the knowledge graph; session
state, vault entries and replay traces live only in the volume archive. Use
the volume backups — not the export — as the recovery mechanism.

## Troubleshooting

### Everything returns 401

Expected on a fresh install — `AUTH_ENABLED=true` is the default. Confirm the
service is actually up with the keyless `/health`, then check that your client
connection has an API key. `firekeep doctor` reports a missing or rejected key
directly.

### Memories save, but recall finds nothing

On a server installed in the last few minutes, this is the model pull, not a
fault. The install stops blocking once the stack is up, and until the ~3.3 GB
embedding model has finished downloading, writes return `status="partial"`: the
memory is **stored and queued for backfill**, but it is not searchable yet.
Nothing is lost and nothing needs re-writing — the backfill makes it findable.

Confirm with `firekeep doctor`, which shows an `embeddings` warning row until
the model is loaded, and watch it with `docker compose logs -f ollama-pull`. To
avoid the window entirely on the next install, use `bash install.sh
--wait-for-models`.

### Connection refused from another machine

Also expected. `BIND_ADDR=127.0.0.1` means the ports answer only on the host.
Attach the machine with a join code rather than pointing it at an address:
`firekeep join fk_join_…` if it already has the kit, or the `FIREKEEP_JOIN=…`
one-liner the server printed if it does not — a code issued by a loopback-bound
server carries its own SSH route. An SSH tunnel, private network or HTTPS
reverse proxy of your own works too, as does the operator shortcut `firekeep
connect user@host --remote-dir ~/.firekeep/server`. Pointing a client at an
unbound public address simply times out.

If `firekeep doctor` instead reports *this machine has a Firekeep client but no
server to talk to*, there is no server configured at all — see [Start with
doctor](#start-with-doctor).

### The dashboard says a service is unreachable

Check the service directly on the host with `curl` against `/health`. If that
answers and the dashboard disagrees, the proxy in front of the dashboard is
misrouting rather than the service being down.

### Containers restart or get killed

Almost always memory. Check `docker stats` against the requirements above. The
symptom is misleading: health checks keep passing while the process inside is
killed and restarted.

### The agent does not see the tools

MCP servers are registered at install, and a running agent session does not pick
up new ones. Restart the session. If they are still missing, `firekeep install
--runtime <name>` re-renders that runtime's configuration.

On Linux or macOS, if the gateway connects but exposes only
`firekeep_gateway_status`, you are on client 0.1.37 — a resolution bug fixed in
0.1.38. `firekeep update` resolves it.

### The Symdex tools are gone

Check `firekeep dex list`. An existing install is grandfathered across the
update, but if that listing says `available` rather than `registered`, run
`firekeep dex add symdex` — and note that it takes effect on the **next** agent
session, not the current one. If it says `registered (wheel missing!)`, the
wheel did not land: re-run the installer, or `firekeep dex remove symdex`.

### Documents never appear in recall

In order: is the dex registered (`firekeep dex list`); is the folder added
(`firekeep docdex list`); has a sync actually run (`last sync` in that listing,
or the `docdex` row in `firekeep doctor`); does the runtime have a hook surface
at all — on Codex, Claude Desktop and generic MCP clients nothing syncs until you run `firekeep
docdex sync`; and is personal mode on (`firekeep personal status`), which
suspends syncing. A source shown as `refused` holds more indexable files than
`FIREKEEP_DOCDEX_MAX_FILES` — narrow the folder or raise the cap.

### Still stuck

Run `firekeep doctor` and include its output when you
[get in touch](mailto:support@firekeep.ai?subject=Firekeep%20support). It
reports the whole chain — connection, auth, versions, permissions — rather than
one link of it.
