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...). Same code on any machine: pip install local-wiki or uvx local-wiki.

Version 1.0.0 — production release. Runs either from PyPI (uvx local-wiki) or as a standalone executable (see Executable release).

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_write creates new pages directly (op=create, 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(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 C:/path/to/wiki

HTTP (Streamable)

local-wiki --wiki-root C:/path/to/wiki --host 127.0.0.1 --port 8000

Standalone executable (no Python required)

local-wiki.exe --wiki-root C:/path/to/wiki

Wire into clients

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

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.

Claude Code

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

MCP Tools (12)

Tool Description
wiki_write(profile, rel, content, op, session?) Create (direct) or stage update/delete into the pending queue
wiki_commit(once?) Serialize 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_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_search(query, profile?) Search in-memory index by rel/title/keywords (case-insensitive)
wiki_index(profile?, action, rel?, keywords?) Browse & maintain index.json public keywords (read tree / update / delete)
wiki_create_root(key, name, workdir?, type?, owner_profile?) Register a new wiki root (project shard) + root_index
wiki_update_root(key, name?, workdir?, owner_profile?, type?) Update a root's meta
wiki_delete_root(key, purge?) Unregister a root (purge=True deletes its folder, irreversible)

Storage Layout (v1.0.0)

<wiki-root>/
├── index.json                  # root registry: {global + <project roots>} → meta
├── global/                     # cross-profile knowledge (writable)
│   ├── pages/  index.json      # public keywords per layer
├── <project-root>/             # one folder per registered project root
│   ├── pages/  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)
│   ├── merge-log.md            #   AI merge audit trail
│   └── .lock                   # OS file lock (committer holds)

Layer naming — normalized to snake_case (v1.0.0)

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

  • smart-park / smartPark / SmartParksmart_park
  • all_layers dedupes legacy hyphen profiles against registered roots (smart-park + smart_park → one 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 layer

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 are two-layered: public keywords live in each layer's index.json (maintained via wiki_index, deleted with the page); a document's frontmatter keeps only its own unique keywords (duplicates of public ones are stripped on write, remaining total ≤ 60 chars).
  • 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 (v1.0.0 — hardened)

  • create on a missing target → written directly (atomic), index updated.
  • update on a missing target → rejected (page not found).
  • update/delete/create-on-existing → staged into pending/, committed serially.
  • Target vanished after enqueue (deleted externally between enqueue and commit):
    • update → commit reports error (never silently re-creates the page);
    • create → treated as a fresh create and written.
  • All write paths normalize content through one validator (_normalize_content): frontmatter validity, keyword strip/length, auto-add missing frontmatter — including wiki_resolve merged/draft, so bad content can never enter the wiki through conflict resolution.

Concurrency

  • Cross-profile: physical sharding (<root>/pages/) — different files, no collision.
  • Same-profile, 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 (v0.3.8+): 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 --onefile --name local-wiki \
  --collect-data mcp_server_wiki \
  --collect-all mcp \
  src/mcp_server_wiki/__main__.py
# → dist/local-wiki.exe

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

Dev

pip install -e .[dev]
pytest                     # unit tests
python scripts/wiki_mcp_test.py            # MCP stdio integration (12 tools + boundaries)
python scripts/wiki_concurrent_stress.py   # multi-process lock stress

Companion skill

skills/local-wiki-usage/ ships the Hermes skill that teaches agents how to read/write the wiki correctly (project routing, pending-queue workflow, conflict resolution, pitfalls). Import it into Hermes (skills/public/local-wiki-usage/) for the guided workflow.

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.0.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.0.0-py3-none-any.whl (1.9 MB view details)

Uploaded Python 3

File details

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

File metadata

  • Download URL: local_wiki-1.0.0.tar.gz
  • Upload date:
  • Size: 1.9 MB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.12.5 {"installer":{"name":"uv","version":"0.12.5","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":null,"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

File hashes

Hashes for local_wiki-1.0.0.tar.gz
Algorithm Hash digest
SHA256 9b9e9d95af6a2560b4ab370445a4cc37c6d1d2a9c57119f00fab4534fff8fc52
MD5 ec9971788c3c7405f181a15b8cd500c6
BLAKE2b-256 9a93affac54b3fa6df8229243e5ea5e14bb8067b8722e0ece31da28530d87f89

See more details on using hashes here.

File details

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

File metadata

  • Download URL: local_wiki-1.0.0-py3-none-any.whl
  • Upload date:
  • Size: 1.9 MB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.12.5 {"installer":{"name":"uv","version":"0.12.5","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":null,"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

File hashes

Hashes for local_wiki-1.0.0-py3-none-any.whl
Algorithm Hash digest
SHA256 1f95e889b2beafdc50568c4910eca6cbf01c61479d7f9100e34a1a1922252199
MD5 5b26baae7320831358cb4cd201962c28
BLAKE2b-256 9a4923e4207a012956af80652d292b984883ca7adcccd2099b2c99ee05b9b089

See more details on using hashes here.

Release history Release notifications | RSS feed

1.3.0

2 files

1.2.0

2 files

1.1.0

2 files

This release

1.0.0 This release

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