Personal LLM Wiki — Bản full không che, không âm thanh

I. Vì sao có wiki này — vấn đề mà tools hiện tại không giải

Tháng 2/2024, Andrej Karpathy tweet một idea: thay vì chunk-and-embed tài liệu vào vector DB rồi cosine-search, LLM distill nguồn vào page có cấu trúc, retrieve bằng title + tag + [[wikilink]] thuần. Page là đơn vị tri thức nén, không phải đoạn text 200-token.

Vấn đề mà tweet này giải quyết — và tools hiện tại không giải:

  1. Vector RAG hallucinated citation. Cosine-similarity retrieve chunk gần đúng → LLM tổng hợp → trả lời nghe-hợp-lý-nhưng-sai. Citation trả về là chunk ID không readable. User không verify được.
  2. Knowledge dùng-một-lần. Bạn ingest 50 PDF, chat 1 lần, kiến thức không “compound” — không có page-level concept để link tới, không có decay khi outdated. Lần sau ingest lại từ đầu.
  3. Vendor lock-in. Pinecone / Weaviate / OpenAI Assistants — đẹp đến khi giá tăng / API breaking change. Personal KB không cần scale đó.

LLM wiki theo Karpathy pattern bỏ cả 3 cùng lúc bằng cách distill, không retrieve. Bài này dẫn bạn qua toàn bộ implementation choices — 2 trục compounding:

  • Knowledge (sections II-V): page schema, forgetting, trust, scale
  • Integration (sections VI-VII): wiki như tool cho agent khác + agent đọc code của bạn

Mỗi section là 1 load-bearing decision. Skip 1 section = chấp nhận alternative + hậu quả của nó.


II. Distill, không chunk-and-vector — foundations

Karpathy pattern in 1 sentence

LLM đọc source (text / PDF / URL) → compile thành page có cấu trúc (title + summary + body + tags + domain), lưu file JSON. Khi hỏi, agent search title + tag, đọc page liên quan, trả lời + cite [[Page Title]].

So với vector RAG:

Vector RAG LLM Wiki
Embed chunk → vector DB Compile source → page file
Retrieve top-K bằng cosine Search title/tag/[[wikilink]]
Citation = chunk_id (opaque) Citation = [[Page Title]] (human-readable, navigable)
Chunk 200 token, mất context Page 500-2000 token, self-contained
Hallucination khó detect Refuse-or-cite enforceable (section IV)

Page schema

Mỗi page là 1 file JSON ở data/wiki/<owner>__<slug>.json:

{
  "title": "RAG Hybrid Search",
  "slug": "rag-hybrid-search",
  "owner": "thuvt",
  "summary": "BM25 + vector hybrid với reranker. Khi nào dùng, khi nào không.",
  "body": "## Khi nào hybrid > pure vector ... [[Vector DB]] ... [[BM25]]",
  "tags": ["rag", "retrieval", "hybrid"],
  "domain": "ML",
  "ingested_at": "2026-05-12T14:00:00Z",
  "last_verified_at": "2026-05-12T14:00:00Z",
  "source_authority": "blog",
  "confidence": 0.85,
  "valid_from": "2026-05-12T14:00:00Z",
  "valid_until": null,
  "superseded_by": null,
  "supersedes": []
}

body chứa Markdown thuần với [[wikilinks]] — resolve runtime sang page khác.

Compile pipeline 2-pass

Source → page là 2-pass LLM call:

  1. Pass 1 — Compile: LLM nhận source raw + existing-domain list. Output JSON với title, summary, body, tags, domain proposed. Prompt teaches tolerant JSON parsing (model như Gemma hay emit \$, \( — strip trước parse).
  2. Pass 2 — Lint: another LLM call review pass-1 output. Check: [[wikilinks]] valid, no fabricated facts, summary ≤ 2 câu, title concise. Return adjustments.

Final page save xuống disk + Notion mirror (optional).

3-folder layout

data/
├── wiki/           # compiled pages (the source of truth for queries)
│   ├── thuvt__rag-hybrid-search.json
│   ├── thuvt__vector-db.json
│   └── _index.json     # title → slug map cho fast lookup
├── raw/            # original sources (PDF, HTML snapshot, raw markdown)
│   ├── rag-paper-2024.pdf
│   └── blog-post-2026-05-12.html
└── log.md          # human-readable changelog: "2026-05-12 ingested X (5 pages)"

wiki/ là canonical. raw/ để audit (tại sao page này có dòng kia?). log.md là git-blame của tri thức.

[[Wikilink]] resolution

Khi render page hoặc khi chat-answer, mọi [[Page Title]] được resolve về URL /p/<slug> (FE) hoặc page object (backend). Wikilinks tạo graph KB tự nhiên — không cần “knowledge graph” ceremony.


III. Forget ≠ Delete — bi-temporal honest forgetting

Vấn đề: stale knowledge

Wiki chạy 6 tháng. Page về “Best LLM model 2024” giờ outdated. Bạn:

  • (a) Delete? Mất audit “tôi từng tin điều này”.
  • (b) Để nguyên? Chat trả lời sai dựa trên page cũ.
  • (c) Edit? Mất context decision điểm A → điểm B.

Cần một pattern thứ 4: honest forgetting — biết kiến thức stale, biết khi nào stale, biết replace bằng cái gì, vẫn audit-able.

Bi-temporal schema

Mỗi page có 4 thời gian-track:

Field Ý nghĩa
ingested_at System time — khi page lưu vào disk lần đầu
valid_from / valid_until Decision time — page valid trong khoảng nào
last_verified_at Lần cuối bạn xác nhận page vẫn đúng
superseded_by / supersedes Chain — page này thay thế page nào / được thay bởi page nào

Bi-temporal nghĩa là phân biệt “khi nào kiến thức được biết” (system time) vs “khi nào kiến thức là đúng” (decision time). Trong real-world KB, 2 cái này luôn lệch.

4 forget actions

  1. soft_delete_page(slug): chuyển page sang _archive/. Không bị query default tìm thấy, nhưng list_archived ra. Reversible. Dùng khi: “tôi không chắc đây có còn đúng, ẩn đi đã.”
  2. mark_superseded(old, new): gán old.superseded_by = new, new.supersedes = [old]. Old vẫn ở wiki/, queries trả lời “see [[New Page]]”. Dùng khi: “page B thay thế page A, nhưng audit chain A → B.”
  3. reverify_page(slug): update last_verified_at = now(). Page vẫn đúng. Dùng cho periodic review.
  4. decay_scan(): liệt kê pages có last_verified_at > 90 ngày trước. UI prompt: re-verify / supersede / archive cho mỗi page. Trigger thủ công (cron optional).

Drift detection

Khi chat trả lời từ 2+ pages, nếu các pages mâu thuẫn (vd: “best model = X” vs “best model = Y” cùng tag “ml-2026”), agent flag drift. 2 cách handle:

  • Auto: pages mới hơn (lớn valid_from) override pages cũ
  • Manual: agent surface conflict, user quyết supersede

Bi-temporal làm cái này thấy được. Vector RAG cũng có drift nhưng hidden — chunk gần nhất “win” không transparent.

_archive/ + restore + hard-delete

wiki/
├── thuvt__rag-hybrid-search.json
└── _archive/
    ├── thuvt__llm-best-2024.json     # soft-deleted, restorable
    └── thuvt__obsolete-prompt.json

restore_page(slug) đưa page từ _archive/ về wiki/. hard_delete_page(slug) xoá hẳn — admin only, không expose qua MCP (xem section VI).


IV. Cite-or-Refuse — 2-pass trust mechanism

Vấn đề: LLM bịa citation

Vector RAG cơ bản: retrieve top-K → stuff vào context → LLM answer. Vấn đề: LLM có thể compose answer dùng training-data + retrieved chunk lẫn nhau, citation [doc 3] chỉ là cosmetic.

Wiki cần stricter contract: citation must be a real page that contains the cited claim. Otherwise refuse.

2-pass enforcement

User asks: "what's the best vector DB for personal KB?"

Pass 1 — Answer attempt:
  Agent searches title/tag → finds [[Vector DB]], [[RAG Hybrid Search]]
  Composes answer:
    "For personal KB, pgvector via Postgres is sufficient.
     See [[Vector DB]] for tradeoff vs Pinecone."

Pass 2 — Judge:
  Independent LLM call (or same model, fresh context)
  Input: answer + cited pages content
  Check: does [[Vector DB]] actually say "pgvector sufficient for personal KB"?

  If YES → return answer to user.
  If NO → replace with refusal:
    "Tôi không có wiki entry confirms 'pgvector cho personal KB'.
     Available: [[Vector DB]] (covers tradeoffs but doesn't pick), [[RAG Hybrid Search]].
     Bạn muốn ingest source mới về cái này?"

Judge có 3 verdict:

  • Pass: claim truly cited
  • Fail — citation doesn’t support: claim không có trong cited page
  • Fail — claim outside wiki: claim nằm ngoài wiki, dù có citation gì cũng không OK (strict mode)

Strict-mode addendum

Default mode: judge pass nếu citation HỖ TRỢ claim. Strict mode: pass chỉ khi claim được PHÁT BIỂU TRỰC TIẾP trong page. Khác biệt:

  • Default: page nói “pgvector handles 1M vectors”, agent claim “pgvector good for personal KB” → PASS (suy luận hợp lý)
  • Strict: page không nói chữ “personal KB” → FAIL refusal

Strict mode cho compliance / regulated context. Default cho personal use.

Refusal heuristic

Refusal không phải lúc nào cũng “tôi không biết”. 4 dạng refusal:

  1. No relevant page: “wiki không có entry cho topic này”
  2. Page exists but doesn’t say claim: “[[Vector DB]] có nói về tradeoffs nhưng không pick winner. Bạn muốn rephrase?”
  3. Conflicting pages: “[] và [[Y]] mâu thuẫn về point này. Cần resolve trước.”
  4. Page stale: “[] last_verified 6 tháng trước, có thể outdated. Reverify?”

Mỗi dạng → next action khác.

Web search citations

Khi user enable enable_web=true, agent có thể search web (DuckDuckGo HTML scrape, không API key). Citations trả về phải bao gồm URL gốc, không bịa. Judge cũng check URL accessibility.

Web search KHÔNG bypass cite-or-refuse — chỉ thêm source nguồn ngoài wiki, vẫn phải cite + verify.


V. Scaling Out — multi-user + TOC ingest

Multi-user, single Notion DB

Khi 2+ người dùng cùng wiki, có 2 approach:

  • (a) Database per user: complete isolation, nhưng cross-user sharing phức tạp.
  • (b) Single DB + Owner + SharedWith fields: 1 Notion DB, mỗi page có Owner (select, 1 user) và SharedWith (multi-select, N users).

Wiki chọn (b) vì sharing là use case chính (Alice ingest page về RAG, share với Bob để cùng review).

Visibility rule trong dispatcher:

def visible_to(page, viewer):
    if viewer == "":           # admin context (server-side cron)
        return True
    if page.owner == viewer:   # owner sees own
        return True
    if viewer in page.shared_with:   # explicit share
        return True
    return False               # default: hidden

Mọi read action (list_pages, get_page, chat_wiki) filter qua hàm này.

HMAC cookie auth (vs JWT)

User login → server trả cookie:

Set-Cookie: wiki_session=alice.<base64(HMAC_SHA256(secret, "alice"))>

Cookie format: <user>.<signature>. Verify: re-compute HMAC từ user part, so signature. Stateless — không cần DB session table.

So với JWT:

  • JWT carry payload (exp, claims) → larger cookie, parsing complexity, secret rotation harder
  • HMAC: chỉ có user, không có expiration trong cookie → expiry là cookie Max-Age=30d ở browser

Personal scale (1-10 users): HMAC đủ. JWT là over-engineering.

Anti-spoof rule: backend MUST derive viewer từ cookie HMAC, KHÔNG trust client-sent X-User-Id header. Trojan agent có thể đặt header bất kỳ; HMAC verify mới là source of truth.

TOC-driven ingest

Khi ingest PDF dày (50+ trang) hoặc Markdown nhiều ##, naive approach là 1 page = 1 document. Vấn đề:

  • Page quá lớn (10k+ token) — không fit vào 1 LLM context
  • Search “topic in chapter 7” returns toàn document, không section-specific

TOC-driven: extract document structure trước khi compile.

def parse_pdf_structured(path):
    pdf = pypdf.PdfReader(path)
    outline = pdf.outline   # PDF bookmarks
    if outline:
        return outline_to_sections(outline, pdf)
    else:
        return [(pdf.pages_full_text, "Whole document")]

def detect_markdown_sections(md):
    # Split on H2 (##) — H1 is doc title
    sections = []
    current = {"title": None, "body": []}
    for line in md.split("\n"):
        if line.startswith("## "):
            if current["title"]:
                sections.append(current)
            current = {"title": line[3:].strip(), "body": []}
        else:
            current["body"].append(line)
    sections.append(current)
    return sections

Mỗi section → 1 page riêng. Search “vector DB tradeoffs in personal KB chapter” → page section cụ thể.

Trade-off: 1 PDF 100 trang có thể tạo 30 page → tốn LLM calls hơn. Bù lại: search precision tăng, citation specific hơn.

One-section-one-page

ingest book "Designing Data-Intensive Applications" PDF:
  → outline = [Ch1 Reliable, Ch2 Data Models, Ch3 Storage, ...]
  → 12 pages created, each titled "DDIA: ChapterN — TopicName"
  → each linkable: [[DDIA: Chapter 5 — Replication]]

Một book = một “domain” tự nhiên trong wiki.


VI. MCP — exposing wiki to other agents

Từ “wiki của tôi” → “tool cho any agent”

Khi đã có wiki, câu hỏi tiếp theo: agent khác của tôi (Claude Desktop, Cursor, cron task, custom GPT, AgentBase agent) — có dùng được wiki này không?

2 path:

  1. Custom REST API per agent. Mỗi integration làm lại từ đầu. Không reusable.
  2. MCP (Model Context Protocol). Standard interface. Mọi MCP client cắm vào dùng được.

MCP = USB-C cho AI ↔ tool/data. Spec mở do Anthropic công bố 11/2024, giờ cross-vendor (OpenAI, Google, AWS Bedrock, GreenNode). Client-server qua JSON-RPC 2.0. Transport: stdio (desktop) hoặc Streamable HTTP (remote).

Phân biệt nhanh với A2A (Agent-to-Agent Protocol, Google 2025): MCP = agent → tool/data. A2A = agent ↔ agent peer (negotiate, delegate). Wiki là data tool, không phải peer → MCP đủ, đơn giản hơn.

Thiết kế MCP server cho wiki — 3 quyết định đắt

Code ở src/wiki_mcp.py (~250 LOC) — tool definitions + adapters + JSON-RPC router, transport-agnostic. Mount qua POST /mcp route trong local_main.py (~25 LOC). Technically nothing novel. 3 design decisions mới là bài học.

Decision 1 — Tool nào expose, tool nào giữ internal?

Wiki có ~15 backend actions. Expose hết là sai.

  • Destructive (hard_delete_page, mark_superseded) — DO NOT expose. Risk: agent wipe data nhầm. Keep destructive sau UI confirm.
  • Admin (notion_pull, github_resync, _debug_fs) — meaningless cho agent ngoài.
  • Health / debug (stats, decay_scan) — internal.

Còn lại các tool thực sự matter:

Tool Backend Purpose
wiki_search chat_wiki Grounded Q&A với citations
wiki_list_pages list_pages Discovery
wiki_read_page get_page Read 1 page đầy đủ
wiki_list_users list_users Cho share picker
wiki_ingest_text ingest_text Distill 1 source
wiki_save_page save_page Direct save (no LLM compile)
wiki_archive_page soft_delete_page Soft delete — reversible

LLM client đọc description để quyết khi nào call tool nào. Description là primary UX surface — viết kỹ. Vague description → agent pick sai tool / skip.

Decision 2 — Adapter pattern: wrapper mỏng, không logic mới

Mỗi MCP tool map 1-1 với backend action. Adapter chỉ transform args:

MCP_TOOL_MAP = {
    "wiki_search":       ("chat_wiki",         _adapt_search),
    "wiki_list_pages":   ("list_pages",        lambda a: a),
    "wiki_save_page":    ("save_page",         lambda a: a),
}

def _adapt_search(a: dict) -> dict:
    return {
        "messages": [{"role": "user", "content": a.get("question", "")}],
        "domain": a.get("domain", ""),
        "enable_web": bool(a.get("enable_web", False)),
    }

Backend không biết về MCP, MCP không biết backend internals. Bug fix backend → MCP benefit miễn phí.

Decision 3 — Auth: Bearer = wiki user token, không OAuth

MCP spec mới có OAuth 2.0 dance — clean nhưng overkill cho personal/small-team. Pragmatic: mỗi wiki user 1 token, vừa làm UI login password vừa làm MCP Bearer.

Agent calls:    Authorization: Bearer <token>
                       ↓
Proxy:          reverse-lookup token → username
                       ↓
Forward:        X-User-Id: <username>
                       ↓
Backend:        filter pages bởi viewer=<username>

Token = 32 URL-safe chars (~192 bit entropy). Agent dùng token alice → act as alice → see alice’s KB + shared. Identity = token.

4 use case agent → wiki qua MCP

  • A. Daily note-taking: User “save meeting today” → agent wiki_ingest_text(content=notes, source_label="Meeting 2026-06-15") → freshly-named page cite-able sau.
  • B. Research while writing blog: User “notes của tôi về vector DB?” → agent wiki_search(question="notes vector DB") → grounded answer + citations.
  • C. Co-writing: User “draft section về Mamba based on my notes” → agent wiki_read_page(title="Mamba") → đọc full → draft.
  • D. KB maintenance (cron): Weekly wiki_list_pages filter last_verified_at > 90d → agent suggest verify / supersede / archive cho mỗi stale page.

Trade-offs + pitfalls

  • Latency: mỗi tool call = 1 HTTP roundtrip + 1 backend. Interactive chat OK; batch ingest 100 page slow — dùng backend trực tiếp.
  • Description quality > code quality: LLM client pick tool bằng description. Vague → agent pick sai / skip. Invest viết description.
  • Destructive action: dễ slip thêm vào sau, khó undo. Default safe = không expose.
  • Token leakage: token = full access KB. Commit claude_desktop_config.json lên public repo → leaked. Tutorial Part 6 warn explicit.
  • Rate limit: agent có thể loop tool calls. Backend không có rate limit → spike risk. Production nên add 100 calls/min/user ở MCP layer.

MCP vs A2A — khi nào dùng cái nào

MCP A2A
Relationship Agent → tool/data Agent ↔ agent peer
Server Passive (data, no reasoning) Agentic (internal reasoning)
Wiki use case :white_check_mark: Wiki là data service :x: Wiki không tự-reason / negotiate

Personal use: MCP đủ. Upgrade lên A2A khi wiki có “auto-organize agent” reason về KB và propose actions, hoặc external agents muốn delegate tasks.


VII. CODEMAP — agent navigation

Cold-start cost

Mỗi session AI mới, agent phải build mental model về codebase từ đầu:

Agent: "list directory" → 30 files
Agent: "read README" → 200 lines
Agent: "read main file" → 1000 lines
Agent: "grep cho concept X" → 5 results
...

10-15 tool call burn ~30-40% context budget trước khi viết được dòng code đầu tiên. Mỗi session lặp.

Giải pháp: file-by-file map ~300 dòng

CODEMAP.md ở root project. Cấu trúc 5 phần:

  1. Top-level tree — ASCII diagram thư mục + 1-line per item
  2. Per-file table — mỗi file đáng kể: path / role 1-line / key symbols / notes
  3. Reverse-lookup cookbook — “Nếu bạn đang làm X, đọc Y trước, edit Z”
  4. Gotchas section — production warnings, drift hazards, dual-codebase rules
  5. How-to-use (cho agent) — đọc CODEMAP trước, dùng reverse-lookup thay grep, update sau significant work

Anatomy example

## 🏭 Production stack

### `llm-wiki-agent-deploy/` — AgentBase Runtime backend

| File | What | Key symbols | Notes |
|---|---|---|---|
| `main.py` | AgentBase entrypoint + dispatcher (~1100 LOC) | `@app.entrypoint handler`, `dispatch(action, payload, viewer)`, `call_llm()`, `run_agent()` | LIVE. Refactor cần redeploy + smoke. |
| `wiki_store.py` | Persistent storage (JSON + index, bi-temporal) | `save_page()`, `get_page()`, `list_pages(viewer=)`, `mark_superseded()`, `decay_scan()` | Bi-temporal fields: `valid_from/until`, `last_verified_at`, `superseded_by/supersedes`. |
...

Reverse-lookup cookbook — chỗ đắt nhất

Task Files đọc FIRST Files edit
Add backend action mới main.py dispatch() main.py + matching MCP tool nếu expose
Change page schema wiki_store.py _index_entry() + save_page() All: wiki_store.py, notion_sync.py, migration script
Touch frontend llm-wiki.html — search section // ── X ── Same file. Test browser.
Deploy production agentbase-deploy/SKILL.md Build + push vCR + runtime.sh update

Agent đọc cookbook → biết exactly đụng file nào → skip 80% grep.

Gotchas — drift hazards

Production warnings section bắt buộc cho codebase có hazards real:

  • /data wipe khi AgentBase redeploy → re-hydrate từ Notion canonical
  • HF Space PUBLIC → MCP /mcp accept anyone với valid Bearer
  • save_page cosmetic bug: returns merged=True always (existed_before check sau save)
  • Cookie HMAC ≠ JWT — format-specific
  • Multi-user viewer filter apply mọi read; bypass = viewer=""

Mỗi item: hazard + cause + mitigation hoặc workaround.

Wire-in mechanism

CODEMAP chỉ valuable nếu agent thực sự đọc. 2 mechanism:

  1. AGENTS.md orient first: “Nếu CODEMAP.md tồn tại, read it BEFORE grepping. Saves 10+ tool calls per session.”
  2. Update habit sau significant work: thêm dòng “Update CODEMAP.md nếu thêm/xoá top-level file” vào checklist review của bạn. Stale CODEMAP > absent CODEMAP, but try stay current.

Measured speedup

Trước (1 session refactor MCP layer): 14 tool call build mental model. ~22k token đọc xong tự thấy “OK giờ tôi hiểu”.

Sau CODEMAP.md tồn tại: 1 read CODEMAP (~7k token) + 2-3 targeted reads → đủ. 10-20× tool call reduction — measured trên 4 session khác nhau.

Anti-patterns

  • :x: CODEMAP describe ý tưởng tổng quát (“kiến trúc microservice”). Cần file paths cụ thể.
  • :x: CODEMAP refer file đã rename / xoá. Stale > absent. Update khi rename.
  • :x: CODEMAP thay thế comments inline cho complex function. Đó là docstring’s job.
  • :x: CODEMAP cover EVERY file <100 LOC. Focus file có business logic, skip config / boilerplate.

Wrap up

2 trục, 6 implementation pieces, 1 working wiki + ecosystem.

Bạn không cần adopt hết. Sweet spot vào việc của bạn. Pick section gần với pain point nhất, start ở đó. Mỗi section đứng vững một mình; nhưng adopt nhiều hơn = compound nhanh hơn.

Cost: ~10 giờ đọc + cuối tuần dựng. Payoff: compound suốt thời gian project sống.

Karpathy idea tweet ngắn (Feb 2024) chỉ là khởi điểm. Tất cả còn lại — bi-temporal, cite-or-refuse, MCP, CODEMAP — là implementation choices một project thực sự buộc bạn phải làm. Bài này là tổng kết những choice đã đi qua.

Wiki không phải app. Là second-brain readable, editable, shareable, auditable, pluggable into agent ecosystem. Không vector DB anywhere.