Skip to content

The Interface ​

Ship of Tools is three columns: navigation, preview and agent. The bottom drawer, when open, sits under the navigation and preview columns; the agent column always runs the full height. The columns are fixed in role; the drawer swaps between four contents. Every pane is keyboard-driven and every chord is rebindable; see Keybindings.

The default layout with the Julia drawer open.
The default layout with the Julia drawer open.

Ctrl+J, Ctrl+T, Ctrl+M and F1 swap the REPL, Terminal, Monitor and Help into the drawer. Column order, widths and the drawer height come from the layout preset; the portrait preset drops the agent column.

This page walks each pane in turn — what it is, what fills it, and the keys that drive it — and links out to the guide behind it. For a first-session walkthrough, see Your first session.

The Help drawer (Ctrl+? twice): Tab widens the scope to all panes, typing zoom filters the actions, Down steps through them.
The Help drawer (Ctrl+? twice): Tab widens the scope to all panes, typing zoom filters the actions, Down steps through them.

The panes ​

PanePositionWhat it isDeep dive
Navigationleft columnthe mode tree — a collapsible outline whose root you switch with a hotkeyModes
Previewmiddle columnthe selected entry, renderedPreviews
Agentright columnthe session's agent (Claude Code, Codex or a plain shell)Running several agents
REPLbottom drawerthe persistent Julia session, figures inlineThe REPL

The bottom drawer ​

The bottom drawer is shared by the REPL, the Terminal, the Monitor and Help, each with its own global toggle. Pressing a second toggle swaps the content in place rather than stacking:

DrawerToggleWhat it is
REPLCtrl+Jpersistent Julia session; values, stdout, and figures stream back as structured frames
TerminalCtrl+Ta local OS shell on the frontend machine — canonical use is SSHing out to backend hosts
MonitorCtrl+MCPU / GPU / RAM history across your configured hosts
HelpF1, or Ctrl+? twicethe focused pane's actions and their bindings, searchable

Drawer toggles are global — they fire even when another pane holds focus.

Help drawer ​

F1 opens the Help drawer for the pane you were using. Type to search, use arrows to select an action, Tab to browse all panes, and Enter to open its manual. Escape restores the previous drawer and focus. The Help view preserves its source context while you read; switching away leaves Julia and terminal processes running.

Focus and layout ​

ActionKey
Move focus between the panes on screen (spatial; hidden panes are skipped)Ctrl+Arrow
Navigate within the focused paneplain Arrow
Maximize the focused paneAlt+=
Wide-preview: hide the LLM column, hand its width to the previewAlt++
Restore the layout, one layer per press (un-maximize, then exit wide-preview)Esc (only while maximized or in wide-preview)
Switch to the next / previous sessionShift+Arrow
Temporary pane actions / promote to Help drawerCtrl+? / press again
Open Help directlyF1

Alt+= maximizes the focused pane, Escape restores.

Plain arrows, Ctrl+Arrow, and Shift+Arrow are scoped so they do not conflict: Shift+Arrow switches sessions, except in a text editor, where it extends the selection, and in an image preview, where Shift+Up and Shift+Down zoom. The authoritative, build-current chord list is in Keybindings; when in doubt, Ctrl+? (or F1) is the source of truth.

The Navigation Pane ​

Left column. The navigation pane is the mode tree — a single collapsible outline (a parent context, the current level, and its children, shown through indentation and disclosure carets) that you walk with the arrow keys. It is the pane you steer from; the Preview pane reflects whatever node the cursor is on.

Files mode with src/DemoProject.jl selected; its source in the preview.
Files mode with src/DemoProject.jl selected; its source in the preview.

What fills it ​

A mode is a switchable root for this tree. A hotkey swaps which tree fills the columns, and cursor position is preserved per mode, so jumping between modes and back lands you where you left each one. The modes bound today:

KeyModeRoots the tree at
fFilesthe project filesystem
mModulesmodules and their definitions (types, functions, macros and submodules), read-only, from JuliaSyntax.jl
sSessionsthe workspaces this backend is hosting
hHoststhe remote hosts you can target

The mode keys are plain single characters, so they are scoped to navigation focus: typing f into the REPL or a prompt inserts an f, it does not switch modes. Details of each built-in mode are on the Modes page.

Driving it ​

ActionKey
Move up/down the tree↑ / ↓
Descend into the cursored node→
Back up a level←
Switch nav modef / m / s / h
Focus this pane from elsewhereCtrl+Arrow (spatial)
Copy the cursored file's path to the clipboardc

The lines around the tree ​

  • status: above the tree usually reads connected · <host>:<workspace> · rev N: the connection, the active workspace, and the last revision of backend state the window has received. An action such as a pin replaces it with a short message (pinned · <path>).

  • annotation: below the tree is the concept annotation status of the cursored entry: present, (none) when no annotation file exists for it, loading, or (no target for this row) for rows such as sessions that cannot carry one.

  • key: shows the last key the window received, which helps when a binding does not do what you expect.

  • In Sessions mode, a tag in brackets after a session's name is the state of its supervised process: [starting], [ready], [ending], [stopped] (no process has run for it yet), [terminal] (it ended and will not restart), or [unreachable] (the backend could not query it). The row is coloured by the agent's work state and ends with the reason the agent last reported.

  • The bottom border of the window shows the frontend (fe) and backend (be) versions.

The four navigation modes above are built into the frontend and backend. ADR 0051 specifies future view extensions as mods with tree_children and the three-argument preview, shown in a generic Mods navigation mode. The mod system is not built. See The Dispatch ABI for the current extension surface.

The Preview Pane ​

Middle column. The preview pane renders whatever the Navigation pane cursor is on, at the fidelity appropriate to its type — markdown rendered, PNGs shown, .jl syntax-highlighted, PDFs paged, video as a poster frame.

A markdown note with typeset math and a table of each leg's bearing.
A markdown note with typeset math and a table of each leg's bearing.

What fills it ​

The backend first asks the Julia kernel: a FileType plugin claims the path with matches and returns a PreviewPayload from preview. When no plugin claims it, the backend reads the file itself; that is how raster images and plain text are served. The payload's mime tells the frontend which renderer to use; the bytes are opaque to Rust, so adding a format is a Julia-only change. A sampling of the built-ins:

KindRendered as
.jlsyntax-highlighted source (tokenized kernel-side, no client re-parsing)
.mdrendered markdown, including inline math
.json / .toml / .txtplain text (JSON/TOML pretty-print / colour planned)
.pdfone rasterized page; with the preview focused (Ctrl+Right) n / p turn pages
video (.mp4, .webm, …)a poster frame; o opens playback in the browser

The full table — every built-in type and its MIME — is on the Previews page.

Beyond static rendering ​

  • Pin a file. p keeps a file in the preview pane while you browse elsewhere in the tree; p again unpins it and the cursor returns to it. A pinned preview still updates as the file changes on disk.
Pressing p pins the preview to src/DemoProject.jl while the cursor moves on; pressing p again unpins it; the cursor returns to the pinned file, and the preview then follows the cursor.
Pressing p pins the preview to src/DemoProject.jl while the cursor moves on; pressing p again unpins it; the cursor returns to the pinned file, and the preview then follows the cursor.
  • Opens in the browser, not the pane. Interactive HTML, Pluto notebooks, and Quarto documents pop out to the OS browser through the backend connection, local or remote, rather than rendering in-pane; the source still previews as text. Same policy as video.

  • Capture a region for the LLM. On a raster image you can crop a zoomed region and hand it straight to the agent — "what's the artifact here?". The crop is taken from the source image on the backend, so the agent sees exactly what you do.

  • No silent blanks. When an external tool is missing (ffmpeg, poppler), the plugin returns a text/markdown note explaining the gap instead of an empty pane.

The Agent Pane ​

Right column. The agent pane holds the session's agent: Claude Code, Codex or a plain shell. Its title bar reads llm. You describe what you want; the agent reads code, writes code, and runs code in the REPL to get there. See Agent sessions.

What fills it ​

Each session runs Claude Code, Codex or a plain shell. A session belongs to a workspace on the backend daemon, so it sits alongside project state and survives a frontend restart.

Several sessions run concurrently — one per workspace/session, across machines — coordinated over the inter-agent communication system. You start and switch them from Sessions mode (s); cycle the active workspace with Shift+Arrow. What is still deferred is a dedicated in-UI Agents mode (a tasks → timeline → step-detail view).

How it acts ​

  • Its own tools, plus a few skills. The agent brings its own tools and context management. The installer adds skills that let it run code in the workspace REPL (sot-fe repl), show a file in your preview pane (show-result) and message other sessions, and hooks that report its work state. See How agents use the REPL.

  • Permissions come from the agent CLI. Sessions launch Claude Code with --permission-mode auto and Codex with --dangerously-bypass-approvals-and-sandbox --dangerously-bypass-hook-trust; Ship of Tools adds no permission layer of its own. See Agent sessions.

Hand it a file, or a crop of an image ​

In Files mode, c copies the selected file's path to the clipboard for pasting into the agent's prompt.

Pressing c in Files mode copies the selected file's path; Ctrl+V pastes it into the Claude Code session in the agent pane; asked what the script does, it answers in one sentence.
Pressing c in Files mode copies the selected file's path; Ctrl+V pastes it into the Claude Code session in the agent pane; asked what the script does, it answers in one sentence.

With the preview focused (Ctrl+Right), c on a zoomed image crops the visible region instead and drops a ready-to-send line into the agent pane's input, naming the source file, the region, and the crop saved under .sot/captures/.

With the preview focused, pressing c on a zoomed figure sends the visible region to the Claude Code session in the agent pane; asked which leg in it is the shortest, the agent reads the answer off the crop.
With the preview focused, pressing c on a zoomed figure sends the visible region to the Claude Code session in the agent pane; asked which leg in it is the shortest, the agent reads the answer off the crop.

The REPL Pane ​

Bottom drawer — Ctrl+J. The REPL pane is a persistent Julia session available throughout a session: the same bindings, the same loaded packages — but its output is structured, and the figures it produces render inline in the native window rather than as terminal text. It shares the bottom drawer slot with the Terminal and Monitor.

This page covers the pane itself — what fills it, its keys, how to drive it. For the frame schema, dispatch semantics, and figure-rendering mechanics in depth, see The REPL.

The Julia drawer after running scripts/route.jl: the printed output and the figure inline.
The Julia drawer after running scripts/route.jl: the printed output and the figure inline.

What fills it ​

A Julia process supervised by the backend daemon, distinct from the kernel that does project introspection. They are separate on purpose: the kernel owns the file-type dispatch table, previews, project scans, and the file content hash; the REPL owns your interactive state. Because they are different processes, killing the REPL does not kill the kernel — you can restart your interactive session to clear state or recover from a wedged computation without losing the project view.

Driving it ​

Two ways to drive it, without retyping:

  • Run a .jl file from Files mode — Shift+R includes it into the current session; r first resets the REPL into the file's own project, then runs it.

  • Type at the prompt — Enter submits, Shift+Enter inserts a newline.

Per-line and per-block dispatch (the line at the cursor, or the surrounding top-level form) are planned, not yet built.

A long-running evaluation runs on its own task, so you can interrupt it mid-eval whenever it yields (sleep, I/O that waits, yield()) — a real InterruptException, the same as Ctrl-C in the stock REPL. One that never yields but allocates or calls (a sin loop) is stopped by the one interrupt signal the daemon sends, as in the stock REPL; code with no safepoint (an integer loop) ignores it, and a second Ctrl+C forces it, which can crash or hang the REPL inside C code, compiling or garbage collection, and under heavy CPU load may not stop it (r on a .jl file restarts the REPL and ends it).

Structured output ​

Instead of one undifferentiated text stream, the display shim emits typed frames — stdout, stderr, value, image, error, and a terminal done — so the frontend renders each correctly: stdout/stderr stream as bytes arrive, errors carry a structured stacktrace with file:line links, and a showable value (a CairoMakie Figure, a Plots.Plot, anything with show(io, MIME"image/png")) becomes an image frame drawn inline through the preview layer — never a degraded terminal-graphics protocol.

The Terminal Drawer ​

Bottom drawer — Ctrl+T. The Terminal is a local OS shell on the frontend machine. Its canonical use is SSHing outward to backend hosts, and because it is local — not proxied through the daemon — it works even when the backend is unreachable. It shares the bottom drawer slot with the REPL (Ctrl+J) and the Monitor (Ctrl+M): each key toggles its own content, and pressing another swaps it in place.

Ctrl+T opens the Terminal drawer, a shell in the project root.

KeyDrawer closedShowing this contentShowing other content
Ctrl+T→ Terminal→ closed→ Terminal
Ctrl+J→ REPL→ closed→ REPL

Scrollback ​

Plain PgUp/PgDn page the drawer's scrollback ring (a one-third-pane step, the same convention as the LLM pane) — so a claude session running here scrolls exactly like one in the LLM pane. Typing snaps back to the live tail. Two escape hatches keep full-screen apps working:

  • Apps on the alternate screen (vim, less) page themselves — they receive the raw key automatically.

  • Shift+PgUp/Shift+PgDn forwards a plain PgUp/PgDn to a primary-screen app that wants the key itself.

The mouse wheel scrolls the same ring, or is forwarded as SGR mouse events when the running app has enabled mouse tracking (vim, htop).

The Monitor Drawer ​

Bottom drawer — Ctrl+M. The Monitor shows CPU / GPU / RAM history across all your hosts at once — small-multiples, one compact panel per host (a multi-GPU box shows each GPU as its own trace). It shares the bottom drawer slot with the REPL (Ctrl+J) and the Terminal (Ctrl+T); the toggle is global, so it opens even when another pane has focus.

How it gets the data ​

The declared hub is the aggregator: it alone runs the [monitor] roster, and the drawer subscribes to the hub regardless of which host you're navigating — "see all servers at once" is solved at the data layer, on the one daemon that's the monitoring authority. For each host in the roster the hub runs a small sampler:

  • its own host locally, on Linux only (a macOS or Windows daemon does not sample its own host); remote hosts as ssh <alias> bash -s, with the script fed over stdin — zero footprint, no daemon, nothing written to the remote's disk.

  • CPU% from /proc/stat, RAM% from /proc/meminfo, GPU from nvidia-smi. All world-readable, so no sudo and no privileges are needed.

A daemon that is not the hub samples only the host it runs on (on Linux) — it never ssh's anywhere, so a wrong or unreachable alias on some other box can't wedge it. Dialling a non-hub daemon directly shows that daemon's own single panel, not the fleet; to see the fleet, dial the hub.

Sampling is always on for the life of the daemon, so the drawer shows real history the moment it opens rather than starting from a blank axis; what monitor.subscribe gates is per-connection delivery of ticks, not collection. (ADR 0020 originally specified reactive spawn-on-subscribe; the implementation went the other way and the ADR carries a note.) The hub keeps an in-memory tiered ring buffer per host, so the time axis can rescale to wider windows without a round-trip. Restarting the hub restarts that history.

Which hosts appear comes from the [monitor] section of ~/.config/sot/hosts.toml (or the file $SOT_HOSTS names); see Configuration Files. With no [monitor] section, or on a non-hub daemon, only that daemon's own host is sampled (on Linux); the [host.*] entries are frontend connection targets and are not monitored implicitly.

The list binds when the daemon starts. Editing hosts.toml while the hub is running changes nothing until it restarts — there is no reload and no file watch. If the drawer is missing a host you just added, restart the hub.

On-philosophy rendering ​

Traces are drawn as real SVG, rasterized through the same resvg → wgpu-quad pipeline that renders typeset math — not braille/cell plotting, which is the same class of degraded hack the project rejects for images. A host whose SSH or sampler dies renders an explicit gap, never a silent flatline or a quiet fallback to "looks fine."

An agentic development environment for Julia. Released under the AGPL-3.0-or-later, with a commercial license on request.