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 / SmartParksmart_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

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

local_wiki-1.3.0.tar.gz (1.9 MB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

local_wiki-1.3.0-py3-none-any.whl (2.0 MB view details)

Uploaded Python 3

File details

Details for the file local_wiki-1.3.0.tar.gz.

File metadata

  • Download URL: local_wiki-1.3.0.tar.gz
  • Upload date:
  • Size: 1.9 MB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for local_wiki-1.3.0.tar.gz
Algorithm Hash digest
SHA256 40c3df5cc14567f2105b66cfce980548a43a6416906671b737fd00a6c25237ea
MD5 7df7d40c2040c7bf16deaf6bd7daefb8
BLAKE2b-256 38c715fa44d610a0e9c2e811e52a6c4acd4ec3280734d8317382e1a0793490d5

See more details on using hashes here.

File details

Details for the file local_wiki-1.3.0-py3-none-any.whl.

File metadata

  • Download URL: local_wiki-1.3.0-py3-none-any.whl
  • Upload date:
  • Size: 2.0 MB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for local_wiki-1.3.0-py3-none-any.whl
Algorithm Hash digest
SHA256 84f3a5deefd26cf3d0d7192f6748c982ab4fe0f3c671c18b8e8fa71d866b8357
MD5 3a2bfb157b74fc53dd1c2ca30bb67b44
BLAKE2b-256 162f50e869b6ed3099ce18b511c0de14dfcb97fc0e0fe94f744f27877f95f1ed

See more details on using hashes here.

Release history Release notifications | RSS feed

This release

1.3.0 This release

2 files

1.2.0

2 files

1.1.0

2 files

1.0.0

2 files

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page