Login

Tortoise Documentation

A graph database for agent memory: claims are Points, relationships are edges, and belief scores are computed by propagating evidence through the graph.

What is Tortoise? How it works Quickstart (5 minutes) Connect your agent (MCP) API reference Next steps FAQ — design questions

What is Tortoise?

Tortoise is a graph database for agent memory. Data is stored as Points (nodes) connected by labeled edges that express how beliefs relate. It keeps the structure of what an agent learned — which claims support which, and which contradict each other — and computes a belief score for every Point by propagating evidence through the graph.

It is used via a REST API or an MCP server (see below). An organization account gets an isolated graph; all writes carry provenance back to the source session.

How it works

The data model

Everything is a Point: an atomic claim with an id, content (text), pointKind, and a status. The kind vocabulary is statement — the asserted belief — plus the write kinds decision, vision, strategy, plan, goal, target, observation, hypothesis, humanApproval, and any domain kinds registered by expansion packs. Canonical vocabulary: docs/ONTOLOGY.md §4.1 and §5. (evidence is an additional kind the SDK registers for decision records. event exists only internally, as the kind on captured episodic turns — it is not a supported write kind: the REST point-create endpoint and the SDK's bundle validator reject it, while create_point only warns.)

The hosted API, the SDK and the graph draw on the same kind vocabulary, plus whatever your expansion packs register.

A content hash (SHA-256 of content) is stored on every Point. With dedup enabled — the default over MCP — creating the same content twice returns the existing Point instead of duplicating it; the REST endpoint defaults dedup to false and will create a second Point.

Edges (operators)

Points are connected by operators. The two epistemic edge types are:

Part/whole edges (composedOf, decomposesInto, contains, wraps) and provenance edges (wasDerivedFrom, aboutSubject, …) also exist and are transferred on supersede.

Confidence (EP)

Each live Point carries a belief score. Tortoise computes it with expectation propagation (tortoise/ep.py, read back via sdk.get_confidence()): evidence is passed back and forth along the IMPL/NAND edges until the values settle. Support pulls a Point's belief toward its source's level; a NAND edge penalises believing both at once, so it pulls the pair's joint belief down. Both remain in the graph, so the disagreement stays visible instead of being resolved by deleting one side.

The two edge types are different constraints rather than one signed weight (tortoise/quadrature.py). A NAND is a joint-truth penalty — exp(-w·ca·cb) — so it is strongly penalised when both claims are held at once and relaxes as either side's belief falls toward zero; the weight w sets how hard the contradiction bites. An IMPL is a difference coupling — exp(-w·(ca−cb)2) — which pulls its target toward the source's level rather than lifting it regardless of the source, carries its own weight and direction, and means a strong source weakening weakens what it supports.

The result is a distribution, not a single number — a mean and a variance. A tight spread means the evidence has settled; a wide spread means the posterior is weakly determined — thin evidence, or evidence pulling in opposite directions — which is how a Point gets flagged as contested. The hosted API exposes the mean as confidence on search and read results.

Confidence is derived, never a stored verdict. Change the evidence — add a claim, supersede one, record a contradiction — and the affected scores recompute.

Sessions & provenance

Agent conversations are captured as Session nodes (with turn counts and metadata). Points created from a session are linked to it, so every insight traces back to the conversation that produced it. Listing sessions and their linked Points is a first-class API operation.

The episodic model underneath is richer than a session record: an Event carries eventKind, startedAt/endedAt and produces/uses edges. A capture commit on the hosted path writes an AgentSession Event carrying the capture timestamp and the session summary alongside its :Session node, so the same occurrence is queryable as both a session and a first-class event.

Search

Search fuses three strategies with RRF (Reciprocal Rank Fusion): full-text search (FTS) on content/title/name, vector similarity (embeddings, when available), and structural matches. Results are ranked by fused relevance, not recency.

Quickstart (5 minutes)

1. Create an account

Sign up at tortoise.premiselabs.co/auth. You'll get an API key in the in-app welcome card — copy it (it's shown once), then export it in your shell:

export TORTOISE_API_KEY=YOUR_KEY

2. Write your first Point

curl -X POST https://api.premiselabs.co/v1/points \
  -H "Authorization: Bearer $TORTOISE_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"content": "the production port is 16379", "kind": "statement"}'

Response includes the Point's id.

3. Search

curl "https://api.premiselabs.co/v1/search?q=port" \
  -H "Authorization: Bearer $TORTOISE_API_KEY"

Returns ranked results (FTS + vector + structural, RRF-fused).

4. Manage in the dashboard

Open app.premiselabs.co, paste your key, and you can create/revoke API keys and view sessions.

Connect your agent (MCP)

The fastest way to give an agent Tortoise memory is the MCP server — no code required. Your agent (Claude Code, Cursor, Codex, Pi, Claude Desktop, Claude Web, or ChatGPT — or any other MCP client) reads and writes your Tortoise graph automatically. (ChatGPT connects key-less over OAuth through OpenAI's custom MCP server — its steps are below.)

Setup — hosted (no install)

The hosted server speaks Streamable HTTP at https://api.premiselabs.co/mcp/ — the slashed form the keyed config-file rows below use. Keep your API key in an environment variable — never paste a literal tt_… key into a file that can be committed. In client JSON config the transport value is "http" (the protocol's spec name is Streamable HTTP): Claude Code accepts "streamable-http" as an alias and Cursor's IDE may tolerate it, but Cursor's CLI can drop the whole config file and Pi ignores type — so "http" is the only universally safe value.

Claude Code

Export TORTOISE_API_KEY in the shell you run this in — and in your profile for later sessions (if your profile is version-controlled, use a non-committed include instead). Prefer the one-liner — it writes local scope into ~/.claude.json, under this project's entry: private to you, this project only, never committed, and it skips the project-scope server approval below. (No scope is approval-free, though — Claude Code asks permission the first time it calls each tool.) claude mcp add --transport http tortoise https://api.premiselabs.co/mcp/ --header "Authorization: Bearer $TORTOISE_API_KEY"

Sharing the config with the repo instead? Add to a committable .mcp.json at the project root (keep the key as the env reference shown). ⛔ Re-using the one-liner's --header there? Single-quote it so the shell writes the reference, not your key, into a file you are about to commit. Claude Code requires "type": "http" — a url entry with no type is read as a stdio server and skipped. A project-scope server stays ⏸ Pending approval until you approve it once — start claude in the project and allow the prompt, or run /mcp and approve it there (claude mcp reset-project-choices resets the choice).

{
  "mcpServers": {
    "tortoise": {
      "type": "http",
      "url": "https://api.premiselabs.co/mcp/",
      "headers": { "Authorization": "Bearer ${TORTOISE_API_KEY}" }
    }
  }
}

Cursor — .cursor/mcp.json

Cursor expands ${env:…} and infers the transport from url, so the entry carries no type. The file is committable (project scope), so it carries the env reference, never the key.

{
  "mcpServers": {
    "tortoise": {
      "url": "https://api.premiselabs.co/mcp/",
      "headers": { "Authorization": "Bearer ${env:TORTOISE_API_KEY}" }
    }
  }
}

Pi — .mcp.json

Add to, or merge into, a committable .mcp.json in your project (project scope — it carries the env reference, never the key). Pi's mcp-client expands plain ${…} — no env: prefix — and infers the transport from url, so type is not needed (and is ignored if present).

{
  "mcpServers": {
    "tortoise": {
      "url": "https://api.premiselabs.co/mcp/",
      "headers": { "Authorization": "Bearer ${TORTOISE_API_KEY}" }
    }
  }
}

Codex CLI

Codex manages its own config — run this in your terminal:

export TORTOISE_API_KEY=YOUR_KEY
codex mcp add tortoise --url https://api.premiselabs.co/mcp/ --bearer-token-env-var TORTOISE_API_KEY

Codex Desktop (no terminal)

Codex Desktop shares ~/.codex/config.toml with the CLI but does not read shell exports. Add an [mcp_servers.tortoise] block with url = "https://api.premiselabs.co/mcp/" and bearer_token_env_var = "TORTOISE_API_KEY", and put that variable into the app's environment (launchctl setenv on macOS, setx on Windows) — or use the literal http_headers form on a shell-less machine (private file — never commit it).

Claude Desktop

Add the server from Settings → Connectors → Add custom connector: Server URL https://api.premiselabs.co/mcp/, request header Authorization: Bearer YOUR_KEY. Do not use claude_desktop_config.json — it only accepts local stdio servers, so a remote HTTP server placed there is silently ignored.

Claude Web

In claude.ai go to Settings → Connectors → Add custom connector, name it Tortoise, and use the Server URL above. Claude Web has no environment to read a variable from, so its request header holds the key literally: Authorization: Bearer YOUR_KEY. The key is held by Anthropic in your account — keep it private and revoke it from the dashboard if it leaks.

ChatGPT

Connect it once — ChatGPT is key-less, so there is no config file to edit and no key to store. Custom connectors need a paid ChatGPT plan (Plus or higher).

  1. Open chatgpt.com/plugins → the Add button (top right) → Add custom MCP server.
  2. Fill in the dialog it opens:
    • Name: Tortoise
    • Description: leave it empty, or write your own
    • Connection: leave it on Server URL
    • Server URL: https://api.premiselabs.co/mcp
    • Authentication: leave it on OAuth
    • Icon: optional — skip it
    Then tick I understand and want to continue and click Create as a plugin.
  3. Open chatgpt.com/settings/plugins-settings → Tortoise → View details → Connect → confirm Connect Tortoise in the dialog it opens. Sign in to Tortoise, pick the Organization you are onboarding — the page names it, or asks you to choose when your account can use more than one — then click Authorize. That Organization is the one the connector writes to.
  4. Install the Tortoise skills — once, and they apply to every chat: Then open chatgpt.com/skills → Add → Upload from your computer and select all three files together. The Tortoise setup document is deliberately not one of them: onboarding is a one-time flow you read, not a reusable skill, and installing it would leave setup steps in every chat — read it at the Tortoise onboarding document. To update one later, upload that file again and choose Replace existing — packs and taxonomy need no update, because the agent reads those from Tortoise.
  5. Start a new chat and ask it to set up Tortoise: it verifies the connection and files your first memory — that first write is what the dashboard reads as connected. Once it has landed, ask the same chat to call tortoise_overview(section="graphs") (or its tortoise_list_graphs alias) to see which graph the write went into — and ask it for the pack set too (tortoise_packs_list), since those packs are the vocabulary its extractions use. Which tortoise_* tools appear can be plan-dependent — if none do, re-open chatgpt.com/settings/plugins-settings and check Tortoise shows as connected. A grant of read tools only can read your graph but cannot finish connecting: write tools may not be granted on your OpenAI plan, so if no write tools were granted, connect a keyed harness (Claude Code, Cursor, Pi, or Codex) to the same Organization to complete onboarding.

What it writes to. The connector carries no graph of its own — a call that names no graph writes to the default graph of the Organization you authorized. A call can be pointed at another graph of the same Organization with the graph argument (a graph id, or a name that is unique in the workspace); tortoise_list_graphs lists them. On ChatGPT, naming a graph on a write does not work: a write carrying graph was refused by ChatGPT's own safety check before it reached Tortoise, while the same write without the argument succeeded (measured 2026-10-26). Reads are unaffected — the argument reaches Tortoise and is resolved against your workspace there. Do not retry a refused write and do not look for a way around it: to write to a graph other than the default, connect a keyed harness to that graph (Claude Code, Cursor, Pi, or Codex) and write from there.

What ChatGPT will not do. ChatGPT refuses deletions: ask it to delete a point and its safety check blocks the call, even after you allow it in the dialog. Creating a point, searching and computing confidence all work, and a scheduled task can call the connector while you are away. Where a deletion is genuinely needed, connect a keyed harness (Claude Code, Cursor, Pi, or Codex) to the same Organization and delete the point there. To remove a graph you created, open the Tortoise dashboard and delete it from its row; the Organization's default graph cannot be deleted.

Which packs it understands. A graph recognises the kinds of the packs it holds, so it is worth knowing the default before odd extraction shows up. The default graph holds the five starter packs (dev, marketing, product-strategy, pm, agent-ops). To change them, open app.premiselabs.co → Settings → Configured packs and pick the graph — the dashboard's create form does not choose packs, so configuring them is a separate act (and turning a pack off stops new classifications only into packs this graph holds no memories from yet — kinds already here stay searchable and writable). A graph with no pack records and no pack set of its own is the one case that recognises the entire catalog — reading its set (in Settings, or by asking for it) seeds the five starters into it.

If ChatGPT asks for a key (they start tt_… or tk_…) or an Authorization header, you have left the OAuth path — refuse it. The onboarding document also carries keyed recipes for the other harnesses; they do not apply to ChatGPT.

The hosted MCP server runs on our infrastructure — no Python or tortoise install needed — so your client just needs network access to it.

After editing a config file, restart that client (Claude Code, Cursor, Pi, and both Codex surfaces). The Claude Desktop, Claude Web, and ChatGPT connectors are live immediately.

Setup — self-hosted (stdio)

Running Tortoise on your own infrastructure? Point the MCP client at a local process over stdio instead (see the self-hosted guide). A stdio server gets its database from TORTOISE_DB_URI — do not set TORTOISE_API_KEY here: the stdio transport cannot carry auth tokens, and setting it disables every tool:

{
  "mcpServers": {
    "tortoise": {
      "command": "python3",
      "args": ["-m", "tortoise.mcp_server"],
      "env": {
        "TORTOISE_DB_URI": "docker://:falkordb@localhost:6379/tortoise"
      }
    }
  }
}

Cursor requires "type": "stdio" on stdio entries; Claude Code and Pi infer stdio from command. The block above is the durable sidecar path; stdio against an embedded database (with TORTOISE_DB_URI unset) is a single-writer, eval-only fallback — never a durable team setup.

Need python3 and tortoise installed where the client runs? pip install tortoise-graph — the MCP server ships with the package.

What your agent can do

For the keyed harnesses (Claude Code, Cursor, Pi, and both Codex surfaces) the in-app welcome flow includes a ready-to-copy setup with your key pre-filled.

API reference

Base URL: https://api.premiselabs.co. All endpoints require Authorization: Bearer tt_<key>.

EndpointMethodPurpose
/v1/teamGETTeam info: tier, limits, point count
/v1/pointsPOSTCreate a Point
/v1/pointsGETList Points
/v1/points/{id}GETGet one Point
/v1/searchGETHybrid search (query param q)
/v1/team/keysGETList API keys
/v1/team/keysPOSTCreate API key (plaintext shown once)
/v1/team/keys/{id}DELETERevoke API key
/v1/sessionsGETList captured sessions
/v1/sessionsPOSTRecord a session
Note: The context field is deprecated (removed from Point metadata). Use kind to classify content.

Next steps