ipymini supplies Python language semantics to the Rust kernmini engine. IPython handles execution, history, completion, inspection, display, magics, configuration, and debugging. Kernmini handles Jupyter transport, messages, queues, output, stdin, interrupts, subshells, and lifecycle.
The user-facing installation and kernelspec instructions are in README.md. Kernmini's DEV.md documents the language boundary and protocol engine.
- Keep the Jupyter engine in Rust and Python semantics in IPython.
- Use IPython machinery instead of reproducing Python behavior.
- Match ipykernel where clients can tell: message content, output ordering, history, inspection, completion, comms, debugging, and interrupts.
- Avoid traitlets and Tornado in the kernel host. IPython configuration remains supported through
InteractiveShellApp.
ipymini/kernel.py: a shell factory passed to the synchronous Rust-backedkernmini.run_kernel.ipymini/shell/: the IPython language adapter: execution, kernel operations, completion, inspection, history, comm binding, and debugging.ipymini/term/: Python stdout/stderr, input, display, andget_ipython()capture.ipymini/debug/: DAP/debugpy integration and ipykernel-compatible cell filenames.ipymini/__main__.py: CLI entry and kernelspec installation.tests/: protocol and behavioral integration tests, including unmodified jupyter_client clients.
ipymini.kernel.run_kernel creates a closure around one shared namespace and passes it to kernmini. The first factory call creates the parent InteractiveShell singleton. Child calls create independent InteractiveShell instances sharing that namespace and the parent's in-memory history.
The parent shell runs on a persistent loopmini loop in the Python main thread. Each child runs on its own OS thread and persistent loopmini loop. Kernmini's Tokio runtime drives protocol and control work independently, so a synchronous Python cell cannot block the kernel engine or another subshell.
The executable requests process-group ownership. On POSIX this isolates the kernel and lets kernmini terminate user-created child processes during shutdown. Kernmini also watches the original parent PID. Embedded callers can disable process-group ownership, but the ipymini executable does not.
The Rust engine validates and queues the request, publishes busy and execute_input, then calls MiniShell.execute with an execution-scoped context. The PyO3 adapter installs live stream, display, and input senders on each shell and stores the current Rust ExecutionContext in a Python ContextVar.
MiniShell.execute runs the cell through IPython's run_cell_async. Its execution context resets capture state and binds this shell's IPython instance, stdout, stderr, input, and display hooks. The display hook publishes each expression result through kernmini as it occurs, including multiple results with ast_node_interactivity='all'. The returned snapshot supplies errors, user expressions, and payload; kernmini publishes the reply before idle. Standalone shells without a bound kernel retain the last result in their snapshot.
Every shell enables fastcore's enable_async_magics when it's created, so IPython awaits a coroutine that a line or cell magic returns.
A synchronous cell runs run_cell inside kernmini.user_code(). kernmini interrupts code there by raising KeyboardInterrupt. An interrupt cancels an async cell's task at its next await. ipymini reports the CancelledError as KeyboardInterrupt.
kernmini's DEV.md specifies three kernel operations: schemas, call and eval. A client sends one as an execute with empty code and names the operation in the execute's metadata. MiniShell.execute reads the metadata from get_ipython().kernel.get_parent() and runs the operation in place of the cell. kernel_info() lists the three operations in supported_features.
shell/ops.py implements them with fastcore.funccall:
schemasbuilds each named tool's JSON schema withget_schema_nm. Names stay as written, such ascalc.add.callruns a tool withcall_func, then awaits the result if it's awaitable. The tool's prints and displays go out as usual.evalevaluates each expression in the user namespace, without awaiting, and returns oneuser_expressionsrecord per expression.
ipymini formats a call or eval result with IPython's display formatter, with two exceptions. A str is its own text rather than its repr. A bytes value that fastcore.xtras.detect_mime recognises, such as a PNG image or a PDF, becomes data of that MIME type. ipymini base64-encodes every bytes value in the bundle, such as a PIL image's image/png. It adds full: true to the metadata of a result whose class sets full = True, as fastcore's Safe and PrettyString and aidialog's FullResponse do. Clients don't truncate such results. Each eval record's metadata also gives the value's type name as type.
Operations run their synchronous user code inside kernmini.user_code(). An interrupt stops a blocking tool there as it stops a synchronous cell.
term/io.py installs process-wide dispatchers for sys.stdout, sys.stderr, input, getpass, and get_ipython. Their targets come from execution ContextVars. threading.Thread.start and ThreadPoolExecutor.submit copy the current context, so output from user-created threads remains attributed to the cell that created them.
MiniStream is a file-like stdout/stderr sink. During kernel execution it sends complete lines through kernmini's live stream callback; standalone shells can instead retain and coalesce events. MiniDisplayPublisher and MiniDisplayHook publish rich displays and expression values. The display hook flushes pending stream text before publishing a result.
The adapter's input callback crosses into Rust, which sends input_request to the correct client and blocks only the calling Python thread until input_reply. An interrupt completes the request with KeyboardInterrupt.
Each language session executes one cell at a time. Completion, inspection, history, comms, debugging, and control requests remain responsive while it runs.
subshell() temporarily routes subsequent execute requests from the same client session to a child shell. The child has its own thread and IPython instance but shares the namespace. This allows genuinely concurrent synchronous work without requiring frontend JEP 91 support.
sidecar() uses the same routing mechanism but retains the named kernel-wide sidecar subshell for reuse. Both helpers are available through from ipymini import sidecar, subshell and get_ipython().kernel.
Kernmini also recognizes priority and hold execute metadata. These are Answer.AI extensions; normal Jupyter clients are unaffected.
MiniShell.comm_info and MiniShell.message adapt the standalone comm package to kernmini's language boundary; kernmini contains no IPython-specific comm code. shell/comms.py binds outgoing messages to kernmini's small Python kernel proxy. Inbound comm messages run under an output context, so callback output has the inbound message as its parent. Outbound comm messages publish through the current Rust execution context, including from threads spawned by a cell.
These delegate to IPython: HistoryManager, object_inspect_mime, the IPython completer, and the input transformer's completeness checker. IPYMINI_USE_JEDI overrides the completer's Jedi setting.
Completion adds a rank to each _jupyter_types_experimental item for clients to sort by: 1 for parameters, 2 for names defined in __main__, 3 for the rest, plus 0.1 for a private name. It leaves out dunder names unless the code contains __.
Inspection adds signature help for the call at the cursor as application/json: the callable's label, type, module, docstring and parameters, and idx, the index of the argument being typed. shell/sighelp.py gets it from the live object, or from jedi for a call that the namespace can't resolve, such as one to a function defined in the same cell.
A small InteractiveShellApp loads ipython_kernel_config.py, configured extensions, and profile_default/startup/*.py during first shell construction. tests/kernel/test_ipython_startup_integration.py covers both paths.
debug/dap.py implements Python-specific Jupyter DAP requests in front of debugpy. Kernmini's Rust DapClient owns byte-framed TCP, request correlation, timeouts, events, and connection failure. ipymini owns debugpy startup, Python tracing, cell sources, variable inspection, and IPython integration.
inspectVariables lists the user namespace without IPython's hidden names, such as In and Out, and without names that start with _. Each value is its repr, cut to 200 characters.
debug/cells.py hashes cell source with the same Murmur2 algorithm and seed as ipykernel, allowing debugger frontends to map notebook cells to temporary Python files. KERNMINI_CELL_NAME overrides that filename.
IPYMINI_USE_JEDI=0|1: override IPython's Jedi setting.KERNMINI_CELL_NAME: override the debugger cell filename.KERNMINI_HOLD_TIMEOUT: held-execution backstop in seconds, default 3600.KERNMINI_IOPUB_QMAX: Rust IOPub queue capacity, default 10000.
Run non-slow tests:
pytest -qRun the full suite once, including slow lifecycle stories:
tools/run_tests.shThe vanilla-client compatibility tests use unmodified jupyter_client. Most protocol stories use ConKernelClient through tests/aclient.py; use the raw tests/kernel_utils.py harness only when the client or transport shape is the subject.
Prefer narrative protocol tests over unit tests of private machinery. Avoid sleeps where a protocol event can provide synchronization.
Use fastai Python style and run chkstyle after Python edits. Release through the repository's fastship workflow.