Notebook tool runtime internals
This document describes the current notebook tool implementation and its relationship to the kernel-backed Python runtime.
The critical distinction: notebook is a JSON notebook editor, not a notebook executor. It edits .ipynb cell sources directly; it does not start or talk to a Python kernel.
Implementation files
src/tools/notebook.tssrc/ipy/executor.tssrc/ipy/kernel.tssrc/session/streaming-output.tssrc/tools/python.ts
1) Runtime boundary: editing vs executing
notebook tool (src/tools/notebook.ts)
- Supports
action: edit | insert | deleteon a.ipynbfile. - Resolves path relative to session CWD (
resolveToCwd). - Loads notebook JSON, validates
cellsarray, validatescell_indexbounds. - Applies source edits in-memory and writes full notebook JSON back with
JSON.stringify(notebook, null, 1). - Returns textual summary + structured
details(action,cellIndex,cellType,totalCells,cellSource).
No kernel lifecycle exists in this tool:
- no gateway acquisition
- no kernel session ID
- no
execute_request - no stream chunks from kernel channels
- no rich display capture (
image/png, JSON display, status MIME)
Notebook-like execution path (src/tools/python.ts + src/ipy/*)
When the agent needs to run cell-style Python code (sequential cells, persistent state, rich displays), that goes through the python tool, not notebook.
That path is where kernel modes, restart/cancel behavior, chunk streaming, and output artifact truncation live.
2) Notebook cell handling semantics (notebook tool)
Source normalization
content is split into source: string[] with newline preservation:
- each non-final line keeps trailing
\n - final line has no forced trailing newline
This mirrors notebook JSON conventions and avoids accidental line concatenation on later edits.
Action behavior
edit- replaces
cells[cell_index].source - preserves existing
cell_type
- replaces
insert- inserts at
[0..cellCount] cell_typedefaults tocode- code cells initialize
execution_count: nullandoutputs: [] - markdown cells initialize only
metadata+source
- inserts at
delete- removes
cells[cell_index] - returns removed
sourcein details for renderer preview
- removes
Error surfaces
Hard failures are thrown for:
- missing notebook file
- invalid JSON
- missing/non-array
cells - out-of-range index (insert and non-insert have different valid ranges)
- missing
contentforedit/insert
These become Error: tool responses upstream; renderer uses notebook path + formatted error text.
3) Kernel session semantics (where they actually exist)
Kernel semantics are implemented in executePython / PythonKernel and apply to the python tool.
Modes
PythonKernelMode:
session(default)- kernels cached in
kernelSessionsmap - max 4 sessions; oldest evicted on overflow
- idle/dead cleanup every 30s, timeout after 5 minutes
- per-session queue serializes execution (
session.queue)
- kernels cached in
per-call- creates kernel for request
- executes
- always shuts down kernel in
finally
Reset behavior
python tool passes reset only for the first cell in a multi-cell call; later cells always run with reset: false.
Kernel death / restart / retry
In session mode (withKernelSession):
- dead kernel detected by heartbeat (
kernel.isAlive()check every 5s) or execute failure. - pre-run dead state triggers
restartKernelSession. - execute-time crash path retries once: restart kernel, rerun handler.
restartCount > 1in same session throwsPython kernel restarted too many times in this session.
Startup retry behavior:
- shared gateway kernel creation retries once on
SharedGatewayCreateErrorwith HTTP 5xx.
Resource exhaustion recovery:
- detects
EMFILE/ENFILE/"Too many open files" style failures - clears tracked sessions
- calls
shutdownSharedGateway() - retries kernel session creation once
4) Environment/session variable injection
Kernel startup receives optional env map from executor:
PI_SESSION_FILE(session state file path)ARTIFACTS(artifact directory)
PythonKernel.#initializeKernelEnvironment(...) then runs init script inside kernel to:
os.chdir(cwd)- inject env entries into
os.environ - prepend cwd to
sys.pathif missing
Implication:
- prelude helpers that read session or artifact context rely on these env vars in Python process state.
5) Streaming/chunk and display handling (kernel-backed path)
The kernel client processes Jupyter protocol messages per execution:
stream-> text chunk toonChunkexecute_result/display_data->- display text chosen by MIME precedence:
text/markdown>text/plain> convertedtext/html - structured outputs captured separately:
application/json->{ type: "json" }image/png->{ type: "image" }application/x-pisces-status->{ type: "status" }(no text emission)
- display text chosen by MIME precedence:
error-> traceback text pushed to chunk stream + structured error metadatainput_request-> emits stdin warning text, sends emptyinput_reply, marks stdin requested- completion waits for both
execute_replyand kernelstatus=idle
Cancellation/timeout:
- abort signal triggers
interrupt()(REST/interrupt+ control-channelinterrupt_request) - result marks
cancelled=true - timeout path annotates output with
Command timed out after <n> seconds
6) Truncation and artifact behavior
OutputSink in src/session/streaming-output.ts is used by kernel execution paths (executeWithKernel):
- sanitizes every chunk (
sanitizeText) - tracks total/output lines and bytes
- optional artifact spill file (
artifactPath,artifactId) - when in-memory buffer exceeds threshold (
DEFAULT_MAX_BYTESunless overridden):- marks truncated
- keeps tail bytes in memory (UTF-8 safe boundary)
- can spill full stream to artifact sink
dump() returns:
- visible output text (possibly tail-truncated)
- truncation flag + counts
- artifact ID (for
artifact://<id>references)
python tool converts this metadata into result truncation notices and TUI warnings.
notebook tool does not use OutputSink; it has no stream/artifact truncation pipeline because it does not execute code.
7) Renderer assumptions and formatting
Notebook renderer (notebookToolRenderer)
- call view: status line with action + notebook path + cell/type metadata
- result view:
- success summary derived from
details cellSourcerendered viarenderCodeCell- markdown cells set language hint
markdown; other cells have no explicit language override - collapsed code preview limit is
PREVIEW_LIMITS.COLLAPSED_LINES * 2 - supports expanded mode via shared render options
- uses render cache keyed by width + expanded state
- success summary derived from
Error rendering assumption:
- if first text content starts with
Error:, renderer formats as notebook error block.
Python renderer (for actual execution output)
Kernel-backed execution rendering expects:
- per-cell status transitions (
pending/running/complete/error) - optional structured status event section
- optional JSON output trees
- truncation warnings + optional
artifact://<id>pointer
This renderer behavior is unrelated to notebook JSON editing results except that both reuse shared TUI primitives.
8) Divergence from plain Python tool behavior
If "plain Python tool" means python execution path:
pythonexecutes code in a kernel, persists state by mode, streams chunks, captures rich displays, handles interrupts/timeouts, and supports output truncation/artifacts.notebookperforms deterministic notebook JSON mutations only; no execution, no kernel state, no chunk stream, no display outputs, no artifact pipeline.
If a workflow needs both:
- edit notebook source with
notebook - execute code cells via
python(manually passing code), not throughnotebook
Current implementation does not provide a single tool that both mutates .ipynb and executes notebook cells through kernel context.