Tusk
БесплатноНе проверенLocal-first agent brain — a markdown vault indexed as a schema-validated, semantically-searchable graph, queryable from the CLI or any MCP-compatible agent.
Описание
Local-first agent brain — a markdown vault indexed as a schema-validated, semantically-searchable graph, queryable from the CLI or any MCP-compatible agent.
README
A local-first agent brain. Tusk turns a directory of markdown files into a schema-validated, semantically-indexed graph — queryable from the CLI and from any MCP-compatible agent (Claude Code, Cursor, etc.).
Files are the source of truth. Git is the history. Tusk is the indexer and the retrieval engine.
markdown vault ──▶ tusk indexer ──▶ SQLite graph + embeddings
│
├─▶ CLI (tusk query, tusk node …)
└─▶ MCP tools (tusk_query, tusk_node_create, …)
- Local first. No service to log in to. The index lives in
.tusk/next to your files. - Schema-validated. Node and edge types are declared in
tusk.toml. Off-schema content is warned, never rejected. - Structural + semantic. A compact filter grammar for the graph (
key=value/key:value, ranges, edge traversal, boolean composition), Ollama-backed embeddings for similarity, and a hybrid mode that filters then ranks. - External edits are first-class. Vim, Obsidian, an LLM piping markdown — they all work; the watcher keeps the index live.
- One engine, two surfaces. Every read/write graph verb has a 1:1 MCP tool; workspace bootstrap (
tusk init) and the web app (tusk web, graph + reading views) stay CLI-only.
Installation
Prerequisites
- (Optional, for semantic search) Ollama running locally with an embedding model, e.g.
ollama pull nomic-embed-text.
One-liner (prebuilt binary)
curl -fsSL https://raw.githubusercontent.com/germanamz/tusk/main/install.sh | sh
Detects your OS/arch, downloads the latest GitHub release, drops the tusk binary into ~/.local/bin (override with INSTALL_DIR=/usr/local/bin), and installs its man pages into ~/.local/share/man (override with MAN_DIR; man tusk works once that dir is on your MANPATH). Pin a specific release with TUSK_VERSION=v1.1.0. Prebuilt archives ship for darwin/linux/windows on amd64 + arm64.
From source
Requires Go 1.26+.
git clone https://github.com/germanamz/tusk
cd tusk
make build
# binary at ./bin/tusk — move it onto your PATH
install bin/tusk /usr/local/bin/tusk
Or, without cloning:
go install github.com/germanamz/tusk/cmd/tusk@latest
Verify:
tusk --version
Updating
If you installed a prebuilt binary, tusk update replaces it in place. Your workspace and .tusk/ index are untouched.
tusk update # latest release
tusk update v1.2.0 # pin a version (or roll back to one)
tusk update --check # report what's available, change nothing
The archive is verified against the release checksums before anything is extracted, and the previous binary is kept aside until the replacement is in place, so a failed swap rolls back rather than leaving you with no binary. Man pages are refreshed alongside it.
A binary managed by Homebrew, installed with go install, or built from source is owned by that tool, so tusk update refuses it and prints the right command instead (--force overrides):
# From source
cd tusk && git pull && make build && install bin/tusk /usr/local/bin/tusk
# go install
go install github.com/germanamz/tusk/cmd/tusk@latest
Re-running the install one-liner also still works, and remains the way to bootstrap a machine that has no tusk yet:
curl -fsSL https://raw.githubusercontent.com/germanamz/tusk/main/install.sh | sh
curl -fsSL https://raw.githubusercontent.com/germanamz/tusk/main/install.sh | TUSK_VERSION=v1.2.0 sh
After a major upgrade, run tusk reindex to pick up any indexer changes, then tusk doctor to confirm the workspace is healthy.
Quickstart
For per-command reference (flags, examples), see docs/cli/. For multi-command recipes, see docs/cli/workflows.md. Man pages are in man/ —
man -M man tuskafter cloning.
# 1. Initialize a workspace in the current directory
mkdir my-brain && cd my-brain
tusk init --name my-brain
# 2. Add a built-in type pack so you have some node types
tusk pack add vault # note, meeting, decision + references/relates-to edges
tusk pack add tags # tag node type + tagged edge
tusk pack add kanban # ticket node type + workflow + parent/blocks edges
# 3. Create a node
tusk node create --path notes/hello.md --type note --title "Hello, Tusk"
# 4. Build the index (also runs after every CLI write)
tusk reindex
# 5. Query the graph
tusk node list 'type=note'
tusk query 'type=ticket status=active' --sort '+priority,-due'
# 6. Get a quick health check
tusk status
tusk doctor
How information is structured
A Tusk workspace is just a directory:
my-brain/
├── tusk.toml # workspace manifest (committed)
├── .tusk/ # gitignored — local SQLite index
│ └── tusk.db
├── .gitignore
├── notes/
│ └── auth-rfc.md
├── tickets/
│ └── fix-login-bug.md
└── tags/
└── auth.md
Nodes are markdown files
Every .md file with a type: field in YAML frontmatter is a node. The file path (minus the extension) is the canonical node id — no separate id field.
---
type: ticket
title: Fix login bug
status: active
priority: high
due: 2026-05-15
parent: tickets/auth-epic
blocks: [tickets/refactor-storage]
tags: [auth, security]
---
# Fix login bug
The bug occurs when users with SSO accounts hit the password reset flow.
See [[notes/auth-rfc]] for context.
typeis the only universally reserved key.- Other frontmatter keys are either properties (string / int / date / enum / ref / list-of) or edges (declared in
tusk.toml). [[notes/auth-rfc]]body wikilinks materialize as edges to that node id for any edge type declared withwikilinks = true(e.g. thevaultpack'sreferencesedge). The Obsidian aliased form[[notes/auth-rfc|the auth RFC]]links to the same node id — the text after|is display only — and atusk node moveretargets the id while keeping the display text.
Edges connect nodes
Edges are typed, declared in the manifest, and can be created two ways:
- Frontmatter — the natural place.
parent: tickets/auth-epicdeclares aparentedge. - CLI / MCP —
tusk edge add --type blocks --source tickets/a --target tickets/b.
Edge declarations enforce legality (from/to types), cardinality, ordering, and optional acyclic = true (cycles are rejected at write time).
The manifest defines the schema
tusk.toml is the contract between you and the engine. A minimal manifest:
[workspace]
name = "my-brain"
ignore = ["bin/", "node_modules/", "*.test"]
[embeddings]
provider = "ollama"
endpoint = "http://localhost:11434"
model = "nomic-embed-text"
dim = 768
[node-types.note]
description = "A free-form markdown note"
properties = []
[node-types.decision]
description = "A captured decision"
properties = [
{ name = "decided-at", type = "date", required = true },
{ name = "status", type = "enum", values = ["proposed", "accepted", "rejected", "superseded"] },
{ name = "supersedes", type = "ref", to = "decision" },
]
[edge-types.references]
description = "Implicit edge materialized from body wikilinks"
from = ["*"]
to = ["*"]
cardinality = "many-to-many"
inverse = "referenced-by"
ref properties auto-materialize edge types of the same name — declaring supersedes as a ref to decision gives you a supersedes edge for free.
Edited tusk.toml while a daemon is running? tusk reload (or the tusk_reload MCP tool) re-reads and validates the manifest, hot-swaps the schema in place — no restart — and converges any sibling daemons via the .tusk/manifest-epoch sentinel. It then reindexes to re-validate your content against the new schema. Validation matches startup, so a reload lands the same state a restart would.
Type packs (built-in templates)
Instead of declaring everything by hand, tusk pack add <name> splices a curated TOML block into your manifest:
| Pack | Adds |
|---|---|
vault |
note, meeting, decision; references (wikilinks) + relates-to |
tags |
tag node + tagged edge (with tags: [a, b] frontmatter shorthand) |
kanban |
ticket node with workflow-validated status; parent (WBS) + blocks edges |
dev |
spec, plan, handoff, package — dogfooding pack for tracking software projects |
Packs compose: add vault + tags + kanban and you have notes, decisions, tags, and a kanban workflow on top.
tusk pack add vault
tusk pack add tags
tusk pack add kanban
You can also load a pack from a URL or local file:
tusk pack add https://example.com/packs/research.toml
tusk pack add file://$PWD/my-pack.toml
Indexing
The index lives in .tusk/tusk.db (SQLite + WAL). It is derived state — delete .tusk/ and tusk reindex rebuilds it identically.
One-shot reindex
tusk reindex
# Reindex done: 142 indexed, 0 removed, 3 skipped
reindex walks the workspace, parses every .md, validates frontmatter against the manifest, resolves refs + wikilinks into edges, and enqueues embeddings.
Live watcher
tusk watch
Runs fsnotify against the workspace and applies edits incrementally. Drains the embed queue in the background.
What gets indexed
- Every
.mdfile with atype:field, anywhere in the workspace. - Every
.html/.htmfile with a<meta name="tusk:type">tag — indexed over its prose (tags stripped, entities decoded), with<meta name="tusk:*">becoming typed node properties anddata-*attributes captured as lenient signals under the reserveddatakey. - Filtered through
.gitignore+[workspace] ignorepatterns. .tusk/and.git/are always ignored.
Off-schema content is warned, not rejected — a file with an unknown type: or a property violation still gets indexed (so it stays queryable) and surfaces in tusk doctor.
Querying
Structural filter
A compact filter grammar that compiles to parameterized SQL. Property
comparisons accept = or : interchangeably; the rest of the grammar uses
the operators below.
# Property predicates (`=` and `:` are equivalent)
tusk query 'type=ticket status=active priority=high'
tusk query 'type:plan shipped-at>=2026-04-01' # date ordering, chronological
# Edge traversal: -> outgoing, <- incoming
tusk query 'type=ticket blocks->type=ticket' # tickets that block other tickets
tusk query 'type=note <-references type=spec' # notes referenced by specs
# Multi-hop
tusk query 'type=ticket parent->parent->title="auth-epic"'
# Sort + pagination
tusk query 'type=ticket status=active' --sort '+priority,-due' --take 10
Semantic search
Requires [embeddings] configured (Ollama by default). Embedding runs asynchronously after writes; until a node is embedded, it's invisible to semantic queries (and surfaces in tusk doctor).
tusk query 'type=*' --semantic "auth bug in password reset flow" --take 5
Hybrid (recommended for agents)
Structural filter narrows the candidate set; semantic similarity ranks within it.
tusk query 'type=ticket status=active' --semantic "login flow" --take 10
JSON output
Add --json to any query for structured output that's easy to pipe into an LLM or a script.
tusk query 'type=decision' --semantic "storage backend" --top 3 --json
MCP server (Claude Code, Cursor, …)
tusk mcp runs an MCP server backed by the same indexing engine as the CLI, exposing the graph verbs as tools. Agents should prefer these tools over shelling out to tusk — they run in the warm daemon with the index already open. Stdio is the default transport; SSE is available on a port.
tusk mcp # stdio (for Claude Code / Cursor / Codex)
tusk mcp --transport sse --addr :8765
It holds the workspace open for the lifetime of the session: a single SQLite handle, an embed-queue drainer, and an fsnotify watcher all live in the same process so the index stays warm across tool calls.
Wiring it into Claude Code
claude mcp add tusk -- /usr/local/bin/tusk mcp
Or directly in ~/.claude.json:
{
"mcpServers": {
"tusk": {
"command": "/usr/local/bin/tusk",
"args": ["mcp"],
"cwd": "/path/to/my-brain"
}
}
}
Available MCP tools
| Tool | What it does |
|---|---|
tusk_status |
node counts by type, edge count, queue depth, last reindex |
tusk_doctor |
validation warnings, dangling refs, embed-queue retries |
tusk_node_get / tusk_node_list |
read by id or filter |
tusk_node_render |
render a node's content as plain text (HTML tags / markdown markup stripped) |
tusk_node_create / tusk_node_modify / tusk_node_move / tusk_node_delete |
write |
tusk_edge_add / tusk_edge_remove / tusk_edge_list |
edge CRUD |
tusk_query |
structural + optional semantic ranking |
tusk_context |
composed warm-context digest (pinned nodes, recent activity, aliases) |
tusk_run |
invoke a manifest-declared alias by name |
tusk_reindex |
force a full walk |
tusk_reload |
hot-reload tusk.toml: validate + swap the schema, no restart |
tusk_reset |
drop and rebuild the index from files (confirm: true) |
tusk_pack_add |
merge a built-in type pack's node/edge types into tusk.toml and hot-reload the schema |
Workspace bootstrap (tusk init) and the web app (tusk web, graph + reading views) stay CLI-only.
Health and diagnostics
tusk status # node counts, edge count, embed-queue depth, last reindex timestamp
tusk doctor # validation warnings, dangling refs/wikilinks, embed-queue errors, embed stats
doctor is the place to look when:
- semantic queries seem to be missing nodes → check the embed-queue depth and last error
- a wikilink points to nothing → dangling-ref warning surfaces it
- a manifest change just landed → re-validate every affected node
Architecture
Single Go binary, single SQLite index, single embedding provider (Ollama for now).
flowchart TD
workspace["Workspace<br/>(markdown + tusk.toml)"]
engine["Engine (cmd/tusk + internal/*)<br/>manifest · node · edge · reindex · filter · embed<br/>watcher · behaviors · mcp"]
db[".tusk/tusk.db<br/>(SQLite WAL, embeddings table)"]
workspace -->|"fs walk / fsnotify"| engine
engine -->|"reads / writes"| db
- Filesystem > index, always. The index is a cache; if it is stale, wedged, or corrupt, run
tusk reset(or thetusk_resetMCP tool withconfirm: true) to drop and rebuild it from your files. The markdown files are the source of truth, so nothing is lost. - Stateless across machines. Clone the vault, reindex, get an identical brain.
- Single-writer, many-readers. SQLite WAL + a workspace-wide advisory lock so
tusk mcpand one-shot CLI calls coexist.
Product vision and design principles live in PRODUCT.md. Per-package notes live in docs/packages/.
Development
make build # ./bin/tusk
make test # unit tests
make test-race # with race detector
make vet
make lint # golangci-lint
make fmt
See STYLE.md for the codebase conventions and CONTRIBUTING.md for how to propose changes.
License
Установка Tusk
У этого сервера нет опубликованного пакета — он собирается из исходников. Открой репозиторий и следуй инструкции в README.
▸ github.com/germanamz/tuskFAQ
Tusk MCP бесплатный?
Да, Tusk MCP бесплатный — установка в пару кликов через Unyly без оплаты.
Нужен ли API-ключ для Tusk?
Нет, Tusk работает без API-ключей и переменных окружения.
Tusk — hosted или self-hosted?
Self-hosted: сервер запускается локально на твоей машине командой из раздела установки.
Как установить Tusk в Claude Desktop, Claude Code или Cursor?
Открой Tusk на unyly.org, выбери вкладку своего клиента (Claude Desktop, Claude Code, Cursor) и нажми Install — конфиг сгенерируется автоматически, без правки JSON.
Похожие MCP
wenb1n-dev/SmartDB_MCP
A universal database MCP server supporting simultaneous connections to multiple databases. It provides tools for database operations, health analysis, SQL optim
автор: wenb1n-devPostgres Server
This server enables interaction with PostgreSQL databases through the Model Context Protocol, optimized for the AWS Bedrock AgentCore Runtime. It provides tools
автор: madhurprashPostgres
Query your database in natural language
автор: AnthropicPostgreSQL
Read-only database access with schema inspection.
автор: modelcontextprotocolCompare Tusk with
Не уверен что выбрать?
Найди свой стек за 60 секунд
Автор?
Embed-бейдж для README
Похожее
Все в категории data
