CraftBot outputs made with the Fable 5 agent and rendered in Blender with parts of the cladding hidden: experiment 02 (left), experiment 06 (middle) and experiment 14 (right).
CraftBot is an architect AI agent. It reads a design brief, grounds itself in domain knowledge by ingesting documents, images and other references, and outputs Python code that defines a building procedurally. Instead of producing meshes or images, CraftBot writes scripts that construct architectural geometry in Blender. From there it can produce the industry's standard representations: floorplans, sections, elevations, BIM models, bills of quantities. It can run in a fully automated loop of code generation, execution, visual feedback and revision. CraftBot is a research project asking whether large language models can take part in architectural design when they are constrained to work through executable CAD code.
This repo holds the code, iteration steps, references and outputs of the experiments so far, so anyone can build on CraftBot by pointing their own agent at it. Python and Blender were chosen for ease of access: both are open source, maintained, widely used, and free for commercial use.
A draft paper describing the project is in papers/:
CraftBot: An LLM Interlocutor in a Code-First Approach to Architectural Design (short paper, submitted to ICCC 2026)
papers/ Draft paper PDF
experiments/ 14 experiments, one folder each
manuals/ Timber construction manuals as extracted .md summaries, with INDEX.md (the PDFs themselves are gitignored)
skills/ Distilled modelling knowledge from the experiments, one skill per folder
tools/ Shared modelling kits and harness scripts (see tools/README.md)
viewer/ Web viewer: static site plus the exported models in viewer/models/
visuals/ Figures used in this README
outputs/ Default folder for headless renders (gitignored)
.claude/ Claude Code command /run-experiment, which starts an experiment run, and the six agent definitions in .claude/agents/
.github/ GitHub Pages workflow that publishes viewer/ on every push to main
Each experiment folder follows the same layout:
input/holds everything given to the model: the prompt log (experiment_XX_prompts_chatgpt51.txt), reference images, and the shared Python geometry library (craftbot_lib.py) with an element placement template. Experiments 04, 08, 09, 11 and 13 also used a construction manual PDF; it was removed from the repository for copyright reasons, anoriginal_pdf_provenance.txtin the folder names it, and the extracted summary used during the run stays next to it.ChatGPT 5.1/holds the outputs per iteration: generated Python scripts (vXX.py) and Blender viewport screenshots of the resulting models.Fable/(eleven experiments so far) holds the same per-iteration outputs from the Fable runs, plus the design rationale document, callouts file and archived conversation. From experiment 15 on it also holds the hand-off files of the agent team (agent.md,brief.md,concept.md,sources.md,requirements.md,design_notes.md,version_notes.md, inspection and close-out reports).- Any other run folder (
Opus 5.1/,Sonnet 5/, ...) is a run of the same workflow by another model. Every subfolder exceptinput/andreferences/is a run folder, named after the model, with file names carrying its slug (experiment_NN_opus51_v01.py); the tools and the viewer discover them from the folder names. references/(some experiments) holds additional reference and annotation images used during the iteration loop.
Eight different experiment outputs shown with a fully clad model alongside their corresponding exposed timber frames.
The experiments started in November 2025 with ChatGPT 5.1 (runs through January 2026); other agents followed, with Fable runs added in August 2026, and the series is ongoing as of September 2026. Grouped by the type of reference material used:
Visual references. 01 Carport (king post truss), 02 Gothic Carport (hammer beam truss), 03 VIPP Shelter, 06 Prouvé 6x6 Demountable House
Hybrid references (images and text). 04 Construction Manual (prefabricated timber house), 05 Construction Manual Meta-Prompt, 07 Gehry Deconstruction, 08 The Segal Method, 09 How to CLT (ten-story building), 10 Staircase, 11 to 13 Hip Roof, Dormer Window and Roof Sheathing, 14 Becher Studies (a timber coaling tower refurbished as a multi-storey home, from one photograph and five manuals)
skills/ holds knowledge distilled from the experiments: principles, methods and techniques extracted from the Fable design rationale documents and the reference documents, formatted as agent skills. Each skill is a folder with a SKILL.md whose frontmatter description says when to load it. They are written to be usable outside this repo. For Claude Code, place them in your .claude\skills\ folder, or add one line to CLAUDE.md telling the agent to check skills/ and load the relevant SKILL.md files before starting an experiment. The second method works with any agent.
| Skill | Load when |
|---|---|
| working-from-reference-documents | the experiment is grounded in a construction manual or guide |
| reading-visual-references | dimensions or topology must be read off photos, drawings or plans |
| procedural-geometry | writing Blender Python that generates construction geometry |
| non-orthogonal-geometry | sloped, tilted, twisted or warped surfaces are involved |
| verifying-models | setting up or running the render-inspect-revise loop |
| structural-logic | every building, any material: bearing stacks, load paths, lateral stability, sizing without a manual, the structural review |
| timber-framing | modelling framed timber structures or reviewing them as structures |
| roof-framing-and-sheathing | modelling pitched roofs, sheathing or coverings |
| modular-grids-and-panelization | prefabrication, panels, sheet materials, CLT, multi-storey systems |
| extending-previous-models | continuing or layering onto a previous experiment's model |
| writing-design-rationale | closing out a run, before archiving the transcript |
| running-craftbot-experiment | running a whole experiment in this repo, start to finish: the agent team, its hand-off files, phases, rules and the repo mechanics |
The interpenetration check the skills refer to is tools/check_overlaps.py, the SAT test from the Fable render harnesses. It runs on any saved .blend. Its blind spot, geometry that is absent or touches nothing, is covered by tools/check_contacts.py and by the Inspector agent below.
From experiment 15 on, a run is done by six Claude Code sub-agents defined in .claude/agents/, one file each. The split follows a context audit of the experiment 14 run: of its 455 k tokens of context, 38 percent were render screenshots, 22 percent manuals and 8 percent the tools and the script itself, all read by one agent that then needed each of them only as a few lines of decisions. Each agent file is that agent's system prompt: its role, inputs, outputs, procedure and rules, and the list of skills in skills/ it reads at startup. The domain knowledge stays in the skills, so there is one copy of every rule.
+--------+
| USER |
+--------+
| ^
| :.. rationale, final report
v :
+==================+
| CRAFTBOT |
+==================+
+--------------------------------------+ ^ | ^ ^ +---------------------------------+
| : | : : |
| concept, requirements : | : : close-out reports |
| ....................................... | : .................................. |
| : | : : |
| : | :.. scripts, version notes : |
v : v : : v
+------------+ +------------+ +------------+
| DESIGNER |------------------+ | BUILDER |.......................>| RUNNER |
+------------+ | +------------+ version rendered +------------+
| ^ ^ | | ^ ^ :
| : : | | : : :
| : : comparison round | | : ..................................
| : ................... | | : close-out report
| : : | | :
| :.. sources, : | | :
| : figure snippets : | | :.. inspection report
| : : +-----------+ | :
v : : v v :
+------------+ : +------------+
| RESEARCHER | ............| INSPECTOR |
+------------+ +------------+
-------> spawns .......> reports back, with the hand-off it delivers
| Agent | Does | Reads | Writes |
|---|---|---|---|
| CraftBot | orchestrates, owns the brief and the scope, compiles the rationale, reports to the user; the session that receives /run-experiment plays this role |
the invocation, input/, every hand-off file |
brief.md, the rationale, the callouts, the prompt file |
| Designer | spatial and construction concept, requirements checklist, photo comparison round, structural review | brief.md, input/, references/, sources.md |
concept.md, requirements.md, design_notes.md |
| Researcher | searches the manuals for what the concept needs, crops figure snippets, asks the Designer before searching online | concept.md, manuals/ |
sources.md, snippets in references/ |
| Builder | implements the concept as versioned scripts, runs the render and check loop, triages the pair families, patches | the hand-off files, tools/API.md, inspection and close-out reports |
the scripts, views_<slug>.py, version_notes.md |
| Inspector | looks at one version's renders against the requirements and the reference, reports absence and misplacement; images only | the PNGs, the view legend, requirements.md |
inspection_vXX.md |
| Runner | closes out every version and the run with tools/closeout.py, archives the transcript last |
the close-out reports | closeout_vXX.md, closeout_run.md |
Rules that hold the team together: the spawning tree is flat under CraftBot (the Designer may spawn Researchers and Inspectors, the Builder may spawn Inspectors); CraftBot first detects which model runs it and names the run folder after it (Fable/, Opus 5.1/, ...), so the team is not tied to one model; every hand-off is a file in that run folder, so any agent can be restarted from disk; when the Builder cannot meet a requirement the Designer rewrites it, and when that changes scope CraftBot decides and records it in brief.md, so the Builder never narrows the brief on its own. The workflow itself, phases, files, rules and mechanics, is skills/running-craftbot-experiment/SKILL.md.
To use the team, run /run-experiment NN [brief] in Claude Code from the repo root, or claude --agent craftbot for a whole session in the orchestrator role. The agents load from .claude/agents/ (Claude Code's project agent folder); they read the repo skills by path, so nothing needs copying into .claude/skills/.
tools/ is the code side of the same distillation: the geometry helpers the Fable runs converged on, extracted into importable modules so a new experiment starts from them instead of deriving them again. tools/README.md lists every module and function. In short:
| Module | What it gives an experiment script |
|---|---|
craftbot_lib.py (V 2.0) |
place_element as before, plus nested collections, box from corner coordinates, convex prisms |
geometry2d.py |
polygon clipping, member spacing, sheet tiling with holes, walls minus openings (pure Python, unit-tested) |
planes.py |
half-spaces, plane intersections, Roof planes, members built long and clipped (rafters, hips, braces, plates) |
ruled.py |
bilinear surfaces for warped and leaning walls and roofs |
framing.py |
stud walls with openings, cladding, decks, boards, solid walls with openings, roof layers, stairs |
sheathing.py |
mitred board sheathing on planar facets, hip dropping, protrusion check |
render_views.py |
headless renders with outlines, colours, hidden layers, close-ups and section cuts, plus the overlap check, the pair families and the contact check |
check_contacts.py, triage.py |
members that touch nothing; penetrating pairs grouped by cause |
api_card.py, API.md |
the generated one-page card of the kits that the Builder reads instead of the modules |
closeout.py |
one command to close out a version or a run: viewer export, layers, index, view set, screenshot, rationale and callout checks, transcript |
experiment_template.py |
starting point for a new experiment script |
export_members.py, cutlist.py, drafting.py, hidden_lines.py, vector_pdf.py, qr_code.py |
the fabrication kit: A2 plans, sections, axonometrics and glue-up templates as vector PDF, plus a cut list and stick order, for building a finished model by hand from stock sticks (API_FABRICATION.md) |
Point your agent at both folders in its project instructions, one line each, as described under Skills above.
Drafting fabrication plans is separate from an experiment run. skills/draft-fabrication-plans/SKILL.md is the procedure, invoked with /draft-fabrication-plans NN in Claude Code or $draft-fabrication-plans NN in Codex; Each set lives in experiments/<NN>/fabrication/<Run>/, one subfolder per run folder, so the models of one experiment stay apart; experiments/16_Expressive_Structure/fabrication/GPT-6/ is the worked example, a 1:15 stick model on 22 A2 sheets.
tools/run_experiment_headless.py executes an experiment script without opening the Blender GUI. It runs the script in background Blender, frames the generated geometry with an orthographic camera so the whole model is always visible, renders four orbit views with the Workbench engine (close to solid-mode viewport screenshots), and saves the resulting .blend file:
blender --background --python tools/run_experiment_headless.py -- <experiment.py> <lib_dir> [out_dir]
Cycles presentation rendering uses the stored material, light, camera and
quality defaults. Full controls and the line-compositing workflow are documented
in tools/README.md.
blender --background --factory-startup --python-exit-code 1 --python tools/render_views.py -- \
experiments/14_Becher_Studies_Amalgamated_Structure/Fable/experiment_14_fable_v09.py \
"outputs/$(date +%Y-%m-%d_%H%M%S)_cycles/cycles" \
--style cycles --views tools/views_cycles_layer_series.py --no-check<experiment.py>is the path to the experiment script to execute.<lib_dir>is the folder containingcraftbot_lib.py(the experiment'sinput/folder).[out_dir]is an optional output folder forview_1..4.pngandmodel.blend; it defaults tooutputs/<experiment_name>/(gitignored).
Example (Windows):
"C:\Program Files\Blender Foundation\Blender 5.1\blender.exe" --background --python tools\run_experiment_headless.py -- "experiments\01_Carport_Assembly_Blender_Python\ChatGPT 5.1\experiment_01_chatgpt51_v01.py" "experiments\01_Carport_Assembly_Blender_Python\input"
CraftBot viewer interface: exp 06 Prouvé Cabanon (left, MONO 1 mode) and exp 04 Construction Manual (middle, BLUEPRINT mode) with callouts linking to a design rationale document.
A WebGL viewer for all generated models lives in viewer/ and deploys to GitHub Pages (live link).
Since .blend files are not stored in the repo, each experiment script is executed once in headless Blender and exported to a compact JSON model format in viewer/models/, typically 10 to 20 times smaller than the corresponding .blend. The viewer is a static site: vanilla ES modules with three.js from a pinned CDN, no build step.
Features:
- A model and agent picker with an iteration slider. The camera stays put when switching iterations and only the elements that changed animate in.
- Seven render styles: plaster, solid, random, mono, wireframe, blueprint and 1-bit dither. Mono, wireframe and dither each have a light and a dark mode; clicking random again re-rolls its palette. The shaded styles carry screen-space ambient occlusion and thin black outlines to match the Blender Workbench renders, glazing is drawn semi-transparent, and the GUI re-themes itself with the active style.
- GPU-driven entry animations: elements drop, rise or assemble in construction order or layer by layer.
- Knolling: every element rearranged onto a grid, either flat (each piece on its widest face, one band of shelves per layer, seen from the top) or stacked (identical pieces bundled palette-style, seen in axo). Elements lift, turn and settle between any two states, stacks building from the bottom course up, and the camera glides to a view framed on the result while a slider scrubs the move. Hover and selection keep working on the arranged pieces, and the callouts toggle labels each layer band with its count and length range. The layouts are solved in the browser per iteration, with a progress line while they compute.
- Eight layer toggles (frame, exterior and interior cladding, interior boards, roof, floors, foundations, fixtures) with an always-visible material takeoff (length, volume, weight).
- Section planes on three axes, and a Blender-style navigation cube with a 4-view mode in which the three fixed views share one wheel zoom.
- Hover and click element inspection: a name and layer tag at the cursor, picking that works through section cuts, and the element's true oriented length, width and thickness drawn in the active style.
- For runs that ship one (the Fable runs and the agent-team runs), the design rationale document in a panel under the view cube, with callouts on the model (tags with leader lines, authored per run in
experiment_NN_<slug>_callouts.json) that link groups of elements to passages of the document. Click a tag to jump to the passage; hover a heading to see its callouts. - On phones the GUI starts collapsed with one section open at a time.
Run locally:
python -m http.server -d viewer 8123
Re-export models after adding or changing experiment scripts (uses headless Blender):
python tools/export_all_models.py # everything
python tools/export_all_models.py --only 08 # one experiment / pattern
python tools/export_all_models.py --index-only # no Blender: index + rationale docs only
python tools/layers.py --audit # every element name family -> viewer layer
python tools/layers.py --bake # re-bake layers into viewer/models/*.json
python tools/callouts.py --check # validate the rationale callout files
python tools/callouts.py --names 04 # element name patterns, for authoring callouts
python tools/closeout.py version 14 v09 # all of the above for one version, plus view set and viewer screenshot
python tools/closeout.py run 14 --session-id ID # run-level checks, then the transcript copy
The index step also copies each run's experiments/<exp>/<Agent>/experiment_NN_<slug>_design_rationale.md to viewer/models/<exp>/<slug>_rationale.md so the viewer can show it. The exporter re-winds meshes to face outward (some generators emit inside-out prisms); the viewer applies the same fix when parsing older exports.
tools/capture_knoll.mjs records the knolling sequence to an mp4: the model, every element rearranged into stacked bundles, then flat on its widest face, then back to the model on the opening framing so the clip loops. It drives the real viewer in headless Chrome over the DevTools protocol, so what you get is the page itself, GUI included. --sequence names the arrangements to visit and in what order, so a clip can open on the stacks and assemble from there instead. The camera flies to each arrangement in turn; --lock pins one framing for the whole clip so only the geometry moves, and the arrangements that do not fit it run off the edges of the frame.
ffmpeg has to be installed and on PATH, and Chrome at the usual install path or in CRAFTBOT_CHROME. Both are checked before a frame is captured. The script starts and stops the static server itself; nothing else needs to be running.
node tools/capture_knoll.mjs # every default: exp 08 Fable v06, mono, 1344x1080, 30 fps, 12.6 s
node tools/capture_knoll.mjs --style blueprint --stacked-view top-front-right
node tools/capture_knoll.mjs --model 06_Prouve_Cabanon_Blender_Python/fable_v07.json --no-loop
node tools/capture_knoll.mjs --sequence stacked,model --no-loop --stacked-view axo # stacks assembling into the house
node tools/capture_knoll.mjs --sequence stacked,model --no-loop --lock model --zoom 1.15 --transition 6 # camera never moves
node tools/capture_knoll.mjs --help
| Option | Default | What it does |
|---|---|---|
--model REL |
exp 08 Fable v06 | model file under viewer/models/ |
--style NAME |
mono |
plaster, solid, random, mono, wireframe, blueprint, dither |
--mode N |
0 |
style variant: 0 light, 1 dark for mono, wireframe and dither, which have two each; the other styles have one, and on random each step re-rolls the palette |
--model-view NAME |
axo |
direction the model beats are framed from |
--stacked-view NAME |
top-front |
direction the stacked arrangement is framed from |
--flat-view NAME |
top |
direction the flat sheet is framed from |
--sequence LIST |
model,stacked,flat |
arrangements to visit, in order; the clip opens on the first one, already settled, and each later one is one transition. model, flat, stacked |
--lock NAME |
off | hold that arrangement's framing for the whole clip, so the camera never moves and the other arrangements pass through the frame, cropped as they are |
--zoom N |
1 |
magnify the framing: 1.15 draws it 15 % bigger than the fit |
--transition S |
3 |
seconds one arrangement takes to become the next; the camera flight alongside it stretches to match |
--hold S |
1.0 |
seconds held on each arrangement; with --loop the opening beat holds 60 % of it, so the seam does not stall |
--no-loop |
off | stop on the last arrangement instead of returning to the one the clip opened on |
--fps N |
30 |
frames per second |
--width N, --height N |
1344, 1080 |
frame size in CSS px |
--scale N |
2 |
pixel ratio to render at; frames are downsampled to the frame size, which antialiases the outlines |
--out FILE |
outputs/knolling_<model>_<timestamp>.mp4 |
dated, so runs never overwrite each other |
--frames DIR, --keep-frames |
a temp folder, deleted after stitching | the PNG frames |
--port N |
8123 |
port for the static server |
View names are the navigation cube's own regions: top, bottom, front, back, left, right, the edges and corners between them (top-front, top-front-right, ...) and axo, the viewer's three-quarter view. Under --lock only the locked arrangement's view name is read, since it is the only framing the clip uses. A transition runs the 3 s main.js gives it unless --transition stretches it, which the driver does by wrapping anims.startKnoll rather than patching the viewer; --zoom works the same way, riding on the bounds handed to views.frameTo, because the fit is to their projected extent.
The capture is frame-exact rather than a real-time screen grab: performance.now() is frozen when the page loads and stepped by exactly 1/fps per frame, and both clocks the viewer animates on read it. Recording the animation as it plays does not work, since headless Chrome will not paint a timed animation faster than it can screenshot it. The driver reaches the viewer's arrangements and camera through a ?debug=1 hook in main.js that puts them on window.craftbot; nothing is exposed without that parameter.
Deployment: pushes to main publish viewer/ via .github/workflows/pages.yml. One-time repo setting: Settings, Pages, Source: GitHub Actions.
- Original ChatGPT conversation logs are not yet included; they may be added later.
- This README will be updated as the research progresses.
The project author is Luka Piškorec, previously a lecturer at Aalto University and researcher at ETH Zürich. He is a co-founder of TEN Studio (Zürich and Belgrade) and {protocell:labs} (Helsinki), practices that work at the intersection of architecture, design, digital art and research.
The project is part of the art-ai-fact initiative funded by Aalto University in 2025-2026.


