Skip to main content

local-wiki

Local-first agent wiki with caller-side AI conflict resolution. Stores knowledge as plain Markdown on your local filesystem, safe for multi-profile / multi-session concurrent writes via a pending-queue commit model — no torn writes, no silent overwrites.

Built as a standard MCP server, so it works with any MCP client (Hermes, Claude, Cursor, Codex, OpenCode...). Same code on any machine: pip install local-wiki or uvx local-wiki.

Version 1.3.0 — production release. Runs from PyPI (uvx local-wiki / uv tool install local-wiki), as a standalone executable (see Executable release), as a one-shot CLI, or as a global HTTP service.

Why

  • Skills should hold experience (how); knowledge (what) belongs in a wiki — this is the knowledge store for that.
  • Naive file writes / single-writer tools have no concurrency control → multi-agent writes corrupt pages.
  • This project fixes it with a pending queue: writes are staged (one file per draft in pending/), a single committer serializes them under an OS lock, and conflicts are resolved by the caller (the agent) in its own conversation — the server itself never calls an LLM.

Design: AI resolution lives in the caller's conversation

The committer is intentionally dumb and deterministic (lock → hash check → atomic write → index update). It does not call any LLM:

  1. wiki_add creates new pages directly (target missing) or stages a draft into pending/<id>.json (recording the base-hash of the version the writer read).
  2. wiki_commit drains the queue under a single LockFileEx/flock lock (FIFO by file mtime).
    • No drift → commit directly.
    • Drift (someone else committed meanwhile) → write a conflict record pending/<id>.conflict.json (current + draft full text), keep the draft queued.
  3. The caller sees the conflict (wiki_conflicts returns both versions), merges them in its own conversation (the agent is the AI), and submits the result:
    • wiki_resolve(conflict_id, side='merged', merged_content=...) → apply your merge.
    • or side='draft' / side='current'.
  4. Resolution removes the draft from the queue; wiki_commit can then drain the rest.

Result: zero LLM/key dependencies in the server, fully offline, and semantic merge quality comes from the caller's model — not from a hardcoded prompt.

Install

pip install local-wiki          # or
uvx local-wiki --help           # runs the latest PyPI release

Run (MCP server)

stdio (default)

local-wiki --wiki-root <dir>

HTTP (Streamable)

local-wiki serve --wiki-root <dir> --host 127.0.0.1 --port 8300   # 推荐(v1.3.0 子命令)
# 或旧形式(等价):
local-wiki --wiki-root <dir> --host 127.0.0.1 --port 8300

默认端点为 http://127.0.0.1:8300/mcp。--host 127.0.0.1 仅本机回环——MCP 无鉴权,勿绑 0.0.0.0 暴露公网。

Standalone executable (no Python required)

local-wiki.exe --wiki-root <dir>

Run (one-shot CLI — no service needed, v1.3.0)

Every wiki_* MCP tool has a CLI mirror. Each command builds its own Storage, prints a single JSON document (ensure_ascii=False), and exits — nothing stays running. --wiki-root defaults to the LOCAL_WIKI_ROOT env var.

LOCAL_WIKI_ROOT=C:/Users/Administrator/AppData/Local/hermes/wiki
local-wiki search nginx              # → {"count": N, "matches": [...]}
local-wiki list --json               # JSON always
local-wiki read global <rel>
local-wiki lint
local-wiki add "K8s 排障" --root global --keywords k8s,pod,集群 --content "..."
local-wiki commit                    # write commands honour the pending gate
local-wiki service status            # Windows service management
local-wiki update --check            # PyPI version check

Run (global HTTP service + lifecycle, v1.3.0)

uv tool install local-wiki                       # managed install (upgradeable)
local-wiki service install --start-now --wiki-root C:/Users/Administrator/AppData/Local/hermes/wiki
local-wiki service status                        # liveness = port + HTTP 200
local-wiki update --check                        # compare against PyPI
local-wiki update --wiki-root ...                # upgrade + restart service

service / update 命令速查

local-wiki service install [--start-now] [--host 127.0.0.1] [--port 8300] [--wiki-root <root>]
local-wiki service start   [--host] [--port] [--wiki-root <root>]   # 立即拉起(端口探测就绪)
local-wiki service stop    [--host] [--port]                        # 按端口找 PID 终止
local-wiki service status  [--host] [--port]                        # 判活: schtasks 注册 + 端口/HTTP + serverInfo 版本
local-wiki service uninstall [--host] [--port]                      # 删任务/进程/文件
local-wiki update [--check] [--no-restart] [--host] [--port] [--wiki-root <root>]

并发与索引新鲜度

  • 单进程收敛:所有档案经 url 连同一个 HTTP 进程 → 单一 Storage 内存索引,多档案无内存索引竞争;写路径仍由 pending 队列 + OS 锁(LockFileEx)串行(MCP 库对 sync 工具用线程池执行,锁覆盖真多线程并发)。
  • 索引自愈(ensure_fresh):CLI/外部进程直写磁盘后,服务端读路径按磁盘指纹(目录 mtime)自动重载索引——无需重启服务即可查到刚写的内容。

Wire into clients

Hermes (config.yaml) — standard PyPI run, no local source build

Single global HTTP service (recommended for multi-profile sharing):

mcp_servers:
  wiki:
    url: "http://127.0.0.1:8300/mcp"   # Streamable HTTP, single global process

Or per-profile stdio:

mcp_servers:
  wiki:
    command: uvx
    args: ["local-wiki", "--wiki-root", "C:/Users/Administrator/AppData/Local/hermes/wiki"]

Hermes historically used uvx --from <local-src-path> (build from source each launch). Since 1.0.0 the canonical setup is the PyPI package above; the standalone .exe can be pointed to directly with command: <path>/local-wiki.exe. The local-wiki dev setup in this repo (local-wiki-dev profile) instead runs the editable venv via python -m mcp_server_wiki (see "OpenCode / Hermes local-wiki-dev" above) — source edits take effect on MCP restart, no PyPI/PyInstaller needed.

Claude Code

claude mcp add local-wiki -- uvx local-wiki --wiki-root ~/wiki

OpenCode / Hermes local-wiki-dev (this repo's dev setup)

The dev wiki MCP runs from the editable venv via python -m mcp_server_wiki (the reload switched off the local-wiki.exe launcher to the source module):

E:/wyd_work/local-wiki/.venv/Scripts/python.exe -m mcp_server_wiki --wiki-root E:/wyd_work/local-wiki/dev-wiki

The venv is an editable install of this repo, so source edits take effect after restarting the MCP server process — no PyInstaller rebuild needed.

MCP Tools (13)

Tool Description
wiki_add(title, keywords=[], content="", root="") Unified knowledge write entry. Routes by title match → root-keyword intersection ≥5 → page-keyword intersection ≥5 → else {error}. New page = direct create; existing = staged to pending. Replaces the old wiki_write.
wiki_delete(title, root="") Delete a page by normalized title; staged to pending.
wiki_commit(once=False) Serialize pending queue → commit; on drift write conflict record, keep draft queued.
wiki_conflicts() List open conflicts with full current+draft content for in-dialogue merge.
wiki_resolve(conflict_id, side, merged_content="") Apply draft / keep current / apply caller's merged content.
wiki_lint() Health check: index completeness, orphans, dead links, queue backlog, open conflicts, body length.
wiki_index(profile, action="read", rel="", keywords=[]) Maintain root-level keywords only. action='update' with rel omitted sets the root's category keywords; per-file keywords are no longer supported.
wiki_search(query, profile="") Search the in-memory index by rel/title/keywords (case-insensitive).
wiki_read(profile, rel) Read a page's full content (markdown with frontmatter).
wiki_list(profile="") List page index entries (rel/title/updated/words/hash).
wiki_create_root(key, name, workdir="", type="project", owner_profile="") Register a new wiki root (project shard) + its index.json.
wiki_update_root(key, name="", workdir=None, owner_profile="", type="") Update a root's meta.
wiki_delete_root(key, purge=False) Unregister a root (purge=True deletes its folder, irreversible).

Note: wiki_write was removed in the refactor — use wiki_add. profile (read/search/list) and root (write) are the same thing: the root key (e.g. global, smart_park).

Storage Layout (current)

<wiki-root>/
├── .wiki/
│   ├── global/                    # cross-project knowledge (a normal root)
│   │   ├── <rel>.md               #   pages directly here (NO pages/ subdir)
│   │   └── index.json             #   {version, meta, keywords:[...]}
│   └── <root>/                    # one folder per registered root
│       ├── <rel>.md
│       └── index.json
└── .pending/                      # flat draft queue — one file per change
    ├── <id>.json                  #   draft record (target_path, op, content, base_hash)
    ├── <id>.conflict.json         #   conflict record (current + draft)
    └── .lock                      #   OS file lock (committer holds)
  • Roots are registered by the existence of .wiki/<dir>/index.json (directory name = root key). There is no top-level registry file.
  • global is just another root.
  • index.json per root holds {version, meta, keywords:[...]} where keywords is the root-level category list (a top-level array). Per-page keywords are NOT stored in index.json — they live only in each page's frontmatter.

Root naming — normalized to snake_case

Root folder names are normalized: camelCase, hyphens and whitespace all resolve to the same snake_case key.

  • smart-park / smartPark / SmartPark → smart_park
  • wiki_create_root accepts any spelling and stores the normalized key (must match [a-z0-9_]+ after normalization)
  • Passing an alias (e.g. smart-park) to read/write/search/update/delete works — it resolves to the canonical root

Data model

Pages

Every page is Markdown with an optional YAML frontmatter; one is auto-added if missing (title = first heading, keywords: []).

---
title: Docker 使用
keywords: [特有kw]
---
<正文 body>
  • Keywords: page frontmatter keywords are limited to a count (default 10; configurable via env WIKI_KEYWORDS_MAX). Root index.json keywords are category tags with no count limit. Invalid frontmatter is rejected on every write path.
  • Body length: must be < 10 000 tokens (DeepSeek-V4 official BPE tokenizer, offline, checked by wiki_lint).
  • Invalid frontmatter (starts with --- and has a closing marker but fails YAML) is rejected on every write path.

Write semantics (current)

  • wiki_add on a missing target → written directly (atomic), index updated.
  • wiki_add / wiki_delete on an existing target → staged into pending/, committed serially by wiki_commit.
  • wiki_add routing when the target is ambiguous: title match → root-keyword intersection ≥5 → page-keyword intersection ≥5 → otherwise returns {error, hint} (not written; the caller may create a new root with wiki_create_root).
  • Empty-root direct create (1.2.0): a root with no pages and no root-level keywords (brand-new wiki-root or freshly created root) falls back to direct create instead of erroring — pointing --wiki-root at an empty directory and writing your first page just works, no setup step required. Non-empty roots keep the strict routing above (no silent misrouting).
  • All write paths normalize content through one validator (_normalize_content): frontmatter validity, keyword count/length, auto-add missing frontmatter — including wiki_resolve merged/draft, so bad content can never enter the wiki through conflict resolution.

Concurrency

  • Cross-root: physical sharding (.wiki/<root>/) — different files, no collision.
  • Same-root, multi-session: single committer + OS file lock serializes the queue.
  • Atomic writes: temp-file + rename — readers never see partial state.
  • Conflict: base-hash drift → both sides exposed; caller merges in-dialogue; nothing silently dropped.
  • Delete drift: a delete based on a stale version surfaces as a conflict (op=delete); resolve(side='draft') executes the delete, side='current' keeps the concurrent update.

Executable release

Since 1.0.0 a standalone Windows executable is built with PyInstaller (no Python/uvx needed):

# from repo root
pyinstaller packaging/local-wiki.spec --distpath dist/exe
# → dist/exe/local-wiki.exe

The tokenizer asset (src/mcp_server_wiki/assets/tokenizer.json) is bundled into the executable, so offline token counting works in the exe too.

Build env needs typer installed (pip install typer) — collect_all('mcp') imports mcp.cli, which exits with "typer is required" if it's missing.

Dev

pip install -e .[dev]
pytest                     # unit tests (tests/test_*.py)

After editing src/, restart the running MCP server — a running process holds the old code and will not reflect your changes.

Companion skill

skills/local-wiki-usage/ ships the agent skill that teaches how to read/write the wiki correctly (write routing, pending-queue workflow, conflict resolution, pitfalls). It has been updated to the current wiki_add / .wiki/<root>/ API.

License

MIT

Metadata

Release files for local-wiki 1.3.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for local-wiki 1.3.0
File Size Uploaded
local_wiki-1.3.0.tar.gz 1.9 MB Details

Built distribution (wheel)

Table of built distributions (wheels) for local-wiki 1.3.0
File Interpreter ABI Platform
local_wiki-1.3.0-py3-none-any.whl Python 3 none any Details

Total release size: 3.9 MB

Release files / local_wiki-1.3.0.tar.gz

Download URL local_wiki-1.3.0.tar.gz
Size 1.9 MB
Tags Source
SHA-256 checksum
How to use checksums
40c3df5cc14567f2105b66cfce980548a43a6416906671b737fd00a6c25237ea
BLAKE2b-256 checksum
How to use checksums
38c715fa44d610a0e9c2e811e52a6c4acd4ec3280734d8317382e1a0793490d5
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.13.14

Release files / local_wiki-1.3.0-py3-none-any.whl

Download URL local_wiki-1.3.0-py3-none-any.whl
Size 2.0 MB
Tags Python 3
SHA-256 checksum
How to use checksums
84f3a5deefd26cf3d0d7192f6748c982ab4fe0f3c671c18b8e8fa71d866b8357
BLAKE2b-256 checksum
How to use checksums
162f50e869b6ed3099ce18b511c0de14dfcb97fc0e0fe94f744f27877f95f1ed
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.13.14

Release history Release notifications | RSS feed

This release

1.3.0 This release

2 release files

1.2.0

2 release files

1.1.0

2 release files

1.0.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page