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. On macOS, Ctrl in the chords below is ⌘ (Primary).

The window with the Julia drawer open, and the session strip along the bottom.
The window with the Julia drawer open, and the session strip along the bottom.

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+Right / Shift+Left
Temporary pane actions / promote to Help drawerCtrl+? / press again
Open Help directlyF1

Plain arrows, Ctrl+Arrow, and Shift+Left / Shift+Right are scoped so they do not conflict: Shift+Right / Shift+Left switch sessions, except in a text editor, where they extend 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 are f Files, m Modules, s Sessions and h Hosts; the Modes page describes each and what is planned.

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.

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>), and so does the agent pane when it attaches to a session's running program (attached to leg started …).

  • 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 phase of its supervised process, for example [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 colored 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 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 needs no Rust change when the plugin returns a MIME the window already renders. 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 / color 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 ​

A session belongs to a workspace on the backend daemon, so it sits alongside project state. It survives a window relaunch and a dropped connection; closing the last window on a computer ends that computer's sessions unless you keep them (see Sessions and persistence).

Several sessions run concurrently, coordinated over the inter-agent communication system. You start and switch them from Sessions mode (s); cycle the active workspace with Shift+Right / Shift+Left. A dedicated in-UI Agents mode is not built.

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. 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, separate from the kernel that does project introspection, so restarting your interactive session never costs you the project view. See A supervised, separate process.

Every drawer on a row shows every run on it: what you type, a run another window started (labeled ⟨window ▸ …⟩) and a session's run (labeled ⟨session ▸ …⟩). On a Linux backend the REPL has a display of its own, so GLMakie works; see The REPL.

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 restarts the REPL in the session's project (the session root when it has a Project.toml, else the nearest one above the file), 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). Ctrl+C stops the running evaluation if it is one the drawer shows as running; see The REPL.

Structured output ​

The display shim emits typed frames, 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. The frame kinds are listed in The REPL.

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 on this computer in the window's working directory.

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. 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.

How the traces are drawn ​

Traces are drawn as real SVG, rasterized through the same resvg → wgpu-quad pipeline that renders typeset math. A host whose SSH or sampler dies renders an explicit gap.

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