1d414c1383
- Added IPython-backed Python tool with streaming output and image/JSON rendering. - Implemented Jupyter kernel gateway integration with WebSocket communication. - Added Python prelude with 30+ shell-like utility functions for file operations. - Migrated environment variables from PI_ to OMP_ prefix with automatic migration. - Added streaming output system with automatic spill-to-disk for large outputs. - Reorganized settings interface into behavior, tools, display, voice, status, lsp, and exa tabs.
9.8 KiB
9.8 KiB
IPython kernel REPL tool plan (research/design)
Assumptions
- REPL state lives only for the current agent session (no disk persistence, no restore on restart).
- No sandboxing/security constraints (trusted code execution only).
- New NPM dependency is acceptable if required for ZeroMQ (confirm with maintainers).
- Tool surface will be parallel to
bash(streaming output, truncation, expand/collapse behavior).
Goals
- Add a Python REPL tool backed by IPython kernels.
- Integrate streaming/truncation behavior with the existing
bashtool UX and renderer expectations. - Provide a clean executor adapter that fits the
bash-executor/bash.tsstreaming model. - Support concurrent kernels (parallel REPL sessions) without persistence.
Non-goals
- Security sandboxing, containerization, or permission gating.
- Kernel persistence across agent restarts.
- Rich notebook UX (no cell metadata editing, no execution history storage).
Architecture overview
New components
- KernelManager (session-scoped registry)
- Owns kernel lifecycles, keyed by
kernelIdorsessionId. - Spawns kernels, tracks connection info, routes execute requests.
- Owns kernel lifecycles, keyed by
- KernelProcess
- Spawns
python -m ipykernel_launcher -f <connection.json>. - Produces
KernelConnection(ZMQ sockets) and manages shutdown.
- Spawns
- KernelConnection
- ZMQ sockets:
shell,iopub,stdin,control,hb. - Implements Jupyter messaging protocol (JSON frames + HMAC signature).
- Provides
execute(code)returning outputs + status.
- ZMQ sockets:
- KernelExecutorAdapter
- Implements a streaming executor interface mirroring
BashOperations.exec. - Converts IOPub messages to text chunks for
onChunkand captures full output.
- Implements a streaming executor interface mirroring
- PythonTool (new tool surface)
- Tool name:
python(oripython), parameters:code, optionalkernelId,timeout. - Uses
KernelExecutorAdapterwithexecuteBashWithOperations-like flow or parallel executor.
- Tool name:
Integration points
- Tool execution and streaming should match
packages/coding-agent/src/core/tools/bash.tsbehavior. - Truncation uses
truncateTailandDEFAULT_MAX_BYTESfromtools/truncate. - TUI uses existing renderer logic (collapsed/expanded, visual truncation).
Kernel lifecycle
- Create
- Generate
connection.json(temp dir). - Spawn kernel process:
python -m ipykernel_launcher -f /tmp/<kernel-id>.json
- Wait for heartbeat (HB) or a
kernel_info_requestresponse to confirm readiness.
- Generate
- Use
- For each
execute_request, setparent_header.msg_idto correlate IOPub messages. - Stream IOPub outputs to the tool renderer.
- For each
- Shutdown
- Send
shutdown_requestover control channel. - Kill process tree if shutdown fails or on timeout.
- Send
- Disposal
- Remove from registry when agent session ends or user explicitly closes.
Lifecycle constraints
- No persistence; kernel is per session.
- A kernel ID can be auto-generated per tool call unless user supplies
kernelId. - Kernel cleanup on
AbortSignal/timeout.
IPC details (Jupyter protocol essentials)
Connection file structure (generated per kernel):
{
"ip": "127.0.0.1",
"transport": "tcp",
"signature_scheme": "hmac-sha256",
"key": "<random-hex>",
"shell_port": 57541,
"iopub_port": 57542,
"stdin_port": 57543,
"control_port": 57544,
"hb_port": 57545
}
Message frames (ZMQ multipart):
identities...(routing frames)DELIM(<IDS|MSG>)signatureheader(JSON)parent_header(JSON)metadata(JSON)content(JSON)buffers...(binary)
Signature
- HMAC SHA-256 of
header|parent_header|metadata|contentusingkey.
Channels
shell: sendexecute_request, receiveexecute_reply.iopub: receive streamed outputs, status (busy/idle).stdin: input requests (can reject/auto-respond as non-interactive).control:shutdown_request,interrupt_request.hb: heartbeat for liveness.
Execute flow (mapping to current executor interface)
- Tool call:
pythontool receives{ code, kernelId?, timeout? }. - Kernel selection: KernelManager returns existing kernel by id or creates a new one.
- Executor adapter
- Calls
KernelConnection.execute(code, { onMessage, signal, timeout }). - Emits
onChunkevents by transforming IOPubstream,execute_result,display_data,error.
- Calls
- Result capture
- Aggregate output in rolling buffer (same behavior as
createOutputSink). - On completion, return final output +
exitCodeequivalent (0/1).
- Aggregate output in rolling buffer (same behavior as
ExitCode mapping
exitCode = 0ifexecute_reply.status === "ok".exitCode = 1ifstatus === "error".cancelled = trueifAbortSignaltriggered or timeout.
Streaming output mapping to renderer expectations
Source messages → text chunks
iopub: streamcontent.nameinstdout|stderr→ direct text.
iopub: execute_result- Convert
data["text/plain"]to text chunk. - If
image/pngpresent, surface as image output (kitty image rendering supported). - If
application/jsonpresent, surface as a collapsible JSON tree (reuse task renderer tree format).
- Convert
iopub: display_data- Prefer
text/plain. - Render
image/pngandapplication/jsonthe same way asexecute_resultwhen present. - If
text/htmlis present withouttext/plain, convert HTML to markdown and emit that as text output (same pattern as tools that return_renderedcontent, e.g.git-toolfetch rendering).
- Prefer
iopub: error- Join
traceback[]into lines; stream immediately.
- Join
Mapping to existing tool streaming
- Use the same
currentOutput += chunkstrategy asbash.ts. - For each chunk, call
onUpdatewith{ content: [{ type: "text", text: truncateTail(currentOutput).content }] }. - Populate
details.truncationanddetails.fullOutputwhen truncated (same as bash).
Collapsed/expanded behavior
- Render context should match
bashToolRenderer:renderContext.output→ truncated text (tail).details.fullOutput→ full output buffer when expanded.- Use same preview line count as
BASH_DEFAULT_PREVIEW_LINES.
- Expectation: collapsed view shows tail, with expand note; expanded shows full output when available.
Bun integration notes
- Use
Bun.spawnto launchpythonkernel process. - Use
node:fsfor directory creation; useBun.writefor connection file contents. - Use WebCrypto
subtle.importKey+subtle.signfor HMAC signing (avoidnode:crypto). - ZMQ library compatibility with Bun must be validated (native deps).
Tool surface design
Tool name: python (or ipy)
Prompt updates
- Update all prompts that mention the bash tool to be rendered via prompt templates so tool availability is dynamic per settings (bash-only vs ipy-only vs both).
- Add a Python tool prompt similar to
prompts/tools/bash.mdexplaining:- Execution semantics (kernel-backed, persistent within session)
- Streaming output and truncation
- Recommended uses vs. when to use other tools
- Built-in helpers (e.g.,
bash()bridge) - Matplotlib note:
plt.show()is headless; preferplt.savefig()or returning the figure object for display
Schema
Type.Object({
code: Type.String({ description: "Python code to execute" }),
kernelId: Type.Optional(Type.String({ description: "Kernel session id" })),
timeout: Type.Optional(Type.Number({ description: "Timeout in seconds" }))
})
Tool result
{ content: [{ type: "text", text: outputText }], details?: { truncation, fullOutputPath?, fullOutput? } }- Use the same detail structure as
BashToolDetailsfor consistency.
Adapter design (data structures)
KernelSession
interface KernelSession {
id: string;
connection: KernelConnection;
process: Subprocess;
createdAt: number;
lastUsedAt: number;
}
KernelExecuteResult
interface KernelExecuteResult {
output: string;
exitCode: number | undefined;
cancelled: boolean;
truncated: boolean;
fullOutputPath?: string;
}
KernelExecutorOptions
interface KernelExecutorOptions {
timeout?: number; // ms
onChunk?: (chunk: string) => void;
signal?: AbortSignal;
}
KernelOperations (bash-style adapter)
interface KernelOperations {
exec: (
code: string,
kernelId: string,
options: { onData: (data: Buffer) => void; signal?: AbortSignal; timeout?: number }
) => Promise<{ exitCode: number | null }>;
}
Execution state machine
execute_requestsent; status = busyiopubstream/output/error messages emitted →onChunkexecute_reply(shell channel) setsexitCodeiopubstatus = idle → complete
Concrete implementation steps
- KernelManager
- Session-scoped registry with
getOrCreateKernel(kernelId?)andshutdown(kernelId).
- Session-scoped registry with
- KernelProcess
- Create connection file (temp), spawn kernel, connect ZMQ sockets.
- Implement handshake:
kernel_info_requestuntil response or timeout.
- KernelConnection
- Implement message signing, send/recv, routing.
- Correlate
parent_header.msg_idto filter IOPub outputs per execute.
- KernelExecutorAdapter
- Provide
execmethod compatible withexecuteBashWithOperationsor a parallel executor. - Convert IOPub messages →
onData(Buffer.from(text)).
- Provide
- Python tool
- Wire to executor; update
toolRenderersor reuse bash renderer with context.
- Wire to executor; update
- Streaming integration
- Use
truncateTailduring updates and final output. - Provide
details.fullOutputwhen truncated.
- Use
- Shutdown/cleanup
- On tool abort or session end, dispose kernel and temp files.
Open questions (need confirmation)
- Preferred ZMQ dependency (native
zeromqvs pure JS fallback). - Whether to reuse
bashToolRendereror create a dedicated python renderer. - How to handle rich outputs (images/HTML) in future iterations.
- Whether to support input requests (stdin channel) or always error.