chore: updated docs

This commit is contained in:
can1357
2026-05-31 04:36:09 +02:00
parent c1a70f7261
commit 1dba122c53
90 changed files with 1931 additions and 1614 deletions
+94 -125
View File
@@ -1,77 +1,79 @@
# Notebook tool runtime internals
# Notebook file runtime internals
This document describes the current `notebook` tool implementation and its relationship to the kernel-backed Python runtime.
This document describes current `.ipynb` handling in `coding-agent` 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.
The critical distinction: **notebook support is file conversion/editing, not notebook execution**. `.ipynb` files are exposed as editable cell-marked text through `read` and the edit pipeline; no notebook-specific tool starts or talks to a Python kernel.
## Implementation files
- [`src/edit/notebook.ts`](../packages/coding-agent/src/edit/notebook.ts)
- [`src/edit/read-file.ts`](../packages/coding-agent/src/edit/read-file.ts)
- [`src/tools/read.ts`](../packages/coding-agent/src/tools/read.ts)
- [`src/tools/eval.ts`](../packages/coding-agent/src/tools/eval.ts)
- [`src/eval/py/executor.ts`](../packages/coding-agent/src/eval/py/executor.ts)
- [`src/eval/py/kernel.ts`](../packages/coding-agent/src/eval/py/kernel.ts)
- [`src/session/streaming-output.ts`](../packages/coding-agent/src/session/streaming-output.ts)
- [`src/tools/eval.ts`](../packages/coding-agent/src/tools/eval.ts)
## 1) Runtime boundary: editing vs executing
## `notebook` tool (`src/edit/notebook.ts`)
## `.ipynb` file conversion (`src/edit/notebook.ts`)
- Supports `action: edit | insert | delete` on a `.ipynb` file.
- Resolves path relative to session CWD (`resolveToCwd`).
- Loads notebook JSON, validates `cells` array, validates `cell_index` bounds.
- 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`).
- `read` treats `.ipynb` files as notebooks unless the selector is `:raw`.
- The default notebook view is editable text with markers:
- `# %% [code] cell:N`
- `# %% [markdown] cell:N`
- `# %% [raw] cell:N`
- Line selectors and multi-range selectors operate on that virtual text.
- Edit/write paths round-trip virtual text back to notebook JSON through `serializeEditedNotebookText(...)`.
- Existing notebook metadata is preserved when a marker references an existing `cell:N`; new cells get fresh empty metadata.
- Missing notebooks edited through this path start from an empty nbformat 4.5 notebook.
No kernel lifecycle exists in this tool:
No kernel lifecycle exists in this path:
- 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)
- no code execution
- no stream chunks from Python
- no rich display capture
- no output artifact pipeline from execution
## Notebook-like execution path (`src/tools/eval.ts` + `src/eval/py/*`)
## Kernel-backed execution path (`src/tools/eval.ts` + `src/eval/py/*`)
When the agent needs to run cell-style Python code (sequential cells, persistent state, rich displays), that goes through the **`eval` tool** with `language: "python"`, not `notebook`.
When the agent needs to run cell-style Python code (sequential cells, persistent state, rich displays), that goes through the **`eval` tool** with per-cell `language: "py"`, not through notebook file handling.
That path is where kernel modes, restart/cancel behavior, chunk streaming, and output artifact truncation live.
That path is where Python subprocess lifecycle, reset/cancel behavior, chunk streaming, rich displays, and output artifact truncation live.
## 2) Notebook cell handling semantics (`notebook` tool)
## 2) Notebook cell handling semantics
## Source normalization
`content` is split into `source: string[]` with newline preservation:
Notebook JSON `source` is converted to virtual text by joining source arrays. When virtual text is serialized back, cell source is split with newline preservation:
- each non-final line keeps trailing `\n`
- final line has no forced trailing newline
- each line ending in `\n` stays as a separate source entry with the newline
- a final non-newline-terminated line is stored without forcing a trailing newline
- empty content becomes an empty `source` array
This mirrors notebook JSON conventions and avoids accidental line concatenation on later edits.
## Action behavior
## Marker parsing and cell preservation
- `edit`
- replaces `cells[cell_index].source`
- preserves existing `cell_type`
- `insert`
- inserts at `[0..cellCount]`
- `cell_type` defaults to `code`
- code cells initialize `execution_count: null` and `outputs: []`
- markdown cells initialize only `metadata` + `source`
- `delete`
- removes `cells[cell_index]`
- returns removed `source` in details for renderer preview
- The first representation line must be a marker; text before the first marker, including a blank line, is rejected.
- Markers must match `# %% [code|markdown|raw]` with optional `cell:N`.
- If `cell:N` points at an unused existing cell, that cell is cloned, its `cell_type` and `source` are updated, and unrelated metadata is preserved.
- If no valid unused original index is present, a new cell is created.
- Code cells ensure `execution_count` exists and `outputs` exists.
- Markdown/raw cells remove `execution_count` and `outputs`.
## Error surfaces
Hard failures are thrown for:
- missing notebook file
- missing notebook on read
- invalid JSON
- missing/non-array `cells`
- out-of-range index (insert and non-insert have different valid ranges)
- missing `content` for `edit`/`insert`
- invalid cell objects or cell types
- invalid editable representation (for example, text before the first cell marker)
These become `Error:` tool responses upstream; renderer uses notebook path + formatted error text.
These surface through the caller (`read`, edit, or `write`) as normal tool errors.
## 3) Kernel session semantics (where they actually exist)
@@ -82,136 +84,103 @@ Kernel semantics are implemented in `executePython` / `PythonKernel` and apply t
`PythonKernelMode`:
- `session` (default)
- kernels cached in `kernelSessions` map
- max 4 sessions; oldest evicted on overflow
- idle/dead cleanup every 30s, timeout after 5 minutes
- per-session queue serializes execution (`session.queue`)
- kernels are cached by `(session id, cwd)`
- multiple owners can share a retained kernel for the same key
- execution is serialized by the tool's exclusive concurrency and backend execution path
- dead kernels are replaced before execution
- `per-call`
- creates kernel for request
- creates a subprocess for the request
- executes
- always shuts down kernel in `finally`
- always shuts down the subprocess in `finally`
## Reset behavior
`eval` passes `reset` only for the first cell in a multi-cell Python call; later cells always run with `reset: false`.
Each eval cell has its own optional `reset` flag. `reset: true` resets the selected Python session before that cell executes; it is not a top-level tool parameter.
## Kernel death / restart / retry
In session mode (`withKernelSession`):
In session mode:
- 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 > 1` in same session throws `Python kernel restarted too many times in this session`.
Startup retry behavior:
- shared gateway kernel creation retries once on `SharedGatewayCreateError` with HTTP 5xx.
Resource exhaustion recovery:
- detects `EMFILE`/`ENFILE`/"Too many open files" style failures
- clears tracked sessions
- calls `shutdownSharedGateway()`
- retries kernel session creation once
- if the retained subprocess is not alive before execution, it is replaced
- if execution fails because the subprocess died, the kernel is replaced and the code is retried once
- explicit `reset` is rejected while another reset for the same session key is already in progress
## 4) Environment/session variable injection
Kernel startup receives the optional session file path from executor:
Kernel startup and per-execution environment patching can receive:
- `PI_SESSION_FILE` (session state file path)
- `PI_SESSION_FILE`
- `PI_ARTIFACTS_DIR`
- `PI_TOOL_BRIDGE_URL`
- `PI_TOOL_BRIDGE_TOKEN`
- `PI_TOOL_BRIDGE_SESSION`
`PythonKernel.#initializeKernelEnvironment(...)` then runs init script inside kernel to:
- `os.chdir(cwd)`
- inject env entries into `os.environ`
- prepend cwd to `sys.path` if missing
Implication:
- prelude helpers that read session context rely on this env var in Python process state.
The runner initializes process state so code executes in the requested cwd, managed env entries are reflected in `os.environ`, and cwd is available on `sys.path`.
## 5) Streaming/chunk and display handling (kernel-backed path)
The kernel client processes Jupyter protocol messages per execution:
The Python backend uses an NDJSON subprocess runner. The host processes frames per execution:
- `stream` -> text chunk to `onChunk`
- `execute_result` / `display_data` ->
- display text chosen by MIME precedence: `text/markdown` > `text/plain` > converted `text/html`
- structured outputs captured separately:
- `application/json` -> `{ type: "json" }`
- `image/png` -> `{ type: "image" }`
- `application/x-omp-status` -> `{ type: "status" }` (no text emission)
- `error` -> traceback text pushed to chunk stream + structured error metadata
- `input_request` -> emits stdin warning text, sends empty `input_reply`, marks stdin requested
- completion waits for both `execute_reply` and kernel `status=idle`
- `stdout` / `stderr` -> text chunks to `onChunk`
- `display` / `result` -> MIME bundle rendering
- `error` -> traceback text and structured error metadata
- `done` -> final status, execution count, cancellation state
Display text MIME precedence:
1. `text/markdown`
2. `text/plain`
3. converted `text/html`
Structured outputs captured separately include:
- `application/json` -> JSON display output
- `image/png` / `image/jpeg` -> image output
- `application/x-omp-status` -> status event
Cancellation/timeout:
- abort signal triggers `interrupt()` (REST `/interrupt` + control-channel `interrupt_request`)
- result marks `cancelled=true`
- timeout path annotates output with `Command timed out after <n> seconds`
- abort/timeout sends `SIGINT` to the runner
- if the runner does not settle after the interrupt grace window, shutdown escalates and the kernel is recreated on the next call
- timeout output is annotated with a timeout message
## 6) Truncation and artifact behavior
`OutputSink` in `src/session/streaming-output.ts` is used by kernel execution paths (`executeWithKernel`):
`OutputSink` in `src/session/streaming-output.ts` is used by kernel execution paths:
- sanitizes every chunk (`sanitizeText`)
- sanitizes every chunk
- tracks total/output lines and bytes
- optional artifact spill file (`artifactPath`, `artifactId`)
- when in-memory buffer exceeds threshold (`DEFAULT_MAX_BYTES` unless 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)
- optionally spills full output to an artifact file
- keeps a UTF-8-safe in-memory tail buffer when output exceeds the configured threshold
`eval` 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.
Notebook file conversion 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`)
## Read/edit notebook representation
- call view: status line with action + notebook path + cell/type metadata
- result view:
- success summary derived from `details`
- `cellSource` rendered via `renderCodeCell`
- 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
Error rendering assumption:
- if first text content starts with `Error:`, renderer formats as notebook error block.
Notebook files are rendered to the model as text. The visible cell markers are part of the editable representation, not comments that are ignored during serialization.
## Python renderer (for actual execution output)
Kernel-backed execution rendering expects:
- per-cell status transitions (`pending/running/complete/error`)
- optional structured status event section
- per-cell status transitions (`pending` / `running` / `complete` / `error`)
- optional structured status events
- optional JSON output trees
- image outputs
- truncation warnings + optional `artifact://<id>` pointer
This renderer behavior is unrelated to `notebook` JSON editing results except that both reuse shared TUI primitives.
This renderer behavior is unrelated to notebook JSON editing except that both reuse shared TUI primitives.
## 8) Divergence from eval Python backend behavior
## 8) Practical workflow
If "plain Python execution" means the `eval` tool with `language: "python"`:
If a workflow needs both notebook mutation and execution:
- `eval` executes code in a kernel, persists state by mode, streams chunks, captures rich displays, handles interrupts/timeouts, and supports output truncation/artifacts.
- `notebook` performs deterministic notebook JSON mutations only; no execution, no kernel state, no chunk stream, no display outputs, no artifact pipeline.
If a workflow needs both:
1. edit notebook source with `notebook`
2. execute code cells via `eval` with `language: "python"` (manually passing code), not through `notebook`
1. read or edit the `.ipynb` file through the normal file tools
2. copy the desired cell source into `eval` cells with `language: "py"` to execute it
3. write resulting source changes back to the notebook if needed
Current implementation does not provide a single tool that both mutates `.ipynb` and executes notebook cells through kernel context.