The software your agents are built on.

Estelle gives the coding agent you already use a lasting, grounded memory of your codebase. Context survives from session to session, every answer cites the real code behind it, and the memory learns your team's conventions as you work. Connect over MCP with one command, or point any OpenAI-compatible tool at one base URL. Your own model, your own key, always.

Get started in 60 seconds. One command detects your editors and writes the MCP config:

npx @fatelabs/estelle init

Writes the config for Cursor, Cline, Windsurf, VS Code, JetBrains and Claude Code; prints the manual step for Claude Desktop and Continue. Needs a free key from /signup, no card. Prefer the API path? See Call the API.

Zero dependencies, built in CI with a signed provenance attestation, from a public source repo you can read: the package on npm · the source.

Introduction

FIG 01 the layer under your agent

Estelle reads your repository once and keeps it. Every file is embedded and cited to file and line. After that your agent answers from that memory instead of from whatever happened to be pasted into the window.

The memory outlives the session. Close the window, come back next week, and the context is still there. Conventions Estelle picks up from your code get applied on later runs.

Two ways in. Use either, or both.

  • MCP. Add Estelle as an MCP server in the tool you already use. Your model and plan keep doing the reasoning. See Connect over MCP.
  • The OpenAI-compatible API. Point any tool that can call OpenAI at one base URL and get completions grounded in your codebase. See Call the API.

You bring the model and the key. Estelle never trains on your code and never marks up a token. See BYOK and privacy.

Quickstart

A key, a command, a sweep. About a minute.

Create an Estelle key

Generate one in the dashboard. Keys look like estelle_live_…. The free tier needs no card.

Connect your editor's agent

npx @fatelabs/estelle init

This detects your installed MCP clients and writes Estelle into each config, leaving your other servers untouched. Existing files are backed up first.

Written automatically: Cursor, Cline, Windsurf, VS Code and JetBrains, plus the claude mcp add command for Claude Code. Guided, not written: Claude Desktop launches local stdio servers only, and Continue reads ~/.continue/config.yaml rather than a JSON mcpServers block. init prints the exact step for both rather than writing a file they would ignore.

It will ask for your key. Pass --key to skip the prompt. Zero dependencies, Node 18 or newer.

Ingest your repo

bash
npx @fatelabs/estelle sweep --key $ESTELLE_KEY

This uploads the working tree into your memory. Run it again whenever you want the memory to catch up.

Restart your editor. Your agent now has find_definition, find_references, blast_radius and verify over your repo. Want the API path instead? Three values, any OpenAI-compatible client: Call the API.

Connect over MCP

Nothing about your agent changes. Same model, same base URL, same plan. The hosted server at https://api.fatelabs.ca/mcp adds grounded tools over your repository, scoped to your key. Both the endpoint and the key are in the dashboard after signup.

The quickstart command writes this config for you. To wire a client yourself:

Claude Code

Claude Code takes the server as a command instead of a config file.

bash
claude mcp add --transport http estelle https://api.fatelabs.ca/mcp \
  --header "Authorization: Bearer $ESTELLE_KEY"

Any other MCP client

Paste the server into the client's MCP config.

json
{
  "mcpServers": {
    "estelle": {
      "type": "http",
      "url": "https://api.fatelabs.ca/mcp",
      "headers": { "Authorization": "Bearer estelle_live_..." }
    }
  }
}

Per-tool walkthroughs

Step-by-step guides for OpenCode, Cursor, Cline, Aider, Continue, and Zed live at /setup, covering both the MCP and API paths per tool.

Connect your model

Estelle does not sell tokens. Over MCP you need no provider key at all, because your existing plan does the reasoning. On the API path you bring your own key and pay your provider directly at their rate.

  • Claude, by Anthropic
  • OpenAI
  • Kimi, by Moonshot
  • GLM, by Zhipu
  • DeepSeek
  • Any OpenAI-compatible endpoint, including OpenRouter or a local one

Set it in Dashboard → Provider. Switch whenever you like without touching your tools: the model name stays estelle.

Authentication

A bearer token in the Authorization header, exactly like OpenAI. Send your Estelle key, not your provider key.

http
Authorization: Bearer estelle_live_...

Key format

Production keys carry the prefix estelle_live_.

Rotating keys

Create and revoke keys in Dashboard → API keys. Revocation is immediate. Rotate on a schedule, and the moment you suspect a leak.

Your provider key

Stored separately, encrypted at rest with a key only our server holds. No endpoint returns it, so a database leak cannot expose it.

MCP tools

Once your editor is connected, your agent has these. They answer from your swept memory and the code graph built over it, scoped to your key. Your agent calls them like any other tool.

Repository navigation

The code graph. Questions an agent otherwise answers by guessing, or by reading half the repo back into its window.

ToolArgsWhat it answers
find_definitionsymbolThe file and line where a symbol is defined.
find_referencessymbolEvery site that refers to a symbol.
find_usagessymbolThe files that reference a symbol by name.
locatequeryFind a file or symbol when you only half-remember the name.
blast_radiusfileEvery file that transitively depends on this one. What breaks if it changes.
dependency_pathfrom, toHow one file reaches another through the import graph.
core_filesnoneThe load-bearing modules the most code depends on (PageRank).
chokepointsnoneWhere a change ripples widest (betweenness centrality).
subsystemsnoneThe repo's natural modules, as connected components of the import graph.
import_cyclesnoneCircular imports. Usually a design smell, always a refactor hazard.
refactor_ordernoneA safe read or refactor order: dependencies first (topological sort).
verifycodeCheck a block of code for APIs that do not exist in this repo.

Memory

Your session diary and stored decisions, from any MCP client rather than only the dashboard.

ToolArgsWhat it answers
memory_chatquestionAsk your stored memory a question. The answer quotes the stored text and cites where each fact lives; when nothing matches it refuses rather than inventing.
list_sessionsnonePast sessions, newest first: title, times, who worked on it, repos touched.
get_sessionidOne session's full diary: operations, skills fired, repos, memories, outcomes.
edit_memorykey, valueCorrect a memory in place. The prior value is superseded, not overwritten, so the change stays queryable.
reinterpret_sessionmeaning, idTell Estelle what a session actually meant; it updates the record, preserving provenance.

The two write verbs need the ingest permission, which member and above hold. A viewer is refused. Both preserve provenance, so a correction adds to the record rather than replacing it. See Teams and roles.

Context survival

Long sessions end when the window fills. These three move detail into durable memory and page back only what the next step needs.

ToolArgsWhat it answers
estelle_checkpointsession_id, messagesStore a session's full detail durably and get back a compact brief.
estelle_resumesession_idThe lean briefing that continues a session past the context wall.
estelle_governmessages, window_tokensCompress an arbitrarily large history into a prompt that fits the window, reciting invariants so gate-verified facts survive curation.

Command line

The same capabilities without an editor. Zero dependencies, Node 18 or newer. Every command takes --key or reads ESTELLE_KEY from the environment.

Working with your repo

Ingest the working tree, then ask it things. recall searches memory and code together. --repo scopes it on a multi-repo account.

bash
npx @fatelabs/estelle sweep --key $ESTELLE_KEY
npx @fatelabs/estelle ask "why does /refresh 401 after deploy?"
npx @fatelabs/estelle recall "rate limiter" --repo api

Checking code before it lands

verify checks one file for APIs that do not exist in your repo. gate runs the merge gate over your staged diff, or against a base ref. Same deterministic check as Grounding and the gate.

bash
npx @fatelabs/estelle verify src/api/routes.py
npx @fatelabs/estelle gate --base main

Every command

CommandFlagsWhat it does
init--keyAuto-detect your editors, write the MCP config, then verify it answers.
sweep--keyIngest the current repo (the git-visible files) into your memory.
connect<client>Print the one-liner or config for a single client.
removenoneDisconnect: delete Estelle from every editor config.
ask<question>Ask Estelle about your codebase, grounded, on your plan.
recall--repoSearch your memory and code together.
verify<file>Check a file for hallucinated APIs against your repo.
gate--base <ref>Run the merge gate on your staged diff, or against a base ref.

Always-on grounding

In Claude Code the check can be unconditional rather than something the agent decides to run. Hooks fire on every edit and every command.

bash
npx @fatelabs/estelle install-hooks

Open /hooks in Claude Code, or restart it, to activate. uninstall-hooks removes only Estelle's hooks. Your own are untouched, and the MCP server is a separate switch.

Call the API

The part of the OpenAI API that coding tools use, from one base URL:

BASE https://api.fatelabs.ca/v1

  • POST /v1/chat/completions sends a conversation and returns a grounded completion.
  • GET /v1/models lists the models available to your key.

Any OpenAI client is three values away:

config
base_url = "https://api.fatelabs.ca/v1"
model    = "estelle"
api_key  = "estelle_live_..."

Keys are created at /signup. The free tier needs no card.

bash
curl https://api.fatelabs.ca/v1/models \
  -H "Authorization: Bearer $ESTELLE_KEY"

From the command line

bash
curl https://api.fatelabs.ca/v1/chat/completions \
  -H "Authorization: Bearer $ESTELLE_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "estelle",
    "messages": [
      { "role": "user", "content": "what changed in auth last week?" }
    ]
  }'

From Python

python
from openai import OpenAI

client = OpenAI(
    base_url="https://api.fatelabs.ca/v1",
    api_key="estelle_live_...",
)

resp = client.chat.completions.create(
    model="estelle",
    messages=[{"role": "user", "content": "why does /refresh 401 after deploy?"}],
)
print(resp.choices[0].message.content)

Chat completions

POST /v1/chat/completions

The main endpoint. Send messages, get a completion from your provider grounded in your codebase memory. Request and response shapes match OpenAI.

Request body

FieldTypeDescription
modelstringAlways estelle. Estelle routes to the provider you connected.
messagesarrayThe conversation so far, as role and content objects.
streambooleanWhen true, tokens stream back as server-sent events. Defaults to false.
temperaturenumberPassed through to your provider. Optional.

Example

bash
curl https://api.fatelabs.ca/v1/chat/completions \
  -H "Authorization: Bearer $ESTELLE_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "estelle",
    "messages": [
      { "role": "user", "content": "what changed in auth last week?" }
    ]
  }'

Streaming

Set stream to true for server-sent events in the OpenAI streaming format. Existing streaming code works unchanged.

List models

GET /v1/models

Returns the models available to your key. There is one, estelle, which routes to the provider you connected. It is also the cheapest way to check your key works.

bash
curl https://api.fatelabs.ca/v1/models \
  -H "Authorization: Bearer $ESTELLE_KEY"

Errors

Standard HTTP status codes, with an OpenAI-style error object.

401 means fix your credential. 402 means the credential is fine and the account needs funds. They are never interchangeable.

StatusNameWhat to do
401unauthorizedYour Estelle key is missing, malformed, or revoked. Check the Authorization header.
402paymentYour key is valid, but the account is out of credits or capacity. Add credits or upgrade in Billing.
429rate limitToo many requests. Back off and retry with exponential backoff.
5xxserverA failure on our side or the provider. Retry, then contact support.

Rate limits

Limits scale with your plan. Exceed them and you get a 429 with a Retry-After header. Back off exponentially and honour it. Limits apply to request volume; recall itself is never metered.

How memory works

FIG 02 retrieval field

Estelle reads the repository once and embeds it. After that every request is grounded in that memory. You stop pasting files.

What persists

  • The full contents of every file in your connected repositories, cited to file and line.
  • Prior sessions, so context carries from one conversation to the next.
  • Conventions Estelle observes in your code, applied on later runs.

Memory-tokens

Capacity is measured in memory-tokens: how much code and history Estelle keeps embedded for you. Ingest is billed once. Recall is never metered. The free tier holds one repository up to about a 2M-token codebase. Plans and overflow are on the pricing page.

Grounding and the gate

FIG 03 grounding certificate

Every claim has to resolve against your real code before the answer ships. The symbol exists, takes those arguments, returns that type. A claim that cannot resolve is struck before you see it. One that passes ships with its certificate: claims cited, symbols resolved, tests green.

The check is deterministic, not statistical. In our verification eval, on a private repository no model had seen, 0% of invented APIs survived the gate. The method and the numbers behind that live in our scorecard, which we will share on request; on well-known public repos the figure is uninteresting, because a capable model has already memorised those APIs and the gate has nothing to catch.

The completeness gate

FIG 04 completeness gate

Grounding proves what the answer says is real. The completeness gate proves the reverse: it walks the symbols a change was required to touch and flags the ones the answer never reached. Every missing symbol is named and located, before the answer ships.

BYOK and privacy

Your code goes to your provider, under your terms. Estelle structurally cannot train on it. You pay your provider directly at their rate, so there is no markup on tokens; Estelle bills for the memory layer only.

What Estelle keeps

The embedded code, sessions and conventions described in How memory works. Encrypted, isolated to your team, never shared, deletable at any time.

Your provider key

Stored encrypted at rest with a key only our server holds. No endpoint ever returns it, so a database leak cannot expose it. Your Estelle key is stored as a hash only.

Your model, your plan

Over MCP, reasoning stays on the plan you already pay for. On the API path it comes from the provider you connected in Dashboard → Provider. The model stays yours either way.

Teams and roles

Each member holds one role, and each role permits a fixed set of actions. The matrix is cumulative: a role holds everything below it plus its own additions. No model is involved, and it fails closed. An unrecognised role or action is denied.

RoleAddsWhat that means
viewerrecall, read_wiki, view_usageRead the memory and see usage. Cannot change anything.
memberingest, review, save_context, run_searchEverything a viewer can do, plus sweeping repos, correcting memory, and running searches.
adminmanage_team, rotate_keys, set_provider, configure_autonomyEverything a member can do, plus managing people, keys, the provider, and the autonomy dial.
ownermanage_billing, delete_accountEverything an admin can do, plus billing and deleting the account.

Roles manage strictly downward. An admin can change a member, never another admin or the owner.

Support

Quota questions or a deployment in your own cloud: khai@fatelabs.ca. Account and billing live in your dashboard. Reading this as an agent? The machine-readable version is at /llms-full.txt.