LLM Wiki
Inspired by Karpathy's LLM OS wiki concept — persistent knowledge that compounds over time. Humans curate sources, agents maintain the structure.
Writestead gives you:
- Structured wiki: frontmatter, wikilinks, page types (source, entity, concept, analysis)
- Raw ingest: add local files or URLs, extract text from PDF/DOCX/PPTX/images via liteparse
- MCP server: expose wiki tools to any MCP client
- Obsidian sync: headless Obsidian Sync via
obCLI - Lint: detect orphans, broken links, stale logs, duplicate titles, missing frontmatter
# npm (macOS, Linux, WSL)
npm i -g @ahkohd/writestead
# homebrew (macOS, Linux)
brew install ahkohd/writestead/writestead
# cargo
cargo install writestead --locked --force
# verify
writestead --versionOptional tools (install any you need, writestead doctor checks availability):
lit— PDF/DOCX/PPTX/image text extractionpoppler-utils— PDF utilitiesrg,fd— faster search/listingob— headless Obsidian Sync
writestead init --vault-path ~/Documents/writestead --sync-backend obsidian
writestead doctor
writestead startIf you already have a vault syncing via Obsidian Sync, set up sync before init so existing files are preserved:
# 1. login and link to remote vault
ob login
ob sync-list-remote
ob sync-setup --path ~/Documents/writestead --vault <vault-id>
ob sync --path ~/Documents/writestead
# 2. init without --force (skips files that already exist)
writestead init --vault-path ~/Documents/writestead --sync-backend obsidian
# 3. start
writestead doctor
writestead startdocker run -d \
-v writestead-vault:/vault \
--name writestead \
ghcr.io/ahkohd/writestead:latest
# setup sync inside container
docker exec -it writestead bash
ob login
ob sync-list-remote
ob sync-setup --path /vault --vault <vault-id>
ob sync --path /vault
writestead init --vault-path /vault --sync-backend obsidian
exit
# restart to pick up synced vault
docker restart writesteadwritestead init— create vault structure and configwritestead start/stop/status— daemon lifecycle (start --foregroundfor attached mode,status --jsonfor machine output)writestead doctor— health checks for vault, sync, extractors, accelerators (--jsonfor structured output)writestead sync— run sync backendwritestead help-wiki— print workflow guide and conventions
writestead read <path>— read wiki page with line paginationwritestead search <query>— case-insensitive content searchwritestead edit <path> --old-text ... --new-text ... --log-action ... --log-description ...— exact-match replacementwritestead write <path> --content-file ... --log-action ... --log-description ...— write full pagewritestead list— list wiki pages with paginationwritestead lint— run structural lint checkswritestead lint --fix— apply safe lint fixeswritestead lint --fix --dry-run— preview safe lint fixeswritestead index— read wiki/index.md
writestead raw add <source>— add local file or URL to raw/ (--name,--force)writestead raw list— list raw source files with paginationwritestead raw read <path>— extract text from raw source with pagination
writestead config path/show/get <key>/set <key> <value>/unset <key>
The CLI talks to a local HTTP daemon (default: http://127.0.0.1:8765).
Endpoints:
GET /healthGET /metrics(Prometheus format)POST /mcp(MCP over HTTP JSON-RPC)GET /mcp(returns 405)DELETE /mcp(terminate MCP session)
Configure bind address with config keys (host, port) or env (WRITESTEAD_HOST, WRITESTEAD_PORT).
POST /mcp exposes the writestead MCP server. Tools are discoverable via tools/list:
| Tool | Description |
|---|---|
wiki_read |
Read wiki page (1-indexed line pagination) |
wiki_search |
Case-insensitive content search |
wiki_edit |
Exact oldText/newText replacement with log |
wiki_write |
Write full page with log |
wiki_list |
List pages (0-indexed item pagination) |
wiki_lint |
Validate vault structure, frontmatter, links, orphans, and stale logs |
wiki_index |
Read wiki/index.md |
wiki_sync |
Run sync backend |
wiki_help |
Print workflow guide |
raw_list |
List raw source files (0-indexed pagination) |
raw_read |
Extract text from raw source (1-indexed line pagination) |
raw_upload |
Add source via url, path, or base64 content |
MCP clients receive workflow instructions automatically on initialize.
Local no-auth:
writestead:
url: http://127.0.0.1:8765/mcp
tools:
resources: false
prompts: falseBearer auth:
writestead:
url: http://127.0.0.1:8765/mcp
headers:
Authorization: Bearer ${WRITESTEAD_BEARER_TOKEN}
tools:
resources: false
prompts: falseDefault path: ~/.config/writestead/config.json (or $XDG_CONFIG_HOME/writestead/config.json).
{
"name": "writestead",
"vault_path": "~/Documents/writestead",
"host": "127.0.0.1",
"port": 8765,
"sync": { "backend": "obsidian" },
"mcp": { "auth": { "mode": "none" }, "session_ttl_seconds": 3600 },
"search": { "backend": "auto" },
"raw": { "upload_max_bytes": 52428800, "url_timeout_seconds": 30 }
}name— vault display namevault_path— path to vault roothost— daemon bind address (default:127.0.0.1)port— daemon port (default:8765)sync.backend—obsidian|none(default:obsidian)mcp.auth.mode—none|bearer(default:none)mcp.session_ttl_seconds— session expiry (default:3600)search.backend—auto|builtin|rg-fd(default:auto)raw.upload_max_bytes— upload size cap (default:52428800)raw.url_timeout_seconds— URL download timeout (default:30)raw.pdf_liteparse_max_pages— max PDF pages routed to liteparse (default:30)raw.pdf_liteparse_timeout_ms— liteparse timeout (default:60000)raw.pdf_liteparse_mem_limit_mb— liteparse memory cap (default:4096)
WRITESTEAD_CONFIG_FILE— config file path overrideWRITESTEAD_RUNTIME_DIR— runtime directory overrideWRITESTEAD_PID_FILE— PID file path overrideWRITESTEAD_LOG_FILE— log file path overrideWRITESTEAD_BEARER_TOKEN— bearer token (required whenmcp.auth.mode=bearer)WRITESTEAD_MCP_AUTH_MODE— auth mode override
Token is env-only. Setting mcp.auth.bearer_token in config is blocked by design.
writestead config set mcp.auth.mode bearer
export WRITESTEAD_BEARER_TOKEN='your-token'
writestead startraw adddetects mode by prefix:http:///https://downloads, otherwise copies local fileraw readsupports:.md/.txt/.json/.yaml/.csv/.html/.xml/.rst/.tex/.log— direct text read.pdf—lit parseorpdftotextby size.docx/.pptx/.xlsx—lit parse- images (
.png/.jpg/.tiff/.webp) —lit parsewith OCR - unknown types rejected
raw upload(MCP) accepts exactly one of:url,path(vault-relative), orcontent(base64)raw/assets/is excluded from listing and reading (deferred)- PDF page windows:
writestead raw read manual.pdf --page-start 1 --page-end 20
When search.backend=auto (default), writestead uses rg and fd if found in PATH, falling back to built-in search. Set search.backend=rg-fd to require them.
# install (arch)
pacman -S ripgrep fd
# install (macOS)
brew install ripgrep fd
# verify
writestead doctorwiki_read/raw_read/writestead read: offset is 1-indexed (line number)wiki_list/raw_list/writestead list: offset is 0-indexed (item index)- All paginated responses include:
offset,limit,total(ortotal_lines),has_more
obsidian(default): runsob sync --path <vault_path>— headless Obsidian Syncnone: explicit no-op
GET /metrics exports Prometheus counters and gauges:
writestead_uptime_seconds
writestead_mcp_sessions_active
writestead_mcp_requests_total
writestead_mcp_tool_calls_total
writestead_mcp_tool_calls_by_tool_total{tool="..."}
writestead_mcp_tool_errors_total
writestead_mcp_tool_errors_by_tool_total{tool="..."}
writestead_raw_uploads_total
writestead_raw_upload_bytes_total
writestead_raw_reads_total
writestead_raw_reads_by_format_total{format="..."}
writestead_sync_runs_total{trigger="..."}
writestead_sync_errors_total{trigger="..."}
writestead_sync_duration_seconds_sum
writestead_sync_duration_seconds_count
Alert suggestions:
- Tool error spike:
increase(writestead_mcp_tool_errors_total[5m]) > 10 - Per-tool regressions: watch
writestead_mcp_tool_errors_by_tool_total{tool=...} - Upload pressure: sustained growth in
writestead_raw_upload_bytes_total
- Run
writestead doctorfirst - If daemon won't start, check
writestead statusand~/.config/writestead/writestead.log - If MCP auth fails, verify
WRITESTEAD_BEARER_TOKENis set andmcp.auth.mode=bearer - If raw reads fail for PDF/DOCX, install
lit(npm i -g @llamaindex/liteparse) - For large PDFs, install
poppler-utils - If search is slow on large vaults, install
rgandfd