Skip to main content

LangChain deepagents that maintain and consult a wiki knowledge base in Open Knowledge Format (OKF).

Project description

deep-wiki-agent

LangChain deepagents that maintain and consult a wiki knowledge base in the Open Knowledge Format (OKF).

An OKF bundle is a directory of markdown pages with YAML frontmatter, linked to each other into a graph. It is a format, not a platform: no database, no embeddings, no vendor. This library gives you two agents over such a bundle — one that writes it, one that reads it.

from deep_wiki_agent import create_deep_wiki_agent, create_wiki_manager_agent

# Writes: ingests documents, keeps pages, links, indexes and log in sync.
manager = create_wiki_manager_agent(
    model="anthropic:claude-sonnet-5",
    wiki_path="./my-wiki",
)

# Reads: answers strictly from the bundle, or says it found nothing.
reader = create_deep_wiki_agent(
    model="anthropic:claude-sonnet-5",
    wiki_path="./my-wiki",
)

Why two agents

The two jobs pull in opposite directions, and the difference is enforced, not merely suggested:

create_wiki_manager_agent create_deep_wiki_agent
Writes to the bundle yes never — every write is denied at the tool boundary
raw/ (source documents) write-protected unreachable for writing
Creates the bundle yes, on demand no, must already exist
Extra tools okf_lint + whatever you pass none by default
Answer when the wiki is silent ask the user, propose sources a fixed not found sentence

The reader's read-only guarantee comes from deepagents' FilesystemPermission rules, applied by FilesystemMiddleware before a tool runs. Replacing its system prompt — even with one that tells it to write — does not lift it.

Installation

pip install deep-wiki-agent
# or
uv add deep-wiki-agent

Python 3.12+. You also need a provider package for the model you pass, e.g. pip install langchain-anthropic.

To ingest PDFs and other binary formats, add the optional documents extra — pip install "deep-wiki-agent[documents]" — which enables the ready-made read_document tool. The core install stays free of loader dependencies.

The knowledge lives in the system prompt

The substantive rules — bundle layout, frontmatter conformance, the ingest workflow, the query protocol, the lint checklist, bootstrap, the log format — are in the agents' system prompts. They are in force from the first turn: nothing to load, no round trip spent reading a file, and no way for the model to skip them.

The content is split by audience. The manager carries all of it. The reader carries bundle structure, enough conformance to read frontmatter and follow links, and the query protocol — not ingest, bootstrap or the log format, which it can never act on.

skills/okf-wiki/SKILL.md stays in the repository as the canonical human-facing statement of the format, usable in Claude Code and other skill-aware harnesses. It is not shipped in the wheel and no agent reads it; tests/test_prompt_drift.py fails if it and the prompts drift apart.

To change what an agent follows, pass system_prompt=.

The virtual filesystem the agent sees

/                          -> your OKF bundle (wiki_path)
├── AGENTS.md
├── raw/                   write-protected: sources are immutable
└── wiki/
    ├── index.md
    ├── log.md
    ├── assets/
    ├── documents/
    ├── entities/
    ├── concepts/
    └── syntheses/

Everything the wiki owns lives under wiki/; raw/ sits beside it, not inside it, so a page in a category directory reaches its source two hops up (../../raw/...).

One FilesystemBackend, rooted at your bundle, with virtual_mode=True so the agent cannot escape it via ../ or ~/. Nothing is added to your wiki directory.

Usage

Building a wiki

manager = create_wiki_manager_agent(
    model="anthropic:claude-sonnet-5",
    wiki_path="./my-wiki",
)

config = {"configurable": {"thread_id": "session-1"}}
result = manager.invoke(
    {
        "messages": [
            {
                "role": "user",
                "content": (
                    "Bootstrap a wiki about our vendor contracts, "
                    "then ingest raw/contract-acme-2026.md"
                ),
            }
        ]
    },
    config=config,
)
print(result["messages"][-1].content)

The agent bootstraps the bundle structure (agreeing categories and types with you first), writes AGENTS.md, ingests the source, creates and cross-links the pages, updates the indexes and wiki/log.md, and runs okf_lint.

Put your source documents in <wiki_path>/raw/ beforehand. They are write-protected, so the agent reads them and can never alter them.

Consulting a wiki

reader = create_deep_wiki_agent(
    model="anthropic:claude-sonnet-5",
    wiki_path="./my-wiki",
)

answer = reader.invoke(
    {"messages": [{"role": "user", "content": "What is the notice period in the Acme contract?"}]},
    config={"configurable": {"thread_id": "q-1"}},
)

If the bundle covers the question, you get an answer citing the wiki pages, which in turn cite the position in the source document. If it does not, you get exactly:

I could not find the requested information in the wiki knowledge base.

Not a guess, not the model's general knowledge, not an answer to a nearby question. A partial hit is answered in part, with the uncovered part named explicitly.

Change the sentence — for another language or another product voice — with not_found_message:

reader = create_deep_wiki_agent(
    model="anthropic:claude-sonnet-5",
    wiki_path="./my-wiki",
    not_found_message="Non ho trovato queste informazioni nella knowledge base.",
)

Getting the answer as data

Matching that sentence to detect a miss is brittle — it breaks the moment you translate it. Pass structured_output=True and the reader answers with a WikiAnswer instead:

from deep_wiki_agent import WikiAnswer, create_deep_wiki_agent

reader = create_deep_wiki_agent(
    model="anthropic:claude-sonnet-5",
    wiki_path="./my-wiki",
    structured_output=True,
)

result = reader.invoke(
    {"messages": [{"role": "user", "content": "What is the notice period?"}]},
    config={"configurable": {"thread_id": "q-1"}},
)

answer: WikiAnswer = result["structured_response"]
if not answer.found:
    ...                      # the bundle covers none of the question
elif answer.not_covered:
    ...                      # partial hit; `not_covered` names the gap
for path in answer.citations:
    ...                      # e.g. "/wiki/concepts/preavviso.md"
Field Type Meaning
answer str the answer; the not-found message, verbatim, when found is false
citations list[str] bundle paths of the wiki pages the answer rests on
not_covered str | None the part of the question the bundle does not answer
found bool false only when the bundle covers none of the question

The trade-off, and why free text stays the default: a schema buys you a record you can branch on, and costs the model the freedom to shape an answer to the question — a table, a staged explanation, an aside about two pages that disagree. Take it when a program consumes the answer, leave it when a person reads it.

The flag is mutually exclusive with create_deep_agent's response_format passthrough; pass your own schema that way instead if WikiAnswer does not fit, and note that only structured_output=True adds the prompt section telling the model how to fill the fields.

Ingesting PDFs, docx, web pages

No loader is installed by default — which formats your wiki ingests is domain-specific, and loaders drag in heavy dependencies. The well-trodden path ships as an opt-in extra:

pip install "deep-wiki-agent[documents]"
# or
uv add "deep-wiki-agent[documents]"

That pulls in markitdown and enables create_read_document_tool, a ready-made read_document tool covering PDF, docx, pptx, xlsx, html, epub and the rest of markitdown's format list:

from deep_wiki_agent import create_read_document_tool, create_wiki_manager_agent

manager = create_wiki_manager_agent(
    model="anthropic:claude-sonnet-5",
    wiki_path="./my-wiki",
    tools=[create_read_document_tool("./my-wiki")],
)

The agent then calls read_document("paper.pdf") and gets markdown back. Two things are not up to the model: reads are confined to /raw, so the tool cannot be aimed at the wiki's own pages or anywhere outside the bundle, and the result is truncated past max_chars (200 000 by default) with a note saying so, so one oversized source cannot swallow the context window.

Bytes are pulled through the backend's download_files, not off the local filesystem, so the tool reads the same tree the agent's file tools do — including a bundle held in a state, store, or sandbox backend:

tool = create_read_document_tool(backend=my_backend)

To convert a source yourself, outside an agent, read_document is the same code path without the truncation:

from deep_wiki_agent import read_document

markdown = read_document("paper.pdf", wiki_path="./my-wiki")

Writing your own loader instead remains supported — anything you pass in tools= is handed straight to the agent.

Human approval before writes

Both factories forward every remaining create_deep_agent argument, so the usual deepagents controls apply:

from langgraph.checkpoint.memory import MemorySaver

manager = create_wiki_manager_agent(
    model="anthropic:claude-sonnet-5",
    wiki_path="./my-wiki",
    interrupt_on={"write_file": True, "edit_file": True},
    checkpointer=MemorySaver(),  # interrupts require a checkpointer
)

A bundle that is not on the local disk

Pass your own backend instead of wiki_path. It is used verbatim, and there is nothing else to mount — the agent's instructions travel in the prompt, so any backend serving the bundle at its root works as-is:

from deepagents.backends import StoreBackend

reader = create_deep_wiki_agent(
    model="anthropic:claude-sonnet-5",
    backend=lambda rt: StoreBackend(rt),
)

The okf_lint tool is attached regardless of backend: it walks the bundle through the backend's own glob/read/edit methods, so a state, store, or sandbox-backed bundle is just as self-validating as a local directory.

Linting

okf_lint validates the bundle against OKF v0.1 — frontmatter present, the mandatory type field, ISO 8601 timestamps, broken internal links, links written as absolute paths instead of relative to their page, the same two defects in the path-valued frontmatter fields resource and sources, orphan pages, stale or missing index.md, type values not declared in AGENTS.md, the log.md entry format, the same concept created twice under different paths, misused reserved names.

--fix repairs what is mechanical and leaves the rest reported: malformed timestamps (keeping the date they state) and absolute links or frontmatter paths whose target exists, rewritten relative to their page. An absolute link whose target does not exist is left alone — rewriting it would only move a broken link around.

The manager agent runs it as a tool. You can run the same check yourself:

from deep_wiki_agent import run_okf_lint

report = run_okf_lint("./my-wiki")
print(report["errors"], report["warnings"])

or from the shell — the validator is stdlib-only and exits 1 on errors, so it drops straight into CI:

okf-lint ./my-wiki [--fix] [--json]

# without the package installed on the PATH
uvx --from deep-wiki-agent okf-lint ./my-wiki
python -m deep_wiki_agent.okf_lint ./my-wiki

For a bundle held in a non-local backend, pass it instead of a path:

report = run_okf_lint(backend=my_backend)

Customizing the instructions

Start from the shipped template and extend it, rather than writing one from scratch:

from deep_wiki_agent import MANAGER_SYSTEM_PROMPT_TEMPLATE
from deep_wiki_agent.prompts import LINT_TOOL_BLOCK

prompt = MANAGER_SYSTEM_PROMPT_TEMPLATE.format(
    wiki_root="/", raw_dir="/raw", lint_block=LINT_TOOL_BLOCK
) + "\n\nAlways write the pages in Italian."

manager = create_wiki_manager_agent(
    model="anthropic:claude-sonnet-5",
    wiki_path="./my-wiki",
    system_prompt=prompt,
)

Passing system_prompt replaces the built-in instructions wholesale, so restate whatever you still want in force. For the reader that includes the not-found contract and the query protocol — the read-only enforcement, by contrast, lives in the permissions and survives any prompt.

To add genuine skills alongside the agent, create_deep_agent's own skills= parameter passes straight through.

API

  • create_wiki_manager_agent(*, model, wiki_path=None, backend=None, ...) — read/write agent over an OKF bundle.
  • create_deep_wiki_agent(*, model, wiki_path=None, backend=None, not_found_message=..., structured_output=False, ...) — read-only agent that answers only from the bundle.
  • WikiAnswer — the reader's optional structured response: answer, citations, not_covered, found.
  • create_okf_lint_tool(wiki_path=None, *, backend=None) / run_okf_lint(wiki_path=None, *, backend=None, fix=False) — OKF conformance validation, against a local directory or any deepagents backend.
  • create_read_document_tool(wiki_path=None, *, backend=None, root="/raw", max_chars=200_000) / read_document(path, *, wiki_path=None, backend=None, root="/raw") — source-document loading via markitdown, confined to /raw. Requires the documents extra.
  • read_only_permissions() / write_protect_permissions(paths) — the permission sets, reusable in your own agents.
  • MANAGER_SYSTEM_PROMPT_TEMPLATE / READER_SYSTEM_PROMPT_TEMPLATE — the instructions each agent follows, as str.format templates.
  • BUNDLE_SKELETON — the bundle layout the manager bootstraps, as bundle-relative paths.

Upgrading from 0.1.x? build_wiki_backend, bundled_skills_dir, okf_wiki_skill_dir, normalize_mount, and the skills_mount / skills_dir / extra_skills parameters are gone — see the migration table.

Full reference: https://giurlanda.github.io/deep-wiki-agent/

Development

uv sync --all-extras
uv run pytest
uv run ruff check .
uv run ruff format --check .
uv run mkdocs serve

License

MIT

Project details


Download files

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

Source Distribution

deep_wiki_agent-0.6.1.tar.gz (222.6 kB view details)

Uploaded Source

Built Distribution

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

deep_wiki_agent-0.6.1-py3-none-any.whl (42.3 kB view details)

Uploaded Python 3

File details

Details for the file deep_wiki_agent-0.6.1.tar.gz.

File metadata

  • Download URL: deep_wiki_agent-0.6.1.tar.gz
  • Upload date:
  • Size: 222.6 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for deep_wiki_agent-0.6.1.tar.gz
Algorithm Hash digest
SHA256 cf7d494c2b1a7b4a4e2cb2e56a82723306d6235c718901db049bbaaf36002172
MD5 6b4382f60e0fba8a8f8c1c18a397e4b6
BLAKE2b-256 18645b75bbfc9a6296067f7d4bbdec0bb375a239939aa3a16518c31b9ca142d5

See more details on using hashes here.

Provenance

The following attestation bundles were made for deep_wiki_agent-0.6.1.tar.gz:

Publisher: publish.yml on giurlanda/deep-wiki-agent

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file deep_wiki_agent-0.6.1-py3-none-any.whl.

File metadata

  • Download URL: deep_wiki_agent-0.6.1-py3-none-any.whl
  • Upload date:
  • Size: 42.3 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for deep_wiki_agent-0.6.1-py3-none-any.whl
Algorithm Hash digest
SHA256 b351aa71c86deecbf225823dbeffc6a235beb74653a47c28df696d2054e98dec
MD5 9b3dbf942b1107d475e4b2d10cb39ca6
BLAKE2b-256 f453bbf38db9de0af5890f943401e6e42510f1cb0fa32cf9ee579fefb1a9d0a9

See more details on using hashes here.

Provenance

The following attestation bundles were made for deep_wiki_agent-0.6.1-py3-none-any.whl:

Publisher: publish.yml on giurlanda/deep-wiki-agent

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

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