ipyai

A terminal IPython extension that adds Claude and Codex powered prompting

View the Project on GitHub AnswerDotAI/ipyai

DEV

UI/compositor design notes live in teleprint’s DEV.md. This file covers ipyai’s own architecture after the gateway/session-files clean break (2026-08), and what is deliberately not done yet.

Architecture

ipyai is a rustygate client, like clikernel: rustygate runs all the time and hosts the kernels (its default spawn is ipymini); ipyai starts and stops per session and holds a pointer. KernelSession (kernel.py) wraps a JupyAsyncMultiKernelManager plus one kernel’s ws client: bare ipyai creates an owned kernel (started in our cwd/env, seeded with ipykernel_helper.core, shut down on exit); -k PREFIX attaches to an existing kernel, taken as found — no seeding, never stopped by us. An unreachable gateway fails loudly at startup with the command to run; there is no auto-start.

The wire conveniences (reply, eval, sig_help, get_schemas, xpush, …) are jupyasyncclient’s eval family; kernel_bridge.py is a thin tool-dispatch layer over them (call_tool via the kernel’s call_tool for input coercion, FullResponse via aidialog.msg_parts). Kernel-side services come from ipyfuncs (signature help with active-param tracking, ranked completion, schemas), loaded via ipykernel_helper (which also provides call_tool).

Sessions are files: each session is one aidialog Dialog written whole (atomically) to ./.ipyai/sessions/<uuid>.ipynb on every event (solveit-precedented; the file is the only copy). Dialog.meta['ipyai'] carries kernel_id/model/think. Resume (-r PREFIX) paints the transcript and continues in the same file, warm-attaching the stamped kernel when it is still alive (transcript + live state), else cold on a fresh kernel; bare -r opens the picker over this directory’s files. Load (%ipyai load) remains the separate rebuild-state feature. History/ghost text mine the session files, mode-scoped by message type; directory scoping is the filesystem. IPython’s history.sqlite is neither read nor written.

The AI layer is aidialog: dlg2hist + the default prompt envelope; $/! refs parse with get_exprs/sigil_pat and render as one merged variables turn via vars_hist (! outputs keyed by their full ref form); missing vars warn via warning_tag; the interrupt marker is aidialog.dialog.INTERRUPTED. Tool activation still uses CUSTOM_TOOL_NAMES seeding; dialog-referenced &`tool` activation over get_refs is a small planned follow-up.

The shell is a gateway terminal (settled 2026-08-04): shell-mode submissions run in an owned rustygate terminal (shell.py’s GateShell over JupyAsyncTerminalClient), fresh per session and deleted on exit, spawned in the kernel’s cwd with the app’s TERM/COLORTERM overlaid. The rc/sentinel choreography is teleprint’s original (moved here when teleprint/jobs.py was deleted): it is entirely in-band, so relay keeps the old relay_shell contract over ws frames, and the shell↔kernel cwd sync is now coherent by construction — both live on the gateway’s box. Watch-items: a gap control frame (client fell behind the gateway’s 1MB replay ring) may in principle swallow a sentinel — the relay sinks a visible “press Enter if stuck” note rather than probing, since a probe newline could reach a foreground job’s stdin; revisit if it ever bites (the 20k-line burst test streams gap-free today). Full-screen fidelity/latency through the ws bridge is untested beyond the alt-screen test; judge it in daily use.

Deliberately not done yet