start-here
Skill26 starsChanged 16 days agoMITView on GitHub
Start here for topopt. Checks that the topopt server and the machine are ready, then takes the user from a part (described in words, or STL and YAML files) to a printable STL through check, run, dashboard and result. Use when the user wants a lighter or stronger 3D-printed part, or asks to start, set up or try topopt.
- Pipes a download into a shell
- Installs packages
11 other projects use a version of this.
Adopted by
Most recently taken up by
- kevinph4n/skillstheir own version, first seen Nov 8, 2026
- advisorppc-org/advisorseo-claude-plugintheir own version, first seen Nov 1, 2026
- CorvusWing/CorvusWingtheir own version, first seen Oct 25, 2026
- impactbrussels/AINativeOStheir own version, first seen Oct 20, 2026
- AndyMelnykov/pm-loopstheir own version, first seen Oct 20, 2026
- Abilityai/corneliustheir own version, first seen Oct 8, 2026
Other versions11 projects use a version of this.
Word for word · 0
None found.
Edited · 0
None found.
Wrote their own version · 11
- AIDevGTM/gtm-cofounder★ 334same name
- Abilityai/cornelius★ 109same name
- ZICXR/A-Stock-Skills★ 26same name
- impactbrussels/FounderOS★ 2same name
- jordnlvr/MendixToolkit★ 2same name
- AndyMelnykov/pm-loops★ 1same name
- CorvusWing/CorvusWing★ 1same name
- impactbrussels/AINativeOS★ 1same name
- kevinph4n/skills★ 1same name
- advisorppc-org/advisorseo-claude-plugin★ 0same name
1 more not listed
How matches are found. Word for word: the exact same text. Edited: shares passages with this file (“99% same” means 99% of its five-word phrases also appear here; 10% or more counts). Their own version: similar meaning, or, weakest, the same name. We can’t tell which project had it first. Projects are counted among the 200 most-starred candidates, and mentions are links to the repository we found, up to 50 per site, so both counts are a floor.
In this repository5 other files it gives its agents.
- AGENTS.mdAGENTS.md
- CLAUDE.mdCLAUDE.md
- part-to-topoptSkill
- part-to-topoptSkill
- start-hereSkill
The file
We split this skill into the rules it gives an agent
11 rules. 0 are common, 6 are distinctive, and 5 are very specific to this project.
0
Common
Most of the 9 edited versions we read keep these.
6
Distinctive
This author's own choices. Worth a look.
5
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Distinctive: line 21
Look at the picture before you report, and say in one or two lines what it shows: the regions before the first run (Step 3), the part after …
- Distinctive: line 22
After a run, call the finishing pass (Step 5), then say what was checked and what was not (Step 5).
- Very specific: line 38
Look for the tools of the server topopt, for example check_problem (Claude Code with the plugin names them mcp__plugin_topopt_topopt__....
- Very specific: line 40
If a topopt tool answers topopt is still setting up its Python environment, the first setup runs: tell the user, wait the time that the …
- Distinctive: line 42
If the tools are not there, run the doctor first.
- Distinctive: line 44
Run the doctor. Use this prefix for every setup command (Claude Code fills in the two paths.
- Very specific: line 55
Call environment before the first run.
- Very specific: line 136
Call run_topopt with the YAML path.
- Very specific: line 144
Before you report, answer the final review checklist of the lead-design role (team_guide with lead-design).
- Distinctive: line 172
After a run, say what was checked and what was not (stress, fatigue, the printed part), the files the user has, and what the user can ask …
- Distinctive: line 189
After a continuation, tell the user the volume and the compliance of the new run, and that a continuation is not proven equal to a fresh …
“Common” is measured: kept by at least 6 in 10 of the 9 edited versions of this file we could read. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
topopt: start here
topopt makes a part lighter. You give it the space, the mounting and the loads. It removes the material that does little work and writes a printable STL. It runs on this computer.
This file is a path with facts. Skip, reorder or repeat a step when the user's request needs it. You decide the steps. Ask the user when an answer changes the part (the load, the mounting, the size). If the user does not say what the part holds (an object, a mass or a force), ask for it before the run, also when the user says "run it". Install or build something only after the user agrees.
Talking to the user
These are offers. Use the ones that the request needs.
- A request with no numbers ("a pen", "a shelf bracket"): offer three ways and let the user pick: the full list of questions (the intake items of the
lead-designrole); the 2 or 3 questions that change the part most (the load, the mounting, the size); or a first design from stated guesses (origininferredinrecord_brief). Every way keeps the load question: in the third, ask the user to confirm the guessed load before the run. In Claude Code, oneAskUserQuestioncall with three options can carry the offer; another client gets a short list. A user who says "just run it" can skip the offer. - Say early whether and how topology optimization helps for this object. A thin shell, or a part with no load path, may not fit.
- For a small detail (not the load, the mounting or the size), choose a sensible value and say so.
- Before the first run, call
environment(Step 1) and tell the user the device and the time, and that the user can pick another solver or device (settings.solver,settings.gpu_device; Step 1). The time is an estimate, not a limit:check_problemgives the start estimate (Step 3); thenrun_statusshows the progress. - Name the work folder in your first reply. Answer in the user's language. Use plain words first (the share of the block that stays, how much the part flexes, how fine the grid is) and give the technical numbers when the user asks.
- Look at the picture before you report, and say in one or two lines what it shows: the regions before the first run (Step 3), the part after each run (Step 5).
- After a run, call the finishing pass (Step 5), then say what was checked and what was not (Step 5).
Facts to tell the user (when they matter)
- Target: a Mac with Apple silicon. Linux x86_64 also works. An Intel Mac (or an x86_64 Python under Rosetta) is best effort: tested on Linux only, with an older lock,
autogivesamg, and the time estimate is not calibrated. On Apple silicon, a native arm64 Python is the fast path. Windows does not work. uvis not a prerequisite. The launcher uses the user'suvif it is uv 0.6.8 or newer, and skips an older one on the PATH. Else the setup downloads a pinned uv release from the official uv GitHub releases (about 20 MB, once, into the plugin data folder) and checks its SHA256. uv gets Python 3.13 itself. If that download does not work, the official installer iscurl -LsSf https://astral.sh/uv/install.sh | sh(orbrew install uv). For a uv that is too old, the fix is an update of that uv with the tool that installed it:brew upgrade uv,pip install --upgrade uv, oruv self updatefor a uv from the install script.- First start: the server downloads Python 3.13 and the packages: about 200 MB (150 MB of packages on Linux x86_64, measured) and about 500 MB on disk. Measured: 23 s on a fast link, so 1 to 3 minutes is normal. Claude Code waits only 30 s for a server, so the launcher connects at once with all the tools, and a tool call answers with the setup status until the setup is done. The setup goes on in the background, detached from the server, so a client that ends the process group or the process tree of the server (Codex does the first each time it closes the server) does not stop it. Where neither
setsidnorperlexists, the detach is only partial, anddoctorsays which way this machine uses. In a client that still stops the setup, the user can runsh scripts/topopt-env.sh syncin a terminal, in the repo root, before the first start (docs/clients.md). Other clients wait for other times. Later starts take 1 to 2 s. - Fast solver on a Mac: Apple Accelerate, through a small C shim. Building it needs the Xcode Command Line Tools (
xcode-select --install). Without a working shim,settings.solver: autoruns the AMG solver and prints a warning with the build command. AMG is slower.check_problemgives the time estimate for the solver that runs, andenvironmentgives it for each solver. No full AMG run is measured on a Mac. The measured hook times (one Linux machine, one M1) are in the README. - Runs, STLs and results go in
runs/in the work folder. With the plugin, that is the folder whereclaudestarted. With the Claude Desktop extension, it is the Work folder field in Settings > Extensions > topopt (empty:~/Documents/topopt); a change needs a full quit of Claude (Cmd+Q on a Mac).environmentshows the folder in use. When a reply haswork_folder_note, tell the user that text.
When a tool fails or a check refuses
The table "First-run problems" in docs/reference/install.md maps the message of a failed tool or of a refused check to its cause and its fix. In a checkout, the file is docs/reference/install.md in the repo root. In the plugin, it is ${CLAUDE_PLUGIN_ROOT}/docs/reference/install.md. Look for the message in the first column, and give the user the fix in plain words, with its command. When the message is not in the table, the doctor (Step 0) and check_problem (Step 3) give the facts.
Step 0. Is the server connected?
Look for the tools of the server topopt, for example check_problem (Claude Code with the plugin names them mcp__plugin_topopt_topopt__...; another client adds its own prefix). If they are there, go to step 1.
If a topopt tool answers topopt is still setting up its Python environment, the first setup runs: tell the user, wait the time that the text gives, and do not call the tool again at once. The tools work in the same session when the setup is done.
If the tools are not there, run the doctor first. It changes nothing, it installs nothing, and it works with a broken or a missing environment:
- Run the doctor. Use this prefix for every setup command (Claude Code fills in the two paths; in a checkout, run
sh scripts/topopt-env.sh doctorin the repo root, and usestatusandsyncin the same way):CLAUDE_PLUGIN_DATA="${CLAUDE_PLUGIN_DATA}" sh "${CLAUDE_PLUGIN_ROOT}/scripts/topopt-env.sh" doctorIt prints the facts of the setup: uv, the environment folder, the stamp, a cut setup, a running setup and an import test of the packages. When the environment runs, it adds the report oftopopt doctor: the checkout (git or not, the revision, how far it is behindorigin/mainas of the last fetch, and when that fetch was), the packages, the known environments and which client uses which, the offers and the next commands. It ends with the command to run next. In a checkout venv,topopt doctor(or.venv/bin/python -m topopt doctor) gives the second report alone, and--jsongives it as data. A copy of topopt that is older than this command says so in the report. Updating the copy is then the first step. - Read it and tell the user the facts. A checkout that is behind
origin/mainis a fact to tell: the update command is in the report, and the user decides. The numbers are as of the last fetch; the report gives the fetch command. Run a fix command only after the user agrees. - The row
uvsaysnot found:andthe setup downloads uv ...: there is nothing to install, the setup gets it (see 5). The row can also name a uv on the PATH that is too old (older than uv 0.6.8): the setup skips it and downloads its own. If the launcher says that the automatic download did not work (offline, a platform with no pinned uv, a failed SHA256 check, or aTOPOPT_UVthat does not run), tell the user the reason. When the message also saysThe uv on PATH is too old, offer the update of that uv with the tool that installed it (the commands above): a new uv from the install script does not help while the old one comes first on the PATH. Otherwise offer the official installer (the command above). Run it only after the user says yes. Then the user runs thesynccommand (5), and the tools work in the same session (6). - It says the setup is running: wait for it.
syncwaits until the running setup ends, or wait a minute and runstatusagain. The log issetup.login the same folder as the environment. - It says not ready and no setup is running: tell the user what happens (a download of about 200 MB when the uv cache is empty, 1 to 3 minutes), get a go-ahead, then run the same command with
syncin place ofdoctor. Give the shell call a 10-minute timeout, if the client lets you set one. A setup that stopped (a network error, a killed process) leaves a marker, and the nextsyncinstalls the packages again. One fact: an environment that an older launcher left cut has no marker,statuscalls it ready, and a tool fails with anImportError. The import test of the doctor finds it. Then run the command that the doctor prints at the end: it makes the marker file and runssync, which installs the packages again. This needs the network, also when the uv cache is warm; withUV_OFFLINE=1in front of the command, uv works offline when its cache has every wheel (docs/clients.md). In Codex, a command that you run has no network by default: the user can runsyncin their own terminal, or approve the command outside the sandbox. - When it says ready: the tools work in the same session, because the launcher hands its open connection to the Python server. If a tool still is not there, ask the user to reconnect the server. In Claude Code, type
/mcpand reconnecttopopt(or/mcp reconnect all). Another client has its own way (restart the client, or turn the server off and on:docs/clients.md). You cannot do this yourself.
Step 1. The machine
Call environment before the first run. Read the platform, the solver backends, what auto means here, the RAM and the cores. Tell the user the facts that matter, and the device that will run.
The device that will run is the solver that auto picks first here (auto_solver: gpu-mg on a GPU that passes a short self-test, else cholesky, pardiso or amg on the CPU). gpu_solver says which: used, the device, the self-test and, when no GPU is used, why and to_use_the_gpu. opencl.devices lists the OpenCL devices (kind tells a GPU from a CPU device; an empty list means that gpu-mg cannot run here). Tell the user the solver and the device. If gpu-mg fails during a run, auto goes on with the CPU solver (auto_choice.cpu_solver) from the last checkpoint, and run.json and run.log say so. Tell the user that they can pick another solver or device. auto stays the default until the user asks. In the YAML, settings.solver takes auto, cholesky, pardiso, amg or gpu-mg. topopt run and topopt check take --solver with the same values, for one command. run_topopt has no solver argument: edit the YAML, then call check_problem. For gpu-mg, settings.gpu_device is auto, a number from the list that topopt check prints (the index in opencl.devices), or a part of the device name.
If recommendations has an offer (build_accelerate_shim on a Mac, install_pypardiso on Linux), tell the user the reason, the speed-up and the license note, and ask. Run the command only after a yes. If the user says no, the run goes on with AMG: auto does that without a change to the YAML. The shim is built in the plugin folder. A plugin update needs a new build.
The answer has checkout too: git or not, the revision, and how far the code is behind origin/main as of the last fetch (last_fetch_utc; nothing is fetched). Tell the user when the code is behind, and give update_command.
gpu_solver is a fact, not an offer. When used is false and to_use_the_gpu has a step (for example the install command of pyopencl), tell the user the reason and the step, and ask before you run a command. settings.solver: gpu-mg asks for the GPU solver on any OpenCL device, also a CPU device, and it has no fallback (run_topopt has no solver argument: edit the YAML; --solver gpu-mg does it for topopt run and topopt check). The speed of gpu-mg is measured on GPUs. gpu_solver.measured has the figures of one machine (the hook example on a MacBook Air M4: seconds per iteration of the default solver against gpu-mg); another machine and another GPU can differ. topopt gpu-check measures this machine (one command, one report to share), and check_problem gives the time and memory estimates for a problem. A gpu-mg run that stops with did not reach the relative residual names its options in the error: settings.gpu_tol (the target of each solve), settings.gpu_backward_error (a solve at the FP64 rounding floor is accepted at this backward error; 0 turns the rule off), settings.gpu_max_iter (case B: the PCG iterations of the first pass, default 1000), a stiffer start design, or another solver. The residual history of the failed solve is in gpu_mg.jsonl of the run folder. What gpu-mg takes: springs, joints and per-axis supports (a fixed entry with axes), alone or together, with or without a fixed region. In its case C (a device without 64-bit float) the joint term is computed in 32-bit float and the inner PCG removes the rigid modes of each body and 6 harmonic columns for each bonded patch, and solves them in 64-bit on the host (run.log says so): check prints the beam over the bond stiffness on its softest mode, with the cases that were measured on pocl and on one Intel Arc iGPU (one host each; revision 133e5a1; a cube that only a joint holds solved at 9.9e-12 and above with the rigid modes removed, and at 3.3e-7 and above without; a part with two tube joints and no fixed region solved with the harmonic columns and stopped without them) and the memory of the columns, which the memory estimate does not count. These are facts, not a limit. A run that stops gives the ratio and the options: another solver, settings.gpu_case: B on a device with 64-bit float, or a stiffer beam (a larger section, a shorter beam).
Step 1b. Preferences
The reply of environment has the key preferences. It tells whether the user saved their answers in the file topopt-preferences.yaml in the work folder.
existsis false: ask the user once, in one message, for five things. The printer: the model only, for example "Bambu P1S". The user does not give the print bed or the printer type; see "The printer model" below. The filaments the user has: the names only, for example "Bambu PLA Basic"; see "The filaments" below. The material, when the user gives no filament (a name such as PETG, and E in MPa, nu and density in g/cm3 if the user knows them; the resourcetopopt://materials/referencehas typical values). The quality, which is the voxel size in mm: a smaller voxel gives a finer part and a slower run, andcheck_problemgives the time for a part. The outputs after a run:renders(images),step(a STEP file) andverify(a stress check); the final STL and the part card are made in every run. The user can answer "keep the defaults": then callsave_preferenceswith no section, so that nobody asks again. Otherwise callsave_preferenceswith the answers.existsis true: use the values and do not ask again. The user can change them at any time. A new answer is a new call ofsave_preferences, or an edit of the file by hand. The server reads the file at each call, so a change is live at once.problemsis not empty: tell the user what is wrong. The server uses no value from the file until it is fixed.save_preferencescan repair a wrong key. It does not change a file that is not valid YAML.- A preference does not change a run by itself.
build_regionswrites it into the YAML only for a block that the spec does not have, and says so (preferences_facts).check_problemnames a difference between the YAML and the file. The problem YAML stays the record of a run.
The printer model
The user names the printer. The tool knows the print bed (bed_mm), the build height and the printer type. Call find_printer with the name that the user said, and read status.
match: show the user what the profile gives (the print bed, the build height, the printer type) and say which values arenull(unknown). Then callsave_preferenceswithprint={"printer_model": <id>}. A YAML withprint.printer_model: <id>gets the bed and the printer type from the profile, and the print report compares the part height with the build height. Abed_mmor aprinter_typethat the YAML gives wins over the profile.ambiguous: offer thecandidatesand the facts indiffers, and let the user pick one. Do not choose for them.none: the printer is not in the catalog. Ifsuggestionshas names, ask whether the user means one of them (a suggestion is not taken by itself). If not, follow the steps below.
A printer that is not in the catalog:
- Look up the page of the maker (a web search). Take these values from that page: the printer type (
printer_type:fdmfor a filament printer;save_printerneeds it), the bed size, the build height, the highest nozzle and bed temperature, the enclosure (open,enclosedorheated), the nozzle material and the extruder. - Show the user the values with the address of the page, and ask whether they are right. A value that you cannot find stays unknown (
null): ask the user for it, and leave itnullif the user does not know. A client with no web access asks the user for the values. - After the user agrees, call
save_printerwith the values and their source:source_url(the page) andopened(the date), orsource: "user"when the user said the values. A value from another page can have its own address infield_sources. The tool writestopopt-printers.yamlin the work folder. - Then call
save_preferenceswith the new id asprinter_model.
The user can also skip all of this: give print.bed_mm and print.printer_type in the YAML, as before.
The filaments
The user names the filaments they have; the catalog knows the modulus, the density and the print needs of each. Ask once, after the printer: "Which filaments do you have?" (the names, or the label of the spool). Call find_material with each name and read status.
match: show the user what the profile gives (derives: E, which is the lower of the XY and Z modulus, and the density; nu stays the assumed 0.35, because no data sheet gives it) and say which values arenull(unknown). The numbers of a data sheet are for printed test bars of one maker, not for the part.ambiguous: offer thecandidatesand the facts indiffers, and let the user pick one. A family alone ("PLA") fits many filaments, so the answer isambiguous. Do not choose for them.none: the filament is not in the catalog. Ifsuggestionshas names, ask whether the user means one of them (a suggestion is not taken by itself). If not, follow the steps below.
Keep the answers: save_preferences with filaments={"available": [<ids>]}. If the user has chosen the filament for the part, material={"filament": <id>} keeps it as the material too. A YAML with material.filament: <id> gets E and density from the profile; a key that the YAML gives wins, and topopt check says where each number came from.
Then show what the printer can print: call printer_materials (it takes the printer of the preferences, or printer=<name>). Say the answer as facts: each filament is ok, care (it can work, and the reason says what to do or know), no (a stated need is above a stated limit of the printer as it ships) or unknown (a value is missing), and each reason names the two values that it compares. Start with the filaments of the user (the key available); list the others when the user asks. The states are facts, not a stop: the user may have changed the nozzle or the printer, and a no does not stop a run. The user decides.
A filament that is not in the catalog:
- Look up the data sheet of the maker (a web search). Take these values from it: the family, the tensile modulus and strength in the layer plane (
E_xy_mpa,strength_xy_mpa) and across the layers (E_z_mpa,strength_z_mpa), the density, the nozzle and bed temperature ranges, whether an enclosure and a hardened nozzle are needed, and the drying setting. A run needs a modulus (the tensile one; if the sheet gives only a bending modulus, give that asbending_modulus_xy_mpaorbending_modulus_z_mpa) and the density. - Show the user the values with the address of the sheet, and ask whether they are right. A value that you cannot find stays unknown (
null): ask the user for it, and leave itnullif the user does not know. A client with no web access asks the user for the values. - After the user agrees, call
save_materialwith the values and their source:source_url(the sheet) andopened(the date), orsource: "user"when the user said the values. A value from another page can have its own address infield_sources. The tool writestopopt-filaments.yamlin the work folder. - Then use the new id in
save_preferences.
The user can also skip all of this: give material.E, material.nu and material.density in the YAML, as before.
This is an offer. If the user wants to go on at once, ask later, or ask only what the part needs.
Step 2. The part
- The user describes a part in words: use the skill
part-to-topopt(.claude/skills/part-to-topopt/SKILL.md; the same text is in.agents/skills/part-to-topopt/SKILL.md. A client that does not list the skill can read that file). It builds region STLs and a problem YAML (the toolbuild_regions) and gives the assumptions to state. A sketch or a photo with no size gives proportions only: the skill asks for one real size (Step 1, item 4) before the run. - The user has an STL of the part itself ("here is my part", "keep the screw holes"): use the skill
part-to-topopt, section "Your own STL". It reads the file withfind_features, asks which holes take screws and what is mounted, and builds the regions with thestlitem ofbuild_regions. - The user has the region STL files and a problem YAML: write or fix the YAML (see CONTRACTS.md section 2 in the
code_folderthatenvironmentreturns). - The user wants a quick try:
examples/hook/hook.yamlis the T-shirt hook.check_problem,environmentandrun_topoptfind a relative path like this one in the plugin folder, when it is not in the work folder. - Files in the plugin folder are replaced by a plugin update. To change an example, copy its folder (the YAML and the STLs) to the work folder first, for example
runs/<name>-input/, and change the copy.
Before the first run, write the load inventory (the section "Load inventory, verify and ask, buckling" of the skill part-to-topopt; a simple part needs one or two lines). Say what you are not sure of. If an answer changes the part, ask the user; if you go on with a guess, write it as a guess (origin inferred) and say it again at the end.
State your assumptions before the run: the loads and where they come from, the mounting, the size, the material, the volume fraction, the voxel size. The tools return assumptions and derived for this.
Record the brief with record_brief: the text (the goal, the loads, the mounting, the material, the sizes) and the assumptions, each with its origin (user, inferred or default). The tool makes a case: the YAML, the STL files it names, the brief and the runs. case_status shows the brief, the input hashes, the runs and what changed since the brief. Say the brief to the user and go on, or wait for an answer when the answer changes the part. A missing load changes the part. A YAML outside the work folder (an example in the plugin folder) gets no case file: copy it first.
Step 3. Check
Call check_problem with the YAML path. It loads and voxelizes the problem and runs nothing. Read the grid, the free dofs, the time estimate for the solver that runs, the memory estimate, lost_force and the warnings. Tell the user the time (an estimate, not a limit) and the warnings that matter. A guard (max_grid_voxels, max_active_voxels, the memory check) stops a run that cannot work. Its message names the override and the other options. You decide with the facts: the memory estimate against the RAM (a run above the RAM can freeze the machine). Raise the guard or take a larger voxel, and tell the user what you chose, why and the price (part-to-topopt, Step 4).
Look at the problem before the first run. Call render_part with yaml_path (the problem YAML). It draws the regions and one force arrow for each force of each load case, and it runs no solve. Say in one or two lines what the picture shows: is each load on the face that the user means, does its arrow point the right way, is the fixed region where the part is held, does a keep-out cover the hole or the space that it is for. The arrow length does not show the force size. The numbers of check_problem can look fine while a load sits on the wrong face or a region is in the wrong place, and a picture shows this in a second. The cost is a few seconds (the tool text gives 2 to 4 s for a part of 85,000 triangles). Draw the YAML again after each change of the YAML or of a region STL, before the next run. This look is your own check. It is not the renders output of the preferences (Step 1b), which is about the images that the user gets. A client that cannot show images: call the tool with inline=false, read the facts (the bounding box of each region, load_arrows, the missing files), and tell the user that you did not see the picture.
Step 4. Run
Call run_topopt with the YAML path. It returns at once and gives dashboard_url (usually http://127.0.0.1:8765/). Give the URL to the user. The dashboard shows the shape while it forms. On a desktop, the browser can open by itself. One run goes at a time.
A run makes all the passes of settings.iterations. stop_rules=true (or settings.stop_rules in the YAML) lets it end earlier, when the objective has settled. The rules are off by default.
If the YAML has a case file and no current brief (none recorded, or an STL file or a requirement key of the YAML changed since), run_topopt returns awaiting_user. The reply has the facts (the changed files and keys), the options and the override proceed_without_brief=true. Nothing starts. Record the brief and call again. Give the override only after the user agrees: the case log records it. A change of a method setting only (for example settings.solver or settings.voxel_size) does not stop the run: brief_facts names the key with both values, and you tell the user what changed, why and the price. A YAML with no case file runs as before. The reply has brief_stated.
Step 5. Watch and report
Before you report, answer the final review checklist of the lead-design role (team_guide with lead-design).
Nothing calls you back when the run ends: no client starts a new turn when a background job ends. A promise of a later report cannot be kept (for example "I will report when the run finishes"). There are two choices. Keep calling run_status in this turn until the state is done, error or stopped, or process_alive is false, then report. Or end the turn with a plain line that tells the user what to ask to get the result (for example "what is the result?").
Call run_status from time to time. Each call returns at once. In a client with a shell, wait between calls (for example sleep 20 in Bash). In a client without a shell, call run_status again in the same turn. The check gave the start estimate of the time; run_status shows the pass and the time so far.
To end a run that is still going (the user changed their mind, the numbers look wrong), call stop_run. It writes a request and returns at once. The run reads the request at the end of its current pass, saves the checkpoint and ends with state stopped (no result/). A pass can take a long time, so the stop is not at once; run_status shows stop_request while the request waits. The reply of stop_run has state, the facts and the options (a run in its post step has no pass left to stop: the reply says so and writes nothing). A run that an older build started ignores the request: the note says so. To continue a stopped run, see the next paragraph (resume=true; the inputs did not change, so no allow_changed_inputs).
A run that was stopped (by stop_run, or because the process was killed or the machine was off) leaves a checkpoint of the last finished pass. run_status shows it as checkpoint. To continue the run, call run_topopt with the same YAML and out_dir, and resume=true. If the YAML or a region file changed since the checkpoint, the reply names the changes. Tell the user, and use allow_changed_inputs=true only if they want the new inputs. Without resume, a new run in a folder that holds the checkpoint of an unfinished run deletes it: the reply has earlier_checkpoint then. Tell the user, and use resume=true if they want to continue the old run.
To add passes to a run that is done (or stopped), call run_topopt with continue_from (the run folder), more_iterations (how many passes to add, 1 or more, no upper limit) and a NEW out_dir. The new run starts from the last checkpoint of the parent (pass K) and has K + more_iterations passes. The parent folder does not change, and out_dir cannot be the parent or inside it. The new folder gets a copy of the snapshots (the reply facts give the bytes). The reply has continue: the pass K, the pass count, whether pass K runs again (a finished parent: yes, with its update step; a parent that did not finish gives a fork that starts at pass K + 1), the step settings and the facts. Tell the user the pass count and the facts that matter. If the parent ended at a stop rule and the rules are on, the rule is judged again at pass K and can end the new run there: the facts give the options (stop_rules=false, or other thresholds). A change of the YAML or a region file is a refusal with the changes; use allow_changed_inputs=true only if the user wants the new inputs. resume=true is for finishing a run in its own folder. Step 6 says when a continuation fits a change of the problem.
When state is done, call run_result. Tell the user the mass before and after, the stiffness for each load case (n/a with a reason when a case has a moment, a remote force or a pressure), the run time, the solver and the path of final.stl. The verdict in the metrics says whether the run converged, reached the iteration limit or stopped at a rule, and whether the constraint rows are met. For the state iteration_limit, say it in plain words: "the run stopped at its pass limit", not "not converged". converged (the objective has settled) and feasible (the constraint rows are met) are two facts, and a run can have one without the other. The text gives each test with its value, its threshold and met or not met. The default thresholds are not tuned for a part: give the numbers to the user, and do not call a run bad because of the label. lost_force and the warnings are facts to include. The dashboard result panel has a Refine button: a page with a stress overlay and a tool to place marks. The stress is a display of the voxel model. It is not a safety check. For a run with a stress limit, read stress.cases.<case>.summary in the run_result answer. Give the user both facts: the relaxed row of the optimizer (met or not) and the voxel estimates of the part stress against the limit (the grey and the thresholded design give a range). "Met" is about the relaxed measure. Neither number is a check of the printed part. To change the part from the marks or from a comparison, see Step 6.
After each run, look at the part before you report. Call render_part with the run folder (with no argument it takes the last run). It draws final.stl with the regions, and the facts give the share of back faces and the missing files. Say in one or two lines what you see: the main members, a part that is cut off, a region with no material around it, magenta back faces (a flipped or open surface). The numbers of a run can look fine for a wrong part, so the look comes before the report. A change that has no run gets the same look: give stl_paths for the edited.stl of apply_marks. section cuts the part open. The inline=false fallback of Step 3 works here too. A render is not a stress check or a fit check.
Every finished run already has the print check: result/print_check.json, and run_result shows print_check (the counts of pass, fail and unknown, the names of the failed checks, and the unknown checks that have a limit). It reports and stops nothing. Read it first. To check the part for printing with other limits or another build direction, or to check an STL file, call print_check (second call). It runs no solve. Each check (closed mesh, bodies, closed voids, bed fit, overhang, support estimate, plate contact, wall thickness) has a value, a limit with its source, a state (pass, fail or unknown) and a reason. The limits are keys of the YAML print block, such as min_wall_mm; a check with no limit is unknown. Tell the user the facts that matter. strict is an opt-in gate and stops nothing. A print pass is not a stress check.
After each successful run, call the finishing pass before the final report to the user: the role finish (the sub-agent topopt:finish-engineer, or team_guide with finish; one agent that does all the roles does the pass itself). It looks at final.stl with render_part (the views front and fl2:-1.333,-1,0.373, regions off), removes visible voxel steps with the tools that exist, runs the print check again, and reports what it changed, the print check and the renders before and after, the price in mass and stiffness, and what it did not fix. Tell the user that report with the result. The cheap routes (the look and the print state) run after every run. A route that costs a run, such as the fine pass, is an offer to the user with the estimate. A part with no visible steps and no print finding can end the pass with a report of what was looked at and no change. The pass changes a method setting freely, with a report; a user requirement it tightens with a report and its price, and loosens only with the consent of the user.
Steps on the finished part are a known limit of the voxel method in this version, not a fault of the input. Where a surface runs a few degrees off a grid plane, each one-voxel step stretches into a long flat tread (the voxel size divided by the tangent of the tilt: a step every 8.9 mm at 0.5 mm and 3.2 degrees). Long, thin parts show them most; curved or steep faces give short steps, which the mesh step smooths or makes lower. A step is one voxel high and is already in the run's numbers: the stiffness comes from the voxel model, and mass_final_g is the mass of final.stl. For long steps, cheapest first: on the faces of a closed design space, if metrics.mesh.envelope_fill shows that the fill did not run, settings.envelope_fill auto or on in a post-only resume (minutes; the stiffness at the same mass falls a few percent, more on a coarse grid); on member surfaces, a smooth mark with apply_marks (seconds; the mass change is reported; its effect on a long step is not measured, and the stiffness change is known only with a stiffness or stress rule of acceptance); a finer voxel with fine_from (a whole run; the estimate is in run_result.fine_pass), which makes the steps lower but can still show them. settings.mesh_method is no route for long steps. A better smoothing of long steps on member surfaces is planned, with no date. Nothing is measured on fatigue or stress at a step edge.
To check the stress of the finished part with another mesh and element, call verify_run with the run folder (estimate_only is the default: it meshes nothing and gives the memory and time; estimate_only=false starts a background job that verify_status shows). It needs the extra verify, refuses a YAML with a moment, a remote force, a pressure or a per-axis support, and gives pass or fail for an allowable_mpa that the user gives. With gate=true it gives pass or fail for the limits that the YAML already has (stress_limit, buckling.min_factor, limits rows), read as limits of the tet value; allowable_mpa then replaces the stress limits of the YAML, and a larger value loosens them (only with the consent of the user). A joint or a spring of the YAML holds the part like a clamp: the result then has supports (the bond force and moment of each joint, the force of each spring), the press-fit check is skipped for a joint, and the buckling load factors are for buckling of the part, not of the beams. Tell the user what the check does not cover.
After the first finished run of a part, offer the stress check once. It answers "will it break under these loads?" (verify_run). Say what it costs: the extra verify, and the time that verify_run gives first with estimate_only. Say that a yes or no needs a limit: an allowable stress from the user (allowable_mpa), or the limits that the YAML has (gate=true). The user decides. When the job is done, verify_status has plain: one plain line for each load case, with the source of the limit, and with gate=true the line gate: PASSED or gate: FAILED (...). Give these lines to the user as they are.
To give the finished part to a CAD program, call export_step with the run folder (estimate_only first for a large mesh). It needs the extra step (estimate_only does not); without it the reply has the install command. The call returns when the file is written, and a large export holds it for minutes, so read the estimate first. It wraps final.stl (or part_only.stl with part_only) as a closed solid in a STEP file, result/final.step. The faces are the facets of the mesh: a CAD user can cut holes in it and join it to other parts, and cannot edit its surfaces. The report gives the faces, the file size, the sampled distance to the STL, the volume change and the checks of the model and of the file read back; tell the user the tolerance and the distance.
After a run, say what was checked and what was not (stress, fatigue, the printed part), the files the user has, and what the user can ask next (Step 6). A printable part is not a certified part.
Step 6. Refine from feedback (optional)
The user says what they see: "too heavy", "extra film here", "rough", "grey". The lever cards say which key or tool changes it. Read the MCP resource topopt://levers (or run topopt levers <key or word>; the resource topopt://levers/<key or word> gives the cards of one key or word): one card for each YAML key and each engine tool. A card gives the kind of the lever, the cost of one try, the words of the user, the fact in the result that detects the symptom, and the measured evidence with its part class. The cost has four layers: a new run, a continued or branched run, the post step only (run_topopt with resume=true and allow_changed_inputs=true on a finished run: seconds, for the keys that the cards mark), and a local edit of the finished part. Pick the cheapest layer that can work, compare the result with compare_runs, and tell the user what you changed, why, and the price.
Who decides (guidance; no tool refuses a change):
- A method setting (penal, beta schedule, move limit, solver and tolerances, voxel size, iterations, robust, mesh method, smoothing): you change it without asking, and you report what, why and the price.
- A user requirement (loads, supports, keep-outs, interfaces, material, stress limits and safety factors, a stated mass or volume target, print limits, symmetry): you may tighten it without asking, with a report and its measured price. You loosen it only with the user's consent, and you bring the facts.
- A key that can be either (for example
settings.min_member_mm, a print rule or a look lever) follows its origin inrecord_brief:useris a requirement;inferredanddefaultare yours.
A change after a finished run. The user asks for a change to a part that has a run. A test on three parts (hook, bracket, ladle) measured the routes below. These are facts and offers. The user's requirement decides (Who decides, above).
- Less volume: offer "continue from the last run":
run_topoptwithcontinue_from,more_iterationsandallow_changed_inputs=true(Step 5). Measured at -20 % volume only: 44 to 55 % fewer passes than a fresh run, and a compliance at equal volume of 0.94 to 1.00 of the fresh run. No other size is tested. - One factor on all the forces, with no stress, displacement or compliance limit: the design is the same, and a new run is not needed. A fresh run gave a byte-identical
final.stlon all three parts. With a stress, displacement or compliance limit, or a different factor for each load case, a run is needed. - A load direction turned by 10 degrees or more (less than 10 degrees is not measured), or a change of a requirement (the minimum member, the print rules): run fresh. A continuation keeps the old topology (at 30 degrees and equal volume, the compliance was +7.5 % on the bracket and +17 % on the hook, in its worst load case) and the old thin members (with the minimum member at 2 voxels, the thinnest member of the hook and the ladle was 69 % and 80 % of the fresh run's). A key that only the post step reads, such as
print.build_direction, keeps its cheaper route (layer 3, above). - After a continuation, tell the user the volume and the compliance of the new run, and that a continuation is not proven equal to a fresh run. Compare a compliance with another run only at equal volume.
The marks are points of emphasis that the user places on the Refine page and downloads as marks_<name>_<time>.json (units mm, the frame of final.stl). marks_to_hints edits no mesh; apply_marks edits the finished part. Three tools turn feedback into a next run, and a fourth edits the part:
run_historylists the earlier runs with their settings and main metrics, and names a missing or partial file as a fact.compare_runsputs two runs side by side: the setup diff, the metrics diff and the voxel labels that changed. It also says as facts whether the YAML and STL files on disk can be the ones that ran (a newer file, a YAML value that differs fromrun.json), becauserun.jsonholds no hash.marks_to_hintsreads the marks file and writes a NEW folder with a proposed YAML (the YAML of the run, with the changes), region STLs andhints.json. For each mark it gives the facts (position, radius, distance to the surface, voxels touched, overlap with the fixed, load and keep-out regions) and the options. A remove mark becomes a keep-out region, an add mark a forced-solid region, a smooth mark stays a fact because the YAML has no key for it.optionschange the choice (skip, a smallerradius_scale,per_mark). A keep-out from a point mark is a hard void, so a smaller radius is a fair option. The first mark wins an overlap. The reply is short;hints.jsonhas the full facts, anddetailgives them in the reply. The report says if the proposed YAML loads and gives the target volume before and after.apply_marksapplies the marks tofinal.stlin a NEW folder (edited.stl,apply_marks.json): a remove mark is a cut that protects the parent, an add mark a closing, a smooth mark a fairing (iso for a point, fair for a stroke;smooth_modetube rebuilds a whole strut and is asked for). It gives facts for each mark (volume and mass change, the clip by the frozen regions, thickness near the mark, parent check, bodies, stress near the mark) and no verdict. No FE run is made on the edited part. Marks made for anotherfinal.stlare refused unlessallow_staleis set. The other route ismarks_to_hintsand a new run; you choose and tell the user why.- Read the report, tell the user which option the YAML uses and why, draw the proposed YAML (Step 3), then call
run_topoptwith it and a newout_dir, andcompare_runswith the first run. To change the part in another way, change the spec or the YAML and run again. - To cut the thin film at the contact faces of
part_only.stl:metrics.part_only.contactgives, for each removed body,thinnest_wall_mmandarea_below_voxel_mm2(the contact area with a wall below the voxel size), and a note names the bodies with a thin wall. The thinnest wall is the lower end of the film, not a value to use. The wall thickness to cut off is a print or a look decision of the user. Write it assettings.part_only_min_film_mmin a NEW YAML file in the folder of the old YAML (the region STL paths are relative to that folder). Callrun_topoptwith that YAML,out_dirset to the old run folder (or to a copy of it, to keep the old result),resume=trueandallow_changed_inputs=true: the post step repeats and the optimization does not. Then readthin_area_mm2,cut_area_mm2andthin_area_after_mm2for each body inmetrics.part_only.contact, and change the value if the film is still there.compare_runswith the first run shows the key when the two YAML files differ. ratchet_runjudges candidate edits of the finished part (marks or STL files) with the rules of the YAML blockacceptance, and keeps the ones that pass; call it withestimate_onlyfirst, thenratchet_status. Rules are the owner's: add a rule only when the user names it and its limit.ratchet_approveis the opt-in owner gate. A mark candidate is judged by the stress rule like any other; if verify refuses its surface, the verify error is in the log.cleanup_listfinds the spurs, bumps, uneven struts, flat webs and loose bodies of the finished part and writes them as a marks file (marks.jsonin a NEW folder, with the facts of each candidate); it edits nothing and gives no verdict. Call it withestimate_onlyfirst if the part is large. Readapplicabilitybefore you trust the strut candidates (a shell or plate part gives sheet fragments). Thenratchet_runwithsourcemarks:<marks_rel>judges each mark with the rules of the YAML. A part with a loose body gets the body marks only: judge them first, then list theacc_01folder of that ratchet root.
Roles
The plugin has five sub-agents: topopt:lead-design-engineer (talks to the user, states the assumptions, reviews the result), topopt:cad-engineer (regions, shapes, geometry), topopt:fea-engineer (loads, supports, solver, run), topopt:print-engineer (can it be printed?) and topopt:finish-engineer (smooths the finished part and checks printing again; Step 5). A simple part needs none of them. Use them when a separate context helps, for example for a complex part.
A client without these sub-agents gets the same five role texts from the tool team_guide: call it with lead-design, cad, fea, print or finish (with no role it lists them). One agent can read the roles that it needs and do the work itself.
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

