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.
- 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 |
- 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 followed by [func1, func2, func3]. 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:
- 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 - Inspection tools:
symsrc,getval,getdir,gettype,symlen,symslice,symsearch - Web tools:
read_url,web_search
The dialoghelper module provides functions for programmatic dialog manipulation:
add_msg(content, msg_type='note')- Add message below currentupdate_msg(idx, content, msg_type=None)- Update existing messagedel_msg(idx)- Delete messagefind_msgs(pattern)- Search messages by contentread_msg()- Read message at indexurl2note(url)- Fetch URL and add as markdown notetool_info()- Show available tools in a note for AI
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.
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 messagesa/b: Add message above/belowx/c/v: Cut/copy/paste messages (calso copies to system clipboard as markdown),: Copy message input to clipboard.: Copy message output to clipboardh: Toggle hidden from AI (skipped)p: Toggle pinnede: Toggle export1-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 messagesw: 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)i/o: Toggle collapse on message input/outputShift+O: Clamp output height
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.
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
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)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 |
— |
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 |
| 📖↓ | "Shift down" | — | Move message down |
| 💬+ | "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 message in standalone 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 output in standalone tab |
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 whether changed in git diff
- Searches inside collapsed sections (auto-expands on match)
Shift+Fclears all filters
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.
Creates a read-only shareable view at https://share.solve.it.com/d/[hash]. Pinned messages are excluded from published output.
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.