Documentation

Everything tuskd does, in one page.

tuskd is the OpenTusk daemon: local, single-binary memory for AI agent swarms. This page is the full reference — if you just want to get running, the quickstart on the homepage is four commands.

What one install gives you

  • A vault — one Markdown file per memory record in a plain folder. Human-readable, diffable, portable. The files are the source of truth; everything else is derived.
  • An index — embedded SQLite (bundled, FTS5) for hybrid search with metadata and temporal filters. Rebuildable at any time.
  • An MCP server — every agent (Claude Code, Cursor, custom) talks to the same tool surface over stdio or streamable HTTP.
  • Identity & ACL — each agent is a principal with an ed25519 keypair and explicit read / write / promote grants per scope; every call is authenticated and scope-filtered.
  • An intelligence loop — a promotion gate, review queue, feedback telemetry, and automatic graduation of proven procedures into SKILL.md files agents load natively.

Process model: one daemon, one writer

Exactly one tuskd process owns the vault's SQLite index and file-watcher. It serves MCP over HTTP on 127.0.0.1:7477 and over a Unix domain socket. Everything else routes through it:

process model
tuskd start                 → daemon: owns index + watcher; serves MCP-HTTP :7477,
                              /status, the dashboard, and a Unix socket
tuskd mcp --agent <id>      → thin stdio ⇄ UDS proxy to the daemon; if no daemon
                              is running, runs the core embedded in-process

Even in embedded mode an advisory lock (.tusk/lock) guarantees a second instance refuses to start against the same vault — there is never a second writer. The lock dies with the process, so stale locks can't wedge the vault.

Everything is local by construction: tuskd binds to localhost only and makes no outbound network calls. A StorageProvider seam exists for future encrypted sync (SEAL / Walrus), but v0 ships local storage only.

Installing and upgrading

tuskd ships as a single static binary with no runtime dependencies. The installer detects your platform, downloads the latest release, verifies its SHA-256 checksum, and installs to ~/.local/bin. It never asks for sudo.

install
curl -fsSL https://get.opentusk.ai | sh

Options via environment variables: TUSKD_INSTALL_DIR changes the destination; TUSKD_VERSION pins a release. Every release is immutable and stays available at get.opentusk.ai/releases/<version>/…, so pinned installs are reproducible.

Pin a version

shell
curl -fsSL https://get.opentusk.ai | TUSKD_VERSION=v0.4.1 sh

Verify manually

shell
V=$(curl -fsSL https://get.opentusk.ai/releases/latest.json | sed -n 's/.*"version": *"\([^"]*\)".*/\1/p')
curl -fsSLO "https://get.opentusk.ai/releases/v$V/tuskd-v$V-aarch64-apple-darwin.tar.gz"
curl -fsSLO "https://get.opentusk.ai/releases/v$V/tuskd-v$V-aarch64-apple-darwin.tar.gz.sha256"
shasum -a 256 -c "tuskd-v$V-aarch64-apple-darwin.tar.gz.sha256"
tar xzf "tuskd-v$V-aarch64-apple-darwin.tar.gz"

Checksums are published next to every artifact; the installer script performs this same verification automatically. Building from source requires access to the repository (not yet public): cargo build --release on Rust stable.

Supported platforms

PlatformTargetStatus
macOS (Apple Silicon)aarch64-apple-darwin✓ available
Linux x86_64x86_64-unknown-linux-musl (static)planned
Linux arm64aarch64-unknown-linux-musl (static)planned
macOS (Intel) / Windowslater

Homebrew and cargo-binstall channels are planned once releases move to a public repository.

Upgrading

shell
curl -fsSL https://get.opentusk.ai | sh && tuskd restart -d

The installer replaces the binary in place; tuskd restart -d cycles the daemon onto it gracefully. Releases are immutable, so you can always pin back to a previous version.

The full path, start to shutdown

The homepage covers the four-command fast path. This is the whole flow, including explicit identities and the loop.

shell
# 1. Initialize a vault — a directory of Markdown files
mkdir team-vault && cd team-vault
tuskd init

# 2. Start the daemon (-d detaches; log at .tusk/daemon.log)
tuskd start -d
curl -s http://127.0.0.1:7477/status

# 3. Wire up your AI client in one command
tuskd agent setup claude-code

# 4. Optional: create agents with explicit grants instead
tuskd agent create hermes-dev \
  --read project:demo,user \
  --write project:demo \
  --promote project:demo

# 5. The CLI works alongside the daemon — routed through it
tuskd status
tuskd search "env parity" --scope project:demo

# 6. The loop: review queue + graduation into skills
tuskd review list
tuskd graduate

# 7. Web dashboard (one-per-run operator token)
tuskd dashboard

# Shut down (graceful; waits for the vault lock to release)
tuskd stop

Wiring up MCP clients

The fast path is tuskd agent setup <client>: it merges an opentusk entry into the client's existing config (taking a backup first — nothing else in the file is touched), creates the agent identity with sensible default grants if it doesn't exist, and verifies a real MCP handshake before it reports success. Re-running it is a no-op; --remove takes the entry back out.

shell
tuskd agent setup claude-code   # also: claude-desktop | cursor | codex | vscode
tuskd agent setup list          # what's configured, and where each config lives
tuskd agent setup claude-code --remove
tuskd agent setup claude-code --print   # show what would be written

Two transports

  • stdio (default) — the client launches tuskd mcp --agent <id>. Identity is fixed at spawn; no token needed. The session proxies to the running daemon over its Unix socket, or runs embedded if no daemon is up.
  • streamable HTTP (--http) — the client connects to http://127.0.0.1:7477/mcp with Authorization: Bearer <token>. Every request is authenticated (constant-time hash compare); the endpoint is a single stateless POST /mcp.

Manual configuration (any client)

stdio
{
  "mcpServers": {
    "opentusk": {
      "command": "tuskd",
      "args": ["mcp", "--agent", "hermes-dev"]
    }
  }
}
streamable http
{
  "url": "http://127.0.0.1:7477/mcp",
  "headers": { "Authorization": "Bearer tusk_…your token…" }
}

For Claude Code specifically, the CLI route also works: claude mcp add tuskd -- tuskd mcp --agent hermes-dev, or claude mcp add --scope project … to share via .mcp.json in the repo.

Agents, scopes, grants, keys

Every agent is a principal: an ed25519 keypair, a bearer token, and explicit grants. The token and paste-ready MCP configs are printed exactly once at creation — store them then.

shell
tuskd agent create hermes-dev \
  --read project:demo,user \
  --write project:demo \
  --promote project:demo

tuskd agent grant hermes-dev read project:other
tuskd agent list
tuskd agent revoke old-agent
tuskd agent token rotate hermes-dev

Scopes

Every memory record lives in exactly one scope:

  • agent:<id> — the agent's private scope. Every agent implicitly holds read + write on its own scope.
  • project:<name> — shared team knowledge for one project.
  • user — knowledge about the human the agents work for.
  • org — organization-wide knowledge (promotions default to review).

Grants

Grants are explicit and per-scope, checked at one ACL choke-point on every tool call — no tool can touch the store without passing it:

  • read — search and fetch in the scope.
  • write — direct writes into the scope.
  • promote — submit candidates through the gate into the scope.

Patterns are exact scopes or a project:* wildcard prefix; wildcards expand against the scopes actually present in the index.

A useful pattern: give worker agents write only on their own scope and promote on the project scope — so everything shared has passed the gate — and give read-only observers (dashboards, review bots) just read.

Key custody

Each agent's ed25519 private signing key is stored by the daemon at .tusk/keyring/keys/<id>.pem (mode 0600, directory 0700). Keys are excluded from tuskd export along with the operator token. Pass --show-key at creation to print the key once and keep custody yourself; tuskd agent key path <id> shows where a stored key lives. The keys are reserved for future signed authentication and encrypted sync — bearer tokens do the authenticating today.

Lost a token?

Tokens are shown once and never stored in recoverable form. Rotate it: tuskd agent token rotate <id> prints a fresh token (and updates HTTP client configs written by agent setup --http). The agent's memories are untouched — scopes belong to the vault, not the credential.

The memory model

One record = one Markdown file with strict, flat frontmatter and a free-form body. Grep it, diff it, back it up, put it in git.

vault layout
vault/
├── memory/{agent/<id>, project/<id>, user, org}/*.md   # source of truth
├── skills/<scope-dashed>/<record-id>/SKILL.md          # materialized skills
└── .tusk/{index.db, keyring/, queue/review.json,
        tuskd.toml, lock, daemon.log, admin-token}      # derived + private state

Records

Frontmatter fields: id (ULID), type, scope, author, created_at, valid_at, invalid_at, supersedes?, entities[], tags[], trust (0..1), uses, successes, last_used?, and for skills version? and trigger?.

Record types:

  • episodic — what happened (runs, observations, events).
  • semantic — facts and knowledge.
  • procedural — how to do something; the raw material for skills.
  • skill — a graduated procedure with a trigger line and version; the body is a complete SKILL.md payload.
  • profile — standing information about a person or system.

Bitemporal: nothing is edited in place

Corrections never rewrite a body. A new record sets supersedes: <old-id>; the old record gets invalid_at = now and history stays on disk. as_of queries filter valid_at ≤ t < invalid_at — they answer "what did we believe then?", which matters when yesterday's run acted on yesterday's beliefs.

Search & ranking

The SQLite index (FTS5) is derived and rebuildable at any time with tuskd index rebuild — both daemon start and embedded open run an idempotent rebuild, so offline edits are always picked up. Hybrid ranking combines BM25 text relevance with usage telemetry:

ranking
score = -bm25 + success_rate + 0.3·ln(1 + uses) + 0.2·trust
# constants configurable under [ranking] in .tusk/tuskd.toml

So a record that keeps working its way into successful outcomes outranks a textually-similar one that doesn't — the vault learns which memories earn their keep.

The gate: nothing enters shared memory unchecked

Direct writes go to scopes you hold write on — usually your own. Getting knowledge into a shared scope goes through the gate (memory_promote, or memory_reflect for batches). Every submission runs four steps:

  1. Exact dedup — SHA-256 of the trimmed body against all valid records in the target scope; duplicates are rejected.
  2. Near-dup / contradiction probe — the candidate is searched against the target scope; if the top hit overlaps heavily (>0.7 token overlap) the submission is treated as a correction and auto-supersedes it, unless an explicit corrects was given.
  3. Policy — per-scope, auto or review. Defaults: project:* commits automatically, org goes to review, and skills always go to review. Review items persist in the queue with a qid.
  4. Commit — write the record, invalidate anything superseded, ingest to the index. tuskd review approve <qid> runs the same commit later.

The result: agents can't silently overwrite what the team knows, and everything in a shared scope has a traceable path in.

shell
tuskd review list
tuskd review approve <qid>
tuskd review reject <qid>

Feedback closes the loop: memory_feedback records success / failure / partial per use, feeding both ranking and graduation.

Skills: experience, packaged

Procedures that keep working graduate into skills. The scanner (tuskd graduate, plus a daemon timer that runs daily) looks for valid procedural records with uses ≥ 5 and a success rate ≥ 0.8 (thresholds configurable under [graduation]), wraps the body into a skill candidate with a trigger line and provenance, and submits it through the gate — skills always land in review, so a human approves every graduation.

Approving a skill materializes it at skills/<scope>/<id>/SKILL.md with name and description frontmatter — the format agent frameworks load natively. Agents discover them with the skill_list tool, which returns triggers and telemetry across entitled scopes.

shell
tuskd graduate      # run the scanner now; candidates land in review
tuskd review list

MCP tools

ToolWhat it does
memory_writeWrite a record to your own or a write-granted scope (default agent:<id>); supports supersedes.
memory_searchHybrid search over entitled scopes; scopes, type, tags, as_of, k ≤ 50; wildcard grants expand against scopes present in the index.
memory_getFetch by id (read grant enforced).
memory_promoteSubmit a candidate through the gate into target_scope (needs promote or write grant); supports corrects.
memory_reflectBatch of typed candidates (facts / procedures / corrections), each individually gated — the primary loop entry point.
memory_feedbackid + outcome success / failure / partial; increments uses, adds 1 / 0 / 0.5 to successes, sets last_used, re-ingests to the index.
memory_forgetHard delete (author or own-scope records only).
skill_listSkills across entitled scopes with trigger + telemetry.
memory_statusYour grants, index stats, review-queue depth.

ACL failures come back as MCP tool errors with a DENIED: <reason> message; everything else returns structured JSON.

CLI

The CLI works alongside a running daemon — commands route through it over the Unix socket, never a second writer. With no daemon running, commands run the core embedded under the same lock.

tuskd --help
tuskd init | start [-d] | stop | restart [-d] | status
tuskd mcp --agent <id>
tuskd agent create <id> [--read s,s] [--write s,s] [--promote s,s] [--show-key]
tuskd agent grant <id> <read|write|promote> <scope> | revoke <id> | list
tuskd agent setup <client> [--agent <id>] [--http] [--print] [--remove] [--yes]
                  # client: claude-code | claude-desktop | cursor | codex
                  #         | vscode | print | list
tuskd agent token rotate <id>
tuskd agent key path <id>
tuskd index [rebuild] | search "<q>" [--scope --as-of --k]
tuskd review list | approve <qid> | reject <qid>
tuskd graduate
tuskd export <archive.tar.gz> | import <archive>
tuskd dashboard [--no-open]

Web dashboard

While the daemon runs it serves an operator dashboard at /ui on the same loopback listener as /mcp: overview (index stats, review-queue depth, uptime), search with point-in-time as of queries, a memories browser (filter / inspect / forget), review-queue approve/reject, agent management, and housekeeping (index rebuild, graduation scan, vault export download, effective config).

Auth is a per-run operator token (tuskop_…) minted at daemon start and written owner-only to .tusk/admin-token. tuskd dashboard prints the tokenized URL after checking the daemon is alive (--no-open to just print). The token travels in the Authorization header — no cookies — and agent tokens don't work on /api/*. Every dashboard action goes through the same admin plane as the CLI: the dashboard cannot bypass scopes, gates, or the single-writer rule.

shell
tuskd dashboard

Configuration

Config lives at .tusk/tuskd.toml inside the vault. Vault resolution order: --vault flag → $OPENTUSK_VAULT → current directory.

SettingDefaultMeaning
http_port7477Local MCP + dashboard HTTP port (binds 127.0.0.1 only).
uds.tusk/tuskd.sockUnix socket for stdio proxying and CLI routing.
[policies]project:* auto, org reviewPer-scope gate policy; skills always review.
[graduation]uses ≥ 5, rate ≥ 0.8Thresholds for promoting procedures to skills.
[ranking]see rankingWeights for hybrid search ranking.
$OPENTUSK_VAULTEnvironment override for the vault path.

Operations

Daemon lifecycle

shell
tuskd start -d      # detached; log at .tusk/daemon.log
tuskd status
tuskd stop          # graceful shutdown over the admin socket — no signals, no PID files
tuskd restart -d    # stop + start; the upgrade command

Backup, export, moving machines

Back up the vault by copying the directory, or use tuskd export archive.tar.gz / tuskd import archive.tar.gz. Exports exclude private key material and the operator token by design. v0 is deliberately single-machine — move knowledge with export/import, and don't put a live vault on a network filesystem (advisory locks and file-watchers are unreliable there).

Index maintenance

The index is always rebuildable: tuskd index rebuild wipes and re-walks the vault idempotently. Both daemon start and embedded open run a rebuild automatically, so hand-edits made while nothing was running are always picked up.

FAQ

Why exactly one daemon per vault?

SQLite gets one writer; so does the vault. tuskd enforces this with an advisory flock on .tusk/lock that dies with the process — stale locks can't wedge the vault. Everything else (stdio sessions, CLI commands, the dashboard) routes through the owner over its Unix socket, so you get concurrency without corruption.

I lost an agent's token. Now what?

Rotate it: tuskd agent token rotate <id> prints a fresh token once. The agent's memories stay — scopes belong to the vault, not the credential.

Where does my data actually live?

In the vault directory you initialized: one Markdown file per record under memory/, skills under skills/, and derived state under .tusk/. Nothing leaves your machine — tuskd binds to localhost only and makes no outbound network calls.

I edited vault files by hand while nothing was running. Is the index stale?

No — both daemon start and embedded open run an idempotent index rebuild, so offline edits are always picked up. You can also force it any time with tuskd index rebuild.

Can two machines share a vault?

v0 is deliberately single-machine. Move knowledge between machines with tuskd export archive.tar.gz and tuskd import archive.tar.gz. Don't put a live vault on a network filesystem — advisory locks and file-watchers are unreliable there.

macOS says the binary "cannot be verified"?

Releases aren't codesigned yet. Installs via curl … | sh aren't quarantined, so this only appears if you downloaded a tarball with a browser — clear it with xattr -d com.apple.quarantine ~/.local/bin/tuskd. Signing and notarization are on the release roadmap.