Solveit is a "Dialog Engineering" web application for interactive development. Unlike ChatGPT (pure chat) or Jupyter (pure code), Solveit combines three message types in one workspace: code execution, markdown notes, and AI prompts. Users build solutions incrementally—writing a few lines, understanding them, then continuing—rather than generating large code blocks.
The AI sees the full dialog context (code, outputs, notes, prompts) when responding -- but only those ABOVE the current message. Users can edit any message at any time, including AI responses—the dialog is a living document, not an append-only log.
The dialog is a running ipykernel instance. A "dialog" is like a "Jupyter notebook", and uses a compatible ipynb file format, but provides a superset of functionality (in particular, "prompt messages"). A "message" is like a "Jupyter cell", with additional attributes stored as ipynb cell metadata. Most standard jupyter functionality is supported (including cell magics like %%js, %%html, %%bash, and custom magics via @line_magic/@cell_magic decorators), except for ipywidgets. Cell magics can be async; if an async magic returns a FastHTML FT component, it's auto-converted to HTML.
- Instance: A persistent Linux container (virtual private server) with home dir at
/app/data. Each user can have multiple instances. Instances have private URLs (for the solveit interface) and public URLs (for hosting apps on port 8000). - Dialog: An
.ipynbfile containing messages. Each open dialog runs its own Python kernel (ipykernel). Dialogs can be organized into folders. - Kernel: A running Python 3.12 interpreter maintaining state. Variables persist between code messages. Each dialog has its own isolated kernel. Kernels keep running when navigating away; must explicitly stop via UI or shutdown. Use
%load_ext autoreloadand%autoreload 2to auto-reload modules when files change.
CRAFT.ipynb files provide reusable AI context and auto-executed code for a folder:
- Note/prompt messages are prepended to the AI's context for all dialogs in that folder and subfolders
- Code messages are automatically executed in the kernel when opening a dialog
TEMPLATE.ipynb files serve as dialog templates—their cells are prepended to new dialogs created in that folder.
Both use path hierarchy: files from parent folders are included too, letting you build layered configurations (e.g., org-wide settings in root, project-specific in subfolders).
| Type | Purpose | Input | Output |
|---|---|---|---|
code |
Python execution | Python source | ipynb-format output list (stream, execute_result, display_data, error) |
note |
Documentation | Markdown with optional attachments | None |
prompt |
AI interaction | User question/instruction | AI response (markdown string) |
raw |
Frontmatter/metadata | Raw text (no processing, no variable injection) | None |
The Monaco editor dynamically switches syntax highlighting based on cell magics — %%html switches to HTML highlighting, %%js to JavaScript, etc. Raw messages also pick up highlighting from magic headers. Markdown() objects returned from code cells render with full formatting. Markdown is rendered using mistlefoot (an extension of mistletoe) with support for header attributes, todo lists, and other extensions.
- Prompt outputs: Editable via
nkey or clicking title bar. Can undo AI responses withCmd+Z. - Code outputs: NOT directly editable. Clear with
Backspace, or re-run message.
When a user sends a prompt, solveit builds context for the AI:
- Message Collection: Gather all messages up to and including the current prompt
- Filtering: Exclude messages where
skipped=True(hidden viahkey or 👁️ button). Hiding propagates to children when applied to a collapsed section. - Truncation: If total tokens exceed model limit, oldest non-pinned messages are dropped
- History: Messages grouped into chunks by prompt boundaries and LLM history recreated
Pinned messages serve three purposes:
- Context preservation: Pinned messages stay available even when long dialogs get truncated
- Export control: Pinned messages are excluded from exports (Python files, gists, published dialogs)
- Bulk management: Pinning a collapsed header pins all child messages too
Use for API docs, examples, and reference materials that should persist but not be exported.
Context Window Implications: The AI only sees messages ABOVE the current prompt. Working at the bottom of a dialog includes MORE context (all messages above). Working higher up includes LESS context. Adding messages earlier in the dialog doesn't increase context for prompts below them.
Users can inject Python values into AI context using $name syntax in prompts. This works with:
- Variables:
$myvar`` — injects the current value - Expressions:
$len(items)**,$df.shape,$random()` — evaluated fresh each prompt
When the AI sees the prompt, values are fetched from the kernel and included in a separate section of the chat history. Expressions are re-evaluated on every prompt, so $random()`** gives a new value each time.
Images stored in variables (as bytes) are also supported and sent as base64, as are message image attachments if referred to in the content in a link with a #ai hash.
Tools let the AI call Python functions to accomplish tasks. This is the mechanism for AI to read files, search code, modify the dialog, etc.
Users declare tools in any message using & followed by `function_name` or `[func1, func2, func3]`. Object methods can be exposed using dotted syntax: & `obj.method` — dots are converted to dashes for API compatibility and back when calling. Solveit then:
- Finds the function in the kernel's namespace
- Extracts the schema from type annotations and docstring
- Sends the schema to the AI via standard tool calling mechanisms
- When AI calls the tool, solveit executes
function_name(**kwargs)in the kernel - Returns the result to the AI
A function becomes a valid tool if it has:
- Type annotations for ALL parameters
- A docstring describing what it does
- Fastcore param docments are passed in the json schema if present
def get_user(user_id: int) -> dict:
"Fetch user data by ID."
return {"id": user_id, "name": "Alice"}When use_tools setting is enabled (🔧 wrench icon), tools from dialoghelper.stdtools are added. This includes:
- Python execution:
python— safe sandboxed access to the running interpreter (see below) - File tools:
view,create,insert,str_replace,strs_replace,replace_lines - Search tools:
rg,sed,ast_grep - Dialog tools:
add_msg,update_msg,find_msgs,read_msg,read_msgid,dialog_link - Inspection tools:
symsrc,symval,symdir,symtype,symlen,symslice,symsearch,symnth.symtypeandsymvalaccept comma-separated symbol lists. - Web tools:
read_url,web_search
Gives the AI safe, direct access to the running Python interpreter via RestrictedPython. The AI can write and execute arbitrary Python — iterate over data, filter, parse, define helpers — all autonomously within a single tool loop. Access includes: all non-callable globals, registered tools, most builtins (dict, list, Path, comprehensions, sorted, etc.). Callable object attributes are gated — only methods registered via allow() can be called. Symbols named with a trailing _ (e.g. result_) are exported back to the namespace. Async/await is supported. print() and warnings are captured and returned alongside the result.
The dialoghelper module provides async functions for programmatic dialog manipulation:
await add_msg(content, msg_type='note')- Add message below current (supportsplacement='add_after'/'add_before'withidin other dialogs viadname)await update_msg(id, content=None, ...)- Update existing message.log_changed=Trueinserts a diff note.await del_msg(id)- Delete message.log_changed=Trueinserts a note showing deleted content.await find_msgs(re_pattern)- Search messages by content.headers_only=Truereturns just headings.header_section="## Name"returns a section and its children.await read_msg()- Read message at indexawait read_msgid(id)- Read message by ID.add_to_dlg=Truecopies content into current dialog.dialog_link(path)- Generate clickable HTML link to a dialogurl2note(url)- Fetch URL and add as markdown note- Message editing tools (
msg_str_replace,msg_replace_lines,msg_insert_line,msg_del_lines,msg_strs_replace) all supportupdate_output=Trueto edit message output instead of content, andlog_changed=Trueto record diffs.
Note: Nearly all dialoghelper tools are async. Use await when calling from code cells.
use_tools=True+use_fence=False: AI can call tools directly (tool_choice=auto)use_tools=True+use_fence=True: AI writes fenced code blocks instead; onlyread_urlavailable as direct tool (tool_choice=read_url for Claude, none for Gemini)
Tool results are truncated if too long, to 2000 chars. The ToolResponse class can wrap results with metadata. Tools can call input() to prompt the user mid-execution — the prompt appears in the message output and the tool waits for a response. This enables permission gates and interactive feedback within a single AI tool loop.
datapath:/app/data(production) or current working directory (local/test)- Dialogs stored at:
{datapath}/some/folders/{dialog_name}.ipynb - Static files served at:
/static/some/folders/→ maps to{datapath}/some/folders/ - Per-dialog Python modules:
{datapath}/some/folders/{dialog_name}.py(auto-generated from exported messages)
Dialogs are saved as Jupyter notebooks (.ipynb). Solveit converts messages to notebook cells when saving:
note/prompt→ markdown cells- Prompt responses stored by appending to message content with separator
code/raw→ code/raw cells- Metadata stored in cell metadata (skipped, pinned, collapsed states, etc.)
When not editing (blue border shows selected message):
↑/↓orj/k: Navigate messages (Shiftextends multi-selection)a/b: Add message above/belowx/c/v: Cut/copy/paste messages (calso copies to system clipboard as markdown). Works with multi-select.,: Copy message input to clipboard.: Copy message output to clipboardh: Toggle hidden from AI (skipped). Works with multi-select.p: Toggle pinned. Works with multi-select.e: Toggle export. Works with multi-select.1-6: Set heading level (converts to## Headingetc.)Enter: Edit selected messageCmd+Enter: Run code or send prompt (stay on message)Shift+Enter: Run and go to next messageOpt+Enter: Run and create new messageShift+R: Restart kernelShift+T: Open terminalShift+S: Stop AI/code executionShift+A/B: Run all code above/below (skips prompts)Cmd+Slash: Toggle comments on code messages. Works with multi-select.w: Extract fenced code blocks to new code messagesn: Edit AI response (output) of prompt messageCmd+Shift+J/K/L/;: Switch to code/note/prompt/raw mode←/→: Collapse/expand section (based on markdown headers)r: Re-run all code messages in dialog (skips prompts)m: Copy code blocks from AI responsed: Duplicate dialog (opens duplicate with bomb icon 💣 to discard)s: Save dialog (commits to git if versioning enabled)0: Jump to last edited message/: Focus bottom editor/input fieldShift+Esc: Focus back on message listHome/End: Jump to first/last messageCmd+A: Select all messagesF: Open find/search modal (Shift+Fto clear filters)Backspace: Clear outputs from selected message(s). Works with multi-select.i/o: Toggle collapse on message input/output. Works with multi-select.q: Duplicate message. Works with multi-select.Shift+O: Clamp output height
Use Shift+↑/↓ or Shift+j/k to extend selection. Cmd+A selects all. Operations marked "Works with multi-select" above apply to all selected messages at once. Shift up/down moves the entire selected block as a unit.
When editing a message:
Shift+Tab: Show parameter info/documentationCmd+I: Manually trigger symbol dropdownCtrl+Shift+Minus: Split message at cursorCmd+Shift+Comma: Screenshot last output into contextCmd+Shift+Period: Super completion (prefill in prompts) or super edit (with selection)Cmd+Shift+S: Save file (in full-page editor view)ESC: Save changes and exit edit mode→(Right Arrow): Accept ghost text suggestionTab: Select from symbol dropdown
Set per-dialog, affects AI behavior:
| Mode | Behavior | Ghost Text |
|---|---|---|
learning |
Pedagogical, asks clarifying questions, explains step-by-step | Disabled by default |
concise |
Minimal responses, compact code, no boilerplate | Enabled |
standard |
Default Claude behavior, verbose explanations | Enabled |
Inline AI suggestions as you type (faded gray text). Uses smaller/faster FIM model. Press → to accept.
Uses main model for larger completions. Can prefill AI response start in prompt messages.
Select text + Cmd+Shift+. + instruction → AI rewrites selection. Uses edit_call() with GPT-4.1.
Press e on code message to mark is_exported=True. Adds #| export marker. When dialog is saved, exported messages are combined into {dialog_name}.py. Splitting an exported message preserves the export flag on both halves. Merging messages exports the result if any source message was exported.
See "Publishing and Sharing" section for full details. Creates shareable view at share.solve.it.com. Pinned messages excluded.
.ipynb: Full notebook.py(Full Script): All code messages concatenated.py(Exported): Onlyis_exported=Truemessages.md: Markdown format- GitHub Gist: Upload to gist
Solveit is built with FastHTML. Users can also build FastHTML apps inside dialogs:
from fasthtml.common import *
from fasthtml.jupyter import JupyUvi
app = FastHTML()
rt = app.route
@rt
def index(): return H1("Hello")
srv = JupyUvi(app) # Serves on port 8000 → accessible at public URLJupyUvi runs uvicorn in a way compatible with notebook contexts. Changes to routes take effect immediately without restart.
Note: The root route / is used by Solveit itself. Use different routes like @rt def homepage() for your app's entry point.
render_ft()- Render FastHTML components as HTML in message outputsshow()- Alternative with optional iframe wrapping and HTMX processing
Stored in solveit_settings.json under secrets key. Accessed via get_secret(name).
| Flag | Values | Effect |
|---|---|---|
USE_KATEX |
1/true |
Enable LaTeX with safe delimiters ($$, \[, \() |
USE_KATEX |
dollar |
Also enable $...$ inline math |
USE_VIM |
any truthy | Vim keybindings in editor |
Paste into note/prompt → stored as message attachment → visible to AI automatically.
If the clipboard contains both an image AND HTML (e.g., from a DOM screenshot tool), solveit automatically appends the HTML in a fenced code block after the image link—useful for sharing UI screenshots with their underlying markup.
Attachments are stored in notebook message metadata (standard Jupyter format), referenced as . Attachments are message-specific—cannot be referenced from other messages.
in notes → displays but NOT sent to AI→ displays AND sent to AI- For reusable images, save to
/app/data/and use/static/URLs
Images in code output (matplotlib, PIL) are captured and can be sent to AI.
Use /static/ URLs to reference files in /app/data/. Example: Img(src='/static/myapp/image.png') for FastHTML, or  in markdown.
Markdown headings (# H1 through ###### H6) create collapsible sections:
heading_collapsed=Trueon heading message → children havehidden=True- Token count shows:
heading_tokens + hidden_content_tokens - Cut/copy on collapsed heading includes all children
←jumps to section start,→to section end
Token counts estimated and used for:
- Display in UI (message headers show token counts)
- Context truncation decisions
- Image tokens calculated separately
- Errors trigger
msg.uncollapse()to show the problematic message - Missing variables, broken tools, oversized values, and truncated context are collected as warnings and passed to the AI via
important_prompt. The AI continues with what's available and informs the user about issues, rather than failing entirely.
Stored in {datapath}/solveit_settings.json. Common settings:
use_tools: Enable standard tools (🔧 wrench icon in navbar)use_fence: Use fenced code mode (AI writes code blocks instead of tool calls)use_thinking: Enable extended thinking mode (🧠 brain icon in navbar, orCmd+Shift+D)use_diff: Enable diff mode (shows changed messages as unified diffs)default_code: After AI response, default to code mode (not prompt)
Stored in solveit_settings.json under secrets key. Accessed via get_secret(name).
| Flag | Values | Effect |
|---|---|---|
USE_KATEX |
1/true |
Enable LaTeX with safe delimiters ($$, \[, \() |
USE_KATEX |
dollar |
Also enable $...$ inline math |
USE_VIM |
any truthy | Vim keybindings in editor |
From left to right:
Left section:
| Element | Description |
|---|---|
| 📚 solveit | Logo/home link - closes dialog, returns to dialog list |
| 🟢 Green dot | WebSocket connection indicator |
| Dialog name | Editable - click to rename dialog |
Mode dropdown:
| Element | Description |
|---|---|
learning/standard/concise |
AI mode selector - affects response style and ghost text |
Toggle group (blue when active):
| Icon | Tooltip | Setting | Shortcut |
|---|---|---|---|
<> |
"Default new message type to code?" | default_code |
— |
| 🔧 | "Include dialoghelper standard tools in prompts?" | use_tools |
— |
| 🧠 | "Use reasoning model" | use_thinking |
Cmd+Shift+D |
| Fence | "Use fenced blocks instead of tools?" | use_fence |
— |
| Diff | "Toggle diff mode" | use_diff |
— |
Action buttons:
| Icon | Tooltip | Action | Shortcut |
|---|---|---|---|
| ? | "Keyboard Shortcuts" | Open info modal | ? |
| Git | "Checkpoint" | Create git checkpoint | Shift+C |
| Share | "Share your dialog" | Publish to share.solve.it.com | — |
| Cloud ↑ | "Upload File" | Open upload modal | — |
| ⊗ | "Stop" | Stop AI/code execution | Shift+S |
| ▶ | "Run all" | Run all code messages | r |
| ↺ | "Restart" | Restart Python kernel | Shift+R |
| ⚙ | "Settings" | Open settings modal | — |
| Copy+ | "Duplicate Dialog" | Duplicate and open | d |
| 💣 | "DELETE and close" | Delete duplicate (hidden until duplicated) | — |
| Terminal | "Open Terminal" | Open terminal in new tab | Shift+T |
| TOC | "Contents" | Toggle sidebar | Ctrl+Shift+V |
| ↗ | "Dialogs" | Open dialog list in new tab | — |
Each message has a header bar for input (and optionally output). Click the header text to edit.
Left side:
| Element | Description |
|---|---|
| Message type | Code, Note, Prompt, or Raw |
| Token count | Estimated tokens for this message |
Button bar (left to right):
| Icon | Tooltip | Shortcut | Action |
|---|---|---|---|
| 📋 | "Copy" | , |
Copy message to clipboard |
| ▷ | "Run Message" | Cmd+Enter |
Execute code or send prompt (code/prompt only) |
| 👁 | "Toggle AI visibility" | h |
Hide/show message from AI context |
| 🔖 | "Toggle message export" | e |
Mark for export to .py file |
| 🗑 | "Delete Message" | Shift+D |
Delete this message |
| ↑⚡ | "Delete above" | — | Delete all messages above (confirms) |
| ↓⚡ | "Delete below" | — | Delete all messages below (confirms) |
| ↳ | "Run Above" | Shift+A |
Run all code messages above |
| ↲ | "Run Below" | Shift+B |
Run all code messages below |
| ↑ | "Add Above" | a |
Insert new message above |
| ↓ | "Add Below" | b |
Insert new message below |
| 📖↑ | "Shift up" | — | Move message up (works with multi-select) |
| 📖↓ | "Shift down" | — | Move message down (works with multi-select) |
| 💬+ | "Add copy of message" | q |
Duplicate this message |
| 🔀 | "Merge with message below" | Shift+M |
Combine with next message |
| ✂️ | "Split note based on headings" | Ctrl+Shift+Minus |
Split into multiple messages |
| 📌 | "Toggle message pin" | p |
Pin/unpin message |
| 🔗 | "Copy link to message" | — | Copy URL with message anchor |
| ⌃ | "Collapse contents" | i |
Collapse/expand input |
| ↗ | "Open in new tab" | t |
View and edit message in standalone Monaco editor tab |
Only code and prompt messages have outputs. Click the header text to edit (prompt outputs only—code outputs are not editable).
Left side:
| Element | Description |
|---|---|
| Label | Output (code) or Assistant (prompt) |
| Token count | Estimated tokens for this output |
Button bar (left to right):
| Icon | Tooltip | Shortcut | Action |
|---|---|---|---|
| 📋 | "Copy" | . |
Copy output to clipboard |
| 📋 | "Copy code" | m |
Copy fenced code blocks to clipboard |
| 💬+ | "Add fenced block messages" | w |
Extract fenced blocks to new code messages |
| ✨ | "Re-run AI" | — | Re-send prompt to AI (prompt outputs only) |
| ⌃ | "Collapse contents" | o |
Collapse/expand output |
| ↗ | "Open in new tab" | t |
View and edit output in standalone tab |
Toggle with Ctrl+Shift+V (or the TOC icon in the header bar). Displays collapsible sections:
- Bookmarks: Numbered bookmarks (1–9) with click-to-navigate
- Headings: Markdown heading hierarchy with exported/hidden indicators
- Variables: Live kernel variables
- Functions & Classes: All defined functions and classes (including async) with signatures extracted via
codesigs. Click to navigate to the definition message. Populates on dialog open/reset, not just after running code.
Press Shift+T to open an integrated terminal in the current dialog's folder. Useful for:
- File operations (
ls,mv,rm, etc.) - Git commands
- Package installation (
pip install) - Running shell scripts
Press F to open find modal (selection mode, not editing). Features:
- Regex search across message content and outputs
- Filter by message type (colored buttons match UI colors)
- Filter by: changed in git diff, pinned, bookmarked, exported
- Searches inside collapsed sections (auto-expands on match)
Shift+Fclears all filtersheaders_onlyandheader_sectionparameters available programmatically viafind_msgs
Press d to duplicate current dialog. The duplicate opens immediately, with a bomb icon (💣) added to the navbar. This supports a "branching" workflow:
- Duplicate before experimenting with risky changes
- If the branch is useful, keep it
- If not, click the bomb icon to delete and return to the original
Solveit integrates git for version control at the folder level.
Click "Enable versioning" button in any folder to initialize a git repo there. Can create nested repos (repo-in-repo) at any directory level.
- Press
sto save the dialog and auto-commit with Solveit's distinctive message template - Dialogs auto-save on most actions (submit, cancel edit, etc.) but do not commit without
sor checkpoint
Click "checkpoint" button (appears after versioning enabled) to create a manual commit with a custom message. Useful for marking significant milestones.
Solveit automatically squashes commits that match its auto-save message template. This keeps history clean—many small saves become one commit, while checkpoint commits with custom messages are preserved.
Toggle the diff icon in the header bar to highlight messages that have changed since the last git commit. Changed messages display as syntax-highlighted unified diffs (additions in green, deletions in red). New messages are tagged as added. The diff auto-refreshes when HEAD changes. Requires versioning to be enabled. Combine with the "changed" filter in Find/Search (F) to see only modified messages with their diffs.
Creates a read-only shareable view at https://share.solve.it.com/d/[hash]. Pinned messages are excluded from published output. Brain emoji chains (🧠) from AI thinking are stripped from published output. The scrollspy sidebar is visible on medium-width screens and above.
URL suffixes:
.html- Blog-style rendered view.md- Raw markdown
The dialog index page lists dialogs and folders in the current folder, along with clickable breadcrumb path.
Click "show all files" to also show non ipynb files, which can be opened in the file editor. By each file or folder are buttons to duplicate or delete it.
Other features of this page:
- Secrets management (API keys stored as environment variables)
- Running dialogs (can shut down here)
- Upload files/folders (zip and unzip for folders)
| Key | Action |
|---|---|
↑/↓ |
Navigate rows |
Enter |
Open dialog/folder |
N |
Focus "new dialog" input |
T |
Open terminal |
D |
Delete selected |
C |
Duplicate selected |
Add name-value pairs in Secrets section. Common secrets:
ANTHROPIC_API_KEY,OPENAI_API_KEY- LLM API accessAOC_SESSION- Advent of Code session cookieUSE_KATEX,USE_VIM- Feature flags (see Feature Flags section)
Secrets load when opening a dialog or terminal; add secret then open new dialog to use it.
Toggle in dialog list view to see all files, not just .ipynb dialogs. Enables:
- Click text files to open in Monaco editor (VS Code-based)
- Download files via three-dot menu
- Create folders:
mkdirin terminal, or create dialog with path likenewfolder/dialogname - Move dialogs to folders: Rename dialog to include folder path (e.g.,
myfolder/mydialog)
The dashboard at solve.it.com/dashboard provides:
- Instance management (start/stop/restart; rename public instance and get URL; share; allow guests)
- Live session times for courses
- Version updates (stop → wait → start to upgrade)
- Copy Public URL: Get URL for apps running on port 8000 (one URL per instance, all dialogs share it)
- Share button: Add collaborators by email (requires Solveit account)
- Lock icon (Allow Guests): Enable public access to entire instance without authentication
Raw messages (Cmd+Shift+;) are for content that shouldn't be processed:
- Large documents as AI context (no sidebar headings, no variable injection)
- Quarto/YAML frontmatter
- Content with special characters that might interfere with markdown
Access any message content programmatically in dialoghelper: read_msg()['msg']['content'] from code message below it.