#The torno manual
Everything torno does, in the order a first-time user meets it: what it needs, how to start it, a walk-through, then the reference. The README is the short introduction. The same text, with a table of contents at the side, is at torno.luisg.me/manual.
Torno is a desktop workbench for making CAD parts with an AI coding agent: the agent writes build123d Python in a terminal inside the window, torno builds the model every time a file is saved and shows the parts beside the terminal. You point at the parts to say what you mean, measure them, and hold them to numbers; the agent is told what you pointed at and has tools to see what you see.
The window has three columns. On the left, the projects of the folder you opened, and under them the parts, scans and marks of the open one; the marks have the room, and the sections above them fold. In the middle, the parts in 3D, with a bar of views above them and the tools down the left edge. On the right, the terminal the agent runs in. A line at the bottom says how the last build went.
Torno is not a modeller. It has no sketcher, no feature tree and no way to edit a solid by hand. The model is the Python file; torno shows it. It uploads nothing and publishes nothing. The name is Portuguese for a bench vise, which the logo is: one holding a part. The design is in docs/superpowers/specs/2026-10-08-torno-design.md.
#Requirements
Linux or macOS. Developed on Arch with Wayland (Hyprland). macOS is tested on GitHub's macOS runners and not yet by eye.
- Node.js 22.12 or later (developed with 26) and pnpm 10
- uv
- Python 3.11 or 3.12 (uv fetches one if none is installed)
- Claude Code (
claude) onPATH, logged in (see What Claude Code is given) - A workspace: a folder of projects, each a subfolder with a Python model in
it. If the workspace has a
pyproject.toml, its environment must already exist and hold build123d (uv syncthere once, yourself). Torno runs the kernel in that environment withuv run --no-syncand never changes it. A workspace with nopyproject.tomlis built with the build123d of torno's own kernel environment (kernel/.venv, which uv makes the first time it is needed), which also hasbd_warehouse(screws, nuts, clearance holes, bearings), so a model there can import it. A workspace with its ownpyproject.tomlhasbd_warehouseonly if it lists it. - Importing a scan needs
trimeshandpillow, which uv fetches the first time a scan is imported. The checks against a scan use thescipyof the workspace's environment, which build123d brings.
Torno checks these when it starts, before anything fails, and says so in a block
called "Ready to work" on the first screen, in a workspace with no project and on
the page of a new project (below): a line for Claude Code, for uv when it is
missing, and for the kernel's Python environment. When everything is there the
block is one quiet line ("Claude Code and build123d 0.13.0 are ready"), and a
click opens it. It looks for claude and uv on the PATH torno was started
with. It also runs claude --version and claude auth status, once at start and
within 3 seconds each (nothing written, nothing of your Claude Code settings
read), to say its version and whether you are signed in, and uv --version;
under test it only looks on the PATH and never runs claude. "Check what torno
needs" in the folder menu opens the same lines in a dialog and asks again
("Check again"). The first time, torno's own Python environment is made by uv and
about 900 MB is fetched, so that first run needs the network; until then the
block says "Not made yet", and "Make it now" makes it (uv sync on torno's own
kernel project, never on a workspace) with uv's lines shown as it goes, so that
the first build does not have to. A workspace with its own pyproject.toml is
only reported on: whether its .venv is there. Torno never makes one.
#Install
git clone https://github.com/luisg0nc/torno.git
cd torno
pnpm install
#Run
scripts/torno ~/projects # open that folder as the workspace
scripts/torno # open the workspace opened last, or the first screen
pnpm start [folder] # the same, from the repository
scripts/torno --print-command [folder] # the command it would start, one argument a line, the folder first as TORNO_WORKSPACE=…; starts nothing
scripts/torno --print-desktop # Linux only (macOS: see below). The desktop entry for this copy of torno; writes nothing
scripts/torno --install-desktop # writes it: ~/.local/share/applications/torno.desktop, and nothing else
scripts/torno --uninstall-desktop # removes that file
TORNO_OZONE=wayland scripts/torno # or x11: the platform Electron is told to draw on
scripts/torno works from any directory, and through a symlink. It installs
the dependencies if node_modules is missing, builds the app if it has not
been built since the sources last changed, and starts it. A build that fails
is tried again at the next start. It knows a build by app/out/.built, a hash of
what the sources held when its last build that succeeded began: a checkout or a
touch that leaves every source as it was builds nothing, and any change does.
Only the launcher writes it: after a pnpm build or a test run of your own on
changed sources, the first start from the launcher builds again (some ten
seconds, written into app/out).
With no folder to open, the first screen says what torno is, says what is ready
and what is missing (see Requirements), draws how a folder
of projects is laid out and offers "Open folder" (an empty one is fine) and
"Open the example"; coming back, it lists the folders opened lately first. The
example is one small project, an enclosure for a sensor board, made to be
shown: a model with three parts (a body, a lid and a clip), a brief written as a
person would, two marks, a dimension that fails and two saved views. It is
copied to a temporary folder. That copy is
removed when torno quits, with whatever was made in it, and the window says so
under the workspace's name for as long as it is open; one that a crash left is
removed at the next start. To keep what you made in it, press "Keep this as a
folder…" under the workspace's name: choose a name in the dialog and torno copies
the whole example, with the marks, the views and the chat, to a new folder of
that name, whole or not at all, and opens it in place of the copy. It never
writes into a folder that exists, so a name that is taken is refused, and the
new folder is added to the folders opened lately. A folder given to scripts/torno is added to the
folders opened lately, as one opened from the window is.
Once a folder is open, the folder button at the top left opens a menu with the same ways to another: the folders opened lately (one that is not found is struck and can be removed), "Open folder…" and "Open the example", and "Check what torno needs".
Left alone, Electron chooses whether to draw on Wayland or X11. If the window
does not come up, or comes up wrong, TORNO_OZONE=wayland or TORNO_OZONE=x11
has the launcher pass --ozone-platform for it. Any other value is refused,
headless among them: Electron crashes with it here.
On macOS Electron has one platform: TORNO_OZONE is checked but then ignored,
with a line on stderr, and --print-desktop, --install-desktop and
--uninstall-desktop write nothing and exit 2 (make torno.app with
pnpm dist:mac instead). The launcher hashes the sources with sha256sum or,
failing that, shasum -a 256, which a Mac has.
--install-desktop (Linux) writes one file and only when you run it: torno.desktop
under $XDG_DATA_HOME/applications (~/.local/share when that is not set),
after printing the path and the entry. It refuses to run as root. The entry
starts this script, and names its icon, by their whole paths: run it again
after moving the repository. It names no platform: a launcher that should use
one needs TORNO_OZONE in the environment it starts programs with. Started
from the entry (--from-desktop), what goes wrong is also shown as a desktop
notice (notify-send), or, where there is none, written to
~/.local/state/torno/launcher.log: nobody reads what a launcher prints.
Only one torno runs at a time. Starting it again brings the window that is already open to the front and says so on stderr; given a folder, that window opens it, asking first if a session there is in the middle of its work.
On Linux torno has no menu. The window is closed through the window manager, which
ends torno and the sessions in it. When that would cut something short, torno
asks first, in a dialog over the window ("Close torno?", with "Keep it open" in
focus) that names each project and what would be cut short, as a link to the
project: a session working or waiting for an answer, a prompt written and not
sent, a region painted on a scan and not kept, a scan being imported or an
export being written. With none of that it closes at once. Ctrl+Q, Ctrl+W, F11 and
the zoom shortcuts do not exist. For work on torno itself, pnpm dev runs the app from
the Vite dev server, where F5 and Ctrl+R reload the window.
On macOS the menu bar is global, so torno has one there: the app menu (About, Hide, Quit), Edit and Window. Copy,
paste, cut, select all and undo (⌘C, ⌘V, ⌘X, ⌘A, ⌘Z) come from the Edit menu, in fields and in the
terminal. ⌘Q asks first, as closing the window does ("Close torno?" while something would be cut short);
the Dock's Quit and a logout do not ask. There is no Close item, no reload and no zoom. Run from the
repository, the menu bar names the app "Electron" (the bundle of node_modules/electron); a packaged app is
"torno". On a Mac, and on Linux, torno asks your login shell for its PATH at start, so uv and claude are found when torno
is started from the Finder, the Dock or a launcher.
On macOS, pnpm dist:mac makes torno.app in app/dist/: mac-arm64/torno.app on Apple silicon (and a zip
beside it), mac/torno.app on an Intel Mac (pnpm dist:mac -- --x64). Copy it to /Applications and open it from
the Finder or the Dock. It carries the kernel's source, torno's skills for Claude Code, the icon and the example, and
still needs uv and claude installed; it finds them through your login shell's PATH (above). Its Python environment for
a workspace with no pyproject.toml is made at the first build of one, in
~/Library/Application Support/torno/kernel-env, and never inside the app. The app is signed ad hoc and not
notarized: one built on your own Mac opens as it is; one that was downloaded (the zip a CI run uploads, say) is
quarantined, so right-click it and choose Open, or run xattr -dr com.apple.quarantine torno.app. Updating means
building again.
On Linux, pnpm dist:linux makes torno-<version>.AppImage and torno-app-<version>.tar.gz in app/dist/ (make the
AppImage executable and run it, or unpack the tar.gz and run torno-app). They carry the same files as the Mac app,
are not signed, and need uv and claude as well (found through your login shell's PATH); the kernel's Python
environment is made in ~/.config/torno/kernel-env, never inside the package. There is no Windows build.
pnpm pack:check builds the unpacked folder alone, app/dist/linux-unpacked, to check the packaging with
TORNO_PACKAGED=… pnpm -C app exec playwright test e2e/packaged.spec.ts.
#Five minutes with torno
This walk-through uses the example that torno carries, in a temporary copy, so nothing of yours is touched. It needs nothing from the repository, so it works the same in a packaged app. The example is an enclosure for a sensor board: a body, a lid and a clip. The lid rattles, which is what you will fix.
Start torno with no folder (
scripts/torno, or open the app) and press "Open the example" on the first screen. The window opens on a page of cards with one project,enclosure. The copy is removed when torno quits, and the window says so under the workspace's name.Click
enclosure. It is built (a second or two; longer the very first time, while uv makes torno's own Python environment, which the status line says: "Making torno's Python environment, first time only") and drawn: the body, the lid on it and the clip round the front. Drag to turn it, drag with the right button to pan, scroll to zoom,Fto fit.1to4are the iso, front, right and top views. The window says how the pointer moves the view once, in a line at the foot of the view that goes at your first move of the camera, and keeps it, with the keys, in the sheet of keys (?), where the pointer is the first group. Point at a tool in the palette for what it does; with a tool in hand that same line says what it waits for next.Read what is there. The Marks list has three marks the example came with:
M1, a pin on the lid for a label;M2, an arrow at the cable notch, which says the notch is too narrow for the plug; andM3, a dimension: the gap between the lid's lip and the wall, which should be 0.2 mm. Its row saysFails, because the gap is 0.35 mm. Click a row and the view goes to it. The line at the bottom counts it (1 dimension fails). Openbrief.mdin the project's folder to read what the person who made it asked for.Press
E. The parts are drawn apart, for looking only. PressEagain to put them together.Under the bar of views, click "Lid fit". The camera moves to the lip, the clip is hidden and a section is cut through the lid and the body, so that you see the gap. "Cable side" looks at the notch from behind. "+ Save view" keeps how you are looking under a name of your own.
Press
Pand click the top of the body, near a corner. A pin is left there, numberedM4, and its note opens: type "rounder here" and press Enter. The pin is red: it is yours. PressEscto put the Pin tool down.Press
M, hold the pointer on the outer wall of the body and pressT: the wall under the pointer is measured by the kernel, exactly,2.4 mm, and stays on the part as a dimension. Type2.4under "Should be" and press Enter: the row saysPasses. Enter gave the keys back to the view: pressEscto put the tool down.Press
X. A plane cuts the parts in half at once, open towards you; a bar appears under the view. Drag the arrow on the plane, or use the arrow keys, to move it. "Off" in the bar takes the cut away, andEscputs the tool down.In the pane on the right, type "make the notch 14 mm wide and the gap 0.2 mm" and press Enter. A session starts with your prompt, and your marks go with it as text: the face, the point, your note, the measured value and whether it passes. From then on they show as sent. The agent can ask torno for a picture (
look), a measurement (measure) and a cut (section), and it editsenclosure.py; every save is built and drawn. When it is done,M3saysPasses. Then send/mcp__torno__review, which has the agent go through every open mark against the build."Export…" at the bottom right (or
Ctrl+Shift+E) writes one HTML file that someone without torno can open: it needs nothing else.To keep what you made, press "Keep this as a folder…" under the workspace's name and choose a name. Otherwise it is removed when torno quits. Press
?for every key, andWfor the page of projects. Then open your own folder with the folder button at the top left.
Torno wrote only in the temporary copy, and in the one folder you chose if you kept it.
#Projects and builds
A folder is a project when it holds a .py file; dotfiles and __init__.py
do not count, and folders whose names start with . or _, and common,
tests and out, are never projects. The model is the one .py file that is
not a test (test_*.py, *_test.py, conftest.py). Of several, it is the one
named after the folder, with - and _ taken as the same:
helmet-stand/helmet_stand.py. Once a project is open, torno keeps building
the model it chose for as long as that file exists. A folder where no model
can be chosen, or a model with none of the forms below, is listed with the
reason it cannot be built.
With a project open in a folder of several, the list of projects is folded to one row, the
switcher: the open project's name, and "All projects W" beside the title. A click on the name,
or Enter, opens a menu of every project (a dot for a session that works or waits, the reason
under one that cannot be built) with "New project" at its foot; the arrows, Home and End
move in it, Enter opens one, Esc puts it away. When a session of another project waits for
you, the switcher has its dot and says so ("bracket needs you"). The "Projects" title unfolds the
whole list in place. With no project open, or the page of projects up, the list is whole.
The kernel imports the model file as a module (if __name__ == "__main__" does
not run) and takes the first of:
Params()withassembly(p)(layoutassembly) andprintables(p)orprintable(p)(layoutprint), each returning a dict of name to part.Params()with functions namedmake_<part>(p), defined in the model file and callable with the parameters alone: each one that returns a solid is the part<part>(layoutmain).- build123d shapes in module-level variables (layout
main).
In the print layout a model usually poses every part for the bed at the
origin. When parts of that layout overlap seen from above, torno sets them
side by side along X, 10 mm apart, in the order the model gave them; each
keeps its own Y and Z. The picture, the bounding boxes and scene all show
the parts where torno put them, and scene says how far each was moved
(arranged). The model and what it exports are not changed.
Checks the model declares. A model with Params() may also define checks(p), a list of declarations
that torno measures after every build, beside the dimensions you place yourself:
def checks(p):
return [
{"name": "lid fits the tub", "kind": "clearance", "a": "lid", "b": "tub", "min": p.clearance - 0.05, "max": p.clearance + 0.05},
{"name": "tub is one body", "kind": "solids", "a": "tub", "equals": 1},
{"name": "walls", "kind": "thin", "a": "tub", "min": 1.2, "layout": "print"},
{"name": "fits the bed", "kind": "bed", "size": [256, 256, 256]},
]
A declaration is data, not a function: torno measures it with the kernel's own queries, so each result says
whether it is exact. name and kind are required; a and b are part names or group names (a group stands for its parts;
never a face number, which changes with the model), layout is optional (the first layout, assembly before print,
that has the parts; print for the kinds about printing when it has them). The kinds are clearance (the least
distance, mm), overlap (the volume shared, nothing unless told), solids (how many a part has), volume
(mm³), thin (a wall under min mm: found by sampling, so no result is a proof that there is none), overhang
(faces facing down steeper than angle, 45 unless told), mesh (the part's mesh is closed) and bed (the layout fits
size). What is wanted is min and/or max, equals, or expected with tolerance or percent.
A declaration that is not one fails the build and says what is wrong, as a bad groups(p) does. A check that fails
never fails the build, and never holds the agent back when it would stop; it is told with the build
(What the agent is told) and listed in the window under Checks. torno never writes checks(p).
Names for faces and edges. A face number (lid/F12) changes whenever the topology does. A model with Params()
may also define tags(p, parts), which names faces and edges, and a name holds across builds:
def tags(p, parts):
lid = parts["lid"]
return {"lid": {"top": lid.faces().sort_by(Axis.Z)[-1], "screw_holes": lid.faces().filter_by(GeomType.CYLINDER)}}
It is called once for each layout, with the dict of parts that layout's function returned, and returns part name to
tag name to a face, an edge or a list of them, taken from those very shapes. A tag name is a letter or _ and then
letters, digits, _, . or -. The ref of a tag is lid/@top: it names one face or edge (several, lid/@screw_holes,
are taken whole by highlight and ask; where one face or edge is needed, a tag of several is refused, naming them). describe, measure, highlight and the
dimensions take it, scene lists each part's tags with the faces and edges they are now, and the pointer says
lid/F12 · @top. A mark you place on a face or an edge that has a tag keeps the tag, and each build looks for it
first: the mark follows the face by its name when the numbers shift, or the face moves. If the build has lost the tag
(or it names several faces now) the mark is found by its point, as any mark is, and keeps the tag for a build that
brings it back. A tag that names nothing the part has (a shape of another part, a face taken before an edit, an empty
list) fails the build and says which. torno never writes tags(p, parts).
Bought parts. A printed part is usually made around something bought: a screw, a bearing, a board. A model says so
with an optional references(p) beside assembly (name to shape, in the assembly's frame):
def references(p: Params):
return {"608": Pos(0, 0, 4) * SingleRowDeepGrooveBallBearing(size="M8-22-7", bearing_type="SKT")}
A reference is drawn in a neutral grey, listed in Layers with the tag "bought", and picked and measured like a part. It is an
obstacle: check clearance, overlap and interference take it against the parts (never one reference against another), and
checks(p) and groups(p) may name it. It is not made: it is in no print layout, not counted by check bed, has no mass, takes no
thin, overhang or mesh check, and is in no STL or 3MF; a STEP holds it only when ticked in the export sheet. Its name is
its own (not a part's, no /, not of the form M<n>), and a model with only a print layout cannot have one. Standard parts come
from bd_warehouse, which torno's own kernel environment has (simple=True for a screw); a vendor's STEP is read by the
model itself (import_step) and returned among the references, as torno reads no file of its own accord. A reference that is a
library's compound of many solids is taken as the solids it holds, in one shape.
Builds. Saving a file in the project's folder (dotfiles, out/, cache/,
.torno/ and __pycache__/ are ignored, as are build/, dist/,
node_modules/, .venv/ and .git/) or in the workspace's common/ starts
a build 300 ms later. So does saving a file that the model imported from another
project (uses in scene and state.md: box/box.py for a lid that imports the
box's maker), as far as the last good build found it; torno reads this from what
the model imported and never asks you to declare it. A project that imports one
that imports it back shows the other in uses on both cards; Python decides
whether that works. Saving a .py file during a build cancels that build
and starts another. Any other file changing during a build may be the model's
own doing, so the build is left to finish and one more follows it. A build
that runs longer than 600 s is stopped; TORNO_BUILD_TIMEOUT sets another
limit. The line at the bottom says building with the seconds elapsed, built
with the time it took and the revision (r3), or failed. A failed build keeps
the last good parts on screen, and its status opens the error with the
traceback.
History of builds. Every build that worked is kept under .torno/history/ (see Files torno writes): a copy of
the Python files it was made from, its parts' numbers, and a small picture. A kept build has an id of its own, h41,
which goes on counting across runs of torno; the revision (r3) is of one run and is told beside it. The latest 100
are kept, and every one you star. A build that fails the model's own checks is kept too, marked: it is often the one
to compare against. A rebuild of sources the newest kept build was made from is not kept again. History in the status line opens
the builds in a strip, newest first: the picture, the id, how long ago, and what changed from the build before in a
line (tub changed, lid same). Choose one for its facts, Show the source to read the Python it was made from,
read only, Star to keep it, Show over the current build to see it drawn translucent, in grey, over the build on show (for looking only: nothing is measured from it, a picture for the agent does not show it, and a section, parts drawn apart or another layout put it away; the status line says it is there and has Stop), or Ask the agent to go back to this build, which puts a request in the message box of
the session (or starts one) naming the copies, and sends nothing. torno never puts a file back: the agent edits the
project's files, under your permission rules, and the copies are not to be edited. What a kept build had then (checks
that failed, parts with a defect, how your dimensions stood when it was checked) is as of then, never how the project stands
now. A build can be shown over the current one only if it was made in this run and its meshes are still held: they are kept in memory, never on disk, within a budget of 64 MB for the builds before the newest, the oldest let go first. The agent reads the same with history, and compares any two with diff.
A failed fillet or chamfer. build123d and OpenCascade say only that a fillet
or a chamfer could not be made, naming neither the edges nor the radius. When a
build fails inside one, the kernel tries it again in parts before it reports the
failure, for 5 s at most (less when the build's own limit is nearer, and it says
when it ran out): each edge alone at the radius asked; if each works alone, the
pairs, those that share a face first; and the largest radius that works for all
of them, and for each edge that fails alone, to 0.001 mm. The error says it
under its message, such as fillet R6 of 8 edges: each works alone; 4 pairs of adjacent edges fail (edges 4-5, 4-6, 5-7, 6-7: 12 mm apart, two R6 need more); the 8 together work up to R5.999, then a line for each edge of the failures.
The shape being filleted is not a part on screen, so its edges are numbered by
their place in the call, from 0, and their ends are in its own frame (local);
one that is exactly an edge of one part of the last good build (its ends, its
length and the points along it) is also named by that ref, as of that build. Nothing is retried in the model. The agent is told the same with
the result of its edit and in scene, and can try a fillet on the last good
build with try_fillet before it edits the model again.
A failed boolean. A cut, a fuse, an intersect or a split that fails (a StdFail_NotDone, or Null TopoDS_Shape object) is looked into the same way, within the same 5 s, by looking at its operands: nothing is run again. The error
says which of these it found, such as cut failed: tool is a face, not a solid: an operand that is empty, one that is not a
solid, one that is not valid (OpenCascade's check, and what its argument analyzer finds, such as a face that crosses
itself), a tool that ends flush with the face it cuts instead of overshooting it (faces of the two that lie on one
plane facing the same way), faces that lie on each other, shapes further apart than they should be, and shapes that
only touch. Then a line for each operand (its type, whether it is valid, its faces and its box) and for each pair of
faces that lie on each other, with the area they share. The operands are not parts on screen: their faces are numbered
by their place in the operand, from 0 (tool F5), and their numbers are in the operand's own frame (local). A
distance is OpenCascade's and is told with ≈; a count of points of one lying inside the other is of points sampled
on its vertices and the middles of its edges, so it can say that none was found, never that none is there.
The window. On the left, the list of projects, then the layers, the parameters, the print profile and the scans of the
open project, then its marks. Projects, Layers, Parameters, Print profile and Scans each fold by their title; folded, the
title says what matters (Layers 12 · 3 hidden, Scans 1 · unaligned). Marks never folds:
it keeps room for five rows at least, and the sections above give way first. Drag the line
above Marks up for more (Up and Down with it in focus), double click it for the shares
again. Drag the right edge of the column to make it wider (Left and Right with it in focus),
double click it for the default width; the column is never narrower than that, and leaves the view
its room beside the session pane. What is folded, where the line is and how wide the column is are
kept by the window, not in the folder. The
layers (parts) of the open project each have a visibility switch, and for a model of ten parts or twenty they
are also the way to the part: the pointer on a row lights the part in the view, a click on its name selects it (as
a click with Select would, so "Insert" and "Note" work on it) and a double click brings it into view, the target on
the row shows only that part, and when some parts are switched off the title says 3 hidden · Show all. After the
name, a muted number counts the open marks on the part (in the colour of a failing check when one of them is
one); a click on it lists only those marks in Marks, under a line Only marks on lid with a cross that brings the
rest back. That is a view of the list: the agent is told every mark as before. Parameters lists the fields of
the model's Params (folded to its title at first; a model with none has no section): each
field's name, its default in the monospace face (four digits at most, the whole value on hover) and
its comment as the model has it, and, for a default the model works out (an expression, not a
number written in), that it is worked out in the model. The model's comments should begin with where a size
comes from (brief:, M3:, S1 ≈:, ref <file>:, object:, datasheet <name>:, person:, derived: or
assumed:), and under the title a line counts the sizes that say assumed and those that say nothing, their
names on hover; it is what the model says, not something torno has checked. torno never writes the model. A
field whose default is a number or a flag written in the file (wall: float = 2, lid: bool = True), or a
Literal["round", "square"], has a control under its name: type a number and press Enter, or let go of a slider
(the model gives one with field(default=2.0, metadata={"min": 1, "max": 5, "step": 0.2})), tick the box, or choose
from the list. The project is built again with that value, and the row says differs from the file with a
"Reset" beside it; "Reset all" and "Ask the agent to bake it in" (which puts a request in the session's prompt,
never sent) are under the title while any value is tried. What you try is kept in .torno/params.json and the file
always wins: if the model's default for that field changes, your value is let go of and the row says so, and a value
the model no longer offers is let go of the same way. The agent is told, with every prompt, which values the build
is made with. The model only sees a tried value where it is given p: a helper that makes Params() itself does not.
With a session running, a click on a row puts Params.wall in its prompt at the
cursor, never sent, so you can write "make Params.wall 2.4" around it; with no session the rows say
to start one. Checks lists what the model declares in its checks(p) (see Projects and builds), under
Parameters and folded at first to a title that says how many fail (Checks 3 · 1 fails; a model
with none has no section): each check's own name, how it stands in a word (pass, fails, not
measured, not run), what was measured and what was wanted, and, for a thin wall, that it
was found by sampling. They are the model's, marked with the agent's colour; your dimensions
are marks. A check the agent made easier to pass, or removed, since it was first declared is
said under the rows (walls: min 1.2 → 0.6 mm (since r7)). A model that says which of its parts belong together (an optional
groups(p) beside assembly, group name to part names) has them listed under a
header each, which folds the group, hides or shows all of it, or, on "Show only",
shows that group alone in the layout; ask the agent to group parts and it edits
groups(p). In the viewport, drag to orbit (about the point of a part under the pointer when the drag starts), drag with the right or
middle button to pan, scroll to zoom, F to fit the selection (pressed again, all the parts); a triad in the bottom left corner shows which way X
(red), Y (green) and Z (blue) run in the project's own frame, as the agent's pictures
do, and in the bottom right corner are a scale bar (orthographic) or the step of the grid,
grid 5 mm (perspective); the bar at the top switches layout
(assembly, print), view and projection. The folder button (top left)
opens a menu of the folders opened lately (the open one marked "current"),
"Open folder…" and the example; opening another ends the sessions of this one,
so torno asks first, in a dialog that names the projects, if one of them is
working or waiting for an answer. A folder that cannot be opened is said under
the folder's name, and the workspace stays as it was.
"Rebuild", in the line at the bottom beside how the build stands, builds the
open project again from its sources as they are (Ctrl+Shift+B from anywhere).
It is always there, and it is the main button when the last build failed for a
reason outside the model (the build took too long, stopped, or was cancelled) or
was let go to make room: building the same sources again may work. After an error
in the model it stays quiet, since the same sources give the same error. While a
build runs it reads "Building…" and takes no press: that build is of the sources
as they are. The parts on screen are older than that build, so a click cannot pick
them from the first moment; they fade only when it has run for 0.6 s, so a quick
save does not make them blink. A build that fails fades them at once.
When a build lands after another of the same project, the status line says which parts it changed:
"tub changed, lid same", or "no part changed" when a save made no difference to what is drawn. A part counts as
changed when its shape is another or it stands elsewhere or turned. Hover the words for the lines diff gives
Claude Code, for example "tub: volume 6280 → 6410 mm³ (+130, +2.1 %), size 40 × 40 × 40 → 40 × 40 × 42 mm"; a
number the kernel cannot give exactly has "≈". For about a second and a half the parts that changed are also shown
as ghosts: their old edges in the muted colour, where they stood, and their new edges in the accent. They go by
themselves, fading once, or at once on any key or click. The parts that did not change are not touched. There is
no ghost while a section is on or the parts are drawn apart, nor in a picture for Claude Code.
"Build failed" opens the error: what happened in words ("Python error in the model", "The build took too long (timeout)", "The build process stopped (crash)"), the message, why a fillet or a chamfer failed, the traceback and what the model printed. It says who is told: Claude Code is told this with your next prompt, or was told after its own edit, so there is no need to paste it into the chat. "Ask Claude Code to fix it" puts a short request in the message box, opens the session pane if it was away, and never sends it: you read it and press Enter. With no session running it reads "Start a session to fix it" and starts one with the request waiting in its box. After a failure outside the model "Rebuild" is the main button of the sheet instead. "Copy" takes the whole error as text. The line above the box of the chat says "build failed" among what goes with the next prompt.
A part the build says is broken (not valid, inside out, not closed, or with a mesh that disagrees with its volume) has the word beside its name in the layers, with every defect it has in its tooltip, and the line at the bottom counts such parts ("1 part has a defect"). The agent is told the same after its edit (What the agent is told).
When the project is in a git work tree, the status line and the card say what has changed since the last commit: "3 files changed since a1b2c3d", with the files in the tooltip (the .py files and the others of the project's folder, and common/; not .torno/), or "No changes since a1b2c3d". It is read when the project is opened, when a build ends, and when the window is focused again. torno only reads: it runs git status with --no-optional-locks, which leaves the index as it is, gives it five seconds, and never commits, adds or changes anything. Without git, or outside a repository, nothing is said. Commits are the agent's: ask for one in the chat (the Marks panel offers a request, see Marks).
The pointer is told what it is on: a face (lid/F12), or an edge within reach
of it (lid/E3), with a line from the kernel on what that is, such as
plane, normal +Z, z = 20, 1180 mm²; a face or an edge that the model names (tags(p, parts), see Projects and
builds) is told with its name, lid/F12 · @top. Hidden parts are never picked. While the
parts on screen are older than the model (a build is running, or the last one
failed) nothing is picked, and the pointer says why.
#Making a new project
"New project" starts one from what you have. It is at the foot of the list of projects (of the switcher's menu when the list is folded), in the header of the page
of projects and in its empty state: a folder with no project in it leads with "Make your first project", "New project" and,
where torno runs from its repository, "open the example to look around". The way for models you already have (a folder
such as part/part.py, and the note on pyproject.toml and uv) is in a fold, "Already have build123d models?". The page asks for a name and "What is it?" (what it is for, what it must fit or
hold, sizes you know, how it will be made), and then for what you have, each with a note for Claude:
- Photos ("Add photos…", or dropped on the page): PNG, JPEG or WebP, at most 12, 25 MB each and 50 megapixels
(a larger picture is refused with a reason on the card, before it is decoded). HEIC, the iPhone's default, is
not read: set the phone to Most compatible, or export as JPEG. Claude gets each one attached to its first prompt, after a line naming it ("Picture 1: reference/photo-1.jpg"),
scaled to 1280 px on its long edge; the original is copied to
reference/photo-<n>.<ext>in the project. - Doodles ("Draw…"): up to 6, drawn on a board (below). Claude gets each as a PNG with dark ink on white,
over a light grid (1 square = 40 px of the board, no scale, and the prompt says so), whichever theme you drew it in, and
reference/keeps it. - Bought parts ("Add a bought part…", or dropped): a vendor's STEP file (
.stepor.stp) of a screw, a bearing, a board, a motor; up to 4, 50 MB each (a larger file is refused, and is never read whole). The file is copied as it is intoreference/under its own name (made safe for a file name;608zz.step, or608zz-2.stepif two have the name), and the prompt names it and tells Claude to read it in the model withimport_stepand return it amongreferences(p)(see "Bought parts" under the model), not as a part to print. torno reads nothing of it itself. - Scans ("Add a scan…", or dropped): up to 4. They are imported into the project as "Import scan…" does, not
copied and not aligned; Claude is told each scan's file name and id, and that it is not aligned (you align it in
torno);
brief.mdkeeps the path it came from, with~for your home folder. - Other projects (a menu of the projects of the workspace): up to 8. Claude is told to look in
../<name>/and gets the project's summary (its parts and box, with whether the kernel says it is exact; "not built in this run" if it has not been built) and its thumbnail.
At most 6 pictures go with the prompt, photos and doodles first, then the thumbnails of other projects; a picture
beyond that, or one that fails the checks (PNG or JPEG, 2000 px at most, 3.75 MB, so that it is under 5 MB as
the API takes it; a PNG over that is sent as a JPEG), is named in the prompt as not attached, with the file in reference/ to read. Notes are kept to a line each and the description to 20,000
characters.
The name is the folder's and its model's: letters, digits, - and _ (no dots), starting with a letter or digit, at
most 64 characters, and not one that is there already (compared without regard to case, and a-b is a_b). Torno
refuses a name that would stand in for a library (json, typing, numpy, build123d, any module of Python's
standard library), since a model's folder leads the import path and its file would be imported in the library's
place; also Python keywords, the workspace's own common, tests, out and docs, tools, reference, and a
name whose model file would be taken for a test (test_*, *_test). The page says why while you type.
"Create" writes one new folder in the workspace and nothing else: <name>/<module>.py (a placeholder, a 20 mm
cube, to be replaced), brief.md (your words and a line for each reference with its note) and reference/. The
.torno/ folder is made as for any project. It is made in a hidden temporary folder beside it and renamed into
place as the last step, so it is whole or not there, and it never goes into or over a folder that exists (an
empty one of that name is used). A temporary folder a crash left is swept away at the next opening of the
workspace once it is ten minutes old, unless it holds files in reference/ (your doodles and STEP files may be nowhere else):
such a leftover, a hidden .<name>.torno-new-<hex> folder in the workspace, is yours to look at and delete. Then torno opens the project, imports the scans, starts Claude Code in a
chat and sends one prompt with everything. The prompt asks Claude to list the sizes that decide fit or function and
say where each comes from (your words, a note, a scan, another project, or nowhere), and to ask you for those that come
from nowhere in one round (a card in the chat, at most four questions, each with a typical value among its options;
"build with assumptions" is an answer) before it builds. What it builds without an answer is marked "assumed" in the
model, and its reply lists those. If Claude Code cannot start (the command is not found, or the
trust question stands, or the CLI starts and leaves at once because it is signed out or too old), the project is made
and open anyway: the pane says why and the text of the prompt waits in the message box for you to send. Where
torno already knows Claude Code is not found or not signed in, the page says so above "Create", before the folder is made. The
pictures are not sent then: the text says each is "not attached" and names the file in reference/ (and the
.torno/thumb.png of another project) for Claude to read. If the project was made but the workspace does not list it,
the scans were not imported, and the page says so and where the folder is. While "Creating…" shows, Esc, Cancel
and Discard do nothing.
While the page is up the window is about the project being made: no project of the list is selected (folded, the
list reads "New project"), the Marks
section gives way, and the status line says "New project" with the build123d version alone. The session pane says
the first prompt Claude Code will get once you create the project, as it will be sent and read-only, filled in as you go:
your own words in ink and what torno adds in grey, and under it a thumbnail of each photo and doodle, with "attached"
or "named only" (only the first 6 pictures go with the prompt). Change the words on the left; the prompt follows. A session of the project that was open that is
still running stays as it is, a question waiting in it included, under a note saying whose it is: it goes on, and
the new project gets its own. Cancel or Esc brings everything back as it was.
Beside "Create", "Claude Code on" picks the model and the effort the new project's session starts on. Both start
at "Your default", which passes nothing: Claude Code goes by your own settings. A choice holds for that session
alone (torno starts the CLI with --model and --effort) and is never written to your settings; the page keeps
it for the next project you make in this window. The models are the ones Claude Code offered in the last chat
(kept in torno's own data), or its aliases, Opus, Sonnet and Haiku, before any chat has run; the efforts (Low,
Medium, High, Extra high, Max) are the ones the chosen model takes, and a model that takes none leaves the effort
off. Both can be changed in the chat afterwards.
The doodle board (the same board as the chat's "Draw on view") has a thin pen, a thick pen and an eraser, "Undo" and Ctrl+Z, "Clear", and "Done" and "Cancel". The doodle board fills the window and has a light grid
under the ink; hold Shift while drawing for a straight line from where the stroke began. (A drawing on the view has no grid: its ground is the picture.)
Esc puts the board away; if strokes were drawn since it was opened it asks "Discard this doodle?" first. A
doodle can be opened again from its card and changed.
What you have entered is a draft, kept while the window lives: you can leave the page, open another project and
come back. Esc on the page leaves it, asking first when something is in it; so does leaving the workspace, and so
does closing the window ("A new project is set up here and not made yet"). Nothing of the draft is written until
"Create".
#The agent
Each project has its own session: Claude Code running in the project's folder.
It opens in a chat, a pane of torno's own, or in a terminal when you switch to
it. It keeps running while another project is shown. A session that works or
that waits for your answer (a permission, a question, a plan) says so wherever
you are, not only in its own pane. In the list of projects its row has a dot
after the name: a quiet one while it works, and, in the warning colour with the
words "needs you", while it waits. Folded, the list's one row has the dot of any
session of the folder that works or waits, and names the project of one that
waits ("bracket needs you"); its menu has a dot on each row as the list does. Nothing is shown for a session that is idle
or ended. When the session pane is hidden, its button has the same dot for the
open project's session. And when any session of the folder waits for you, the
status line has a button "bracket needs you" (with "and N more" if several do,
the first in the list named): it opens that project, shows the pane and takes you
to the card. If the window is not the one in use when a session starts to wait,
its frame flashes (the dock icon bounces on a Mac), once for each question; torno
posts no system notification. "New session"
starts one, in place of the session running there, if any; torno asks first if
that one is in the middle of its work. "End" ends the session and starts
none, asking first in the same way. "Resume" continues the last
conversation Claude Code had there. In the chat view the empty pane has the box already: write, press
Enter, and a session starts with your prompt. Where a conversation is on file its last turns are shown above the box,
under "Last conversation, 2 days ago", with "Continue" (the default) and "New conversation" beside "Send"; the
buttons "New session" and "Resume" start one with no prompt and leave what you wrote in its box, unsent. If
the folder has Claude Code settings of its own, the question is asked first and your prompt waits in the box. The empty
pane has no "Attach view" or "Draw on view": those are of a session. Ctrl+` moves between the session and
the viewport; F6 and Shift+F6 go round the parts of the window, and Ctrl+Shift+D shows or hides the session pane. In a terminal Shift+Enter is a new line, Ctrl+Shift+C and
Ctrl+Shift+V copy and paste.
#What goes well, and what does not
What torno and the agent do well, and where you have to help. This comes from how the loop has gone on real projects, not from a promise.
Goes well:
- Prismatic parts: brackets, enclosures, holders, a lid and a box, hole patterns, a part with a slot or a rib.
- A part whose sizes you measured. Give them in the first prompt, or in the brief, as numbers.
- Changes you can point at: a pin on a face, a dimension with the value it should have.
Needs more from you:
- A shape that has to fit a place you can only scan, such as an engine bay. Import the scan, align it and mark regions of it. What is taken from a scan is approximate, and torno says so (see Scans).
- An assembly of more than three or four parts that touch each other. Split it into projects of a few parts, and
check each pair with
check. - A fit that depends on how your printer behaves, such as a clearance or a snap. Print one, measure it, and give the agent the number. A clearance the agent assumed is only an assumption.
Does not go well:
- Ergonomic and organic shapes described in words alone. Show a photo, a doodle or a scan, and expect to try several.
- Gears, threads and springs written by hand. Ask for a library part, or for a plain stand-in you replace.
- Sizes read from a photo. The agent will give a number, and it will be a guess. Measure with calipers.
In every case the answer is the same: give a measured number, a scan or a dimension mark, in place of a description. How to work with the agent is in the README's guide.
#The chat
The chat is your own claude, under your own login, started in its headless
mode (JSON on pipes instead of a screen). Torno draws what it says and does;
nothing of Claude Code's settings is read or changed (torno lists the paths and sizes of the instruction files Claude Code reads, never their text). Enter sends, Shift+Enter
is a new line, and a prompt sent while the agent is at work waits its turn
("Queued"; "Stop" interrupts the turn, and what was queued still runs after it; Ctrl+C does the same in the box when nothing is selected there).
- Suggestions. While the box is empty, up to three quiet chips above it offer
the next useful prompt, each only when it applies: "Fix the build" while the
build has failed, "Review N marks" while marks are open (
/mcp__torno__review), "Check printability" for a model with aprintlayout that no turn has checked (/mcp__torno__printable, for which you may add your bed after it), and "Explain this model" in a conversation not yet begun (/mcp__torno__explain). A chip puts its words in the box and gives it the keys; nothing is sent until you pressEnter. The prompts of torno are not offered when Claude Code says torno's tools are not connected. - What torno added. Under each prompt, a "torno added" line shows what went
with it: the marks sent, as chips in their colours, the build (
r12 ok, orbuild after r12 failed), and the selection; "Show text" opens the exact text the agent was given. Above the box, "Goes with your next prompt" says what will go. - The build an edit started. When the agent edits the model, the build that
follows is a line of its own under the edit's card:
Built r2 · ok · 1.2 s, orBuild after r2 failed: NameError: name 'WAL' is not definedin the fail colour (the last line of the traceback), with "Show error" opening the sheet of that failure while it is the latest build. Under it, the dimensions whose verdict that build changed (M3 now passes,M2 now fails) and the parts it changed, a line each as the kernel said them (tub: volume 14710 → 20460 mm³ (+5752, +39 %), with≈where the number is not exact), four at most. "Show text" opens the exact text the agent was given. A revision names a build that worked: a failure has no number of its own, and is told as the build after the one still on show (the first build, when none has worked), in the window and in what the agent is told. Once a build has failed, the chip of the build still on show opens the same sheet. - What the agent pulled from torno. Every tool of torno's it calls is a card
of its own, with its input, its result and its pictures as thumbnails that
open large; the turn ends with a line such as "Pulled from torno: scene, 2
pictures, measure ×3". Other tools are one line each, opened for their input
and result; a tool that went well says nothing on its line, and one that is
running, failed, was denied, cancelled or stopped keeps its word. Once a turn has
ended, three or more of those lines in a row (the agent's own tools that finished,
with no picture and no build under them) are one line,
Read 3 files, Grep ×2, Bash, that opens into the lines; the lines of a turn still going on are all there, and folding changes nothing in the conversation or its log. The refs in a card's line are chips. A finishedlook,sectionorshowhas "See in view" (or "Show again" forshow): it puts what the agent looked at on your screen as your own edit, as a saved view is applied (the layout, the parts shown, the cut or the parts apart, the scans, and the camera), so that you can turn it, measure in it or save it. A look framed on a place (focus) is not framed again. A finishedhighlighthas "Point again", which lights its refs once more, while the window shows the build they were read from and the card was seen as it happened. When a card cannot be gone to (another layout, a part or scan the build lacks, parts apart under a cut) the line says why, in the tools' words, and nothing changes. - The agent's task list. What the agent plans with
TodoWrite, or with the task tools (TaskCreate,TaskUpdate) Claude Code has behindCLAUDE_CODE_ENABLE_TODO_TOOLS, is a checklist above the box while the turn runs and has a task left (Tasks 1 of 4 done, what is being done in the words of "doing"), and a card in the transcript where the call was, as the list stood then. torno reads it from the cards and keeps nothing of it. - What the turn did. The last line of a turn says, from the turn's own items, what it did to the project: the files the agent edited ("hollow.py edited ×2"), the builds those edits started ("r1 → r3 ok", or "build failed"), the marks it resolved and added, and the dimensions whose verdict is another than it was ("M3 now passes"). A turn that did none of these says only how long it took.
- See changes. A turn that edited a
.pyfile has "See changes" under its account. It opens a sheet over the chat with, for each.pyfile directly in the project's folder that the turn changed, the file at the start of the turn against its end as lines removed and added, and the file as it is now (read only: torno never writes the model, and the sheet runs nothing). Under them, the builds of the turn taken together, from the kernel's numbers: a part's volume and face count before the turn's first build against its last (≈where a number is not exact). torno reads the files when you send a prompt and when the turn ends, and keeps the two texts in memory for the last 20 turns of the session: nothing is written to disk, not under.torno/and not in the chat's log, so a turn of an earlier session, or one more than 20 turns back, has no changes to show. Only the project's own folder is looked at, notcommon/, and a file over 200 KB is said to have changed without its text. The button is there when the agent's edit tools changed a.pyfile; the sheet then lists every file that differs, a change made by a command (sed, a script) too. - Undo. The latest turn that edited a
.pyfile has "Undo" beside "See changes", while the session is idle and before you send anything else; an earlier turn, or one that edited nothing, has none. It asks once ("Ask Claude Code to put the model files back as they were before this turn?"). On "Ask to undo" torno writes a prompt of its own, from the two texts of each file it kept for "See changes": put the files back as they were before that turn, with the lines to reverse (-is in the file now and goes,+comes back). It is sent as yours, after a quiet note that says what was asked. The agent makes the edits itself, so each one comes to you as any edit does (a permission card, if your mode asks), and the build runs as after any change of the source. torno does not write the model, and the conversation keeps the turn, so the agent knows it was undone. It covers the same.pyfiles as "See changes": notcommon/, not other files, and a file over 200 KB or too many lines is named without its lines, for the agent to undo from the conversation. A file you saved after the turn is said to have changed, so the agent reads it before it reverses a line. It is a request, not a restore: it can be done wrongly, and the sheet "See changes" shows what the file is now. Nothing is kept of a turn of an earlier session, so there is no "Undo" there. - Refs are live.
M4,lid/F12,lid/E3andlid/@top(a name the model gives a face or an edge) in the agent's text and in the cards are chips: hovering one lights it in the view, clicking a mark focuses it, clicking a face or an edge selects it (a name of several selects them all). A ref that names nothing now (a mark that is gone, a part not shown) says why and does nothing. What is no ref of this project is only text: anM3clearance hole when the project has no markM3, a buildr9that has not been made, a part the build does not have. - When it asks. A permission is a card in the conversation: "Allow",
"Allow for this session" (offered when Claude Code suggests a rule; the rule
holds for this session only and is never written to any settings file) and
"Deny", with what to do instead if you give it. While a permission or a plan waits, what you
write in the box is the answer: "Deny and send" (or
Enterin the box) refuses with it, and "Queue" leaves the card as it is.Enteron the card itself allows it. A change to a file (Edit,MultiEdit) is shown as lines removed (struck through, with a "−") and lines added (with a "+"), the first six of them and "Show all"; the same lines are in its tool card when you open it. They are the lines of the edit, not of the file: where in the file it lands is not shown (the card says so), and an edit that replaces every match says that. A text of more than 400 differing lines is not compared: all its old lines are shown removed and all the new ones added. AWriteshows the first lines of what it will hold. A question of the agent's is a card with its options and an "Other" line; a plan is shown to be approved or kept planning. The session reads "Needs input" while a card is open. "Stop" with a card open ends the turn and the card reads "Cancelled: the turn was stopped"; nothing is run. - Subagents. What a subagent (
Task) says is not drawn as the agent's own words: its card counts the steps it took ("12 steps") and, opened, lists the tools it used. - "Attach view" adds a picture of the viewport as you see it to the next prompt only. Pressed, it shows a small picture of the view as it is now above the box, with "Remove", beside the drawing's row when there is one; the picture that goes with the prompt is the one shown, whatever the view does meanwhile.
- "Draw on view" opens a board over a picture of the view: the very picture
the agent's
lookmakes of it, with the axes, the scale bar and the marks numbered. Draw on it as on the doodle board (two pens, an eraser that takes away only your ink, "Undo" andCtrl+Z, "Clear"); the ink is magenta, edged with a thin line, which no mark is drawn in. "Done" puts the drawing above the box, with "Edit" and "Remove", and the box has the keys again, for the prompt it goes with; it goes with your next prompt only. The row says what the ink lies over, before anything is sent:Over tub/F3, tub/F1, the cut face of lid · stroke 1 partly over no part · stroke 2 over no part, the refs as chips that light their face in the view when the pointer is on them. If the view moved while you drew, it says in the warning colour that this was not worked out, with "Draw again"; if the model has been built again since, it says the faces are of the build you drew on. The prompt, once sent, shows the same line under its picture. Claude Code gets the picture with your ink, and before it a caption torno writes: that the strokes are your drawing, which build it was drawn on, the camera it was seen from (so that it canlookfrom there again), and stroke by stroke the faces of the parts, the cut faces of a section ("the cut face of lid") and the scans your ink lies over, by ref with the kernel's line for each face (lid/F12 (plane, normal +Z, z = 20, 1180 mm²)). No coordinate is taken from the drawing. If the view moved while you drew (the agent'sshow, a build), the caption says what lies under the ink was not worked out; if the model was built again before you sent it, it says the refs are of the build you drew on. The terminal view has no "Draw on view". The picture is never written to disk; the line of what it lies over, refs only, is text in the chat's log like the rest of the prompt. - What Claude Code says of itself. torno reads the account the CLI gives at
the start of a turn and says what a person would miss: a warning when torno's
tools did not connect, with what the CLI said; a warning when torno's skills
did not load, with "Restart with the whole prompt" above the box, which ends the session and
goes on with the same conversation told in the prompt what the skills would say; a quiet
line, once a session, when the chat is billed to an API key (it says
what Claude Code names as its source) and when the CLI is older than 2.1.300 or of a
newer minor than 2.1.x (torno was checked with 2.1.300 and 2.1.301; any other 2.1 is quiet). While the API is being retried the status
at the top reads "Working, retrying (attempt 3)". A permission the CLI
refused by itself, with no card to show it, is one warning at the end of the
turn. When the CLI says a turn could not authenticate (it marks its answer
authentication_failed, as it would shouldclaude -pstop reading your login), a warning says the chat could not use your login, once a session, with "Use the terminal", which runs this project's sessions in Claude Code's own terminal, the conversation going on there. A field the CLI does not print is not a warning. Nothing of your settings is read. /commands. Typing/offers the commands Claude Code has;UpandDownchoose,Tabcompletes,Esccloses the list./compactleaves one note with the tokens before and after. What you type is sent to Claude Code as typed;/modelis not one of the commands torno has checked in the chat, so use the model menu for the model of the session.- The model is chosen in the menu under the header. It changes the model of
this session only;
/modeltyped in a terminal session changes your default (see below). The effort is the menu beside it: "Default effort" until you choose one (or one was chosen when the project was made), then the level, from those the model takes. It too is for this session only, and a note in the chat says each change. The edits menu beside them sets how Claude Code asks before it changes something: "Ask before edits", "Accept edits" (edits go through without a question; other asks still come to you) or "Plan only". It is for this session only too, and your settings are not written. A mode your own settings start the session in stands as it is until you choose one. A line under the box shows the rate limit once more than half of it is used.
"Chat · Terminal" under the header switches the same conversation between the
two: the session is ended (torno asks first if it is busy) and started again,
resuming it. With no session it sets what the next one will be; the choice is
kept in .torno/agent.json. What the chat showed is kept as text in
.torno/chat/, and a resumed conversation shows it under "Earlier in this
conversation"; pictures are never written. A revision counts from 1 in every run of torno, so r12 of an earlier part of the conversation may name another build now: the chip's tooltip says so there, and in the current run it says what the build was made from ("sources 9f3e1a2b4c5d, on a1b2c3d with changes": a hash of the .py files it read, the commit then checked out, and whether files differed from it), also under "Show text" of the build line. .torno/builds.jsonl keeps the same for each build. The photos and doodles you gave when
you made the project are shown again from reference/, by the name the log
keeps (one you have since deleted says reference/doodle-1.png is no longer there); a picture a tool gave says "Picture of an earlier build, not kept",
and any other "Picture not kept".
#What Claude Code is given
Torno runs Claude Code, and changes nothing of its settings: everything it gives it is on the command line, in the environment, or in a file of torno's own that holds no secret. It reads none of its settings either. Of the instruction files Claude Code reads on its own, it lists the paths and sizes (next paragraphs) and never their text.
- Torno's tools:
--mcp-config(the path of a file under torno's own data folder that names torno's MCP server),--allowedTools mcp__torno__*. - The session's token:
TORNO_SESSIONin the environment, which Claude Code sends in theX-Torno-Sessionheader of every request to torno. It is never on the command line (see "Limits and exactness"). - Torno's instructions:
--append-system-prompt, and torno's skills with--plugin-dir. They give the loop: after an edit, numbers first (measure,check,section,diff), then one look with the marks drawn. - Hooks:
--settings; it posts them to torno, which follows its status by them (idle, working, needs input, ended) and adds to its prompts. - What you marked goes with your next prompt, by itself, and counts as sent once the prompt's hook has been answered with it.
- The instruction files Claude Code reads for the project are listed in the
session pane, in a quiet line under the view's buttons ("Claude Code also
reads, by default,
~/objects/CLAUDE.md7 KB"), for the chat and for the terminal. It is what Claude Code loads by default when it starts in the project's folder:CLAUDE.md,.claude/CLAUDE.mdandCLAUDE.local.mdin the folder and in every folder above it,.claude/rules/there (one entry: the folder, "up to" how many.mdfiles, their size: a rule that namespathsin its frontmatter loads only when matching files are used, which torno cannot tell without reading it), your own~/.claude/CLAUDE.mdand~/.claude/rules/, and an organisation's file (/etc/claude-code/CLAUDE.mdon Linux,/Library/Application Support/ClaudeCode/CLAUDE.mdon a Mac). AnAGENTS.mdor.claude/AGENTS.mdis listed only when none of the first three is in the folder or above it, as Claude Code reads it only then. Only paths and sizes are looked at, never a file's text, and a link is listed by its own name and not followed. A name is a button that shows the file in its folder in your file manager. It is a list of what Claude Code reads by default:@pathimports, files of subfolders (which it loads when it works there) and the settings that leave files out (claudeMdExcludes,--setting-sources, "Project instructions") are not followed. The first prompt of every session, and the first after/clear(when Claude Code loads the files again), says the same to the agent, with the paths and sizes (a "torno added" line also says how many, without opening it), and that what torno's tools say of the build is current: aCLAUDE.mdthat names another viewer, report or export step is about another way of working, so its advice on how to model stands and its way of seeing the build does not.
.torno/agent.json keeps the view, chat or terminal, with "agent": "claude". A file that names another agent is not followed: Claude Code is
used, in the chat, and the pane says so, with "Use Claude Code" to write the
file anew. One that names Copilot CLI, which torno also ran until 16 October
2026, says that torno no longer supports it. The .torno/prompt.md and
.torno/agent/ such sessions left are neither read nor removed.
Claude Code is also given torno's skills, a plugin that comes with torno
(app/resources/agent-plugin) and is loaded for that session only, with
--plugin-dir: nothing is installed in your Claude Code. A skill costs a line
until the agent decides to read it. torno:torno-model is the model file
contract in full (which file, Params, assembly, printables, make_<part>)
with a template; torno:build123d the idioms of build123d 0.13, the failures
that leave a valid but wrong part (a boolean that lost volume, an edge taken from
an older shape), the usual OCCT errors (fillets, booleans, StdFail_NotDone; of a
failed fillet, to read why and try one with try_fillet before editing again), an
API reference of build123d 0.13.0 that the agent searches offline in any
workspace, and the sizes of screw, nut and insert holes and how to use bd_warehouse
(which torno's kernel has) and references(p) for bought parts; torno:scans and torno:dimensions what the
instructions would otherwise say of scans and of dimensions; torno:printing
the rules of a part for an FDM printer (fits per side, walls as multiples of the
line width, overhangs, holes, orientation and the bed), with typical numbers
that are only assumptions until you give your printer and material (the
/printable prompt reads it first). So Claude Code's
instructions are shorter: they keep what torno is, refs, marks, the loop and
the model file in short, and name the skills.
Claude Code is also given a subagent, torno-reviewer, on its command line
(--agents; nothing is written to .claude/agents/). It has only the tools
that read (scene, look, marks, measure, check, section,
describe, diff, the scan tools, and Read). It runs on Sonnet, whatever
model the session uses (TORNO_REVIEWER_MODEL=inherit gives it the session's,
or a model name another), and stops after 20 turns. It reads brief.md, checks
the current build against what you asked, every open mark and every dimension,
numbers first and at most two pictures, and ends with one line each that begins
HOLDS, FAILS or UNCHECKED, with the number and the ref. It changes nothing. Claude Code is told to call
it after a change of several steps, before saying it is done.
The status follows the hooks Claude Code posts: UserPromptSubmit,
PreToolUse and PostToolUse mean working, Stop idle (and so does
StopFailure, which ends a turn an error of the API cut short, and is never
held back), PermissionRequest
and a Notification that asks something needs input. A Notification that
only says Claude Code has been waiting at its prompt for a while (or has
signed in) leaves the status as it was. In a terminal, the first time Claude
Code runs in a folder it asks whether you trust the folder; that question is its
own, and it is answered there. Run headless, as the chat runs it, Claude Code
skips that question, so torno asks instead: before the first chat in a project
whose folder, or a folder above it up to your home, has settings Claude Code
would load (.mcp.json, .claude/settings.json, .claude/settings.local.json),
the pane names those files and asks "Start the chat anyway?". "Start the chat"
remembers the folder (its real path, in torno's own data beside the recent folders,
never in the workspace) and does not ask again, whatever those files later say;
"Use the terminal" starts Claude Code in the terminal view, which asks its own
question. Your own ~/.claude is never one of the settings files looked at for it.
/model typed in a terminal session of torno's changes the model of that
session and also your default for every Claude Code session from then on:
Claude Code writes it to ~/.claude/settings.json, torno's or not. The model
menu of the chat does not; nor does starting torno with
TORNO_AGENT_CMD="claude --model sonnet", which saves nothing.
#What the agent is told
With every prompt, a few lines:
[torno] bracket · build ok · r3 (1.2 s) · layout assembly · parts: base, post.
Scans: S1 Bay.glb, aligned in print by 4 pairs (RMS 2.1 mm, worst 3.4 mm), shown at 60 %.
Selected: base/F5 (plane, normal +Z, z = 2.5, 1200 mm²), base/E5 (line, 30 mm)
Marks from the user (drawn in look pictures with these numbers):
M3 pin on the edge lid/E40 between lid/F12 and lid/F40 (line, 40 mm) at (12, 40.5, 20): "this edge is ugly"
M4 thickness 8 mm wall, along the normal, plate/F2 at (30, 0, -10) (expected 10 ± 0.05: FAIL, 2 under)
Dimensions (measured on r3):
M1 distance 6 mm between planes, plate/F2 to plate/F4 (expected 8 ± 0.05: FAIL, 2 under)
1 pinned dimension passes
Open marks: M2 (moved), M6 (unchecked)
- The first line is the build: for a failed build it carries the error; for
a project that is not the one open in the window it says so, and names no
layout; with the parts drawn apart it says
exploded 40 % along z (display only)after the layout. Scansis there when the project has scans, andSelectedwhen you have faces or edges selected and the parts on screen are those of the last build (eight refs at most).- Each mark you left since the last prompt has a line, once: it then shows as
sent. A mark you changed or put somewhere else is told again, as
(changed) M3 …; one you reopened as(reopened by the user). A dimension that is still a draft is told here with its value and verdict. Dimensionslists, with every prompt, each dimension the agent was sent that fails, is not measurable or was not measured on the last build, and how many pass. The head names the build the values are of, and says so while a newer build is running or has failed, so that a FAIL of the build before is not read as a verdict on the edit. A line stays until its dimension passes or is resolved.Checksis there when the model declares checks (checks(p)), after the dimensions: how many pass, fail, could not be measured or were not run, and each that did not pass with its value and what was wanted (Checks (the model's own, on r3): 2 pass, 1 fails: lid fits the tub (0.12 mm, wanted 0.15 to 0.25 mm)). It says what was measured and never that the part is verified; passes that were found by sampling or are approximate are counted.Open marksare the numbers of the marks still open, each with what the last build did to it. After/clear, or after the conversation was compacted, the first prompt that follows lists your open marks once more.- The first prompt of a new conversation (a session that was not resumed, or
after
/clear, or after a compaction) ends with a line naming the files inreference/(twelve at most, then how many more), to read when they matter. - The first prompt of every session, resumed or not, and the first after
/clear, ends with a line naming the instruction files Claude Code reads there by default (six at most, with their sizes; see "What Claude Code is given") and saying which wins for seeing the build: torno's tools. It is under "Show text" in "torno added". Nothing is said when there are none.
When Claude Code has edited the model, the answer to that edit says how the build ended, the same first lines and the dimensions. Between them, of a build that worked, torno says what the build did to the parts and what is wrong with them, which it checks at every build from the numbers the kernel worked out of the shape:
[torno] tub · build ok · r8 (0.9 s) · layout assembly · parts: tub, lid.
Changed since r7: tub: volume 28400 → 26100 mm³ (-2300, -8.1 %), faces 14 → 22, solids 1 → 2; 1 part unchanged.
Check: tub is 2 solids (was 1); the face or edge numbers of tub may name others now (describe refs again).
Dimensions (measured on r8):
M4 thickness 2 mm wall, along the normal, tub/F2 at (30, 0, -10) (expected 3 ± 0.05: FAIL, 1 under)
Changed sinceis each part whose volume, size, place, face, edge or solid count changed since the last good build, in the words ofdiff, with≈where either build's number is not exact; the parts that did not change are counted. A number that is not exact (a freeform part's volume or box) may differ in its last digits from one build to the next: it counts as changed only by more than the kernel says each build's is in doubt.Checknames a part that is not valid (OCC's check finds a defect), one with a solid inside out, one with no solid, one of more than one solid or of another number of them than before, one with an open shell, one whose mesh encloses another volume than its own (by more than the mesh explains), a part that came or went, the parts whose refs may now name other faces, and a dimension that passed on the build before and fails now. With nothing to name it says so. A change of volume is told, never judged: torno cannot know whether a pocket was meant.- At most 1,200 characters, cut by whole parts, with what was left out counted
and the tool to read it with. The first build of a run has only
Check. - A part that is not valid, has a solid inside out, came apart into more solids or has a mesh that disagrees with it, where the build the turn began with did not, holds Claude Code back once when it would stop, if the turn caused that build (see below): it is asked to mend the model or to say why that is meant. A change of volume alone never does.
- The chat shows it under the edit's card (the build line: the parts that
changed, the dimensions that changed verdict), the whole text behind "Show text",
and
state.mdcarries it under the build. It is not told with a prompt.
When Claude Code would stop, torno holds it back for what the turn itself did:
once for each reason, and twice in a turn at most. Every reason is for a build
the turn caused: one that began after the turn first
changed a source, by an edit of a .py of the project or a Bash command after
which one is newer. The first build of a project you have just opened, a save of
yours in your own editor before the agent's first edit, and any build in a turn
that changed no source are told to the agent as ever but never hold it back. It
first waits, up to twenty seconds, for such a build that is still running and
for the dimensions to be measured. Then, by the first that applies:
- The build is still running. It is asked to read the result with
sceneand to look. - The build after the one on show failed (
The build after r7 failed: ...), and it is asked to fix it or say it is still broken. - A part has a defect the build the turn began with had not (above).
- A dimension that passed on the build the turn began with fails on the latest:
M4 passed on r7 and fails on r9 (6 mm, expected 8 ± 0.05). Fix it, or say why it must change.A dimension that already failed when the turn began never holds it back; the agent may rightly stop to ask about it. - A build the turn caused ended after its last
look:r9 was built during this turn, and you have not looked at it since.This is however the file was written, by the agent's editing tool or through Bash. Alookby the reviewer counts. When nothing in the parts changed since the turn began (a comment, a rename: every part is the same shape, by the kernel's hash of it, in the same place), there is nothing to look at and it does not hold. A volume, size and count that stayed as they were do not say so: a hole moved is another shape.
A reason already held is not waited for again: a Stop after one that was held for a running build
does not wait again.
A hold for a defect or a dimension that finds the build unlooked says so too, and counts for both. A hold is said to the agent and changes no status.
The answer waits for the dimensions to be measured on the new build, ten
seconds at most, and ends with what the model printed: Model printed (last 2 lines): and the end of it, fifteen
lines and a thousand characters at most, with a note when earlier output was
cut, and nothing when the model printed nothing. It is the output of the
build that ended last, failed or good, and build123d's own warnings are in it.
So a print() in the model is how the agent sees a value, since it may not run
the model itself; scene gives the same as output. state.md does not
carry what the model printed.
After a build, the agent is also told which sizes the model's comments say are assumed or have no source
(Assumed: wall 2, lip 3 (2 of 14 sizes); no source: 4), only when that set is other than after the build before.
.torno/state.md says the same as text, for an agent without torno's tools:
the build, the layouts and parts, where each size of Params comes from, the saved views, the scans and every open
mark. Its first line says when it was written: nothing keeps it current once
torno has quit.
#Tools
Torno's MCP server (on 127.0.0.1, a port of its own choosing, answering only
requests that carry the token of the project's session) gives the
agent of a project these tools for that project. They answer from the last
build that worked, say which revision that is, and say so when a newer build
is running or has failed. A tool given a key it does not take is refused,
naming the keys it takes.
The tools that only read (scene, look, marks, describe, measure, section, check, diff, history,
try_fillet, try_params, features, scans, scan_clash, scan_deviation) say so, and Claude Code may call several at once. The server also tells Claude
Code at the start, in a few lines, what each tool is for. Claude Code's tool search keeps an MCP server's tools out
of the model's sight until it searches for them; five are loaded from the start because nearly every turn uses
them: scene, look, marks, describe and measure. The others are found by that search.
| Tool | What it gives |
|---|---|
mcp__torno__scene |
The build status and error, what the model printed in the last build (output), the layouts, every part of one layout (the one you have on screen, or layout; the parts of the others are counted) with its size, bounding box, volume and whether it is visible, the model's groups of parts, your camera, section and explode, the saved views, the scans, how many dimensions pass, fail and are not measured, and the checks the model declares with how each stands. It notes when the parts of the print layout were spread along X. Over 200 parts, or when the list would not fit, a part is its name, size and group only (partsTruncated); describe gives the rest. |
mcp__torno__look |
A picture from the renderer you are looking at, with the marks drawn. With no arguments it is your view, cut by your section or drawn apart as you have it. It takes view (iso, front, back, left, right, top, bottom, the name of a saved view, or a camera), layout, parts, size (long edge, 1280 by default, 2000 at most), marks (false leaves them out), section ('current', null, or a plane), explode (0 to 1, or {amount, axis, scale}; null together), scans, scanColour and frame. With focus (a ref such as lid/F12 or lid/E3, a part, a group, a mark such as M4, or a box {min, max} in mm) and margin (a share of the box, 0.3 by default) it frames a detail up close: from the side view names (front, iso, …; without one, the way your camera looks), at the picture's whole resolution, with the parts drawn together. The caption says what was drawn, and, with a focus, what was framed and when the middle of it is hidden behind other parts. Also saved under .torno/views/. |
mcp__torno__marks |
Your marks and the agent's as data, each with a line of text. It lists the marks that are not resolved, 50 at a time; status (resolved too), ids (["M4"], any status), limit (200 at most) and next say which. A stroke is given as a summary of its points (count, first, last, box) unless ids names it. What was left out is said (omitted, next). |
mcp__torno__describe |
The geometry of a part, a face or an edge (lid, lid/F12, lid/E3, or one the model names, lid/@top), or of a group of parts by its name: the box of its parts and their volume. Of a face or an edge it also gives a selector: a build123d expression on part, such as part.faces().filter_by(Axis.Z).sort_by(Axis.Z)[-1], that the kernel evaluated on this build and that gave exactly that shape, as robust by geometry (a direction, an extreme, a radius, a place) or by order (an index into a sort, which the next edit may break), and selectorFrame, which says in whose frame it is. |
mcp__torno__highlight |
Tints faces or edges in your viewport with a note, to ask "this one?", until you click or a minute passes. A group's name tints all of its parts. |
mcp__torno__ask |
Asks you to choose and waits: a card above the box of the chat (under the view for a terminal session) with a button for each answer, and the refs of an answer light up in your view while the pointer is on its button. If it allows, you may answer with the one face or edge you select in the view instead, or with "None of these". The agent gets back what you chose, as refs. The session shows Needs input meanwhile; the question ends by itself after 10 minutes with "not answered", and when the turn ends. |
mcp__torno__show |
Changes your camera, layout, visible parts (a group's name stands for its parts), section, explode and scans to show you something, or puts a saved view on screen by its name; with focus and margin, as look takes them, it puts your camera on a ref, a mark or a box. For now only: what .torno/scene.json keeps stays yours. Refused while you hold a button in the viewport. |
mcp__torno__save_view |
Proposes a saved view: it appears in your strip marked "proposed" until you keep or discard it. Nothing on screen changes. |
mcp__torno__add_mark |
Leaves a mark of the agent's, in green: a pin, a pick, an arrow, a shape, or with kind: 'dimension' a dimension, measured at once and on every build like yours. A point that is not on the part is refused, saying how far off it is. |
mcp__torno__resolve_mark |
Resolves a mark with a line on what was done, which you then confirm or reopen. A dimension of yours with an expected value is refused: it is your check, and only you resolve it. |
mcp__torno__measure |
An exact number from the geometry. See Measuring and dimensions. |
mcp__torno__section |
The outlines a plane cuts of the parts, with their areas, and a 2D drawing of the cut. See Section. |
mcp__torno__check |
Whether two parts overlap, how far apart they are, whether a part's mesh is closed, and whether it prints: overhangs, thin walls, the fit on a bed. |
mcp__torno__diff |
What changed between the last two successful builds (or the two given as from and to: a revision of this run, or a build kept in the history by its id, h41): for each layout the parts added and removed, and for each part its volume (and its change in percent), box size and min corner before and after, with whether each is exact, and its face, edge and solid counts, a hint that refs may name other faces now. The answer to an edit gives the same of each part that changed. builds says what each was made from (a hash of the sources, the commit, whether files differed from it, and of a kept build its id and the checks, defects and dimensions it had then, which are as of then), and a note says when the two were made from the same sources. |
mcp__torno__history |
The builds kept of the project (see Files torno writes, history/), newest first: each with its id (h41), when, its revision if it was made in this run, what changed from the build before it in a line, the checks of the model that failed and the parts with a defect (a build that fails its checks is kept all the same), how your dimensions stood when it was checked, and whether you starred it. With id, one build, with the paths of the copies of its Python files, to read when going back to it. torno never writes a file back. |
mcp__torno__expect |
Called before an edit: what it should change, as checks (a part's volume, faces, edges or size as a value or a change with a tolerance; a measure with the number it expected; unchanged parts). The next successful build is held against it, and the answer to the edit says in a line how many checks were met and which were not, with the numbers (≈ where the kernel's is not exact). Kept in memory for that one build, told once, no mark, and it never holds a turn back. A ref to a face of a part whose face count changed is told as moved and not measured. scene gives the last verdict. |
mcp__torno__try_fillet |
Tries a fillet (or a chamfer, kind: 'chamfer') of radius mm of some edges of a part (part, edges as refs such as lid/E3) on the last good build and throws it away: whether it works, and when it does not, the edges that fail alone or in pairs and the largest radius that works, as a failed build's error says it. It changes nothing. |
mcp__torno__try_params |
Builds the model with other values of its Params (set, the fields scene lists under params as editable: {"wall": 2.4}) in a worker of its own and throws it away, then says whether it built (or the model's error), what changed against the last build part by part, and the measurements (measure: bbox, volume, area, mass or distance of whole parts by name) and checks (check: as the check tool, parts by name) asked for, all worded as those tools word them and said to be of the trial. It changes nothing: not the model, not the values you are trying in the window, not the build on screen; it is made on top of your values, and refused when there is no memory for a second build or while a build runs. To keep a value the agent edits the model. |
mcp__torno__features |
The holes, bosses, fillets, chamfers and patterns of a part, found on its geometry (part, and kinds to ask for some of hole, boss, fillet, chamfer, pattern): for a hole its diameter, axis, where it opens and the face it opens in, depth, whether it is through or blind (and its bottom), and a counterbore, countersink or rounded mouth; for a boss its diameter and height; for a fillet its radius, sweep and length and whether it rounds an outside edge or an inside corner; for a chamfer its size; for a pattern how many holes or bosses alike there are and whether they lie in a row, a grid or on a circle, with the step; each with the faces it is made of and the faces and edges it joins, as refs highlight takes. Diameters, radii, angles and axes are exact, and so are depths between planes and round faces; a depth taken between freeform faces says it is not (≈). Holes and bosses need a cylinder, cone or torus that goes all the way round: a slot end is neither a hole nor a fillet. |
mcp__torno__scans |
The scans of the project and how each is aligned. |
mcp__torno__scan_clash |
What of a scan is inside a part. |
mcp__torno__scan_deviation |
How far a part lies from a scan. |
There is no tool that exports, imports or aligns a scan, or writes a file: those are yours.
#Prompts
The same server offers requests that come up again and again as MCP prompts.
Claude Code lists them as slash commands, and the chat's command menu shows
them when you type /:
| Command | What it asks the agent |
|---|---|
/mcp__torno__review |
Go through every open mark and every unmet dimension against the current build: measure, look, and resolve the marks that are done. |
/mcp__torno__printable |
Check the print layout for 3D printing: thin walls, overhangs, closed meshes, holes too narrow to print, and whether it fits your bed, given as an argument (/mcp__torno__printable 256x256x256, in mm). |
/mcp__torno__explain |
Describe the model for someone new to it: its parts, its Params and how the parts relate. |
#Marks
The tools, each with its key (all keys are under Keys):
| Key | Tool | What it does |
|---|---|---|
V |
Select | Click selects a face or an edge, and a double click its part; the choice in the corner (Any, Face, Edge, Part) says what a click takes. Shift-click adds or takes out, Esc clears. "Insert" puts the selected refs in the session's prompt without sending it; "Note" leaves a mark on each. |
P |
Pin | Click a part: a pin there, and its note opens beside it. A pin on an edge is a pin on that edge: the pointer snaps to an edge within reach, and the agent is told which edge it is and the two faces that meet there. |
A |
Arrow | Drag from a part: an arrow of that length. With Shift it runs along the surface normal or a principal axis. |
D |
Draw | Press and draw. On a locked view (an orthographic front, back, left, right, top or bottom, not turned since; a padlock shows on its button) the stroke is a profile in millimetres on the plane that faces you. With Draw in hand a standard view you choose is taken in orthographic, so it is locked at once, and perspective comes back when you turn the view; in perspective the pointer says "Ortho to draw a profile on the XZ plane". On any other view it lies on the surface. Alt-drag turns the camera with this tool in hand. |
S |
Shape | Click places a box, cylinder, sphere or plane, to add, remove, keep out or refer to. A gizmo moves it (G), turns it (R) or sizes it (T), and its row has the exact numbers. |
X |
Section | Cuts the parts by a plane. See Section. |
M |
Measure | Measures; Enter keeps a dimension. See Measuring and dimensions. |
L, B |
Align scan, Region of a scan | See Scans. |
Esc drops what is being dragged, then puts the tool down for Select. In a
note, Enter saves and Esc leaves it for later. A note is also kept when
you leave it any other way, such as Ctrl+` to the terminal: what you
typed goes with the prompt you type next.
Every mark has a number (M1, M2, never reused), a note, and a row in the
Marks list. Yours are drawn in red, the agent's in green. A mark is a draft
until it has been carried to the agent (see The agent),
then sent, then resolved by you or by the agent, with a line on what
was done. Clicking a row makes its mark the active one and opens the row; the
camera goes to the view the mark was placed from only when the mark is out of
view, and the reticle at the end of the row ("Go to its view") always goes
there. The pointer on a row, or the keys' focus in it, rings the mark's chip in the view, more faintly than the
active one's, and lights the face it is on, as a mention of it in the chat
does.
The chips in the view answer the pointer too. On a chip the pointer is a hand,
the mark's row lights, and its note is read out beside the pointer on one line
(M1 too thin here; for a dimension its numbers, M3 40 mm, expected 42 ± 0.05, 2 under). A click on a chip makes its mark the active one and brings
its row into view, opened, without moving the camera; for a mark of yours not
yet sent, its note opens beside it to be written. A click on a chip is the
chip's whatever tool is in hand: no pin is left under it. A drag that starts on
a chip turns the camera with Select in hand, as anywhere else; with Draw, Arrow, Shape, Region or another
tool that drags, it goes to the tool once it has moved past a click, so a stroke may begin under a chip (and
the camera stays still).
- A mark you change after it was sent (its note, or a shape's place, size,
kind or intent) is a draft again, and is told once more as
(changed). - The Marks list is grouped by who acts next: Checks (every dimension that
has an expected value and is not resolved, sent or not: the failing first,
then those with no value of the build, then those that pass; a draft one says
to sendin its row), To send (yours, "with your next prompt"), From the agent (marks it left for you to read or answer; Answer in chat puts the mark's id in the prompt, and the tick says you have seen it), To review, With the agent (your sent marks), then Resolved, folded. The active row shows its note whole. - A mark the agent resolves does not vanish: "To review" says "The agent resolved 2 marks." Review walks them one at a time: the mark becomes the active one and the camera goes to its view, the line reads "Reviewing 1 of 2 · M5", and its row says "Resolved by the agent" with the agent's line. OK moves it under "Resolved"; Reopen… opens its note with the cursor at the end, so that you can say why, and it is a draft again, with the note, when you leave the field (Esc leaves it as it was); Skip leaves it to review. When the last is done the line says how it went ("Reviewed 5: 4 OK, 1 reopened"). OK all says you have seen them all at once.
- The tick on a row resolves the mark (Resolve: done with it); on a check with an expected value it first asks "Stop checking M3? It is no longer measured." (Resolve or Keep checking). Delete takes the mark at once, and for ten seconds, or until the next delete, the list says "Deleted M3 · Undo": Undo puts it back whole, with its number, note and, for a region, its triangles. Only the last mark deleted can be put back.
- Once you have seen marks the agent resolved, and files of the project differ from the last commit (see Projects and builds), a line under the marks offers Ask Claude Code to commit. It puts a request in the message box of the chat ("Commit the changes for M4 and M6 of this project with a message saying what changed. Add only this project's files."), opens the session pane, and never sends it: you read it first. The agent commits under your permission rules; torno itself never commits. Not offered with a terminal session or none.
- A mark on a part that is switched off in the layers is not drawn, in the window or in the agent's pictures; its row stays and says "its part is hidden".
- After every build each mark is checked against the new geometry. It stays at its point; its ref is refreshed to the face now there. When the surface has moved away from it the row says "moved: check it", and when its part is gone, "its part is gone"; the agent is told the same. Such a pin, pick or arrow of yours offers Re-place: the next click on a part puts that mark there, with its number and note.
- In a
printlayout that torno spread along X, a mark moves with its part when a part that grew pushes it along: it stays where you put it on the part. - When a check cannot be made (the kernel did not answer in time), the marks it was about keep what the last check found and say "not checked against r12", in their rows and wherever the agent reads them. Torno tries again after 2, 10 and 30 seconds, and at the next build.
#Measuring and dimensions
Lengths are millimetres, angles degrees, areas mm², volumes mm³ and masses grams; Z is up.
The Measure tool (M). A strip in the corner of the viewport chooses what
two clicks measure (Distance or Angle) and what a double click on a hole, a
cylinder or a circular edge measures (Diameter or Radius). T has no button:
with the pointer on a face it measures the wall there, from that point along
the inward normal to where the part ends. With Shift a click is a bare point,
tied to no face. While the second click is pending the pointer reads out an
estimate from the mesh, marked ≈. What is measured is never that: torno asks the
kernel, which measures on the exact geometry and says how (6 mm between planes, axis to axis, point to plane, minimum). A distance between two
parallel flat faces is the distance between their planes, between two
cylinders or circles with parallel axes the distance between the axes, and of
anything else the least distance. An angle is between flat faces, straight
edges and the axes of cylinders, cones, tori and circles (between axes of two
bores, axis to plane of a bore and a face); a face that is flat, or an edge
that is straight, within 1e-6 mm but stored as a spline or an extrusion is
taken too, and its angle is not said to be exact. Before the second click of an
angle the pointer says, in place of the estimate, when an end is taken by its
axis (no estimate from the mesh), only if it is flat or straight (a spline),
or not at all (a sphere); the click is sent all the same. When the kernel
refuses (an angle to a sphere), its reason is read out at the
pointer. Esc drops a first click; the tool stays in hand.
A measurement leaves nothing. The kernel's value is read out at the pointer
and stays in the strip as Last: 6 mm between planes (a distance adds
Δx, Δy and Δz, from the kernel's two points) and drawn in the view in ink,
between those points, with its value at the middle, until the next
measurement, Esc with nothing under way, another tool, a new build or another
project. It is no mark, and the agent is not told of it: not with the next
prompt, not in state.md, not in a picture. To keep it, press Enter or
"Keep as a dimension" in the strip: it becomes a dimension, measured again by
main as it is kept, and the strip says Kept as M4.
Dimensions. What you keep of a measurement is a dimension: a mark (M4)
with a value, drawn on the part and listed with the marks. Its row, once
clicked, shows the value, how it was taken, and under "Should be" two fields:
what is expected (=) and the tolerance (±), in mm or degrees. With an
expected value the dimension is a check: it passes when the value is within
the tolerance of it (0.05 mm, or 0.1° for an angle, when you give none) and
fails otherwise, and the row says Passes or Fails: 2 under. A failing one
is ringed on the part and in the agent's pictures, its chip saying by how much
it misses after the cross (40 mm ✗ 2 under), and the line at the bottom
counts those that fail and those with no value.
When the project has checks, a line heads the Marks list: Checks on r12: 3 pass · 1 fails · 1 not measured. Each count is a button that goes to the first such
check. A dimension with no expected value is not counted: it was kept to be
read, and stays in the group its status puts it in.
- Once sent, its row says pinned: it is one of the checks the agent is
told about with every prompt until it passes. Changing what is expected or
the tolerance makes it a draft again, sent as
(changed). - torno remembers what each dimension measured on the last 20 builds of the run,
and its open row lists them, newest last:
r9 38.6 ✗ · r10 39.2 ✗ · r11 40 ✓ · r12 41.3 ✗, with a line above it when a check has changed its mind:fails since r12; passed on r11. That is the first thing to ask when a check fails: did it pass before, and on which build did it stop? The memory is not kept between runs (builds are numbered from 1 again), and each verdict is judged against what is expected now. The agent is not told this way. - A resolved dimension is no longer measured. A dimension of yours that has an expected value is yours alone to resolve.
- After every build, every dimension that is not resolved is measured again
on the new geometry, once its ends have been checked against it. Until then
its row shows the old value greyed and says
not measured on r12: this is what it was on r11, with no verdict. In a new run of torno every dimension is measured at the first build. - An end the build moved away from is measured from the face that is under it
now, and the dimension says
measured from what is under its ends now(to the agent:[moved since it was placed: check it]). - A dimension is not measurable when the part of an end is gone, when the
kernel refuses it on the new geometry (the hole is no longer round), or when
an end that was put on an edge is no longer on that edge: an edge is the
edge of that number, and its end counts as on it while the edge is within
0.5 mm of the point, or the nearest point of the part is on that very edge
and within
max(0.5 mm, 0.5 % of the diagonal of the part's box). A face is found again under the point, so for a check that must hold across edits, click faces, not edges. - A dimension is not put somewhere else as a pin is: delete it and measure again.
measure (the agent's) takes kind, a, for a distance or an angle b,
for a mass density, and layout. An end is a ref (lid/F12, lid/E3, or a
whole part where the kind takes one), the number of a mark (M3: what it is
on, only if the last build's check confirmed it; a dimension is refused, with
a line that says to name its end), {point: [x, y, z]}, or {ref, point}. Each reply is JSON with a reading: the
answer in one line.
kind |
Of | Reply |
|---|---|---|
distance |
a and b |
mm, the two points it is between, and how |
angle |
a and b |
degrees: between the outward normals of two flat faces (0 to 180), or between two straight edges or axes, or a straight edge or axis and a flat face (0 to 90); the axis of a cylinder, cone, torus or circle is taken as a line, and a face flat or an edge straight within 1e-6 mm as a plane or a line, not exact |
radius, diameter |
a cylindrical, spherical or toroidal face, or a circular edge | mm |
thickness |
a face of a solid, at a point of it (the centre of the face when none is given) | mm, along the inward normal to where the part ends; moved says how far the point was from the face before it was laid on it |
bbox |
a part, a face or an edge | min, max, size |
area |
a part or a face | mm² |
volume |
a part that is a solid | mm³ |
mass |
a part that is a solid | grams, with the density used (g/cm³; the material's of the project's print profile when it has one, else 1.24, PLA, and the reply says which) |
check takes kind, a, for two parts b, and layout; a and b
are names of whole parts. interference and bed take neither. overhang, thin and bed ask whether it prints:
they are of the print layout when the model has one and no other is named.
kind |
Reply |
|---|---|
overlap |
the volume two solid parts share, in mm³, and the box it lies in |
clearance |
the least distance between two parts and the points it is between; 0 when they touch, and when they overlap, which overlapping: true then says. With fit (press, sliding or loose) and a material in the print profile, the reply also says whether the gap is within that fit's band per side, how far outside it, and whose numbers they are |
interference |
no part: every pair of the layout's solid parts at once. The pairs that share volume (interfering, each with its volume and the box it lies in, largest first), the pairs that only touch (touching) and how many are clear. Pairs whose boxes are apart are clear without a cut; the others are cut in order of how much their boxes overlap, for 20 s at most, and sampling says how many were measured when the time runs out. Not answered in a layout torno spread for display |
mesh |
whether the mesh the part is drawn and exported with is closed (watertight), its triangles, the number of openEdges and where up to 20 of them are |
overhang |
with angle (degrees from vertical; the profile's material's, else 45, when none is given): the faces of a that face down steeper than that, each a ref (tray/F7) with its area and steepest lean, and their whole area; faces on the bed are left out. Planes are exact; a curved face is judged by its normals at sampled points and is not |
thin |
with min (mm; the profile's least wall when none is given): the faces of a where the wall, measured along the inward normal at sampled points of each face, is under min, with the thinnest of each and of the part; always approximate, and sampling says what was measured |
bed |
with size ([x, y, z] mm; the profile's printer's bed when none is given), no part: whether the layout's box fits the bed, its size, and by how much it is over along each axis; exact only when every part's box is |
The print profile is the printer (bed, nozzle, layer) and the material (density, shrink, the gap per side of a
press, a sliding and a loose fit, least wall, overhang angle) a project is made for. Choose them under "Print profile" in the
left column. The printers are yours, kept in torno's settings (profiles.json, next to config.json; "Add a printer…"
makes one); PLA, PETG and ABS come with typical values, and a material of your own goes in that file (materials: name,
density, and optionally shrink, fits, minWall, overhang). A choice is copied into <project>/.torno/profile.json,
so the project travels with the numbers it was checked against. With a profile, check bed, thin and overhang and
measure mass take their values from it when the call gives none, and the reading says "by your profile (Bambu X1C,
PETG)"; a new conversation is told the profile in one line, state.md holds it, and the skill printing writes
Params from it, not from memory. These are your numbers, not measured: a verdict against them is never a claim that
the part prints.
What is exact, what is approximate and what is refused is under Limits and exactness.
#Section
X cuts at once when nothing is cut yet: a plane through the middle of the
parts, across the axis you look most along, open towards you (the half taken
away is the one on your side). A bar appears at the bottom of the viewport: X,
Y and Z put the plane across that axis, again open towards you, "From face" puts it on the
next flat face you click, the slider and the number beside it say where the
plane is, as the agent is told: across an axis its place in the object frame
(y = 20; type a number to move it), else how far along its normal it is from
the middle of the parts (12 mm along the normal). The tick on the slider and
"Middle" (there while the plane is elsewhere) are the middle of the parts. "Flip" takes away
the other side and "Off" shows the parts whole again. What is on the side the
plane's arrow points to is not drawn. Each solid part is closed where it is cut, in its
own colour a step darker, hatched with fine lines in its edge colour (in a picture for the agent too); a part that is not a closed solid (a scan, an open
shell) is cut open. Drag the arrow on the plane to move it; a drag anywhere
else turns the camera. The arrow keys move it by 1 mm, with Shift by 0.1 mm.
On a cut face there is nothing of the model to pick, and the pointer says so;
a mark on what was cut away stays, drawn faint. The bar stays while there is a
section, whichever tool is in hand, and the section is kept with the project.
The pictures the agent takes are cut the same way.
section (the agent's) takes plane, and optionally parts, layout,
size, deflection and scan. A plane is {origin, normal}, or {axis: 'x' | 'y' | 'z', at}, or 'current' for your own section. The reply has, for
each part, the outlines the plane cuts of it as closed loops of [u, v]
points in mm along the plane's own axes, each loop's area (negative round a
hole), the part's area, how many loops are open, and the extent of the
whole cut. The outlines come from the exact geometry, as polylines within
0.01 mm of it, or within the deflection asked for (0.005 to 0.05 mm). A
reply holds at most 1,500 points: beyond that the loops are thinned, and say
so. Areas are the kernel's, of the true outlines, whatever was thinned.
With it comes a 2D drawing of the cut on a millimetre grid, each part in its
colour, with what is up in the world up on the page. To see the cut in 3D the
agent calls look with a section.
#Scans
A scan is a mesh of the real thing or of where the part goes: an engine bay, a room, a machine, taken with a phone or a scanner. Torno draws it with the parts, so that you and the agent can check the model against it. It is good to a few millimetres at best and shows only what the camera saw. Nothing taken from a scan is exact, and torno never says that it is.
Importing. "Import scan…" in the Scans panel, or a file dropped on the
view: GLB, glTF, OBJ, PLY or STL, with faces (a point cloud is refused). The
file is only read. Torno keeps its path, size, date and hash, and makes one
mesh of it in millimetres and Z up under .torno/scans/, baking a texture
into colours at the vertices. The scan goes on working from that mesh when the
file moves or is deleted (its row then says "source missing" or "source
changed"); when the mesh is gone too, the row offers "Import again". A scan of
more than 2,000,000 triangles is reduced to that many, and its row says from
how many.
Units. glTF is read as metres and Y up; the others as millimetres, or
metres when the whole is under 20 units across, with Y up for OBJ and Z up
otherwise. Right after an import in the window, when either was assumed, a
short sheet at the top of the Scans panel asks once: How is S1 read?, with the
unit and the up axis and the size the scan would have read so (Its size would be 120 × 120 × 0 mm) and, under it, the size of the parts of the layout shown
(The parts of assembly: about 40 × 40 × 41 mm); when the two diagonals are
more than 100 times apart either way, a line in the warning colour says how many
times and names the unit that would bring them near (About 1,000 times the parts: is it in millimetres?). The scan is brought into view with the parts once,
right after the import and again after "Apply"; the views and F still frame
the parts only. The scan is drawn in the view meanwhile. "Keep" says the
guess is right, "Apply" reads it as chosen, "Later" puts the question away. Until
it is answered the row has a dot after the name, and its detail says the size and
what was assumed (120 × 120 × 0 mm, from m, Z up (assumed)) and asks
Read as metres, Z up: right? with "Yes" and the unit and axis under it (not
while the sheet is up, which asks the same).
Changing either drops the scan's alignments, which torno says before it does it.
The Scans panel. Each scan (S1, S2, never reused) has a row of one
line, built like a layer's: its id, its name (whole, with the path of its source,
in the tooltip), how it stands in the layout on screen in one word (its sentence
in the tooltip), an opacity slider when pointed at, and a switch. The arrow at
the end, or a click on the name, opens its detail beneath it, one scan at a time:
its size and how it was read, how it stands in a sentence, what it rests on,
"Frame" (bring it into view: F and the views frame the parts only), "Align",
"Rename" (its name in torno: Enter keeps it, Esc leaves it), then the regions
painted on it as chips that go to the mark (M5 wiring loom), the unit and up
axis (folded under "How it is read" once the reading has been answered), its
colours, and last "Remove". On a row the state word gives way before the name,
which keeps at least ten characters. A scan drawn solid hides what is
behind it and takes the pointer; one drawn see-through lets you point at the
part behind it. The section cuts a scan open like the parts.
Aligning (L). A scan is aligned in one layout at a time; in a layout
where it is not, its row says unaligned (in the quiet colour until the scan
has been aligned anywhere: it is only the next step) and nothing is checked
against it. The detail of such a scan makes "Align" its main button, and the
sheet that asks how a new scan is read offers Next: align S1, which takes up
the tool on Floor. The bar at the bottom of the viewport has three steps,
1 Floor · 2 Pairs · 3 Nudge, which can follow one another. A tick on a step
says it has been done for the scan in this layout, and one line under them says
what the step is for. The bar opens on the first step not done: Floor for a
scan with no alignment that is much larger than the parts (ten times their
diagonal: a room, an engine bay), Pairs for one about their size, Pairs once a
floor is down, Nudge once there are pairs. When a floor or pairs are kept, the
bar goes on to the next. Any step can still be chosen, and the bar comes back
to the one you chose last for that scan, in this window:
- Floor. Click a flat patch of the scan (
[and]change its size), or three points with Shift. "Ground" lays it at a height, "On a face" on the flat face of the model you click next; "Flip" turns it the other way up. A floor says how the scan lies, not where on that floor it is. - Pairs. Click a point on the scan, then the same point on the model, or
type the model point. The bar says which end of which pair it asks for
(
Tabswitches which is asked first). From three pairs that are not in a line the scan is drawn where they put it, with how far each pair is left apart and the RMS of all. Nothing is kept until "Apply". When the fit says the scan is some 10, 25.4 or 1000 times off, the bar says which unit that looks like and offers to read the scan as that. - Nudge. Handles to move (
G) and turn (R) the scan, and fields for where its origin is and how it is turned.
"Undo" puts back the alignment before the last one, "Clear" takes it away. What the row then says of the scan is what the agent is told of it:
| Row | Meaning |
|---|---|
aligned |
Tied to points of the model and checked against the last build. Its detail says what it rests on: 4 pairs, RMS 2.1 mm is a fit of pairs and how well they fitted (the RMS is of the points you clicked, not of the scan); …, then moved by hand is a fit or a floor moved afterwards. |
placed |
Tied to no point of the model: placed by the handles or typed values (by hand), laid on the ground (floor only), or pairs whose model ends were typed. No build can say whether it still holds, so none is checked. |
pending |
Nothing is built in this run yet, or the last build has not been compared with the alignment yet. Torno never builds the model again by itself to make that check. |
check |
The model has moved since: a point you clicked on a part is no longer on it, torno spreads a part otherwise, or the scan was imported again from a changed file. Align again, or press "Still right" in the detail. |
rough |
Fewer than three pairs, pairs almost in a line, or pairs left more than 5 mm apart. Torno asks before it keeps one. |
unaligned |
Not aligned in the layout on screen. |
missing |
Its mesh is gone from the disk; the detail offers "Import again". |
After every build torno looks for the model points of each alignment on their parts again. It tells what it finds; it never moves a scan by itself. A scan aligned in another layout offers "Use this alignment in layout": it is put where it stands there, as a start, and kept as placed by hand.
Regions (B). Drag over a scan to paint a part of it: a brush whose
radius is in the bar ([, ]), "Erase" (E, or Ctrl held), "Box"
(Shift-drag), "Grow to flat", "Undo" (Z), "Clear". What is seen is what is
painted: the brush takes nothing behind a part, nor of what a section has cut
away. "Keep" (Enter) makes it a mark with a number and a note, such as
"wiring loom, can be pushed aside": it is how you tell the agent what a part of
the scan is. A painting waits until you keep or clear it, whatever tool or
project you go to meanwhile. "Edit" on a region's row takes it in hand again.
Removing a scan leaves its regions as marks that say their scan is gone.
The scan tools. The agent cannot import or align a scan: you do. It can
ask about one. Every reply names the revision, the layout and how the scan is
aligned there, has exact: false, and a reading whose numbers follow a ≈
and which opens with whatever puts the alignment in doubt. Lengths taken from
a scan are told to 0.1 mm.
scanslists them: name, source, triangles, size, unit and up axis and whether they were assumed, whether it is on screen, its regions and its alignment.scan_clashtakespart, and optionallyscan,margin(0 to 50 mm, 5 by default),ignore(region marks to leave out) andlayout. It counts the scan's vertices inside the part, in clusters with the deepest point of each, and the scan's triangles that cross the part's surface. It gives no volume: a scan is an open surface. It reads, for a flat sheet stood 0.8 mm inside a plate:≈ 2,157 scan points inside the part in 1 cluster, deepest 0.8 mm at (8, 0.8, 58) under stand/F2; 298 scan triangles cross its surface, afterS1 was placed by hand: no fit says how well.scan_deviationtakespart, and optionallyfaces,from(part, the default: from points of the part to the scan;scan: from the scan's vertices to the part),region,within(0.1 to 200 mm, 50 by default),samples(100 to 20,000),scanandlayout. It gives the least, median, mean and greatest distance and percentiles of the points that have the other withinwithin, the worst place, and how many had nothing within reach.measuretakes{scan: "S1", point: [x, y, z]}or the number of a region as an end of a distance (then approximate);sectionwithscan: "S1"adds the lines the plane cuts of the scan;lookwithscanColour: {part: "tub", range: 20}colours one scan by its distance to a part.
Limits, each said in the reply that meets it: scan_clash checks at most
200,000 scan points near the part and tells the 8 largest clusters; both
checks give themselves 10 s, and a count that was cut short is never told as a
no; scanColour colours at most 60,000 points; an alignment keeps 64 pairs.
Refused, with the way out: a scan that is not aligned in the layout asked; a
part torno moved for display (see Limits and exactness);
a scan whose mesh is gone or that is being imported; a region that no longer
fits its scan's mesh.
#Explode and saved views
Explode. E, or "Explode" in the bar of views, draws the parts of the
layout apart; a bar under the views has the slider, the direction (from the
centre, or along X, Y or Z) and the distance (×0.5 to ×4). It is for display
only: the parts are where they were built, and every pick, ref and number is
of the parts as assembled.
- While the parts are apart nothing is marked, measured or cut: Pin, Arrow,
Draw, Shape, Measure, Section, Align and Region take no click, and the
pointer says
The parts are drawn apart: put them together (E, or the slider at 0) to mark, measure or cut. Select still works, and what it picks is the true point. - A mark is drawn with its part. A dimension between two parts is drawn dashed between them and keeps its value. Scans and the grid stay where they are.
- A section is never drawn exploded. Making a cut puts the parts together;
while there is a section the slider says
Take the section away to draw the parts apart. - The camera stays where it is, unless a part drawn apart would leave the view or go under the bars at its top: then the parts are framed anew, and put together again they are seen from where they were before, if you have not moved the camera meanwhile.
- A project of one part has nothing to draw apart, and the bar says so; parts that share a centre need an axis.
- The agent's
lookandshowtakeexplode; a picture drawn apart says so in its caption. Every other tool answers of the parts as built.
Saved views. "+ Save view" in the strip under the bars keeps what is on screen under a name: the camera, the layout, which parts are switched off, the section or the explode (never both), and how each scan is shown. A click on a chip brings it back; the chip's menu renames it, updates it to what is on screen, or deletes it. A name is 1 to 40 characters and not that of a standard view. A project keeps 24 views; the strip shows six and the rest behind "n more". Applying a view changes no geometry and no mark and starts no build. What a view names that is gone (a part, a layout, a scan) is passed over and said on its chip.
The agent sees the views in scene, draws one with look {"view": "Web cut"} whatever is on your screen, puts one on screen with show, and
proposes its own with save_view: a dashed chip that says "proposed", with
"Keep" and "Discard". A proposal is the agent's until you keep it; it can
replace its own proposals (12 at most) and never a view of yours.
#The page of projects
W, or "All projects" at the top of the list (beside its title once a project is open), shows every project of the folder
as a card; a workspace of several projects starts there. / or a letter
filters by name, the arrows move, Enter opens, Esc goes back. The head (title, buttons,
filter) stays at the top as the cards scroll. "By name" or "Recently changed" orders the cards; the choice is kept
for the folder. A card has:
- A picture of the project's last build as you last looked at it (its
layout, the parts switched on, its section or explode), 480 × 360, without
marks or scans. It is made when a build lands in torno and kept in
.torno/thumb.png. No build is ever started for a picture: a project that was never built in torno saysNo picture yet. The card says when the picture was made, what it leaves out (cut by its section), andA source has changed sincewhen a.pyfile of the object, ofcommon/or one it imported from another project (Uses box/box.pyon the card, for a project opened in this run) is newer. - What git says, in a work tree:
3 files changed since a1b2c3d(the files in its tooltip) orNo changes since a1b2c3d. - How the build stands, for a project opened in this run:
Built, r6, building, or failed, with the picture of the last build that worked dimmed. OtherwiseModel saved 3 h ago, read from the date of its.pyfiles (nothing is built for it; the tooltip saysNot opened in this run). - Marks and dimensions: how many marks are open and how many are not sent
yet; how many dimensions fail and pass. Of a project not opened in this run
there is no verdict, only
1 dimension failed as of its last run. - The session, if one runs.
Looking at the page builds nothing, opens nothing and writes nothing.
#Export
"Export…" in the line at the bottom (the open project), in the head of the page
of projects (any of them), on a card, or Ctrl+Shift+E, writes one HTML
file for someone who has no torno, or a STEP, STL or 3MF file of one
project (below), or, of the project on show, a picture of the view or a
drawing of its section (below). It is made from the last build of this run, so a project must
have been opened first. Nothing is uploaded: the file goes where the save
dialog says, which opens in .torno/exports/.
The sheet lists what would go, with counts and the size, and what is ticked is what is in the file:
| In the file | |
|---|---|
| always | the parts of every layout, in their colours; the explode kept for each layout; the views you saved or kept |
| only when ticked | marks and their notes (drafts go with them, and the sheet says how many); resolved marks; parts that are switched off; each scan, by its own tick, reduced to at most 60,000 triangles |
| never | where anything is on this computer; the model's source; what a model printed; the session with the agent; state.md; a view the agent proposed and you did not keep; what was painted on a scan |
The page opens from file:// with the network off: its script, style and
fonts are inside it, and its policy lets it load and send nothing. It has the
parts with a switch each, the layouts, an explode slider, a section (parts are
drawn open, without cut faces), the saved and the standard views, the marks as
labels and a list with each dimension as torno measured it, and light and
dark. It measures nothing and says so. With several projects, each is unpacked
when it is first shown.
A part costs about 10 bytes a triangle on top of 0.7 MB for the page itself:
a project of one part and 12,000 triangles in two layouts is 1 MB. Over 12 MB
the largest scan is reduced further, then left out, and the sheet says so;
above 64 MB nothing is written. The file is written whole or not at all, and a
name without .html gets it. Once it is saved, the sheet offers Show in folder
and, for a page, Open in browser: they act on the file just written, which
torno itself remembers. A project left out because it has no build of this run
is a link in the sheet: it opens the project, and when its build lands the sheet
comes back with it ticked beside the others.
#STEP, STL and 3MF
The sheet's format buttons write a file of the model instead, of one project:
| Format | What is in it | Layout unless you pick another |
|---|---|---|
| STEP | the exact shape, each part a named body in its colour | assembly |
| STL | one part, as triangles | print |
| 3MF | a plate: each part a named object in its colour | print |
Every file is in millimetres, with the parts where the layout has them: a plate's parts are where torno set them apart, as on screen. You tick the parts that go; an STL or a 3MF is of solids alone. A part of several solids is, in a 3MF, one object made of a mesh of each; an STL of solids that touch is refused, saying to fuse them in the model or export 3MF. Their triangles are made for the file, not taken from the screen: none further than the tolerance from the faces (0.01 mm unless you type another, 0.001 to 1 mm), nor turning more than the angle (5°, 0.5 to 45°). A tolerance that would make more than 500,000 triangles is refused before anything is made: past that the kernel could not make the file in the time it has.
The kernel makes the file from the last build, and torno writes it beside the name you chose. Before the file takes that name, the kernel reads it back and checks it:
- STEP: the same parts by name, each with as many solids, a volume within a millionth of the model's or twice what the two volumes are in doubt, whichever is more, and the same box.
- STL and 3MF: each object closed (every edge of two triangles, wound one way), no triangle without area, in as many pieces as the part has shells, facing out, its box the part's, and its volume within what the tolerance can cut off the model's.
A file that fails is not saved, and the sheet says why. One that passes is
named, and the sheet says what was checked: Checked: 3MF, 2 objects, 184,220 triangles, closed, volume within 0.04 % of the model. Checked, not printed. A
3MF can be opened once saved in the program your system opens 3MF files with,
your slicer, when "Open it once saved" is ticked.
#A picture of the view, and a drawing of its section
The sheet is grouped by who will open the file: a page to look at (the HTML page), the CAD file (STEP, STL,
3MF), and, when the sheet is of the project on show, a picture (PNG) and a drawing of a section (SVG, PDF),
which are of the view as it is on screen. Nothing is uploaded, and no agent has a tool for any of them: the save
dialog opens in .torno/exports/ with a name offered, and the file is written whole or not at all (a name without
the ending gets it).
- PNG is the view as it is (the camera, the layout, the parts that are on, the section or the explode, the scans),
for a forum, a chat or a document (
<name>-r<revision>.png). It is drawn by the same code as the pictures the agent is given. You choose its long edge, 1280, 2000, 3000 or 4000 pixels, whether the marks are in it, and whether the axes and the scale bar are. A picture you keep is not for the agent, so the 2000 pixels the agent's pictures stop at do not hold for it;lookis still held to them. - SVG, while a section is on screen, is a drawing of the cut for someone who will make the part
(
<name>-section-r<revision>.svg). The outlines are the kernel's, within 0.01 mm, laid flat in the plane's own axes at true scale: one unit of the file is one millimetre, so a 40 mm edge is 40 mm long, and the sheet says so (print it at 100 %). It has a scale bar, a title block (the project, the layout, the build, the date, the plane's origin and normal and the sheet's axes in full, how far the outlines may stray, and what was cut of each part with its area,≈where the area is not exact), and each dimension that lies in the plane (both ends within 0.01 mm of it), not resolved and measured on this build, as a line with the value torno measured and, when you set them, what was expected and the tolerance. A dimension that is not in the plane, or not measured on this build, is left out. - PDF is that drawing printed on a page of its own size in millimetres (
<name>-section-r<revision>.pdf), by a window torno never shows.
#Keys
? shows this table in the window, and searches it. Above the keys the sheet lists
the pointer, which has no keys:
| Pointer | What it does |
|---|---|
| Drag | Turn the view about the point under the pointer; with Arrow or Draw in hand, a drag that starts on the part draws instead |
| Right drag | Pan the view (a middle drag does too) |
| Scroll | Zoom, towards the pointer |
| Double click | With Measure in hand: measure something round, its diameter or its radius |
Point at a tool of the palette and a panel beside it says, at once, what the tool does and its key. With a
tool in hand (not Select) a quiet line at the foot of the view says what the tool waits for next: for Measure
click the first end, then click the second end; for Arrow press on the part and drag to where it should go.
Region of a scan and Align scan, with no scan in the project, are drawn quieter and say Import a scan first.
The line that tells how the pointer moves the view shows the first time a project is drawn in this install,
until the camera is first moved; the window remembers that in its own storage, never in a workspace.
| Where | Keys | What it does |
|---|---|---|
| Everywhere | ? |
This sheet of keys, outside a field and the terminal |
| Everywhere | Ctrl+` |
To the session, and from it back to the view |
| Everywhere | F6 |
To the next part of the window: projects, view, session, status line |
| Everywhere | Shift+F6 |
To the part before |
| Everywhere | Ctrl+Shift+D |
Show or hide the session pane |
| Everywhere | Ctrl+Shift+E |
Export to one HTML file, or to STEP, STL or 3MF: the open project, or from the page of projects any of them |
| Everywhere | Ctrl+Shift+B |
Rebuild: build the open project again, as the button in the status line does |
| Viewport | V |
Select: click a face or an edge, double-click for its part; Shift-click adds or takes out. The choice by the selection says what a click takes |
| Viewport | M |
Measure: two clicks, or a double click on a round thing; nothing is kept unless you keep it |
| Viewport | X |
Section: cut the parts by a plane |
| Viewport | P |
Pin: click a point and leave a note |
| Viewport | A |
Arrow: drag from a point |
| Viewport | D |
Draw: a stroke on the surface, or on a locked view |
| Viewport | S |
Shape: place a box, a cylinder, a sphere or a plane |
| Viewport | B |
Region of a scan: paint what something is |
| Viewport | L |
Align scan: stand a scan where the model is |
| Viewport | 1 2 3 4 |
Iso, front, right and top view |
| Viewport | F |
Fit the selection in view, or the parts; again, the parts |
| Viewport | O |
Perspective or orthographic |
| Viewport | E |
Draw the parts apart, or together again |
| Viewport | W |
All projects of the folder, on one page (there a letter filters them by name) |
| Viewport | Esc |
Step back: what is under way, then the tool in hand, then the selection |
| With a tool in hand | T |
Measure: the wall under the pointer |
| With a tool in hand | Enter |
Measure: keep the last measurement as a dimension |
| With a tool in hand | Left Right Up Down |
Section: move the plane by 1 mm |
| With a tool in hand | Shift+Left Shift+Right Shift+Up Shift+Down |
Section: move the plane by 0.1 mm |
| With a tool in hand | Tab |
Align, pairs: which end is asked for first |
| With a tool in hand | Backspace |
Align, pairs: take the last end back |
| With a tool in hand | [ ] |
Align, floor: a smaller or larger patch |
| With a tool in hand | G R |
Align, by hand: move or turn the scan |
| With a tool in hand | [ ] |
Region: a smaller or larger brush |
| With a tool in hand | E |
Region: paint or rub out |
| With a tool in hand | Z |
Region: undo the last stroke |
| With a tool in hand | Enter |
Region: keep what is painted |
| With a shape in hand | G R T |
Move, rotate or size the shape with its handles |
| Page of projects | Left Right Up Down Home End |
Move among the cards |
| Page of projects | Enter |
Open the card in focus |
| Page of projects | / |
Filter by name: a letter or a figure does too |
| Page of projects | Esc |
Clear the filter, then back to the open project |
| Page of projects | Enter |
In the filter: open the first card |
| Page of projects | Down |
In the filter: down to the cards |
| New project | Esc |
Leave the page, asking first when something is in it; on the doodle board, put the board away |
| Session | Shift+Enter |
A new line in the prompt, not a send |
| Session | Ctrl+Shift+C |
Copy what is selected in the terminal |
| Session | Ctrl+Shift+V |
Paste into the terminal |
| Session | Enter |
Chat: send the prompt |
| Session | Shift+Enter |
Chat: a new line in the prompt, not a send |
| Session | Up Down |
Chat: choose among the / commands offered while one is typed |
| Session | Tab |
Chat: complete the / command chosen |
| Session | Ctrl+C |
Chat: stop the turn while Claude Code works, with nothing selected in the box |
| Session | Enter |
Chat: allow what the card in focus asks (in its reason field, Enter denies) |
| Session | Esc |
Chat: put away a picture shown large |
| Session | Left Right |
On the edge of the session pane: wider or narrower |
| Session | Left Right |
On the edge of the left column: narrower or wider |
| In a field, a menu or a sheet | Enter |
Field: keep what was typed |
| In a field, a menu or a sheet | Esc |
Field: leave it as it was |
| In a field, a menu or a sheet | Shift+Enter |
Note: a new line |
| In a field, a menu or a sheet | Down Right |
Menu: the next item |
| In a field, a menu or a sheet | Up Left |
Menu: the one before |
| In a field, a menu or a sheet | Home End |
Menu: the first or the last |
| In a field, a menu or a sheet | Esc Tab |
Menu: close it |
| In a field, a menu or a sheet | Up Down |
On the line above the marks: more room for them, or less |
| In a field, a menu or a sheet | Esc |
Sheet or question: put it away |
| In a field, a menu or a sheet | Ctrl+Z |
Drawing board (a doodle, or a drawing on the view): undo the last stroke |
| In a field, a menu or a sheet | Shift |
Drawing board: hold Shift while drawing for a straight line from where the stroke began |
| For working on torno | F12 Ctrl+Shift+I |
Developer tools |
A tool in hand is offered a key before the viewport is. F12 and
Ctrl+Shift+I open the developer tools whenever torno runs from the
repository.
#Files torno writes
Inside a workspace torno writes only under <project>/.torno/, with one exception: "Create" on the page of a new
project (see "Making a new project") writes the new project's folder, <name>/<module>.py, brief.md and reference/.
Those are the project's own, in git like its model, and torno never writes into them again. It writes under .torno/ only when
that is a real folder inside the project: where a link or a file is in its
place (a workspace copied from elsewhere may hold one) torno writes nothing
there, and says so once in the Marks panel, until a real folder is put in its
place.
| File | What | In git |
|---|---|---|
scene.json |
how you last looked at the project: the layout, the layer visibility, the section, the explode of each layout, how each scan is shown, and the saved views (views) |
travels with the project |
params.json |
the values you are trying in place of the model's Params defaults, with the default the file had; the model file is never written |
ignored |
marks.json |
the marks of the project: yours and the agent's, with their notes, anchors and status; of a dimension also what is expected of it and the value last measured | travels |
scans.json |
the scans of the project: where each source file is and its hash, how it was imported, and its alignment in each layout | travels |
scans/<id>/mesh.bin, <mark>.mask |
the scan as torno draws and checks it, and the triangles of each region | the meshes are ignored (scans/.gitignore): a clone has the record, and the row offers "Import again" |
profile.json |
the printer and material the project is made for (a copy of your choice under "Print profile") | travels with the project |
agent.json |
whether Claude Code is shown as a chat or a terminal; without it, in a chat | travels |
chat/<conversation>.jsonl |
what a chat session said and did, as text (a turn's line holds the tokens it used, as numbers); no picture data, no address (a picture of reference/ by its path) |
ignored |
session.json |
the conversation to resume | ignored |
state.md |
the build, the parts, the checks of the model, the views, the scans and the open marks as text | ignored |
thumb.png, thumb.json |
the card's picture, and what it is of | ignored |
builds.jsonl |
a line for each build that worked: its revision, when, the run of torno it was made in (revisions count from 1 in every run), a hash of the sources it was made from, and the commit then checked out and whether files differed from it; the last 500 | ignored |
history/ |
the builds kept: for each build that worked (not again for sources the newest kept build was made from) a copy of the Python files it was made from, its scene without the meshes, a small picture, and an index of them with an id such as h41, which counts on across runs; the latest 100 and every one you star; never read back into your files |
ignored |
views/<n>.png |
the pictures and section drawings the agent asked for, the latest 20 | ignored (a picture of a scan may show the real world) |
exports/ |
where the save dialog of an export opens; empty unless you save there | ignored |
"Ignored" is by .torno/.gitignore (or one inside the folder), which torno
adds lines to and never rewrites; a line you negate there (!state.md) is
yours, and is not added against. What travels with the project in git is what
is the project's: scene.json, marks.json, scans.json, profile.json and agent.json.
What changes with every build or session, or is of this machine, is
ignored: state.md, session.json, chat/, the thumbnail, builds.jsonl, history/, views/ and
exports/. Add .torno/ to the workspace's .gitignore if you
want none of it in git.
Outside the workspace:
~/.config/torno/(on macOS~/Library/Application Support/torno/):config.jsonholds the workspace last opened and the folders opened lately (eight at most);models.jsonthe models Claude Code offered in the last chat, for the page of a new project;profiles.jsonyour printers and materials (see "Measuring and dimensions"); the rest is Electron's own cache.~/.config/torno/agent/<session>.mcp.json(on macOS under~/Library/Application Support/torno/): the file--mcp-confignames, which tells a session where torno's MCP server is. It holds the placeholder${TORNO_SESSION}, never the token, and is removed when the session ends (and at the next start after a crash).~/Library/Application Support/torno/kernel-envon macOS, for the packaged app only: the kernel's Python environment, made at the first build of a workspace with nopyproject.toml. Never insidetorno.app.- The one file an export writes, at the path you choose.
- The one new folder "Keep this as a folder…" makes of the example, at the path you choose.
torno.desktop, only byscripts/torno --install-desktop.- A copy of the example in the system's temporary folder, removed when torno quits, or at the next start after a crash (only folders its own marker file names).
~/.local/state/torno/launcher.log, only when torno was started from its desktop entry, something went wrong and there was nonotify-sendto show it.
The agent's CLI keeps its own records as it always does.
#Limits and exactness
Exact and approximate. Every number the tools give is measured by the
kernel on the model's own B-rep, never on the mesh on screen. A reply says
exact: true when the value is exact; otherwise the reading starts with ≈
and says approximate, and how says why.
- Distances, angles, radii, diameters and thicknesses are exact. An angle to a
face or an edge that is flat or straight only within 1e-6 mm (a B-spline, a
surface of extrusion) is not:
howendsflat within 1e-6 mm. - A volume is exact only of a part all of whose faces are planes, cylinders,
cones, spheres and tori. Of any other part (a loft, a spline, an offset) it
is integrated and never said to be exact:
≈,volumeExact: falseinsceneanddescribe,exact: falseinmeasure, withhowsayingof the solid, approximate, good to about N significant digits(two to six: what two integrals that differ agree to, one digit fewer), or, when they agree to fewer than two,of the solid, approximate: in doubt by X %. That is agreement, not proof, and the last digits of a freeform volume may differ from one build to the next, within what it says. A mass follows its volume (…; volume approximate, good to about N significant digits), and so does what two parts share (check overlap:of what the two share, approximate, …). - A solid turned inside out (its faces point inward, which OCC integrates as
a negative volume and still calls valid) is told for what it is. Each solid
of a part is measured alone, with its sign: the volume is the sum of their
sizes, and
reversedlists the solids that were negative, their numbers counted from 0 among the part's solids, in the order faces are numbered (reversed: [2]insceneanddescribe, withsolid 2 reversedin the descriptor;describeof a group names the part;measure volumeandmasssay so inhow;check overlapandcheck clearancename the parts). A sound part has noreversed. torno never mends the shape: the volume of a part with a reversed solid is the material it encloses, not a sign that the part is sound, and the model is to be fixed. A cavity whose shell faces into it is counted as material, and positive: what says it is broken is OCC's check, and the part is told as not valid. - What a part's own mesh encloses (
meshVolume) is a sum over the triangles it is drawn with, never exact and never a number to rely on: it is told, with≈, only where it and the volume are further apart than the mesh explains, as a sign that one of them is wrong. - An area is exact for planes, cylinders, cones, spheres and tori, and for a
curve extruded straight. The area of any other face (a loft, a B-spline
surface, an offset) is never said to be exact:
howsaysapproximate, good to about N significant digits, orapproximate: its integrals differ by 0.14 %. - A bounding box from
measureis exact when all the faces of what is boxed are planes or cylinders and all its edges lines or circles; of anything freeform it is approximate and says so. A part has one box, andscene,describe,state.mdandmeasure bboxtell the same numbers of it, with≈(sizeExact: falseinscene,bboxExact: falseindescribe) where it is not exact. The sizes insceneare for orientation:measureis for a number to rely on. - The areas of a section say for themselves (
areaExact). - Nothing taken from a scan is exact.
- The mesh drives only the estimate at the pointer while a measurement is
pending (
≈), which the kernel's value replaces.
What it costs. The volume and the box of every part are worked out during
each build. On prismatic parts that is nothing: the test models build in a
tenth of a second. On a freeform part it is about a second for the volume and
up to a second for the box: a project of three lofted parts of about 90 faces
each, whose model runs in 10 s, builds in 17 s, 5 s of it for volumes and
boxes. area of the whole of such a part takes a second or two on the first
ask (three integrals of every freeform face, to say how many digits agree);
describe of the part takes one integral a face and says its area with ≈.
Both are then kept until the next build. A question the kernel has not answered within
30 s is an error that says so.
The agent's tools are for its session alone. The MCP server and the
address the agent's hooks post to listen on 127.0.0.1 alone, check that a
request comes from no web page (Origin, Host), and answer 404 to any
request without its session's token in the X-Torno-Session header: 32
random bytes made when the session starts, which open that session's hooks
and its project's tools, and nothing once the session has ended. The token is
in the agent's environment (TORNO_SESSION) and nowhere else: not in the
arguments it is started with, which every user of this machine can read (ps),
and not in the file --mcp-config names, which holds only the placeholder
${TORNO_SESSION} that Claude Code expands. Torno does not log it and writes
it to no file. That keeps out another user of this machine, any program that
only finds the port or reads the agent's command line, and the session of
another project. It does not keep out a program running as you, which can read
the agent's environment, nor a script the agent runs, which inherits it: such
a program can call show, add_mark, resolve_mark and save_view of that
project, and post its hooks, while the session lasts.
Parts that fit together. overlap, clearance and a distance between two
parts are of the parts where the layout has them, so fit can only be checked
in a layout that places the parts as assembled (assembly(p) in the model).
In a print layout that torno spread along X the parts are where torno put
them for display: there these three are refused between two parts, with a
sentence that says why and names the layouts that do place both parts; a
dimension between them is not kept, and one that is there already is not
measurable on such a build; a scan is not checked against a part torno moved.
What is of one part is answered as ever (thickness, diameter, bbox, area,
volume, mass, mesh, a distance within the part, an angle), with arranged,
how far torno moved the part. A project with only a print layout can
therefore be measured part by part, but not checked for fit until its model
has an assembly.
Memory. The last build of a project stays in memory, in a process of its own, to answer what is measured and checked of it: half a gigabyte for a small part, and many gigabytes for a large model. At most four are kept. Before a build is started the kernel looks at how much memory is free: when there is less than the last build of that project holds (2 GiB at least) and 2 GiB more, it lets go the builds of other open projects first, the one least recently used first, and then that project's own. The line at the bottom then says "The last build was let go to make room (memory): tools answer when this one lands". A project whose build was let go is built again when something is asked of it, never by a check on a timer; and a model that has just failed to build is not built again until a source changes.
#Troubleshooting
- Every build fails saying to run
uv sync. The workspace has apyproject.tomland no.venv. Torno never makes one: runuv syncin the workspace once, then open it again. - A build fails. The last good parts stay on screen, the pointer says that picks wait for a build that works, and "Build failed" at the bottom opens the error with the traceback. The agent is told the same, after its own edit and with your next prompt, and its tools answer from the last build that worked, saying so. "Ask Claude Code to fix it" in that sheet puts the request in the box for you to send. Save the model again once it is mended: every save is built.
- The chat says "This chat is billed to an API key". The CLI reported a key
as the source of its credentials, and names the source (
user,project,org,temporary); a key is billed per token and not to your Claude login. In the chat's mode anANTHROPIC_API_KEYin your environment wins over your login, so unsetting it where you start torno is one way to use your subscription; a key from a helper or a setting of Claude Code is for you to find in its settings. - The chat says "Claude Code's chat could not use your login". The CLI's
headless mode, which the chat runs (
claude -p), answered the turn that it was not authenticated.--bare(orCLAUDE_CODE_SIMPLE=1) makes-pread no login, and Claude Code says--barewill become the default for-pin a future release. "Use the terminal" runsclaudeas you run it, under your login;claude auth statusin a terminal says whether you are signed in. - The chat says "Claude Code's hooks did not reach torno". A prompt began
and the hook that carries your marks and the check of the Stop never came, in
this session. Torno gives the CLI a secret per session in
TORNO_SESSION, and each hook reads it from the environment; a setting of yours or of your organisation that limits which variables hooks may read (httpHookAllowedEnvVars) makes every hook fail unless it listsTORNO_SESSION. Add it there, or take the limit off, and start the session again. The note changes nothing else; in the terminal view the same fault shows only as a status that never moves. - A build takes minutes. It is the model that takes them: see "What it
costs" above.
TORNO_BUILD_TIMEOUTraises the limit of 600 s. - The agent says to press Rebuild, or a build failed for no reason of the
model's (the kernel stopped, a build was let go and the model has since
failed). "Rebuild" is in the line at the bottom, beside how the build
stands;
Ctrl+Shift+Bdoes the same from anywhere. - Nothing is kept of a project: no mark, no view, no picture. Its
.tornois a link or a file; the Marks panel says so. Put a real folder in its place. A link in the place of.torno/views,.torno/exports,.torno/scansor.torno/chatstops only what goes there (the agent's pictures, an export, the scans, the log of a chat): torno writes nothing through it and deletes nothing behind it. Take the link away. - The first screen says Claude Code is not found on
PATH, or not signed in. Install Claude Code and log in (claudein a terminal), then reopen torno, or press "Check again" in the sheet of the folder menu ("Check what torno needs"). Torno looks on thePATHit was started with, which the line shows, and which a desktop launcher may have shorter than your shell's. The page of a new project says the same above "Create": the project is made all the same, but Claude Code cannot start in it. - The first screen says
uvis not found. Install it from docs.astral.sh/uv and reopen torno. Without it no build can run: the kernel is started by uv. - The first build is slow, or fails offline. The first time, uv makes torno's own Python environment and fetches about 900 MB. The first screen says so ("Not made yet"); "Make it now" does it before the first build, and the status line says "Making torno's Python environment, first time only" if the build does it. Offline it fails with uv's reason, which the kernel's line shows; press "Make it now" again, or rebuild, once there is a network.
- "New session" says the agent's command was not found on PATH. Torno
looks for
claudeon thePATHit was started with. Started from a desktop launcher, that may not be your shell's: start torno from a terminal, or setTORNO_AGENT_CMD. On macOS and Linux torno asks your login shell for itsPATH(and adds~/.local/bin,/opt/homebrew/bin,/usr/local/binand~/.claude/localwhere they exist); if that fails it says so on stderr. - The window does not come up, or stays blank. Try
TORNO_OZONE=x11 scripts/torno, thenTORNO_OZONE=wayland. Torno draws with WebGL 2;F12opens the developer tools, whose console says when the graphics card gave it no context. - On Wayland every start prints
'--ozone-platform=wayland' is not compatible with Vulkan. It is harmless: WebGL goes through ANGLE on OpenGL ES, and WebGL, GPU compositing and rasterization are all on with or without it.--disable-features=Vulkandoes not silence it, so torno passes nothing for it. - An exported page says it cannot draw the parts. That browser gives the
page no WebGL 2, took it away, or drew a frame with nothing of the parts in
it (the page looks at its own view three seconds after it began to unpack
the parts, and says which): open the file in a current Chrome, Edge, Firefox
or Safari with hardware acceleration on.
firefox --headless --screenshottakes its picture before the first frame is drawn, so it shows the page without its parts: that is the screenshot, not the page. TORNO_DEBUG=1prints the workspace opened at start, the address of the MCP server, and the command line and hooks of every session.
#Tests
pnpm test # unit tests of the app (vitest), a few seconds
pnpm test:kernel # the app against the real Python kernel and a stand-in agent (vitest)
pnpm test:e2e # the built app in a hidden window (Playwright)
(cd kernel && uv run pytest -q) # the kernel alone (pytest)
GitHub Actions runs the suites on Linux (.github/workflows/ci.yml, skipped for a change of only the site or the docs) and apart on macOS (.github/workflows/macos.yml, on main and weekly, not a required check), with the e2e suite only when asked.
No test opens a window, and none runs a real agent CLI: a stand-in
(app/test/fake-agent.mjs) takes its place. Under test Electron keeps its own
files (cache, storage, crash dumps) in a folder of the run in the temporary
folder, removed once it has quit, and nothing in ~/.config/torno (on macOS ~/Library/Application Support/torno). The scans in the tests are made
by the test code; none is in the repository. A test holds this file to the
code: its table of keys, and the tools it names.
The evals: prompts to parts with the real Claude Code. app/evals/ measures whether a change to torno makes
the agent build better parts. It is not a test: it runs the real claude under your login and spends your
subscription, so no suite and no CI runs it, and it starts only when you ask three times: TORNO_EVAL=1, no CI,
and spend my subscription typed once it has said the model, the cases and the budget.
pnpm build
TORNO_EVAL=1 pnpm -C app eval # every case, 5 times each, sonnet, $2 a run at most
TORNO_EVAL=1 pnpm -C app eval --case t1-box --repeats 1 --label try
pnpm -C app eval --compare app/evals/out/<a>.jsonl app/evals/out/<b>.jsonl # two reports side by side
A case (app/evals/cases/<id>/case.json) is a brief, often a start model and marks, the prompts, the answers of the
person it plays (by topic, with a default: "I don't know: choose, and say what you chose"), a turn limit and the
checks. Each run copies the case to a new temporary folder (never a workspace of yours), starts the built app on it in
a hidden window, sends the prompts through the chat with claude --model <model> --max-budget-usd <budget> --permission-mode acceptEdits, allows edits and torno's tools and denies every other request, and answers questions
from the case. Then it scores the model as the agent left it: app/evals/score.py, with torno's kernel loader, checks
validity, part names, solids, sizes and ratios, volume, clearance, overlap and holes on the B-rep (each pass or fail
with its number, exact only where the kernel says so), and an mcp check calls a torno tool through the project's
MCP address. The person's dimensions in a case's start (start/.torno/marks.json, with an expected value) are checks
too, read through marks: one passes only with its verdict measured on the last build. A diff check holds the last
edit (the two builds diff keeps) to the parts the case lets it change: a part with other face, edge or solid counts,
or moved, fails it, and so does a part come or gone; the volume and size checks hold the rest against the start.
--ablate takes parts of torno away from the agent, to measure what each is worth: skills (no plugin of skills:
the whole system prompt), reviewer (no torno-reviewer subagent), stop (a Stop is never held back) and look
(the tool answers that it is off) and digest (the answer to an edit leaves out what its build changed; state.md
keeps it), in a comma list; --look-size <px> draws a look that names no size at that long edge (256 to 2000) in
place of 1280, and --holds 1 lets a turn's Stop be held back once in place of twice. torno reads them (TORNO_EVAL_ABLATE) only when both TORNO_TEST=1
and TORNO_EVAL=1 are set, which only the evals set together: in any other run nothing is taken away or changed. The cases: three of primitives and plates (T1), four printable parts with fillets, bosses and a lid
that fits (T2), two of mating parts (T3), two that leave a dimension out (asking is right), two pairs of the same
edit by a mark and by text alone, and two edits that must change nothing else. The report is one JSON line per run
in app/evals/out/<date>-<commit>-<label>.jsonl (ignored by git), with checks passed, the build apart, whether it
asked before it first wrote the model, whether it said done while a check failed, turns, tools by name, pictures,
tokens, cost and time from the CLI's own result, and a Markdown table beside it.
#Environment variables
| Variable | Effect |
|---|---|
TORNO_WORKSPACE=<path> |
the workspace to open at start, instead of the one remembered. scripts/torno <path> sets it. |
TORNO_AGENT_CMD=<cmd> |
the command to run for a session in place of claude, split on spaces and given Claude Code's arguments: TORNO_AGENT_CMD="claude --model sonnet" |
TORNO_BUILD_TIMEOUT=<seconds> |
how long a build may run before it is stopped: 600 by default, a day at most |
TORNO_OZONE=wayland or x11 |
read by scripts/torno only, on Linux: the platform Electron is told to draw on. Unset, Electron chooses. On macOS it is ignored, with a line on stderr. |
TORNO_CONFIG=<path> |
the config file to use instead of ~/.config/torno/config.json (on macOS ~/Library/Application Support/torno/config.json) |
TORNO_DEBUG=1 |
print to stderr the workspace opened at start, the address of the MCP server, and the command line and hooks of every session |
TORNO_OUTSIDE_AGENTS=1 |
a spike (#54): open a socket only you can open, in your runtime folder, through which an agent torno did not start reaches one project's tools by node scripts/torno-mcp.mjs <project folder> (as a stdio MCP server in its config), after you allow it in the window. Off by default. |
TORNO_EVAL=1 |
lets pnpm -C app eval start, which spends your subscription; it still asks before it starts, and never runs under CI |
TORNO_EVAL_ABLATE=<list> |
what an eval run takes away from the agent (skills, reviewer, stop, look); read only with both TORNO_TEST=1 and TORNO_EVAL=1, and set by pnpm -C app eval --ablate |
TORNO_EVAL_LOOK_SIZE=<px>, TORNO_EVAL_HOLDS=1 or 2 |
the size of a look that names none, and how many times a Stop may be held back, in an eval run; read under the same rule, set by --look-size and --holds |
TORNO_EVAL_AGENT=<cmd> |
the agent of the evals in place of claude --model … --max-budget-usd … --permission-mode acceptEdits; the stand-in (node app/test/fake-agent.mjs) checks the runner and spends nothing |
TORNO_TEST=1 |
create the window hidden and never show it; keep Electron's own files in a temporary folder of the run; expose the test hooks; look for claude on the PATH without running it. For tests only. |
The agent inherits torno's environment without these, and without the variables
a parent Claude Code session exports (CLAUDECODE, CLAUDE_CODE_ENTRYPOINT and
the like), so torno can be started from inside one. Variables you set yourself,
such as CLAUDE_CODE_USE_BEDROCK, are passed on. It is given TORNO_SESSION,
its session's token, which no address and no argument holds. A torno started
from inside a session of another torno passes that session's TORNO_SESSION,
TORNO_OBJECT and TORNO_SESSION_ID to nothing it starts: not to the agent,
the kernel, the import of a scan or the checks of what torno needs.
#Not yet verified on a visible screen
Torno was built and tested in a window that is never shown, with a stand-in for the agent.
Verified on a real compositor output (Hyprland, NVIDIA; the session was
locked, so not by eye). On 10 October 2026 torno ran as a real, mapped window
on a Hyprland output, on Wayland and on X11, with the real agents (Copilot CLI too, which
torno has since stopped running); what it drew
was taken from the compositor and looked at afterwards. The first start drew
the parts on the GPU at 60 frames a second on both platforms. Only one torno:
a second start exited and the first opened the folder it was given. There is
no menu, also on Alt. Alt-drag with the Draw tool turned the camera and drew
nothing (sent as events; no binding of the compositor's was in the way).
hyprctl clients showed class torno. Light and dark were legible in both
themes. A large project's thumbnail was on its card at once and the window did
not stall. Ctrl+Shift+E with Claude Code's prompt in focus brought up the
export sheet and nothing reached the CLI; ? outside a field and the terminal
brought up the sheet of keys. Left alone for five minutes with a large project
open, torno's memory stayed flat and its processes idled near zero. With
Claude Code a pin was told with the next prompt and turned from draft to sent,
and scene, look, marks, save_view and add_mark ran without a
permission question. What that run found wrong is mended: closing the
window now asks first when something would be cut short, the Marks list no
longer runs out of its column when a mark has moved, "Needs input" is no longer
shown while Claude Code is only idle, a session can be ended from its header,
and tests no longer write into ~/.config/torno. An exported file showed no
parts in firefox --headless --screenshot, which takes its picture before the
first frame; driven in headless Firefox instead, the page drew the parts.
The chat was driven once with the real Claude Code (Haiku, 15 October 2026) in a hidden window on a temporary copy of intake: a pin was sent with the prompt and shown under it, a torno look came back with its picture and the "Pulled from torno" line, a ref in the reply was a chip, a Write was allowed by the CLI without asking under the person's permission mode (so there was no card), and no .claude/ folder was written in the project. It was not looked at on a visible screen, so the items listed below stay to check.
What remains is yours to try once. Each says what to try and what should happen.
On the screen:
- macOS. Nobody has run torno on a Mac by eye. Try once:
⌘Vand⌘Zin the chat composer and in a note, and⌘C/⌘Vin the terminal view (spawn-helperof node-pty must be executable, or every terminal fails withposix_spawnp failed);⌘Qwith a session working asks "Close torno?"; the Dock shows torno's icon when run from the repository; start torno from the Finder or the Dock and a session findsclaudeand a build findsuv(the login shell'sPATH); on a trackpad a two-finger scroll zooms,⌘-drag or⇧-drag pans (OrbitControls' own), and Ctrl-click is a secondary click. - Resizing.
scripts/torno ~/objectson Hyprland: drag the window wider and narrower, or retile it: it redraws without flicker. If the window does not come up right,TORNO_OZONE=x11, thenTORNO_OZONE=wayland, and keep the one that works. - Focus. Start torno a second time with another folder: the window that is open comes to the front (on Hyprland only where
focus_on_activateis on). Start dragging the section's arrow, switch workspace, come back: the plane is where it was before the drag, and the next drag turns the camera. - Closing. With a session working, close the window through the window manager: torno asks "Close torno?", "Keep it open" keeps everything as it was, "Close torno" ends torno and the session. With nothing under way it closes at once.
- A session that needs you, away from its pane. In two projects, ask Claude Code in one for something that needs a permission, then open the other (and again with the session pane hidden): the first one's row has a dot and "needs you" in the warning colour, the status line has the button "name needs you", which opens it and brings you to the card, and a working session's dot is a quiet grey. In a window that is not in front (another window on top, or a workspace switched away) the frame should flash on Linux, and the dock icon bounce once on a Mac: no test shows a window, so only the rule is tested.
- The left column. With a folder of many projects, open one: the list is one row, "All projects
W" beside its title. Its menu, by the mouse and by the keys, opens another; folding Layers and Scans gives their room to the marks, and their titles say how many parts and scans there are. Drag the line above Marks up and down, and double click it: it moves without lag, the sections above give way down to their titles, and the shares come back. Start torno again: what was folded, and where the line was, are as you left them. Legible in light and dark. - Layers of a model with many parts. Open a project of ten parts or more: the pointer on a layer row lights that
part in the view in the accent (and nothing else changes), the target shows it alone,
Show allin the title brings the rest back, and the count of marks on a part lists only its marks. The rows must not crowd: name, count, and on hover the opacity, the target and the eye, at the width of the column. - Parameters. In a project whose model has a
Paramsdataclass with comments and one default that is an expression, unfold Parameters under the layers: a row for each field in the model's order, the value in the monospace face, the comment as written (cut to two lines, whole on hover), "worked out in the model" on the expression's. With a real session running, in the chat and then in the terminal view, a click on a row putsParams.wallat the cursor of the box (or the prompt) and sends nothing, with the dock hidden as well; with no session the rows are not clickable. Legible in light and dark, and Marks keeps its five rows with the section open. Try a value in a number field and with a slider (the field with ametadatarange), tick a flag and choose from a list: the build runs once the slider is let go, not while it moves; the row says it differs from the file and Reset brings it back. - The clipboard in the terminal. Select text in a session and press
Ctrl+Shift+C, thenCtrl+Shift+V: it is copied and pasted. No test touches the clipboard. "Copy" on the build error sheet likewise. - By eye. On a monitor, in dark and in light: text, marks, part edges and the terminal's colours are legible; the scrollbars are the thin grey ones, also in the list of projects and on the page of projects in the dark theme; a mark that has moved says so on a line under its note.
?typed in a field on your own keyboard (with its dead keys) is a question mark. - The doodle board with a mouse and a pen. Draw with a mouse, a tablet pen and a touch screen: the line follows the pointer without lag, the eraser takes away what it crosses,
Ctrl+Ztakes the last stroke back, and the PNG Claude gets is dark on white in both themes. Tests drew with synthetic pointer events only. A photo dropped from the file manager on the new project's page is taken too; a test cannot drop a file with a path. - The Print profile panel. Open it in the left column: the Printer and Material lists, "Add a printer…" with its three bed fields, the lines under them in the monospace face; legible in both themes, and the section folds to its title (
Print profile Bambu X1C, PETG). - The Scans panel. Import a scan of your own: the sheet
How is S1 read?is at the top of the Scans panel with the size it would have; "Later" leaves a dot on the row. The row is one line at the column's default width, the name cut with an ellipsis and whole in its tooltip, the word legible in both themes, the dot and acheckorroughword told apart from the rest. Open its detail and another's: one is open at a time, and Frame, Align and Rename are on one line. - Drawing on the view with the real Claude Code. In a chat, "Draw on view", ring a face and draw an arrow to it with a mouse and with a tablet pen, "Done", then a prompt ("make this thicker"): the ink follows the pointer, the eraser takes ink and never the picture, the row above the box names the faces the ink lies over before anything is sent (and says so, in the warning colour, when it could not be worked out), "Attach view" shows a small picture of the view before it goes, the chat shows the picture and that line with the prompt, and Claude Code's reply speaks of the ringed place and names the face the caption gave. Tests drew with synthetic pointer events and the stand-in agent.
- Claude Code's own checkpoints for "Undo" (
Part of #131). "Undo" asks the agent in words. Claude Code can also restore the files itself: withCLAUDE_CODE_ENABLE_SDK_FILE_CHECKPOINTING=trueat start,claude -p --resume <id> --rewind-files <uuid>, where<uuid>is the id torno gives the prompt. This is not built:--rewind-filesis absent from--help, and nothing is known of it from a real CLI. Try once by hand, with the CLI's version noted: after a turn that edited the model withEditandWrite, does it restore the file as it was before that prompt, and does it list the files with a dry run? Must the live session end first, or does a control request (rewind_files) work on the running process? What does it do to the conversation, and to a file changed byBashor a subagent, which it does not track? Write the answers in issue #131: if the files and the conversation both come out right, the button can use it and be exact; if not, the asked undo stays. - The history sheet. Build a project a few times with an edit between, then open History in the status line: a card for each build, newest first, in a row that scrolls sideways, each with a picture of the project as it was, legible in light and dark. The chosen card has the accent's edge; Show the source shows the Python in the monospace face and cannot be typed in; Show over the current build draws that build in grey, translucent, over the one on show, and the status line has Stop; Ask the agent to go back leaves the request in the box, unsent. Try it with a real Claude Code: send the request, and check that it edits your files to match the copy and not the copy itself.
- A broken part in the layers. Build a model with a cavity whose shell faces into it, or a part made of a shell with a face left out: its layer row says "not valid" (or "not closed") after its name in the colour of a failure, with what that means in its tooltip, and the line at the bottom says "1 part has a defect"; legible in light and dark, and the row still one line in a narrow column.
- Why a fillet failed, in the error sheet. Build a model whose fillet fails (a 20 mm plate with a 12 mm pocket through it, its eight vertical edges at R6): the status line says the build failed, and the error sheet shows, under the message, a "Why" section with the line and a line for each edge, in the face of numbers, legible in light and dark and in a narrow window; Copy takes it with the rest.
- "Ask Claude Code to fix it" with the real Claude Code. Break the model in a chat, open "Build failed" and press
the button: the dock opens if it was hidden, the box holds "Fix the build of name: see the error." and nothing is
sent; Enter sends it, and Claude Code answers from the error it was told (the sheet says whether after its edit or
with the next prompt). Again in the terminal view: the words are at the CLI's prompt, unsent. With no session, "Start
a session to fix it" starts one and leaves the words in its box. Tests used the stand-in agent. After a crash or a
timeout of the build (stop the kernel's process, or lower
TORNO_BUILD_TIMEOUT) "Rebuild" is the main button in the status line and in the sheet; after a Python error it is not. - A scan file dropped on the view. Drag a GLB or STL from the file manager onto the parts: it is imported as "Import scan…" would.
- The folder dialog. "Open folder" on the first screen, and "Open folder…" in the menu of the folder button at the top left: the system's folder dialog opens, and the folder chosen is opened.
- The save dialog of an export. "Export…", then "Export": the system's save dialog opens in
.torno/exports/with a name offered, and the file is written where you say. - The save dialog of a picture. "Export…", PNG, 4000 px, "Export": the system's save dialog opens in
.torno/exports/with<name>-r<revision>.pngoffered, and the PNG opens in an image viewer at 4000 px on its long edge, with the view as it was on screen. - A section drawing in other programs. "Export…", SVG with a section on screen: the SVG opens in a browser, Inkscape and a CAD program, its 40 mm edge measures 40 mm there, and printed at 100 % a ruler agrees with the scale bar.
- The replace question. Save an export as
xwherex.htmlexists: torno asks before it replaces that file, and "Cancel" writes nothing. - A 3MF opened in a slicer. Export a 3MF of a plate of two parts or more and open it in PrusaSlicer, Bambu Studio, OrcaSlicer and Cura: each part is an object of its name, in millimetres, where torno set it; it slices with no repair asked for. "Open it once saved" opens it in the slicer the system gives
.3mffiles to. Tests read the file back with lib3mf only. - An STL in a slicer, a STEP in a CAD program. Open an STL of a round part in a slicer: no facets you can see on its holes at 0.01 mm. Open a STEP in FreeCAD and in Fusion or Onshape: the parts are named bodies in their colours, in millimetres, and a volume measured there is the one torno told; a part named with an accent (
tampa ção) keeps it there, as torno writes names in UTF-8. Tests read it back with OCC only. - Torno left on another workspace with a session working. Give the agent something that takes minutes, or start a long build, and switch to another workspace for ten minutes, watching
ps -o rss -p <pid of the window>: its memory stays flat and torno idles near zero. Idle with a large project it did; with a session working nobody has watched it. - An exported file elsewhere. Open one from
file://in Firefox, in Safari and on a phone, with the network off, and look: parts, layouts, the explode slider, the section, a saved view and the marks work. Headless Firefox drew the parts; nobody has looked at it by eye there, or anywhere but in Electron's Chromium. - The desktop entry.
scripts/torno --install-desktop, then start torno from your launcher: it is listed with its icon, the window comes up and is grouped under the entry (StartupWMClass=torno) with torno's icon. Given a folder (gtk-launch torno ~/objects) it opens that folder; given two, it refuses. Withclaudeonly on thePATHof a login shell, "New session" says it was not found there: start torno from a terminal, or put the command on the launcher'sPATH. - Build ghosts. After an edit, the part as it was is drawn faint beside the new one: legible in light and dark, and gone when the next build lands.
- The section's hatch and plane label. Cut a part: the cut face is hatched and the plane is named at its edge; both legible in light and dark, and the label clear of the bars.
- The Marks panel's resolve glyph, review walk and undo line. The glyph on a row, "To review" stepped through one mark at a time, and the line that undoes a delete: each in its place, legible in both themes.
- The Checks group and its summary. In the Marks panel the group of dimensions and its one-line count read at a glance; the model's Checks panel lists each check with its words.
- The empty chat pane and its chips. With no conversation, the pane's words and the chips under them fit the column and are legible in both themes.
- The turn summary, "See changes" and "Undo". After a turn, the summary line, the sheet of the diff and the Undo button sit well in the chat and in a narrow dock.
- The page of projects. Its head and the order buttons are legible, and the cards keep their order when the window is narrow.
- The doodle board's grid. The grid is faint on the board in light and dark, and is not in the PNG Claude gets.
- The prompt preview of a new project. The preview of the first prompt reads clearly and matches what is sent.
- The scans walk-through. Import, align and check a scan by the steps the panel offers: each step is where the eye goes next, in both themes.
- The export sheet by reader. Each reader (a person, a printer, a drawing), and the picture, SVG and PDF formats: the sheet is one screen and legible in both themes.
- The sources line of Parameters. A field's source word is shown after its comment, in the monospace face, and "no source" is quiet.
- The splitter of the left column. The line between the left column and the view drags without lag and keeps its place at start.
- The edges of a field and F6. A focused field shows its edge in both themes, and F6 moves the focus between the regions in a sensible order.
- The ask card. A question from the agent shows its options and the faces they light in the view; legible in both themes.
- The edits menu. The menu beside the model shows the three modes and the one in force, in both themes.
With a real agent:
- Claude Code and the trust question. In a folder never opened before, with a
.claude/settings.jsonthat holds a hook (or a.mcp.json), either in the project or in a folder above it: "New session" asks "Start the chat anyway?" and starts nothing; "Start the chat" starts it (and the hook runs, as Claude Code's documentation says it does without a question when run non-interactively), and the next session there is not asked; "Use the terminal" shows Claude Code's own trust question. The measuring on 2026-10-15 (Claude Code 2.1.300) only read the documentation for this. - The instruction files, with the real Claude Code. In a project whose workspace folder holds a
CLAUDE.md(the person's real~/objectshas one that namesout/viewer/report.json), the session pane lists it with its size and your own~/.claude/CLAUDE.md, in the chat and in the terminal view, and a click on a name shows the file in your file manager (nothing is opened or run);/memoryin a terminal session lists the same files and no others (anAGENTS.mdonly where noCLAUDE.mdis above the project). After the first prompt, ask what it reads of the build: the real CLI in~/objectsno longer readsout/viewer/report.jsonto see the shape but usesscene,lookandmeasure, while it still follows the file's advice on how to model (caliper numbers first, blockouts first). The stand-in agent cannot show what the model chooses. - The prompts in the command menu. In a chat, type
/mcp:/mcp__torno__review,/mcp__torno__printableand/mcp__torno__explainare offered, and choosing one sends it; the stand-in names none of them, so no test has seen them in Claude Code's own list. - A failed or old Claude Code in the chat. A
claudethat does not know one of the chat's flags, or is signed out: the pane says "Claude Code stopped (exit code 1)" with the last line it wrote, and "It did not start: isclaudeup to date and signed in?". Not yet seen with a real old CLI; the stand-in prints to stderr and exits 1. - A chat that could not use the login, with the real CLI. The stand-in plays it (
FAKE_AGENT_AUTH_ERROR) in the shape of the Agent SDK's types (an assistant message witherror: "authentication_failed", then aresultwithis_error: true); no real CLI has been seen to print it. Start torno withCLAUDE_CODE_SIMPLE=1(what--baresets) and noANTHROPIC_API_KEY, send a prompt in a chat: the CLI's words, then the warning "Claude Code's chat could not use your login" with "Use the terminal", once. Note what the CLI prints then (claude -p --bare --output-format stream-json --verbose helloin a terminal, with no key: the assistant message'serror, theresult'ssubtype,is_errorandapi_error_status) and whether a signed-outclaude(claude auth logout) or a login that ran out answers the same way or exits (an exit leaves the chat ended, and its "Use the terminal" still starts the terminal). Then start torno without the variable: "Use the terminal" ends the chat and the same conversation goes on in the terminal. - Subagents and thinking. A turn with a subagent (
Task; newer versions may call the toolAgent) and one with extended thinking: the card counts steps and lists the tools, "Thought for N s" shows after a thought. The number was worked out from a recording; nobody has looked at it with the real CLI. - The chat with the real Claude Code. In a session in chat: a prompt with a pin or two marked (the "torno added" line shows them and they turn to sent); a command that asks permission, answered "Allow", then another with "Allow for this session" (the next like it is not asked, and
~/.claude/settings.jsonand the project's.claude/are as they were), then one "Deny"; a question (AskUserQuestion) answered with an option and with "Other"; a plan in plan mode approved; "Attach view" (the agent describes what you see); the model menu (the next answer is from that model, and/modelin the terminal still shows your default); the effort menu (a level chosen is taken,/effortin the terminal still shows your default, and~/.claude/settings.jsonis as it was); a new project made with a model and an effort chosen beside "Create" (its first answer is from that model, at that effort);/compact(one note, the conversation goes on); "Terminal", then "Chat" again: the same conversation, its earlier part shown. - Making a new project with the real Claude Code. "New project" with a name, a description, two photos from a phone (one large), a doodle, a scan and a reference to another project, each with a note, then "Create": the project is opened, its chat shows the one prompt with the pictures, Claude Code's reply speaks of them and of the notes, the scan is in the Scans list,
reference/holds the original photos, and nothing else was written in the workspace. The stand-in agent has taken the prompt; the real CLI has not. - Asking for the missing sizes, with the real Claude Code. Make a project whose description gives one size and leaves out the rest (a lid for a box of an unnamed size, screws not named): before the first write of the model, Claude Code asks once, in a card with at most four questions, each with a typical value among its options and "Other" taking "build with assumptions"; it asks nothing about a fillet radius; after the answers it builds, and a size you did not give is a field of
Paramscommented "assumed" and listed in its reply. Then ask for a new feature with no key size (a hole for "a screw"): it asks once, not again. The stand-in agent only plays the card; the real model's choice to ask has not been seen, and the eval harness (#121) does not yet measure it. - The edits menu with the real Claude Code. In a chat, choose "Accept edits" and ask for a change: the CLI
should take the edit without asking, and the menu should show the mode again after the next prompt; then "Plan
only", then "Ask before edits". torno sends the control request
set_permission_mode(mode), the one the Agent SDK documents, answered here only by the stand-in: check that the real CLI answers it in-pstream-json, that itsinitandstatusreportpermissionModeafter it, and that the "Allow for this session" of an Edit ask still writes nothing in.claude/. - Claude Code and torno's skills.
--plugin-dirworks under-p(chat) and in the terminal view: in each,/skills(or asking the agent) liststorno:torno-model,torno:build123d,torno:scansandtorno:dimensions, a model written in an empty project usestorno-modeland builds with both layouts on the first try, and nothing was written under~/.claudeor in the project's.claude/. The stand-in only shows the flag is passed. Asked how an argument of build123d is written, the agent searchesreference/api-0.13.0.txtof thebuild123dskill (its${CLAUDE_SKILL_DIR}is filled in with the real folder, in the packaged app too) instead of running Python, andclaude plugin validate app/resources/agent-pluginpasses. - A failed fillet, with the real Claude Code. Ask for a fillet that cannot be made as asked (the plate above, all
its vertical edges at R6): the result of the edit says why under the error, and the agent reads it, calls
try_filleton the last good build with the edges it names before it edits again, and changes the radius of the edges that need it (or the selection) inParams, saying so; it does not wrap the fillet intry, lower every radius, or callmax_fillet(). The stand-in agent cannot show what the model chooses. - What Claude Code says of itself, with the real CLI. The stand-in plays each case (
FAKE_AGENT_INIT); the real CLI has not: withANTHROPIC_API_KEYset the chat says it is billed to an API key and names whatapiKeySourceis then (look at the value in a realinit:user,project,org,temporary, or another), with it unset and logged in it says nothing (noneoroauth); in a new session with torno's plugin,initliststornoamong itspluginsand no warning appears, and/skillslists the five skills (whatskillsholds with a--plugin-dirplugin is not recorded: the recordings' lists hold no plugin skill at all, so torno judges the skills only whenskillsshows some with aplugin:name); aclaudeof another minor than 2.1 (or older than 2.1.300) says so once; with the torno server made to fail the warning carries what the CLI said; a real overload shows "retrying (attempt N)" and it goes with the answer; a command refused by a rule ofsettings.jsonleaves the warning "Permission denied: …" (and the fieldsmcp_server_errors,plugin_errorsandpermission_denialsare read as their form is guessed from the documentation). When the CLI moves, record a new stream (app/test/fixtures/RECORDING.md) and move the range. - The torno-reviewer subagent. One run with the real CLI, in chat and in the terminal: after a change of several steps Claude Code calls
torno-reviewer, which uses only its read-only tools, reports with numbers frommeasureand changes nothing; whether the subagent's MCP calls go through--allowedTools 'mcp__torno__*'without a permission prompt; and that an invalid--agentswould have stopped the start (it did not); that the CLI acceptsmodelandmaxTurnsin--agents(the subagent runs on Sonnet and stops by 20 turns), thatTORNO_REVIEWER_MODEL=inheritleaves it on the session's model, and that its report ends with the HOLDS, FAILS and UNCHECKED lines. The stand-in only shows the argument is passed. - Pictures of a resumed chat, with the real Claude Code. Make a project with one photo and one doodle and send the first prompt; let Claude Code take one
look; quit torno. Start it again and open the project (the chat resumes): the photo and the doodle show, and thelookpicture says "Picture of an earlier build, not kept". Ask "Without reading any file, describe the doodle I gave at the start": a right answer confirms that--resumegives the model its pictures again. Then/clearand ask what references the project has: it names them from the line inreference/. The stand-in agent shows only that the window reads the files and the line is sent. - The session's token with the real Claude Code. Start a chat and a terminal session: torno's tools connect (no
"torno's tools are not connected" warning;
lookanswers) and the hooks arrive (the status moves to working and back, a pin turns to sent), all with the token in theX-Torno-Sessionheader.ps -eo argsshows the--mcp-configpath and no token. The documentation says${VAR}is expanded in theheadersof a.mcp.json, and an http hook interpolates$VARfor the names in itsallowedEnvVars; that a file given to--mcp-configis expanded the same way it only implies. If it is not, Claude Code sends the placeholder, torno answers 404 and the chat warns that the tools are not connected; the way out would be a small stdio bridge that reads the token from its environment. - The digest of a build with the real Claude Code. Ask it to raise a boss off a plate until it no longer touches
it: the answer to its edit says
Changed since rN:andCheck: plate is 2 solids (was 1), the chat shows it under the edit's card behind "Show text", and when Claude Code would stop it is held back once and either mends the model or says why two solids are meant. Then ask for a pocket: the volume falls inChanged since, nothing is held, and it reads the line rather than asking for a picture first. Whether it heeds the line is the agent's; no test has seen a real CLI read it. - The holds of a
Stopwith the real Claude Code. Have it edit the model and say it is done without alook(once through its editing tool, once withcat > model.pyin Bash): theStopis held with the reason about the build "built during this turn", the agent looks, and the turn ends. Open a project and ask something that changes nothing while its first build runs, then save the file in your editor in the middle of a turn: neither holds it. Pin a dimension, then have it change the model so that it fails: it is held once and names it. Let a build run for twenty seconds while it would stop: theStopwaits, and the hook's 25 s limit is enough (the stand-in cannot tell). Whether the secondStopof a turn that was held arrives withstop_hook_activeand is held again for another reason is what the code assumes; the stand-in plays it as the documentation says. Whether the hooks of atorno-reviewer'slookarrive on the session's address (so that its look counts) has not been seen. askwith the real Claude Code. (1) Ask it to callmcp__torno__askand leave the card open for six minutes: it should still be open and the call not time out, because torno sends a progress notice every 30 s, but only when Claude Code sends a progress token with the call, and whether its 5-minute idle limit counts those notices is not known. (2) With a card open, press Stop in the chat: the card should go and the session be idle. Claude Code's cancel of one call does not reach torno's server, which keeps no sessions, so torno ends the question when the turn ends (theStophook); that was only tried with the stand-in.- Claude Code and the newer tools. Ask it to cut the part, to look at it drawn apart and to check a part against a scan:
section,lookwithexplode,scan_clashandscan_deviationare called without a permission prompt and their replies are used. - The evals with the real Claude Code.
pnpm -C app evalwas run end to end only with the stand-in, on the_smokecase. Its first real run, the spike of one T1 case and then the baseline of every case five times, is yours: whether a chat of minutes holds up in the hidden window, what a run costs, and whether the cases' checks and answers are fair to a real agent. - What torno says it needs, with the real CLI, a machine without
claudeand one withoutuv. Tests look forclaudeon thePATHand never run it; the stand-in plays--versionandauth status. With the real CLI: the first screen saysclaudeis found, its version (claude --versionprints it first, as2.1.307 (Claude Code)) and "signed in" (look at the real output ofclaude auth status: torno readsloggedInandauthMethodof its JSON, and says nothing of the account where it finds neither); signed out (claude auth logout) the line says so and the page of a new project warns above "Create"; aclaudethat hangs or is too old to knowauth statusis said to have not answered, within 3 seconds. On a machine withoutclaudeon thePATHthe line says "Not found on PATH", and withoutuvthe uv line does and the kernel's is left out. With nokernel/.venv(or nokernel-envunder torno's data folder, packaged), "Not made yet" and "Make it now" runuv syncon the network and show its lines, then the line says torno's environment is made; offline the reason is uv's. Started from the Finder or a desktop launcher thePATHshown is the one torno has. - An agent torno did not start (#54, a spike). Start torno with
TORNO_OUTSIDE_AGENTS=1 scripts/torno ~/objects, open a project, and in its folder add the bridge to your own agent (claude mcp add torno -- node <torno>/scripts/torno-mcp.mjs ., or the stdio server of Codex, Gemini CLI or opencode). Torno asks in the window before any tool answers; check thatsceneandlookwork, that the server's instructions reach the model, how long a call takes, which tools are worth it outside, and how the agent fares without torno's hooks. Deny, and the agent is told so; quit torno, and it is told how to start it. Whether to keep it, and steps 3 to 5 of #54 (scripts/torno mcp, anewstool that sends marks, the plugin's.mcp.json, a README section), wait for that trial.