The standard loop for letting an agent write
The order of work when an agent edits .muro files.
model_summary → layers → write_layer → check ──errors──→ fix, write_layer again
│
└──green──→ doors / light / site confirm the consequences
The shape is the same as working in git: grasp it, read it, write it, pass the gatekeeper, confirm the consequences. Agents go wrong in one of two ways — writing without reading, or moving on without passing the gatekeeper.
Registering the server is not covered here; it is on Register the MCP server with a client. Each tool's arguments and return shape are on Read / Write / Verify / Ask.
Every output below was actually run. Absolute paths are abbreviated to <dir>/.
The subject
A single-storey pair of rooms. There is one entrance door, and nothing at all between the two rooms.
koyu 1.0
name 平屋
unit mm
grid X 0 3600 7200
grid Y 0 4500
level L1 0 h:2400 slab:150
import ./L1.muro
space /L1/a room X1..X2 Y1..Y2 name:居室A
space /L1/b room X2..X3 Y1..Y2 name:居室B
space /out exterior name:外部
boundary /L1/b /out t:150 spec:EW edge:S
door w:900 h:2100 name:玄関
0. Commit
git add . && git commit -m "before the agent edits"
write_layer replaces a layer whole and has no undo. Skip this and there is no way back.
1. model_summary — grasp the building
One call returns the layer composition, the levels, the zones, the areas and the check counts. This is what decides which file to read next.
{
"name": "平屋",
"unit": "mm",
"layers": [
"<dir>/L1.muro",
"<dir>/main.muro"
],
"levels": [
{
"name": "L1",
"z": 0,
"h": 2400,
"slab": 150
}
],
"spaces": 3,
"boundaries": 2,
"zones": [],
"assets": [],
"totalFloorM2": 32.4,
"semiOutdoorM2": 0,
"floorsM2": {
"L1": {
"rooms": 2,
"subtotalM2": 32.4
}
},
"byUseM2": {
"(unspecified)": 32.4
},
"check": {
"errors": 0,
"warnings": 0
}
}
(The one-line hint field is omitted.)
0 / 0 on check, and the building is still unusable. There is no way out of room A. The summary only says that what is written does not contradict itself.
boundaries counts the composed model, including the walls derived by default. It does not match the number of boundary lines in the source.
2. layers — read the original
Every layer taking part in the composition comes back in full as {file, source}. import is followed automatically, and a file nobody imports is not returned.
What the agent must read before editing is this, not the summary. The summary carries structure only — spelling, ordering and comments are not in it.
3. write_layer — write
Three arguments: the entry (file), the target layer (layer), and the whole text (content). Not a diff.
Hanging a door between room A and room B therefore means rewriting L1.muro in full.
space /L1/a room X1..X2 Y1..Y2 name:居室A
space /L1/b room X2..X3 Y1..Y2 name:居室B
space /out exterior name:外部
boundary /L1/a /L1/b t:120 spec:PW
door w:800 h:2000 name:D-中扉
boundary /L1/b /out t:150 spec:EW edge:S
door w:900 h:2100 name:玄関
The return carries the check result from immediately after the write. Editing and verifying cost one round trip together.
{
"written": "<dir>/L1.muro",
"ok": true,
"spaces": 3,
"errors": [],
"warnings": []
}
The gatekeeper stands before the write
write_layer composes the replacement virtually first. Content that cannot be parsed never touches the original.
{
"written": false,
"target": "<dir>/rooms.muro",
"ok": false,
"parseError": "<dir>/rooms.muro:line 1: Undefined grid line name: X9"
}
Content that parses but fails check is written. That is deliberate: it lets an edit that spans several layers proceed in steps. written carries the path and ok is false. Fix it on the next call.
{
"written": "<dir>/rooms.muro",
"ok": false,
"spaces": 2,
"errors": [
"<dir>/rooms.muro:line 3: References an undefined space: /L1/c"
],
"warnings": []
}
The restrictions on where it may write, and its atomicity, are on Write — write_layer / new_uids. When creating a new layer, put the added import line in the same unit of work — a file nobody imports never joins the composition, so its contents are never checked.
4. check — pass the gatekeeper
It rides along on the write_layer return, so a green result moves straight on. An error means fix it and write again. A workflow that walks past this point produces drawings from a broken building.
The diagnostics array on the return is the structured, coded form. Human-readable check output carries no codes, but the MCP return has them from the start. The table from code to fix is the diagnostic code index.
5. Confirm the consequences
This is the step people skip. check looks at neither circulation nor daylight nor the site. That an edit had the intended consequence has to be established by a separate question.
Before the edit, room A could not reach the outside.
{
"unreachable": true
}
After one door, the same question answers:
{
"doors": 2,
"path": [
"/L1/a",
"/L1/b",
"/out"
]
}
And validate keeps returning architectural findings while check stays green.
{
"findings": [
{
"rule": "envelope.gap",
"level": "caution",
"message": "Perimeter not faced by any envelope: /L1/a — S 3600mm / N 3600mm / W 4500mm (11700mm over 3 run(s)). Write a boundary to the exterior",
"line": 1,
"file": "<dir>/L1.muro",
"path": [
"/L1/a"
]
},
{
"rule": "envelope.gap",
"level": "caution",
"message": "Perimeter not faced by any envelope: /L1/b — E 4500mm / N 3600mm (8100mm over 2 run(s)). Write a boundary to the exterior",
"line": 2,
"file": "<dir>/L1.muro",
"path": [
"/L1/b"
]
}
],
"violations": 0,
"cautions": 2
}
Not one exterior wall has been written. check never mentioned it — boundaries against a space with no region are not derived.
Which question to ask is decided by what was edited.
| What changed | The question to ask |
|---|---|
| Partitions, doors | doors — reachability and door count |
| Windows, room types | light — floor area and effective window area |
| Regions, levels | site — coverage and floor-area ratios |
| Anything at all | validate — the architectural findings |
The rules to hand the agent
Put these in the instructions and the accidents mostly stop.
- Read with
layersbefore writing. Writing from the summary loses spelling and comments. write_layeris a whole-file replacement. Thecontentit sends must be the complete text of the edited file.- When
ok: falsecomes back, the only next action is to fix it and write again. - Never claim it works because it is green. A building with no doors is sealed and
checkis happy. - Nothing mints a
uidon its own. Callnew_uidsonly when a space must be pointed at across renames, and runcheckafterwards. - Form is generated. A plan cannot be "written".
plan_svgderives and returns it.
Related
- Register the MCP server with a client — getting connected
- Drive the MCP server by hand over stdio — take the agent out and inspect behaviour
- Lay measurements over the plan — overriding without rewriting the original
- koyu-mcp — statelessness and the twelve tools
- The scope of the promise — what a green
checkmeans