Wiki Context Provider (Notion database backend)

Mirror a Notion database to local Markdown and update it through WikiContextProvider.

This demo files a customer call summary into a disposable Notion database and reads it back. Use a dedicated database with one data source and Markdown-compatible content; the backend selects only the first data source.

A write commits every mirrored Markdown page, including unchanged pages. Existing blocks are archived and recreated from the supported Markdown subset, so images, tables, callouts, and other unsupported content can be lost even on pages the agent did not edit. Synchronization replaces the local Markdown mirror. Timestamp checks are per page: a later conflict does not roll back earlier remote writes.

wiki_notion.py
"""
Wiki Context Provider (Notion database backend)
===============================================

Same ``WikiContextProvider`` surface as the filesystem and git wikis,
but the wiki is backed by a Notion database. Each row in the database
is mirrored as one ``.md`` file under ``local_path``. Frontmatter
records the Notion page id and last-edited timestamp:

    ---
    notion_page_id: 8a7c2f3e-...
    notion_last_edited: 2026-05-13T10:22:00Z
    title: Acme Corp
    ---

    # Acme Corp
    ...

Notion is the source of truth. Pages are flat (one row per page, no
nesting); the nested-tree mode (``NotionPageBackend``) is on the
roadmap. ``sync()`` wipes the local ``*.md`` mirror and rebuilds from
the database; ``commit_after_write`` pushes block updates, creates
pages for new files, and archives pages whose files were deleted
locally. If a page was edited inside Notion between sync and commit,
the commit raises rather than overwrite — call ``wiki.sync()`` and
retry.

This demo files a customer call summary into a Notion database and
reads it back. The point: the same database is the one your AEs open
in Notion. The agent files structured notes (markdown locally, blocks
in Notion); humans read and edit them in the UI they already use.

Auth is an integration token. Create one at
https://www.notion.so/profile/integrations and invite it to your
database via the database's "Connections" menu.

Requires:
    OPENAI_API_KEY
    NOTION_API_KEY        (integration token, starts with ``ntn_`` or ``secret_``)
    NOTION_DATABASE_ID    (UUID from the database URL)

    Optional:
    WIKI_LOCAL_PATH       (default: ./demo-wiki-notion/ next to this cookbook;
                           override to mirror elsewhere)
"""

import asyncio
import os
import sys
from pathlib import Path

from agno.agent import Agent
from agno.context.wiki import NotionDatabaseBackend, WikiContextProvider
from agno.context.wiki.notion_ops import parse_page_file
from agno.models.openai import OpenAIResponses

TOKEN = os.getenv("NOTION_API_KEY")
DATABASE_ID = os.getenv("NOTION_DATABASE_ID")
LOCAL_PATH = os.getenv("WIKI_LOCAL_PATH") or str(
    Path(__file__).resolve().parent / "demo-wiki-notion"
)

if not TOKEN or not DATABASE_ID:
    print(
        "Skipping Notion wiki demo - set NOTION_API_KEY and NOTION_DATABASE_ID to run.\n"
        "Example:\n"
        "  NOTION_API_KEY=ntn_xxx \\\n"
        "  NOTION_DATABASE_ID=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx \\\n"
        "  .venvs/demo/bin/python cookbook/12_context/15a_wiki_notion.py"
    )
    sys.exit(0)

# ---------------------------------------------------------------------------
# Create the provider
# ---------------------------------------------------------------------------
backend = NotionDatabaseBackend(
    database_id=DATABASE_ID,
    token=TOKEN,
    local_path=LOCAL_PATH,
)
wiki = WikiContextProvider(
    id="wiki",
    backend=backend,
    model=OpenAIResponses(id="gpt-5.6-luna"),
)

# ---------------------------------------------------------------------------
# Create the Agent
# ---------------------------------------------------------------------------
agent = Agent(
    model=OpenAIResponses(id="gpt-5.4"),
    tools=wiki.get_tools(),
    instructions=wiki.instructions(),
    markdown=True,
)


# ---------------------------------------------------------------------------
# Run the Agent
# ---------------------------------------------------------------------------
async def _run() -> None:
    await wiki.asetup()
    print(f"\nwiki.status() = {wiki.status()}\n")

    # The Notion DB is flat (one row per page), so the file must land at
    # the mirror root. The agent names the file; we just nudge it toward
    # a clean slug so the auto-derived Notion page title reads well.
    write_prompt = (
        "File a call summary for Acme Corp as acme-corp.md at the wiki root. "
        "Sections: Attendees, Pain Points, Next Steps. "
        "They're evaluating us against Competitor X and need SSO by Q3. "
        "Keep it under twenty lines."
    )
    print(f"> {write_prompt}\n")
    await agent.aprint_response(write_prompt)

    print()
    read_prompt = (
        "What's the status with Acme Corp? When's their SSO deadline? Cite the page."
    )
    print(f"> {read_prompt}\n")
    await agent.aprint_response(read_prompt)

    # ---------------------------------------------------------------------
    # Round-trip proof: the local .md is real, and the Notion page is
    # clickable. Same content, two surfaces.
    # ---------------------------------------------------------------------
    pages = sorted(Path(LOCAL_PATH).glob("*.md"))
    assert pages, f"agent did not file any pages under {LOCAL_PATH}"
    print("\n[ok] wiki pages:")
    for path in pages:
        fm, _ = parse_page_file(path.read_text(encoding="utf-8"))
        size = path.stat().st_size
        rel = path.relative_to(Path(LOCAL_PATH))
        if fm.notion_page_id:
            page_url = f"https://www.notion.so/{fm.notion_page_id.replace('-', '')}"
            print(f"  - {rel} ({size} bytes)")
            print(f"    open in Notion: {page_url}")
        else:
            print(f"  - {rel} ({size} bytes) — no notion_page_id yet, commit pending")


if __name__ == "__main__":
    asyncio.run(_run())

Run the Example

Set up your virtual environment

uv venv --python 3.12
source .venv/bin/activate

Install dependencies

uv pip install -U agno notion-client openai

Export environment variables

export NOTION_API_KEY="your_notion_api_key_here"
export NOTION_DATABASE_ID="your_notion_database_id_here"
export OPENAI_API_KEY="your_openai_api_key_here"

Prepare a disposable database

Create a separate test database with one data source, share it with your Notion integration, and give the integration the content permissions required to read and write it. Set NOTION_DATABASE_ID to that database’s ID. Use an empty disposable local directory through WIKI_LOCAL_PATH, or leave the default demo-wiki-notion beside the script.

For an existing database, first adapt the example for reading: add write=False to WikiContextProvider, remove the write_prompt call, and replace the Acme question with a question about an existing page. Immediately after await wiki.asetup(), add await wiki.sync() to populate the local mirror before reading. Setup alone does not fetch page content. The default example performs remote writes; do not run it against your operational database.

Run the example

Save the code above as wiki_notion.py, then run:

python wiki_notion.py

Full source: cookbook/12_context/15a_wiki_notion.py