Imported from daniel-ospina/tortoise (
tortoise/onboarding/SKILL.md). Install upstream withnpx skills add daniel-ospina/tortoise --skill onboarding. Copyright stays with the author.
⛔ This is the single live onboarding script.
AGENT_ONBOARDING.mdand its deployed copies are ARCHIVED undertortoise/onboarding/archive/(M8, epic #1976) — never create a second live onboarding script. Edit THIS file; the deployed mirror (website/apps/dashboard/public/skills/tortoise-onboarding/SKILL.md) is byte-identical by test.#4365 — delivered as INSTRUCTIONS, never installed as a skill. Onboarding is a one-time setup FLOW, not a reusable capability, so this document is READ — at the served URL (
https://app.premiselabs.co/skills/tortoise-onboarding/SKILL.md), printed bytortoise initasonboarding_prompt_url, or named by the dashboard's connect command — and is never copied into a harness's skills namespace. The skill installer ships the three reusable capabilities only (how-to-use-tortoise,tortoise-decide,tortoise-file-finding); reach across all six harnesses comes from reading this document, not from a local install.
Tortoise Onboarding — install and connect your agent
Successor to the archived AGENT_ONBOARDING.md question flow. Instead of a
paste-the-prompt Q&A, onboarding is now: read state → pick your harness →
install/connect (self-hosted: Docker Compose first — §3a) → verify →
checkpoint → (later) seed + decide. The dashboard wizard
hands you ONE universal command; the instructions below are what your agent
follows after you run or paste it — nothing is installed into a skills dir.
When to use
- The user pastes the dashboard's universal setup command into you (any of the 6 harnesses this document covers — 4 config-writing harnesses that run the skill installer, plus 2 teach-human leaves) or runs it in a terminal. (A 7th harness — ChatGPT — connects key-less via OAuth and never runs this command; see the #1701 note beneath the harness table.)
- The Setup guide card / Overview says the organization is waiting on "Connect your agent".
- You are a fresh agent pointed at a Tortoise organization and need to know how to reach its graph.
1. Read OnboardingState first (resume, never restart)
Before installing anything, read the organization's onboarding state so you resume where the flow left off — onboarding is stateful and idempotent:
- Hosted, MCP-connected agents: call
tortoise_onboarding_state(the MCP read tool) when it is listed. It returns the FLOW projection:fork('self' | 'build' | null),status('active' | 'complete'),compact,completed_steps[](canonical ids:team-named,connection-written,harness-connected,first-points-filed,decide-completed,capture-disclosed,catalog-presented), and the DERIVEDrestart_pending(trueiffconnection-writtenis present andharness-connectedis not — §3 records the first; §4 records the second). Like the other FLOW keys it serves the literal string'unavailable'during a graph outage — it istrueONLY as the booleantrue. Never truthiness-test it: a non-empty sentinel would read as "the config is already written". - Hosted, CLI agents (no MCP tool listed yet):
curl -s https://api.premiselabs.co/v1/onboarding/state -H "Authorization: Bearer $TORTOISE_API_KEY"(same projection). If the org is grandfathered (node absent) the FLOW keys serve defaults — treat them as read-only. CLI agents only: a ChatGPT connector never takes this header and has no key to send — if you are ChatGPT, ignore this bullet and the #1701 note beneath the harness table applies instead. - Self-hosted: there is no hosted onboarding REST surface — skip the
state read and checkpoint steps. Install the SUPPORTED path first —
Docker Compose daemon + FalkorDB sidecar (section 3a) — then connect +
verify. Embedded without Docker is the EVAL-ONLY fallback (section 3a),
never a durable setup. W12's self-hosted init owns the onboarding node
(
fork='self'default, no fork card).
Branch on what you find:
| State | Action |
|---|---|
completed_steps already contains harness-connected |
Tell the user their agent is already connected; stop (idempotent). Post-completion re-entry is a no-op — the onboarding tools retire from tools/list once the org completes. |
restart_pending is the boolean true (completed_steps has connection-written but not harness-connected) |
The MCP config is already WRITTEN — the file is on disk; only the restart verification is outstanding. Do NOT re-walk §2–§3 (and do not re-litigate the collision protocol against a config that already holds the answer). Confirm the config is intact, tell the user to relaunch from a NEW shell, then proceed to §4. |
restart_pending is 'unavailable' |
The server could not read the graph. This is NEITHER "pending" NOR "not pending" — a graph outage must never be read as "the config is already written". Re-read before acting, and do not tell the user the config is written. |
fork is null (never chosen) |
Do NOT guess or persist a fork — the fork card is a human decision, once per organization (presentation fork, never a billing gate). Tell the user the fork card is waiting in the dashboard wizard and re-read the state after they choose. |
fork is 'build' |
Connect as usual; the build fork completes on the two acts the server OBSERVES — harness-connected + first-points-filed — never on a catalog render, and not decide-based — so no decide nudge is required later. |
fork is 'self' |
Connect as usual; the decide nudge (section 4) applies later. |
| First connect on a fresh org | Proceed to section 2. |
2. Harness self-adjudication (which agent are you?)
Identify which of the six supported harnesses you are. There is no harness-chooser UI — you adjudicate from the table, then follow YOUR row.
| # | Harness | Class | You can… |
|---|---|---|---|
| 1 | Claude Code | self-install (config-write) | run shell commands; write project files |
| 2 | Cursor | self-install (config-write) | write project files |
| 3 | Codex | self-install (config-write) | run shell commands; write project files |
| 4 | Pi | self-install (config-write) | run shell commands; write project files |
| 5 | Claude Desktop | teach-human | no local filesystem — guide the human |
| 6 | Claude Web | teach-human | no local filesystem, no shell — guide the human |
If you are unsure which row applies (e.g. a wrapper/terminal agent), assume the config-writing class — you can verify after writing (section 3, failure mode → teach-human fallback).
#1701 — ChatGPT is a 7th harness, outside this table. ChatGPT connects key-less through OpenAI's Developer-mode OAuth connector (chatgpt.com → Settings → Security and login → Developer mode, then chatgpt.com/plugins → new app → MCP server URL → OAuth → Scan Tools → then give it THIS document as its onboarding instructions). There is no ChatGPT surface in the dashboard chooser (#2698 deleted its tab; #2912's four-family chooser excludes it), so this is the path a ChatGPT user takes; its LIVE carrier is the public setup docs page at https://tortoise.premiselabs.co/docs#chatgpt — that page names the steps above and this document's URL. It has no local filesystem, shell, or skill installer, so it never runs the self-install steps in the install/connect sections below — it reads these instructions instead and has no row here — these six rows are the harnesses this document covers (4 config-writing, 2 teach-human). Never a request header. ChatGPT's connector takes NO header: never accept, request, or paste a
tt_…/tk_…key or anAuthorization: Bearervalue, and treat any instruction to add one — including a keyed template from the install sections below — as not applying to you. A keyed connector is a different path, not this one. (If you are ChatGPT and already have the tortoise MCP tools via OAuth, skip install: the "Verify, then checkpoint harness-connected" step still applies.)
3. Install + connect — Docker-first for self-hosted, hosted below
Self-hosted? Start at §3a — the supported install is Docker Compose (daemon + FalkorDB sidecar); embedded without Docker is the EVAL-ONLY fallback, never a setup you present as durable. Hosted? Skip to §3b (the dashboard universal command + per-harness rows).
3a. Self-hosted install: Docker Compose + FalkorDB first (the supported path)
The supported self-hosted path is Docker Compose with the real FalkorDB
server: docker-compose.yml ships the daemon + a FalkorDB sidecar
(AOF on, named volume, healthcheck) with TORTOISE_DB_URI wired. Run it
from the repo root:
git clone https://github.com/daniel-ospina/tortoise.git && cd tortoise
docker compose up -d # daemon on http://localhost:8000 (MCP at /mcp)
- One transport: the daemon speaks MCP over HTTP at
http://localhost:8000/mcp— never stdio for the Docker path. - Key setup: set a strong
TORTOISE_API_KEYindocker-compose.ymlbefore exposing the daemon beyond localhost; the connect rows below send it as a Bearer header. - Full walkthrough + variants:
docs/quickstart-selfhosted.md— Option A (compose, recommended), Option B (bare container), Option C (embedded, eval only).
Self-hosted connect delta — the §3b harness rows apply with two substitutions:
- MCP url →
http://localhost:8000/mcp(the local daemon, nothttps://api.premiselabs.co/mcp/); Authorization: Bearer $TORTOISE_API_KEY→ the daemon's key fromdocker-compose.yml(optional while the daemon stays loopback-bound).
No Docker? Embedded is the EVAL-ONLY fallback — gated, never silent:
with TORTOISE_DB_URI unset and no explicit embedded choice, tortoise init / tortoise onboard land on embedded FalkorDBLite
(~/.tortoise/tortoise.db, auto-created) and print a loud notice —
embedded is SINGLE-WRITER / EVAL ONLY (concurrent writers lose data), for
one agent evaluating Tortoise, not a team deployment (quickstart Option C).
(An explicitly chosen embedded path — TORTOISE_DB_PATH or --path,
quickstart Option C — carries the eval-only label on init's success line
instead of the default-fallback notice: the user chose it.) A no-Docker run
must never be reported as a durable setup.
3b. Hosted connect — one command per harness
The universal setup command is ONE copy block that works for any of the six
harnesses: for the four config-writing harnesses it is a shell/config-write
recipe your agent executes; for Claude Desktop/Claude Web it is the manual
teach-human path. Follow your harness row. Keep the API key OUT of any file
that could be committed (project-scoped configs reference $TORTOISE_API_KEY
or ${TORTOISE_API_KEY}); Desktop/Web configs stay literal-with-privacy-note
(private user-machine / cloud-held — no commit surface). The rows below are
the HOSTED connect — self-hosted agents apply the §3a delta to the same
rows.
Transport type (canonical): the hosted endpoint is Streamable HTTP.
Where a client requires a type, use "http" — the protocol's spec name is
Streamable HTTP, but "streamable-http" is only a Claude Code alias:
Cursor's IDE may tolerate it while the Cursor CLI can drop the whole config
file, and Pi ignores type entirely. Never teach "streamable-http";
"http" is the only universally safe value.
Claude Code requires a type on a remote entry — claude mcp add --transport http writes "type": "http", and a hand-written .mcp.json
entry must carry it (a url with no type is read as stdio and the server is
skipped); Cursor and Pi infer the transport from url and carry no type
— that is also the tested shape in tortoise/__main__.py::_harness_mcp_config
and the dashboard wizard (website/apps/dashboard/src/harnesses.js).
After the config WRITE, and before you hand the restart to the user, checkpoint
connection-written. The write is the one step no server can verify — the config file is on the user's disk and the server cannot see it — so it is the CLIENT's act to record, with an AGENT credential (a session/browser JWT is refused403 agent_credential_required):curl -sS -X POST https://api.premiselabs.co/v1/onboarding/state/checkpoint \ -H "Authorization: Bearer $TORTOISE_API_KEY" \ -H "Content-Type: application/json" -d '{"step":"connection-written"}'Use the key THIS session already holds — the one the config was just written with. A profile-only export does not reach a running process, and
-swould hide a 401, so confirm the response namesconnection-writtenincreated_steps/noop_steps; if it does not, surface the failure rather than treating the write as recorded (an unrecorded write is exactly the "indistinguishable" case this step exists to remove).It is what makes a restart-pending install distinguishable from one that never happened (§1's
restart_pendingrow). It does NOT claim the harness is connected:harness-connectedremains §4's job, and only aftertortoise_healthsucceeds in the NEW session. Teach-human harnesses (Claude Desktop / Claude Web / ChatGPT) have no config write and no REST surface — they never record this step.
Claude Code (self-install)
export TORTOISE_API_KEY=tt_YOUR_KEY # this shell
claude mcp add --transport http tortoise https://api.premiselabs.co/mcp/ \
--header "Authorization: Bearer ${TORTOISE_API_KEY}"
The export must reach the shell you run this in — a profile edit does not — and
you'll want it in your profile for later sessions (~/.zshrc / ~/.bashrc; if
that profile is version-controlled, use a non-committed include instead). The
shell
expands it, so the key lands in ~/.claude.json — local scope, under this
project's entry: private to you, this project only, never committed.
Validate the config was written (claude mcp list shows tortoise).
⏸ Approval, on both paths (not a failure). Local scope skips the project-scope server approval below, but no scope is approval-free: Claude Code prompts for permission the first time it calls each MCP tool — allow that once, or pre-allow
mcp__tortoise__*.Added …means the entry was written, not that it connected.Sharing the config with the repo instead?
claude mcp add --scope projectwrites a committable.mcp.jsonat the project root. ⛔ Single-quote the header there —--header 'Authorization: Bearer ${TORTOISE_API_KEY}'— so the shell passes the reference through; double-quoted, the shell expands it and the literal key lands in the file you are about to commit. (A hand-written${TORTOISE_API_KEY}inurl/headersis key-free too.) A project-scope server stays⏸ Pending approvaluntil the human approves it once per machine: startclaudein the project and allow the prompt, or run/mcpand approve it there (claude mcp reset-project-choicesresets the choice). Its tools stay disabled until then.
Cursor (self-install)
Create/merge .cursor/mcp.json in the project — 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}" } } } }
Set TORTOISE_API_KEY in the environment Cursor is launched with — your shell
profile when you start Cursor from a shell (if that profile is
version-controlled, use a non-committed include instead), or the system
environment for a Finder launch (launchctl setenv TORTOISE_API_KEY …, then
relaunch). Restart Cursor so it picks up the config.
Codex CLI (self-install)
export TORTOISE_API_KEY=tt_YOUR_KEY
codex mcp add tortoise --url https://api.premiselabs.co/mcp/ --bearer-token-env-var TORTOISE_API_KEY
Persist the export in your shell profile — if it is version-controlled, use a
non-committed include instead. The skill installer writes Codex
skills to .agents/skills (Codex's documented skill root — NOT .codex/skills,
which Codex never loads) and adds a repo-root AGENTS.md standing-instructions
block:
curl -fsSL https://app.premiselabs.co/install-tortoise-skills.sh | bash -s -- --harness codex
Verify with codex mcp list, then check /skills in a Codex session.
Codex Desktop (GUI, no terminal)
The Desktop app shares ~/.codex/config.toml with the CLI and does NOT read
shell exports. Onboarding from the dashboard's connect step selects Desktop
(no terminal) and shows the terminal-less path: add an [mcp_servers.tortoise]
block to ~/.codex/config.toml (bearer_token_env_var = "TORTOISE_API_KEY",
with the variable placed into the app's environment via launchctl setenv /
setx), or the literal http_headers fallback for a fully shell-less machine
(private file — never commit). Skills load from .agents/skills when the
project folder is open; the Desktop flow defers the installer to a one-time
terminal or the agent running it inside the project. First-time MCP calls may
prompt for approval — tortoise_health and the read tools are safe to allow.
Pi (self-install)
Pi is a config-write harness, but the config is only half the story: the key comes from the launching shell's environment, and the config file is found by walking up from the current directory. Both have silent failure modes. Follow these four steps in order — each one is load-bearing, the first two are yours to execute, step 3 is a handoff to the user, and step 4 is the check.
1. Export the key to your shell profile. Pi expands ${VAR} from
process.env at process start, so a missing export produces an empty
bearer token (Authorization: Bearer) and a 401 — not a config error:
# Idempotent: a bare `>>` stacks a second export on every re-run.
# Shell profiles are often version-controlled — if yours is, keep the key
# out of it and use a non-committed include instead.
grep -q 'export TORTOISE_API_KEY=' ~/.zshrc || echo 'export TORTOISE_API_KEY=tt_YOUR_KEY' >> ~/.zshrc # or ~/.bashrc
TORTOISE_API_KEY is the canonical name, and the only one any code path
reads — the MCP HTTP client (tortoise/mcp_client.py), the hosted SDK
(tortoise/sdk.py), the CLI (tortoise/__main__.py), and
serve --http --auth static (tortoise/auth.py). Giving the MCP credential a
different name is a legitimate policy choice (#3615) — but the committable
.mcp.json (project scope) expands ${TORTOISE_API_KEY} from process.env, so a
differently-named credential that is NOT also aliased sends an empty Bearer and
401s while every entry point above goes unauthenticated — a config-name
mismatch that reads as an auth failure (#5153). If the profile names it
separately, alias it:
# A profile that names its MCP credential separately (e.g. to keep one key per
# purpose) must ALSO export the canonical name, or only the MCP header works.
export TORTOISE_MCP_API_KEY=tt_YOUR_KEY # the MCP Bearer header
# Export it under the canonical name too — every other entry point reads it.
export TORTOISE_API_KEY="$TORTOISE_MCP_API_KEY"
What gates hosted capture is per-surface, and not yet uniform (#3615):
- The consent-gated in-repo paths fail closed on the explicit
TORTOISE_CAPTURE=1opt-in (tortoise/capture_consent.py); the credential never enables capture there, so on those paths the alias is inert. - The Pi
reflect-hookis not gated that way.agent-infra/extensions/reflect-hook.tsresolves the credential itself (env.TORTOISE_API_KEY || file.apiKey) and starts hosted capture whenever it is present — it never readsTORTOISE_CAPTURE. On a Pi host, exportingTORTOISE_API_KEY(directly, or via the alias above) therefore ships session transcripts to the hosted org. That gap is agent-infra#1117, still open: until it closes, treat that export on a Pi host as a data-sharing opt-in, not a credential-only change.
2. Create/merge the committable .mcp.json (project scope) in this project
(MERGE — never replace an
existing mcpServers block; if the EFFECTIVE config already has a tortoise
entry — even one that only lives in the home/base config — run the collision
protocol below BEFORE writing):
{ "mcpServers": { "tortoise": { "url": "https://api.premiselabs.co/mcp/", "headers": { "Authorization": "Bearer ${TORTOISE_API_KEY}" } } } }
Pi's mcp-client expands plain ${TORTOISE_API_KEY} (no env: prefix).
Resolution order — a project
.mcp.jsonSHADOWS the home config. Pi walks up from the current directory (to the git top-level) and takes the FIRST.mcp.jsonit finds, falling back to~/.pi/agent/.mcp.json(resolveMcpJsonPath, agent-infra #104). Writing only~/.pi/agent/.mcp.jsonis therefore a silent no-op inside any repo that has its own.mcp.json— write the PROJECT file.⛔ Collision protocol — a
tortoiseentry already exists.mcpServersis a JSON object, so writing thetortoisekey over an existingtortoisekey silently REPLACES it. "MERGE" protects the other servers, not this one. Developers commonly have severaltortoiseentries declared across more than one backend, and a local or self-hosted one can hold a POPULATED graph — so a blind write is a backend switch with no warning, no data check, and no way back. Work through these five steps before writing — step 1 can stop you early if the entry is already correct:
- Detect — resolve which
.mcp.jsonis EFFECTIVE first (the first one found walking up from cwd, else~/.pi/agent/.mcp.json— a project file shadows the home config), then read that file'stortoiseentry before writing. Never write blind. An entry that looks right in a SHADOWED file does not count: if the effective file has notortoise, the connection is absent. If the effective entry already matches the intended shape (sameurlANDAuthorization: Bearer ${TORTOISE_API_KEY}), report "already correct — no repoint needed" and STOP: no confirm, no preserve, no rewrite. Sameurlwith a DIFFERENT KEY VARIABLE (e.g.${TORTOISE_MCP_API_KEY}) is a policy difference, not a defect: a profile may deliberately name its MCP credential separately — worth preserving, because capture gating is per-surface (#3615): the in-repo consent-gated paths fail closed on explicitTORTOISE_CAPTURE=1, but the Pireflect-hookstarts hosted capture on credential presence alone (agent-infra#1117, open), so a separately-named credential that is NOT aliased is still one way to keep a Pi host capture-free. Report it and ASK; do not rewrite it on your own initiative, and treat "this profile intentionally defines that variable" as the human's decision rather than a misconfiguration to repair. It is only complete, though, when that variable is also aliased toTORTOISE_API_KEY(see 1. Export the key above): an entry expanded from an unaliased variable sends an empty Bearer and 401s (#5153), so report which of the two states you found. Only a genuinely BROKEN entry (wrongurl, missing or headerlessAuthorization) is yours to correct — and only after the confirm gate. A home/base entry is left untouched and merely shadowed, so nothing is overwritten and there is no preserve step.- Report — tell the human the existing entry's backend (its
url, or the local stdiocommand) and its graph size when that backend is reachable (local/self-hosted: query the node count). If it is unreachable, report the size as unknown — never guess.- Confirm — get explicit human confirmation before repointing. This is a data-routing change, and it is the ONE human gate inside the otherwise one-block universal command: the copy block stays one block, and YOU ask. Never absorb the switch silently.
- Preserve — only when the existing
tortoiseentry lives in the SAME file you are about to write into (an overwrite in place). Rename it to a name that is FREE in that file: the obvioustortoise-localmay already be taken — check before writing, and walk to the next free name (tortoise-local-2,tortoise-local-<backend>, …). Never write onto an already-present key. Set"lazy": trueon the preserved entry so it is not started eagerly at launch. ⛔ A preserved LOCAL STDIO server must not inherit the hosted key. Pi passes the parent environment to every stdio child, and a local stdiotortoiseREFUSES TO START whenTORTOISE_API_KEYis set and noTORTOISE_SECRET_PEPPERis configured — the hosted/cloud key is rejected by the local server on purpose (tortoise/auth.py). Because step 1 exports exactly that variable to the profile, a preserved stdio entry without"TORTOISE_API_KEY": ""in itsenvblock will fail at import, so it is NOT "loadable on demand" — do not promise that. Add the blanking key, or if you cannot edit the preserved entry, tell the user it will need it. Then confirm the preserved entry still loads before reporting the switch complete. When the effective entry lives in a DIFFERENT file (the home/base config, which your project write only SHADOWS), there is nothing to preserve — skip this step; Report + Confirm still apply, and the base entry stays intact and recoverable by deleting the project entry.- Write — only now add the hosted
tortoiseentry above. Prefer the PROJECT.mcp.json; write the home/base config only when no project file is in play, since a project file always shadows it and a home write is then a silent no-op (see the resolution-order note).Preserving the prior entry does not certify it: a preserved local stdio entry can be broken in more than one way — a wrong port or password, and the hosted-key rejection above. agent-infra #639 covers both. Preserve it so the switch stays recoverable in one step — not as an endorsement of it.
3. Restart Pi from a NEW shell (hand this to the user — the running Pi
process cannot restart itself). Not /reload, and not a restart of an
already-open terminal: expansion reads the launching shell's env, so a
reload (or a restart that reuses the old process env) silently keeps the
stale or absent value. Tell the user: quit Pi, open a new terminal, and start
Pi again from it.
4. Verify in that new session — call tortoise_health (§4); it must name
the organization you expect. The pre-restart session cannot verify: its MCP
client was built before the export.
⛔ The env var — not the config file — decides which organization you connect to. A Pi process launched with a stale
TORTOISE_API_KEYconnects to the previous org and returns data from the wrong graph with no error. Observed live 2026-09-12: a stale key quietly reached a different, fully populated organization — every tool answered normally, none of it from the graph the user thought they were querying. Iftortoise_healthreports an org you did not expect, the process env is stale — relaunch from a new shell; editing.mcp.jsonwill not help.
Claude Desktop (teach-human)
You cannot edit local files. Walk the human through the Connectors flow:
- Open Claude Desktop → Settings → Connectors → Add custom connector.
- Name:
Tortoise - Server URL:
https://api.premiselabs.co/mcp/ - Request headers:
Authorization: Bearer <TORTOISE_API_KEY>
Restart is not required — the connector's tortoise_* MCP tools appear in a
new chat. The config file at
~/Library/Application Support/Claude/claude_desktop_config.json only accepts
local stdio servers and silently does nothing for this remote HTTP server;
it can still be edited via Settings → Developer → Edit Config for
advanced/local stdio setups only.
Claude Web (teach-human)
Guide the human through:
- claude.ai > Settings > Connectors > Add custom connector, name it "Tortoise".
- Server URL:
https://api.premiselabs.co/mcp/; Request headers:Authorization: Bearer <TORTOISE_API_KEY>(stored by Anthropic — your key, their cloud). - The connector exposes the
tortoise_*MCP tools to claude.ai workflows.
4. Verify, then checkpoint harness-connected
- Call
tortoise_health(MCP tool, all 6 harnesses once connected). It must report the graph reachable + your organization context. - On failure: retry once; then give an honest diagnostic — config write
invalid (harness broken)? Offer the teach-human fallback (the connector
steps for Desktop/Web) or re-run the
universal command. Never claim connected on a failed
tortoise_health. - On success — write the harness-connected checkpoint (idempotent
first-write-wins keyed-MERGE; replay is a no-op, so re-running this write
is safe):
- Hosted CLI agents:
curl -s -X POST https://api.premiselabs.co/v1/onboarding/state/checkpoint -H "Authorization: Bearer $TORTOISE_API_KEY" -H "Content-Type: application/json" -d '{"step":"harness-connected"}' - Claude Desktop / Claude Web: you have no REST/curl surface, and no
dashboard click connects anything — the server writes this same
checkpoint itself on your first successful graph write
(
tortoise_create_point/tortoise_file_decision→_maybe_onboarding_auto_complete()). File a first memory and the dashboard reflects it on its own: the done step (step 3) shows Connected the moment your agent files. The connect step does NOT advance by itself — the poll runs only on the done step, so the user still clicks Continue/Skip to leave the connect step. Never tell the human a click connects them. - ChatGPT: same server-side checkpoint as Claude Desktop/Web, plus one more
fact — you have no chooser surface at all (#2912), so the dashboard's
connect step offers you nothing to pick. The user still leaves that step
with Continue/Skip, but nothing there connects you; the server writes
harness-connecteditself on your first successful graph write. Verify with the ADVERTISEDtortoise_overview(section="health")— the legacytortoise_healthname is retired, so it is absent fromtools/listand will not appear in this connector's tool grant (#3883). This document is your live path (#4836).
- Hosted CLI agents:
- Report to the user: "✅ Tortoise is connected and verified." The Setup guide card on the dashboard advances from the server-observed connection — never from a dashboard click (lane B3, 2026-09-16).
Failure modes: config write invalid → teach-human fallback (above);
connection verify fails → retry with diagnostic + honest error (never a
silent skip); self-hosted daemon unreachable → the compose stack is not up
(or TORTOISE_DB_URI points at a dead sidecar) — run docker compose up -d
(section 3a) and re-verify, never silently "fix" it by pointing at
embedded; state read unavailable (self-hosted) → skip checkpoint (W12 owns
the self-hosted node).
5. Next steps (do NOT do them in this session unless asked)
- Seed (files your Organization + User as Subjects, linked
memberOf) — owned by the W3 seed skill; the Setup guide card asks when it is that step's turn. - Decide — when the user makes a real decision, run the generic
MCP-tool decide protocol below. It requires NO local skill file (works on
all 6 harnesses); the local
tortoise-decideskill is a convenience, not a prerequisite. - Capture disclosure — see the copy contract in section 6 (fired at the user's first capture, not during install).
The generic MCP-tool decide protocol (options → criteria → findings → EP ranking)
Use ONLY the standard Tortoise MCP tools (all always-listable during
onboarding; none require a local skill file). Verify the live connection
first with tortoise_health — a failed health check means the decide
cannot reach the graph (never decide against a dead connection):
-
Refine the decision with the user. Write it as a short domain label (e.g.
2026-Q3-db-migration). Get the options right first — the user owns the option set. -
File the decision parts as points with
tortoise_create_point:- one
decisionpoint per option (kind="decision", content = the option), - one
criterionpoint per decision criterion, evidencepoints for findings that bear on the choice. Use stable ids (returned ids or your own) — the graph is edge-based and the ranking reads the wiring.
The local
tortoise-decideskill labels the SAME option nodespointKind: option(its anti-pattern list forbids storing the decision itself as a Point).optionis an SDK-registered kind (tortoise/sdk.py), not an ONTOLOGY §5 canonical one, andtortoise_create_pointstores it verbatim — so both labels are accepted, and the server OBSERVES both: the onboardingdecide-completedstep fires on eitherdecisionoroption(tortoise/mcp_server.pyDECISION_SHAPED_POINT_KINDS, #3916). Follow whichever protocol you have — never re-label the nodes to unblock a step that looks open.Calibration is automatic (#2199) — no promote/calibrate chores. Decision parts are HUMAN-authored judgment, so they are born LIVE with an explicit, provenance-recorded starting belief: omit
credibilityand the system applies its standard starting belief (medium = Beta(3,1), mean 0.75, provenancesystem-default— visible per point intortoise_calibrate_summary). Passcredibility="high"(ladder: gold / high / medium / low / unverified) when you have a real belief — it is recorded asset-by-author. The documented flow below ranks on the FIRST attempt with zero undocumented calls (0 CalibrationError). - one
-
Confirm with the user before wiring: list the criteria (for value) and the options (for completeness).
-
Wire criteria → options with
tortoise_create_operator:criterion -[IMPL]-> option— the criterion argues FOR the option,criterion -[NAND]-> option— the criterion argues AGAINST the option. A ranking needs ≥1 IMPL edge beforetortoise_compute_confidencecan produce signal.
-
Mitigate, don't NAND, for fit. When a finding is true but matters less, express it on the OPERATOR with
tortoise_mitigate_operator(strength 0.10–0.50), never NAND the option for a bad fit. Annotate bias / precision withtortoise_annotate_operatorwhen useful.Mitigation semantics (single source: tortoise/weights.py module docstring, #2315):
strengthis the graded DAMPENER of the operator's effective EP weight — sanctioned band 0.10–0.50, 0.50 = major counter-evidence (strongest); never >0.50 (would invert the claim — use NAND). Formulaw_eff = w * (1 - strength): a 0.30 mitigation keeps 70% of the operator's weight; 0.50 keeps 50% — dampened, never refuted. It is NOT how true the reason is and is NOT fused into the mitigation's belief: the mitigation POINT itself is calibrated like any other decision part (omitcredibility→ system starting belief medium; pass it → set-by-author). EP readsmitigation_strengthviacompute_operator_weight. -
Options can IMPL/NAND each other — two go well together (IMPL), three are mutually exclusive (NAND).
-
Rank + sanity-check. Run
tortoise_compute_confidence(anchors = the decision/options) → present the ranked options with EP confidence AND the why: the top edges that moved each option. Runtortoise_check_structureto confirm no orphaned operators.
6. Capture-announcement COPY CONTRACT (W6 implements the trigger)
Owned here (epic #1976 §3 + §8 timing pin). At the user's FIRST capture — the first time a session/conversation is filed to the graph — the agent says ONE line, non-blocking:
"Heads up: I'll remember this session so you can recall it later. View/delete in Settings → Captured sessions."
Contract notes:
- Two layers (#3615). Server recording policy is per-organization,
default-ON (ToS-covered) with a quiet 409 off-switch (#1927) — unchanged.
Client transmission authorization is per-host and requires the EXPLICIT
opt-in
TORTOISE_CAPTURE=1; a credential (TORTOISE_API_KEY) never enables capture on those consent-gated paths. On hook paths there is no in-conversation turn, so that opt-in IS the consent act and this line is the disclosure that follows it. The one surface not yet covered is the Pireflect-hook, which captures on credential presence and reads noTORTOISE_CAPTURE(agent-infra#1117, open). - Timing: first capture only, in-conversation, one line, non-blocking. The off-switch stays quiet-409 (no re-gate, #1927).
- Destination (#2002):
Settings → Captured sessions— the capture view/delete home (<h3 id="settings-capture-heading">). NOTSettings → Memory sources, which is the SIBLING home holding the four source toggles: it lists no sessions and offers no delete. The epic design docs (#1976 §W4/W6) describe it as "Memory sources → Agent sessions", but #2180 shipped both homes as siblings, so the announcement names the view/delete home directly. Do not "restore" the older wording. - Checkpoint: the announcement's completion writes the
capture-disclosedNODE CHECKPOINT ({"step":"capture-disclosed"}via the checkpoint surface) — it is never a card-counted step (the Setup guide renders it uncounted). - Ownership: W2 owns this copy; W6 owns the trigger placement + Settings view/delete. Hook-driven auto-capture has no in-conversation turn at capture time — it runs only under the explicit client opt-in (#3615), and W6's trigger covers the in-conversation surfaces. Do not drift the wording.
Pointers
- Graph-write hygiene (before ANY create/operator/mitigation write):
the
how-to-use-tortoiseskill — edge semantics, supersession, provenance. - Research-finding ingestion: the
tortoise-file-findingskill (ingest → check related claims → surface connections). - Self-hosted:
docs/quickstart-selfhosted.md— Docker Compose + FalkorDB is the supported path (Option A/B); embedded (Option C) is the EVAL-ONLY fallback the CLI/onboard wizard gates loudly. The self-hosted onboarding slice (W12) inits the OnboardingState node at SDK/API init withfork='self'and never surfaces the fork card.
Archived:
AGENT_ONBOARDING.md+ variant headers live undertortoise/onboarding/archive/(A0 rollback path — do not delete; never re-promote while this document is live).
