Reading and inspection

Readable notebook views plus implementation-oriented context for co-creation.

The public read API is the unified context(target, scope="."). It covers repository orientation, one notebook, one chapter, one cell id, or one concrete implementation without exposing raw notebook JSON. Its filters choose the useful slice: summary, full source, chapter, cell type, query, regular expressions, and nearby context lines.

Notebook chapters use a code-first rhythm: exported code, trailing Markdown explanation, example cells, focused test cells, then the next exported code. The reader preserves that source order and associates a symbol with the cells that follow it.

Reading is the first problem to solve for notebook automation. An agent should not have to inspect raw .ipynb JSON just to learn what a project, notebook, chapter, or implementation contains, and a human reviewer should not have to scroll through outputs and metadata to find the code.

This notebook builds compact context views for four common questions: what project is this, what is in this file, what belongs to this chapter, and what should I know before changing this symbol.

For a chapter, read the story in this order:

## Chapter
- exported code
- trailing explanation
- example(s)
- test(s)
- exported code
- trailing explanation
- example(s)
- test(s)
- ...

Symbol readers use the same rhythm: implementation first, then trailing Docs, then the following example and test cells. They stop the local group at the next exported code cell so one symbol’s explanation does not leak into the next one.

The reader is intentionally a triage tool before it is a renderer. Start with context(target, scope="."); use target="project", a notebook path/name, a chapter title, a cell id, or a Python symbol.

context("project", scope="nbs")
context("nbs/02_write.ipynb")
context("write_nb", scope="nbs/02_write.ipynb")

Reader API

Start with the unified context reader. Select the target and add only the filters needed for the agent’s next action.

Production contract

The unified context reader is the production entry point. It preserves stable notebook paths and cell ids, supports compact summaries and full source, and narrows the same result by chapter, cell type, query, or regular expressions. Lower-level helpers are implementation details, not additional agent-facing read surfaces.

with write_demo_notebook("01_read_example.ipynb") as path:
    write_nb(new_nb([
        mk_cell("# Reader\nFile preamble.", cell_type="markdown"),
        mk_cell("preamble = True", cell_type="code"),
        mk_cell("## Demo", cell_type="markdown"),
        mk_cell("#| export\ndef answer():\n    \"\"\"Return the demo answer.\"\"\"\n    return 42", cell_type="code"),
        mk_cell("This explains why the demo answer exists.", cell_type="markdown"),
        mk_cell("answer()", cell_type="code"),
        mk_cell("assert answer() == 42", cell_type="code"),
    ]), path)
    print("project_context")
    _example_project_context(str(path))
    print("\nfile_context")
    _example_file_context(str(path), include_re="answer")
    print("\nchapter_context")
    _example_chapter_context(str(path), name="Demo")
    print("\ncontext symbol")
    _example_context(f"{path}#answer", scope=str(path), mode="review")

Output shapes

The unified context reader serves the useful attention levels: view="summary" maps cells compactly, view="full" keeps source in order, and chapter, cell-type, query, and regex arguments narrow the same notebook-aware result. A qualified target such as notebook.ipynb#cell-id hands an agent an exact stable cell.

Use the smallest view that answers the next question: summary to orient, a filtered full view to implement, and a qualified cell target to edit. Structured returns keep cell ids and indexes so an edit can target the exact source cell.

Query and selection

Selectors turn short human queries into stable notebook, chapter, cell, and symbol matches.

A small query language

Automation needs stable selectors, but humans need short commands. The query helpers accept aliases like id, type, chapter, and contains, then normalize them into one internal selection shape.

Public notebook context

This is the main notebook-facing surface: it combines the smaller readers and returns a structured result while optionally printing the rendered text.

The public notebook reader

The public reader is context(target, scope="."). The target can be project, a notebook path, a cell id, a chapter title, a Python symbol, or a literal search string when no structured target resolves. Add view, chapter, cell_type, query, include_re, or exclude_re to shape the same result.


source

project_context

def project_context(
    path:str='.', # Project directory, notebook path, or notebook glob root
    verbose:bool=True, # Print rendered context; pass False to only return structured data
):

Show README sections, notebook filenames, and notebook file docstrings.


source

chapter_context

def chapter_context(
    path:str, # Notebook path
    query:str | None=None, # Query for any cell inside the chapter
    name:str | None=None, # Chapter title string or regex
    any_cell_id:str | None=None, # Any cell id inside the chapter
    verbose:bool=True, # Print rendered context; pass False to only return structured data
    overview:bool=False, # Show only notebook head and chapter intro markdown
    cell_type:str | None=None, # Optional cell type or semantic type filter for deep dives
    include_re:str | None=None, # Optional regex that matched cell source must include
    exclude_re:str | None=None, # Optional regex that matched cell source must not include
):

Show one chapter in source order, with optional overview and cell filters.

Python source context

Notebook work often crosses into generated modules. The AST readers keep that handoff compact and navigable.

Python files get a compact AST-backed reader too. python_file_context summarizes public definitions without dumping full source, while python_symbol_context opens one symbol and shows the local callers and callees that matter for an edit.


source

python_file_context

def python_file_context(
    path:str, # Python file path
    include_private:bool=False, # Include private definitions in the summary
    verbose:bool=True, # Print rendered context; pass False to only return structured data
):

Show a compact AST summary for one Python file.


source

python_symbol_context

def python_symbol_context(
    path:str, # Python file path
    symbol:str, # Function, class, method, or assignment symbol to inspect
    verbose:bool=True, # Print rendered context; pass False to only return structured data
):

Show source, local callers, and callees for one Python-file symbol.

with write_demo_notebook("01_read_python_context_example.py") as path:
    path.write_text(
        "PUBLIC_VALUE = 1\n\n"
        "def helper(value):\n"
        "    \"\"\"Prepare a value.\n\n    Hidden detail.\"\"\"\n"
        "    return value + PUBLIC_VALUE\n\n"
        "def add(a, b):\n"
        "    \"\"\"Add two values.\n\n    Hidden detail.\"\"\"\n"
        "    return helper(a) + b\n\n"
        "def uses_add(value):\n"
        "    return add(value, 1)\n",
        encoding="utf-8",
    )
    py_file = python_file_context(str(path), verbose=False)
    py_symbol = python_symbol_context(str(path), "add", verbose=False)
py_file["public_symbols"][0]["symbol"], py_file["definitions"][1]["docstring"], py_symbol["callers"][0]["symbol"], py_symbol["callees"]

source

file_context

def file_context(
    path:str, # Notebook or Python file path
    include_re:str | None=None, # Optional regex for markdown and definition items to include
    exclude_re:str | None=None, # Optional regex for markdown and definition items to exclude
    verbose:bool=True, # Print rendered context; pass False to only return structured data
):

Show compact context for one notebook or Python file; notebook cells stay in source order.

Filtered context is the project-wide escape hatch for questions that are too broad for one target but still need notebook-aware cells instead of raw JSON. It searches notebooks in scope, keeps stable cell ids in the result, and caps both the number of matches and each source snippet so agents can ask broad questions without flooding their context.

context combines discovery and reading. Use context(path, view="summary") for one compact row per cell, cell_type="markdown" or cell_type="code" for role-specific slices, chapter="Name" for one section, and query="regex=term" or include_re="term" for targeted search. context_lines=1 adds neighboring cell summaries without opening the whole notebook.

def _write_read_sample_notebook(path):
    double_example = mk_cell("double(3)", cell_type="code")
    double_example.outputs = [{
        "output_type": "execute_result",
        "execution_count": 1,
        "metadata": {},
        "data": {"text/plain": "6"},
    }]
    add_example = mk_cell("add(2, 3)", cell_type="code")
    add_example.outputs = [{
        "output_type": "execute_result",
        "execution_count": 1,
        "metadata": {},
        "data": {"text/plain": "5"},
    }]
    calculator_example = mk_cell("Calculator(10).total(2)", cell_type="code")
    calculator_example.outputs = [{
        "output_type": "execute_result",
        "execution_count": 1,
        "metadata": {},
        "data": {"text/plain": "22"},
    }]
    error_cell = mk_cell("raise RuntimeError('boom')", cell_type="code")
    error_cell.outputs = [{
        "output_type": "error",
        "ename": "RuntimeError",
        "evalue": "boom",
        "traceback": ["RuntimeError: boom"],
    }]
    nb = new_nb([
        mk_cell("# Sample tool\nNotebook-level note.", cell_type="markdown"),
        mk_cell("More file-level context.", cell_type="markdown"),
        mk_cell("import math", cell_type="code"),
        mk_cell("## Math\nThis chapter demonstrates code-first groups.", cell_type="markdown"),
        mk_cell(
            "#| export\ndef double(value):\n"
            "    \"\"\"Double a value.\"\"\"\n"
            "    return value * 2",
            cell_type="code",
        ),
        mk_cell("Double a value by multiplying it by two.", cell_type="markdown"),
        double_example,
        mk_cell("assert double(3) == 6", cell_type="code"),
        mk_cell(
            "#| export\ndef add(a, b):\n"
            "    \"\"\"Add values.\"\"\"\n"
            "    return double(a) + b",
            cell_type="code",
        ),
        mk_cell("Add the doubled first value to the second.", cell_type="markdown"),
        add_example,
        mk_cell("assert add(1, 2) == 4", cell_type="code"),
        mk_cell(
            "#| export\nclass Calculator:\n"
            "    \"\"\"Calculate values.\"\"\"\n"
            "    def __init__(self, base):\n"
            "        \"\"\"Store the base value.\"\"\"\n"
            "        self.base = base\n\n"
            "    def total(self, value):\n"
            "        \"\"\"Add value to the base.\"\"\"\n"
            "        return add(self.base, value)",
            cell_type="code",
        ),
        mk_cell("Calculator keeps a base and adds a value.", cell_type="markdown"),
        calculator_example,
        mk_cell("assert Calculator(10).total(2) == 22", cell_type="code"),
        mk_cell("### Detail\nNested rationale mentioning add.", cell_type="markdown"),
        mk_cell("detail_value = add(2, 4)", cell_type="code"),
        error_cell,
    ])
    write_nb(nb, path)
    return nb
with write_demo_notebook("01_read_filter_context_example.ipynb") as path:
    nb = new_nb([mk_cell("detail_value = 6"), mk_cell("other_value = 1")])
    write_nb(nb, path)
    filtered = _context_filtered(str(path), query="type=code regex=detail_value", verbose=False)
filtered["total_matches"], filtered["matches"][0]["cell_id"]

Use view="cell" when the selected source needs stable line numbers. Queries such as errors, export, headers, chapter=Name, and regex=term retain the existing notebook selectors while keeping the public surface on context.

Exact cell context

Use context("notebook.ipynb#cell-id", mode="exact") when an edit needs the complete selected cell without docs, callers, or truncation.

with write_demo_notebook("01_read_cell_search_examples.ipynb") as path:
    nb = _write_read_sample_notebook(path)
    summary = _context_filtered(str(path), view="summary", verbose=False)
    numbered = _context_filtered(str(path), query=f"id={nb.cells[8].id}", view="cell", verbose=False)
    errors = _context_filtered(str(path), query="errors", verbose=False)
    exports = _context_filtered(str(path), query="export", view="summary", verbose=False)
    outline = _context_filtered(str(path), query="headers", view="summary", verbose=False)
    section = _context_filtered(str(path), query="chapter=Detail", verbose=False)
    grep = _context_filtered(str(path), query="regex=detail_value", view="summary", before=1, after=1, verbose=False)
summary["matches"][0]["summary"], numbered["text"].splitlines()[10], errors["matches"][0]["error"], len(exports["matches"]), len(outline["matches"]), section["total_matches"], grep["matches"][0]["before"][0]["cell_id"]

Reading through the model

Notebook.context and Notebook.symbol remain convenience methods, while the module-level context function is the canonical reader and filter surface.


source

Notebook.context

def context(
    **kw
):

Read this notebook through the canonical context reader.


source

Notebook.symbol

def symbol(
    symbol, **kw
):

Read implementation context through the canonical context reader.


source

context

def context(
    target:str='project', scope:str='.', targets:list[str] | None=None, mode:str='auto', around:int=0,
    view:str | None=None, query:NoneType=None, include_re:str | None=None, exclude_re:str | None=None,
    max_matches:int=50, max_chars_per_cell:int=1200, context_lines:int=0, chapter:str | None=None,
    cell_type:str | None=None, include_output:bool=False, line_numbers:bool=False, verbose:bool=True
):

Read one notebook-aware target, optionally narrowed by notebook filters.

with write_demo_notebook(
    "01_read_context_filters.ipynb",
    cells=[
        mk_cell("# Notebook", cell_type="markdown"),
        mk_cell("## Addition", cell_type="markdown"),
        mk_cell("#| export\ndef add(a, b):\n    return a + b"),
        mk_cell("Add two values.", cell_type="markdown"),
        mk_cell("add(1, 2)"),
    ],
) as path:
    summary = context(str(path), scope=str(path), view="summary", verbose=False)
    markdown = context(str(path), scope=str(path), view="full", cell_type="markdown", verbose=False)
    code = context(str(path), scope=str(path), view="full", cell_type="code", verbose=False)
    chapter = context(str(path), scope=str(path), view="full", chapter="Addition", verbose=False)

assert summary["selection"]["view"] == "summary"
assert len(summary["selection"]["matches"]) == 5
assert all(item["cell_type"] == "markdown" for item in markdown["selection"]["matches"])
assert all(item["cell_type"] == "code" for item in code["selection"]["matches"])
assert all(item["cell_type"] in {"markdown", "code"} for item in chapter["selection"]["matches"])
assert any("def add" in item["source"] for item in code["selection"]["matches"])

Context modes and edit handoff

Use mode="auto", "overview", "edit", or "review"; use resolution to choose summary, implementation, or full depth. Overview returns notebook identity, headings, counts, and a next action.

mode="edit" is the bounded handoff for a named symbol: the exact source and expected_hash, followed by its trailing Docs, local examples/tests, and direct caller/callee metadata. These sections are tied to the resolved symbol, so an agent can prepare an edit without widening the search. Use around only when physical neighbor cells are also needed.

with write_demo_notebook(
    "01_read_context_example.ipynb",
    cells=[
        mk_cell("#| export\ndef add(a, b):\n    return a + b"),
        mk_cell("Add two values.", cell_type="markdown"),
        mk_cell("add(2, 3)"),
    ],
) as path:
    result = context(f"{path}#add", scope=str(path), mode="edit")
    selection = result["selection"]
    print(selection["cell_id"], selection["expected_hash"])
    print(selection["docs"][0]["source"], selection["examples"][0]["source"])

A compact context result

The public reader returns structured data and can also render the first useful line for a human.

with write_demo_notebook("01_read_output.ipynb") as path:
    result = context("project", scope=str(path))
    print(result["kind"])
    print(result["text"].splitlines()[0])

context keeps the common call useful for implementation work. For example, context("add", scope="nbs/01_read.ipynb") resolves the symbol and prints the implementation, trailing Docs, examples/tests, callers, and callees; mode="overview" keeps only the implementation source. A notebook target such as context("nbs/01_read.ipynb") prints every cell in source order, while mode="overview" keeps the main notebook description, chapter headings, and function signatures.

context("add", scope="nbs/01_read.ipynb")

Symbol documentation

context(path + "#" + symbol, mode="review") answers the symbol-first question: “what should I know before changing this implementation?” It returns the implementation, nearby Docs and examples/tests, plus caller and callee summaries in one structured result.