Skip to main content
Glama

OpenTakeoff

The measurement engine for building plans—built so an AI agent can drive it, and so an estimator wants to.

A takeoff is the act of measuring quantities off a construction drawing. OpenTakeoff does it two ways over one engine: 53 MCP tools for an agent, and a browser canvas for a person. Agents and people share the takeoff document and quantity calculations. Each sheet carries its calibration; measurements carry geometry, method and authorship. Recalibration updates quantities together, incompatible imports report scale conflicts, and agent measurements carry an explicit review status. See the Phase 1 test guide.

License: Apache 2.0 Live demo MCP registry npm Benchmark OpenArena Sponsor

For agents · Try the canvas · The engine's contract · For the person at the canvas · The data layer · Research · Fork it · Contribute

The two manuals: agent manual · user manual

Protocol work: Takeoff Protocol draft and compatibility status—formalizing existing records; current takeoff behavior is unchanged.

Read this in: 日本語 · 한국어 · 简体中文

Watch it: an autonomous agent runs a takeoff, live, no cuts (2:47) · hospital finish plan → report in about a minute (1:14) · canvas walkthrough (1:10) · One-Click Area (0:51)

One-Click Area is temporarily gated. The flood engine is being re-validated against a wider plan corpus. Until that finishes the One-Click tool is off the canvas rail (O reports the gate) and the one_click / detect_rooms MCP verbs are not registered (a default build ships 53 tools). Trace rooms with Area (A) in the canvas and measure_polygon over MCP; every other tool, sweep and derivation is unchanged. A build lifts the gate with VITE_ONE_CLICK=1 (canvas) / OPENTAKEOFF_ONE_CLICK=1 (server). Sections and videos below that show One-Click describe the engine as it returns — see docs/design/ONE_CLICK_GATE.md.


Start here

You are

Go here

An estimator with a bid due

Open the canvas—drag in a plan, no account, nothing uploads. The user manual gets you from a blank tab to an exported takeoff in five minutes, and its working order is the sequence to run on a real bid set.

An AI agent—or the person wiring one up

npx -y opentakeoff-mcp, then the agent manual: the operating model, the standard finish every takeoff ends with, what the engine refuses to guess, and why. Tool-by-tool reference is mcp/README.md.

A developer building on the engine

AGENTS.md is the repo map and the ship discipline; FEATURES.md maps every capability to the code that does it.

A crew that wants its own copy

Fork it—your own instance on your own URL in a few minutes, Apache-2.0, nothing phones home. Same path if you're going to send a pull request.

Windows, macOS, Linux — all of it

OpenTakeoff is a client-only browser app, so the canvas runs the same on Windows, macOS, ChromeOS and Linux in any current Chrome, Edge, Firefox or Safari. Nothing installs, nothing uploads, and no feature is gated on an operating system.

  • Shortcuts are platform-aware. The app labels modifiers for the keyboard in front of you — Ctrl / Alt / Shift on Windows and Linux, / / on a Mac — and the handlers have always treated and Ctrl as the same key. Press ? in the canvas for the current list.

  • The MCP server is tested on Windows. npx -y opentakeoff-mcp runs on Windows, macOS and Linux, and CI runs the full MCP suite — typecheck, tests, build and the packaged smoke test — on windows-latest as well as ubuntu-latest on every change.

  • Optional extras. The bundled capture server is stdlib Python 3 and runs anywhere Python does — on Windows invoke it with python capture\capture_server.py selftest (or the py launcher) rather than python3. Neither it nor the optional server/ AI sandbox is needed to use the canvas.

  • Locked-down enterprise fleets (MSIX packaging, Windows Sandbox, Intune silent deploy) are tracked in #226 and not yet built.

Related MCP server: quote-mcp-server

What this is

Measuring quantities off a plan is the input to every construction bid—how much floor, how much wall, how many fixtures, at what scale, on which sheet. It happens thousands of times a day. Until OpenTakeoff there was no open-source takeoff engine at all, web-based or otherwise, and nothing an autonomous agent could call.

OpenTakeoff is that engine, with two front ends sharing geometry and quantity modules:

  • Review cleanup—agents can edit annotation text with undo while preserving geometry and review; RFI-linked notes require browser review. Scope warnings distinguish numeric edge residue from real small overlaps.

  • A stdio MCP servernpx -y opentakeoff-mcp, 53 tools, on the official MCP registry. An agent opens a plan, reads the title block, sets the scale, traces the rooms to their wall faces (the flood engine stays gated), checks its own work on a rendered overlay, and hands back a marked-up planset PDF.

  • A browser canvas—no backend, no account, no upload. An estimator drags in a plan set and traces it, using One-Click room detection, CAD hatches, roll-goods seam layout, a materials buy list, and exports.

Neither is a wrapper around the other. The MCP server imports shared web modules, so quantity math and takeoff records are compatible. Browser and MCP room-detection paths currently differ; shared code does not guarantee identical boundaries on every plan. One-Click remains gated while that boundary is re-validated.

Provenance is the load-bearing part. Every shape records the scale it was measured at, the method that produced it (vector flood, raster trace, hand-drawn, agent-proposed), whether a human corrected it, and the machine's original boundary frozen beside the correction. This includes agent shapes drawn with manual measurement tools; the actor and drawing method are separate fields. Downstream, that's an audit trail a PM can read. Upstream, it's a labeled (geometry → finish) pair—the training signal takeoff models have never had at scale. That second use is not a side effect; see the data layer.

Recently shipped

  • Tracing rules an agent actually reads—the room-boundary conventions an estimator uses (innermost wall face, door openings crossed on the wall centerline, columns and chases wrapped, never a hatch edge or a door leaf, a tight overlay check per ring) now ship inside the server: takeoff://wiki/workflows, the initialize instructions and the measure_polygon description. On a task with no conventions in it, one unprompted run went from 0 of 6 rooms with door notches to 6 of 6 (#423, MCP 0.9.84)

  • A plan set with reference rings, and blind runs against it—three public floor plans (a VA healthcare finish plan, a VA clinic floor plan, a city public-domain ADU) with estimator-trace references, a scorer profile for concave rooms and finish splits, and published blind agent runs with frozen exports, scores and overlays. The runs found defects in the references first; the references are v2 and still unreviewed by a person, and the open estimator questions are listed (#421, #422, evals/mcp-workflow-bench/plan-set/)

  • The human review loop, start to finish—import an agent's takeoff, correct a ring, accept the batch, export, and reopen the archive on a clean machine with geometry and review state intact, with screenshots of the real app in the user manual (#424)

  • The Takeoff Protocol draft, one shared wiki, and a scripted workflow benchmark—the existing record fields inventoried and formalized as draft schemas with an executable compatibility matrix (10 conform, 1 needs an adapter, 5 unsupported and named); one wiki for people and agents, packaged as MCP resources and checked against source in CI; and a workflow benchmark that drives the built server flat and staged, scores geometry separately from totals, and reopens the export in a fresh process (#406#419, roadmap)

  • Stitched sheets—a floor split across a match line becomes one working surface; a room that crosses the seam traces as one shape, One-Click included (#161)

  • PDF layer roles—CAD-exported sheets state what their ink is, so One-Click reads the layer tree instead of inferring boundaries from hatch, with a Layers panel on the canvas and scored corpus IoU (#85)

  • The sheet graph—an agent asks "what finish is in room 134, and how do you know" and gets the schedule row with a citation per cell, across continuation sheets, rotated headers, and multi-building keys: sheet_graph / resolve_tag / find_schedule (#87)

  • Roll goods—opt a condition into broadloom or sheet material and the engine figures the seams: lanes, multi-roll splits, cuts drawn to scale over their rooms in cutting order, a to-scale roll diagram with drag-to-reorder, and order footage beside the measured quantities (#136)

  • Transitions, at the canvas⟂ Transitions… in the Takeoffs panel derives the line where two finishes meet from rooms you already measured; derive_transitions does the same over MCP. Flood-traced rooms don't share edges, so what's actually there is proximity in two flavors that mean different things: finishes changing inside one open space commit as a butt joint, while rooms parallel across a partition come back withheld as questions—the transition is a threshold in a doorway, and nothing in a trace record says where the doorway is. Committing 34 LF of threshold because two rooms share 34 LF of wall would be a wrong bid with a machine's confidence behind it (#202)

  • symbol_sweep—every instance of a repeated symbol from one marqueed example, crossing scales only by a stated ratio, never a searched one

  • mark_verdict / delete_verdict—an agent signs its own work as a graphite AGENT diamond; only a human hand mints the green APPROVED seal

  • One-Click accuracy wave—face extraction and gap tolerance from RFC #60 (contributed by @knmurphy), hairline runs shadowing a heavier wall classified as annotation rather than boundary, and an in-swing door's sector taken behind the leaf instead of the arc

  • Voice takeoff (gated off the toolbar by default; VITE_COMMAND_BOX=1 builds it back)—hold M and say carpet one, waste seven; recognition is whisper-tiny.en in WebAssembly on your machine, audio never leaves the browser (docs/VOICE.md)

Full history: CHANGELOG.md · every capability mapped to its code: FEATURES.md


For agents — start here

Point any stdio MCP client at the published package. Node 20+, no clone, no build:

{
  "mcpServers": {
    "opentakeoff": {
      "command": "npx",
      "args": ["-y", "opentakeoff-mcp"]
    }
  }
}

If you carry a GitHub identity, fork the repository as well: that's where an engine change you make gets tested—every pull request from a fork runs the full CI with a read-only token—and tested pull requests from agents merge under the same bar as anyone else's. The contributor path is the same one.

Claude Code: claude mcp add opentakeoff -- npx -y opentakeoff-mcp. Claude Desktop users can double-click the opentakeoff-mcp.mcpb bundle from the latest release instead—it excludes the optional native canvas on purpose, so every JSON tool works everywhere and the rendering surfaces (view_sheet, the sheet-image resource) say exactly what's missing where they can't run. Docker and a local clone are both supported: mcp/README.md.

A real run (3× speed): the agent takes off patient rooms 161–163 on a federal finish plan, exporting after each commit. Every shape lands in the app as a dashed pencil proposal and becomes ink only when the operator clicks Accept. The full run, live and uncut, is on YouTube (2:47).

The tools

Group

Tools

Open and orient

load_plan · sheet_info · sheet_context · get_sheet_vectors · read_sheet_text · find_text · view_sheet

Scale

set_scale

Measure

one_click · detect_rooms · measure_polygon · cut_out · measure_line · measure_surface · place_count

Repeat and derive

symbol_sweep · sweep_schedule_row · derive_base · derive_transitions · apply_rules

Read the drawing set

sheet_graph · resolve_tag · find_schedule

Edit and audit

list_shapes · edit_shape · edit_condition · edit_materials · duplicate_condition · split_condition · delete_shape · undo_last

Mark and sign

annotate · list_annotations · link_annotation · mark_verdict · delete_verdict

Ask

create_rfi · list_rfis · resolve_rfi · delete_rfi

Hand off

takeoff_summary · export_takeoff · export_report · export_marked_pdf · export_dxf · import_takeoff

Plus browsable sheet resources (takeoff://sheets) so an agent can see the working set, not only act on it. Multi-document sessions are first-class: a bid set is plans plus schedule plus addenda, and load_plan --merge adds a document without disturbing existing scales, conditions, or shapes—the sheet graph then spans the whole set, so a room tag on one file resolves to a schedule row in another. edit_condition reaches the waste %, the ×N multiplier, and roll_setup, so an agent's takeoff doesn't ship with net === gross.

The agent's manual is docs/AGENT_GUIDE.md—the counterpart to the estimator's: the operating model in six facts, the standard finish every takeoff ends with, the withheld-is-the-answer doctrine, what has no agent verb and why, and a refusal-to-next-move table. Tool-by-tool reference: mcp/README.md. The same surface in prose, with the sheet-graph and sweep behavior in depth: docs/MCP.md.

The contract that makes it drivable

Most measurement APIs are hostile to an agent because they let it be confidently wrong. These are the rules that make this one safe to hand a model, and why each one exists:

  1. One coordinate frame, stated everywhere. Image pixels at render scale 2.0—PDF points × 2, origin top-left, y down, the browser canvas's native space. Every sheet payload carries dims in both px and pt. No tool takes a coordinate in units it has to guess.

  2. Scale is a gate, not a default. The drawn scale note is read off the sheet but never applied silently; adopting it is always an explicit set_scale. Measuring an unscaled sheet refuses. Pixels × a wrong scale² is every number wrong at once, so the engine would rather stop than guess. Disagreeing scale notes inside a measured region raise a warning rather than a silent pick.

  3. The engine traces; the model doesn't invent. one_click returns the ring the wall network produced from a seed point you name. A model cannot hand back a polygon it imagined and have it counted. While One-Click is gated, a model draws the ring itself with measure_polygon, and the packaged rules say where it must sit: on the innermost wall-face strokes it read with get_sheet_vectors, doors crossed on the wall centerline, checked on a tight overlay before the next room. How well unprompted agents follow that is measured, not assumed: see the plan set.

  4. Every record carries how it was made. Method, seed point, whether hatch filtering engaged, whether it came off scan pixels, confidence factors, and the machine's original ring if a human later moves it.

  5. Agent work is pencil until a person inks it. Exports land in the canvas as dashed proposals. mark_verdict lets an agent sign its own work as a graphite AGENT diamond; the green APPROVED seal has exactly one code path and it is the toolbar button under a human hand. No MCP call, no import, mints one.

  6. The deliverable is a marked-up planset, not JSON. export_marked_pdf burns the work into the drawings as drawn—condition colors, hatches, quantity chips, count markers—behind a legend cover with totals and a tally of how much of the set a person has actually reviewed. A takeoff nobody can check is not a takeoff.

  7. Refusals are actionable strings. "That space isn't enclosed on the plan linework—the fill spilled" tells a model what to do next. A silent zero doesn't. Tools that can't answer withhold with a stated reason rather than returning a plausible number.

Prove it — OpenTakeoff Academy

aec.kentucky-ai.com is a standalone open benchmark and certification arena for agents that do takeoff. Bring any model and your own harness; you are scored on operating a real takeoff tool against geometry you don't control—a wrong calibration yields a wrong area—not on emitting a plausible-looking number. Runs emit a signed bundle with full provenance of every tool call, scoring is against held-out ground truth and a human Senior Estimator baseline, and clearing a tier earns a credential that's independently verifiable. The Certified path drives this engine (opentakeoff-mcp) behind the task tools. Repo: Kentucky-ai/opentakeoff-academy.


For the person at the canvas

The agent path exists because the human path is real. Everything below is the production measuring engine carved out of a commercial Division 9 estimating system—not a demo reimplementation.

cd web
npm install
npm run dev        # http://localhost:5173

Or open the live demo. Drag in demo/sample-plan.pdf, accept the detected scale, choose a condition, press A (Area) and click the room's corners. (One-Click Area is temporarily gated — see the note at the top.) Open Report for the breakdown and the exports. That whole loop on video: walkthrough (1:10) · One-Click Area (0:51). The complete zero-to-exported walkthrough is the user manual.

What a real bid looks like on it, in the order an estimator works one:

  1. Drag in the whole .zip off the bid platform—plans, finish schedule, addenda.

  2. Set the scale on every sheet you'll measure, and check a dimension (K) on each. Ten seconds a sheet, and it's the only mistake that gets every number at once.

  3. Pull your conditions off the architect's finish schedule instead of typing them, and set waste and materials before you trace.

  4. Stitch anything split at a match line, align it, and only then start measuring.

  5. Trace the floors room by room — Area (A) while One-Click is gated. Derive base and transitions off the rooms you just traced rather than measuring them a second time—and read what the derivation reports and never counts, because those are doorway thresholds you still owe.

  6. Walk the set and look at what landed, fix with the grips, save a revision.

  7. Export both: the Report for pricing, the Marked set PDF for whoever has to check you.

The full version of that sequence, with the section for each step, is the manual's working order. What every term above means is in its glossary.

Open anything, instantly

A plan PDF, an image (scan, screenshot, photo), or a whole .zip plan set straight off a bid platform. Zips are unpacked and images wrapped to PDF in your browser—multi-page, multi-file, up to 4 sheets side-by-side, with hostile-archive guards so a malformed zip fails cleanly instead of ballooning the tab. No upload step, no conversion service, no account.

A real measuring engine

One-Click Area is the headline — temporarily gated while the flood engine is re-validated (see the note at the top); this is what it does when it is on: click inside a room, the linework bounds a flood fill, the polygon traces itself, the vertices snap to true corners. Hatching and poché don't fool it—tile grids, plank lines, and section fills classify as pattern rather than wall, and the escalation is conservative enough that a misread can never come out worse than the strict fill. Scanned sheets work too: with no vector linework the engine reads rendered pixels—adaptive thresholding, polarity detection for blueprint negatives, a gap-bridging pass for faded ink—and badges the result so you verify the edges before committing. On CAD exports that publish a layer tree, One-Click reads the declared roles instead of inferring them.

Plus the full manual kit—Area, Rectangle, Linear, Curved Line, Surface Area (walls), Count, and Cut Out deducts—and a Zone check that answers "what's in this wing?" without touching the takeoff.

⟂ Transitions derives the line where two finishes meet, from rooms you already measured. Finishes changing inside one open space commit as a dashed butt-joint run you accept; rooms parallel across a wall are reported and never counted, because that transition is a threshold in a doorway no trace can locate—you get its length, the wall thickness, and a link that puts it on screen.

Drafting aids that behave like drafting aids

45°/90° angle lock: come within a few degrees of square or diagonal and the segment locks to the axis—the click commits the exactly on-axis point, so walls come out dead square (hold to force it at any angle). On the canvas the crosshair is the cursor: the OS pointer hides, a star marks the crossing, in-progress work draws in the instrument's own cobalt, committed shapes wear their condition color. The lock reads quietly—the star swells, the preview thickens, a chip shows the locked angle and the live segment length. Snap (beta) pulls onto true PDF endpoints, and a corner beats an axis.

Scale that matches real plan sets

Auto-detects the drawn scale note, or calibrate from any known dimension. Scale is remembered per sheet, because plan sets are never one uniform scale and tools that assume they are get the numbers wrong. Check a dimension (K) is calibrate's read-only twin: pick a printed dimension string, type what the drawing says, and get a graded verdict (green within 1%, amber within 5%, red past it) plus a one-click Recalibrate to this. Every scale acceptance drops an ephemeral calibrated ruler bar on the sheet, so a 2×-off scale is obvious before anything gets traced. Imperial or metric (m²/m, 1:50-style ratios) is a display toggle—takeoffs are stored unit-agnostically, so flipping it never changes a measurement.

Conditions, materials, and the buy list

A condition is one finish (LVP, carpet, tile, base…), carrying a line/fill color, a CAD hatch pattern so the canvas reads like the real drawing, a per-condition waste %, an ×N multiplier, a default wall height, and a thickness that turns a linear run into border SF. Import from schedule parses the architect's finish table off the sheet into conditions behind a verify dialog—you approve what becomes a condition, and the product spec rides along as read-only report columns.

Supporting Materials is the layer most takeoff tools punt on: per condition, a labor type and a subfloor type, plus the consumables that actually go on the order—adhesive, sealer, thinset, grout, cove-base adhesive—each with a coverage rate and a basis (floor SF / linear LF / each / figured seam LF). Order quantity derives automatically: measured ÷ coverage, rounded up to whole units. Adhesive and mortar lines get coverage presets; grout lines get a calculator that derives SF/bag from tile size, thickness, joint width, and bag weight. Preset values are industry-typical round numbers—always verify against the product data sheet.

Roll goods — the seams, figured

Opt a condition into broadloom or sheet material (material class, roll width, max roll length, seam and wall allowances, direction, sell unit) and the engine lays out the cuts: lanes, seam placement, multi-roll splits, and order footage. Cuts draw to scale over their own rooms in material-true colors, numbered in cutting order, and slide or resize in an edit mode that's on the undo stack. The docked Roll panel shows those cuts nested on the roll with dimensions and drag-to-reorder re-packing, and Roll Order LF, Rolls, and Seam LF ride the Report, CSV, and Excel next to the measured quantities. Seam LF is the weld-rod / seam-tape quantity read straight off that layout—counted between adjacent lanes of the same room, net of the wall overage, only where two lanes actually face each other—so a supporting-materials line on the seam LF basis prices the rod off where the cuts meet instead of off a share of the perimeter. A 20-ft-wide room off a 12-ft roll seams once down its length; the same square footage as two separate 10-ft rooms seams not at all, and no factor on area can tell those apart. Available headlessly too, through roll_setup on edit_condition. (The roll-layout engine was contributed by Michael Hartman.)

Multi-sheet reality

Stitching: a floor split across a match line becomes one working surface—align the joint by picking the same drawn point on both sheets, then trace straight across the seam. Levels group a multi-floor set. A visual gallery (G) is where you choose and open sheets, and Regroup restores a side-by-side composition in one click. A trace can't span two grouped sheets—the gap between panels isn't real distance, so the commit refuses and points you at stitching.

Reports, exports, and revisions

A per-condition breakdown—Floor / Wall / Border SF, LF, EA, total SF, SY, with and without waste—plus a combined materials buy list. Waste applies only in the report's order quantity, never to the live measured number, so the takeoff and the buy list stay honest about which is which. Export CSV, JSON, a real Excel workbook (Summary / By-sheet / Materials / Shapes-audit / By floor × room, full-precision cells, formula-shaped names kept inert text), print, or Marked Set PDF—a distribution-ready planset built entirely in your browser for a GC who will never install anything.

When the addendum lands, Revisions makes it data instead of archaeology: save a named revision at each bid revision, then compare any two as quantity deltas per condition, per sheet, and on the buy list, with a compare CSV. The compare is deliberately quantity-level rather than geometric—it tells you which numbers moved, not which wall did. Restore banks the live takeoff first, so it's never a one-way door.

Markups, seals, and RFIs

A separate layer the totals never count: revision clouds, callouts, text notes, highlighter ink, images (upload a PNG/JPEG, or marquee a region of the plan to drop it back as a floating screenshot—move, resize, and it burns into the marked set), and reusable stamps (plank direction, seam direction, pattern origin—build your own, or import an .svg). Approval seals are the estimator's ink: click a committed takeoff to approve it, and the Marked Set's cover gains a tally line—N estimator-approved · N agent-marked — so a PM knows exactly how much of the set a person has looked at. The RFI register turns any markup into a tracked question with status, priority, ball-in-court, and cost/schedule impact flags, exporting as CSV/JSON and as an RFI schedule page in the marked set.

The Agent panel, in the browser

The same proposer/reviewer split as MCP without leaving the canvas: describe a takeoff in a sentence and a model—yours, on your key, from your browser—works the sheet with the app's own deterministic tools and stages dashed proposals you accept, correct, or reject. It cannot invent geometry (propose_shapes rejects anything uncited) and it cannot set a scale. To watch the loop with no AI account at all, run the keyless deterministic mock server in scripts/.

A vector-sharp canvas

Past ~1.15× zoom times your display's pixel ratio, the visible region re-renders straight from the PDF vectors at your current zoom rather than magnifying a fixed bitmap, so fine callouts and hatching never blur—and it engages after a pause in the gesture, so a continuous zoom stays on the fast base layer while you're still moving. It overlays only what's on screen, so there's no full-sheet bitmap to hold. Dark view (☾) inverts the sheet pixels themselves—a true negative print, white linework on black, not a CSS filter—with hatches retuned, and exports follow it.

Yours, locally

Every drawing, scale, condition, markup, and RFI autosaves to your browser (IndexedDB + localStorage). Nothing is uploaded, there's no account, and there's no server in the default build. The flip side is stated plainly in the manual: storage is per browser, per origin, and clearing site data clears your work.

Everything above is the default and it's unchanged: open the page and you're an anonymous, local-only user. A team on Google Workspace can optionally sign in to unlock a shared mode instead: projects live as folders in the team's own Google Drive, the project list is deep-linked from an existing Glide app, and material costs come from a synced pricing.json. It's strictly additive—set nothing and it doesn't exist. The security posture stays honest: still a plain static site, no secrets in the bundle, team-only because the Google OAuth app is Internal to your domain, and the data sits in your own Drive. See docs/GOOGLE_SETUP.md and docs/GLIDE_INTEGRATION.md. A cloud deployment can also opt into local-first sync (VITE_CLOUD_SYNC=1): annotations stay canonical in the browser and sync to Drive in the background, so the canvas is instant and survives a flaky network—docs/SYNC_ARCHITECTURE.md.

OpenTakeoff can ask a vision model you provide to read things off the plan—starting with the drawn scale when a sheet's text doesn't state one (scans, rotated notes, image title blocks). Click AI in the toolbar and point it at an OpenAI-style endpoint (the default; local runtimes on your own machine speak it and need no key) or an Anthropic-style one, plus a vision-capable model id.

  • What's sent, and only when you click an AI button: one snapshot of the sheet region in question, plus the question—to your endpoint. Never the whole plan file, file names, project names, or your takeoff.

  • Nothing configured = nothing exists. Unconfigured builds add zero UI beyond the button and make zero AI network calls. No telemetry either way.

  • The answer is only ever a suggestion, landing in the same confirm-to-apply flow as a text-detected scale, with the calibrated guide bar shown on acceptance.

  • The key is stored in this browser's localStorage—use one you can revoke. Deployers: VITE_AI_ENDPOINT / VITE_AI_MODEL / VITE_AI_PROVIDER bake team defaults, but never set VITE_AI_KEY on a public deploy—Vite inlines it into the shipped bundle.

What's in the box

Area

What you get

Ingest

PDF, image, or .zip plan set—unpacked in-browser, multi-page, multi-file, up to 4 sheets side-by-side

Scale

Auto-detect the drawn note, calibrate from a known dimension, or verify one with a graded check—per sheet

Measure

One-Click Area (vector flood + raster fallback — temporarily gated), Area, Rectangle, Linear, Curved Line, Surface Area, Count, Cut Out deducts, ⟂ Transitions, Zone check—imperial or metric

Drawing aids

45°/90° angle lock with hard-lock, live angle + segment-length readout at the cursor, endpoint Snap (beta)

Conditions

Color + CAD hatch per finish, waste %, ×N multiplier, wall height, border thickness, schedule import, browser-wide library

Supporting Materials

Labor + subfloor type, coverage rate × basis (incl. figured seam LF) → rounded order quantities, trowel/roller presets, grout calculator

Roll goods

Per-condition roll setup → lanes, seams, multi-roll splits, to-scale cuts with drag-to-reorder nesting, Roll Order LF + Rolls + figured Seam LF on every export

Multi-sheet

Sheet gallery, tabs and side-by-side groups, Regroup, levels, stitching across a match line, PDF layer roles

Report

Per-condition Floor/Wall/Border SF, LF, EA, SY with and without waste, plus the combined buy list; columns, grouping, saved templates

Export

CSV, JSON, Excel (.xlsx), print, Marked Set PDF, RFI CSV/JSON

Revisions

Save at each bid revision, compare quantity deltas per condition/sheet/buy list, guarded restore

Markups

Clouds, callouts, notes, highlighter, images (upload or marquee screenshot), stamps, approval seals, RFI register—separate layer, never counted

Voice

Push-to-talk takeoff commands, recognized on-device in WebAssembly; audio never leaves the browser — gated off the toolbar by default (VITE_COMMAND_BOX=1)

View

Light or dark (negative print)—sheet pixels inverted at draw time, exports follow

Storage

IndexedDB + localStorage—client-only, nothing uploaded

MCP server

53 tools + browsable sheet resources on stdio, multi-document sessions (mcp/)

Provenance

Every shape records its scale, its method, its confidence, and whether a person or an agent made it

Capture (opt-in)

Bundled capture server banks each contributed takeoff as (geometry → label) training rows

Deploy

One static build—Netlify, Vercel, GitHub Pages, Cloudflare Pages, S3, any static host


The data layer — why this engine exists

Every finished takeoff is a set of expert decisions: this region gets this finish, at this waste, yielding these quantities. Done once, that's a bid. Recorded every time, it's a labeled dataset that does not currently exist—plan geometry paired with the finish an expert assigned it, which is the exact raw material for training a model that can do takeoff. Today that data evaporates the moment the bid goes out.

The thesis, stated so it can be attacked: markup is label. Professional takeoff software already stores every drawn region as vector geometry, and reconstructing those polygons reproduces the recorded quantities exactly—so two decades of estimating work is an exact, verifiable corpus rather than a noisy one. That claim is what the whole research program tests, and it's patent pending.

OpenTakeoff is the instrument that produces the corpus, with the collection path opt-in and auditable:

  • The Contribute button in the Report builds a derived-only payload—condition labels, shape roles, quantities, geometry normalized 0-to-1 against the sheet, and per-shape provenance (hand-traced versus machine-proposed, and whether a human corrected it, with the machine's original ring beside the fix). The builder is ~150 audited lines (web/src/lib/contribute.js); the normative wire contract is docs/CONTRIBUTION_SPEC.md.

  • Never sent, enforced by a whitelist in the builder: the PDF or any render of it, file or sheet names, project/client names, markup text, absolute coordinates, scale values (only the scale's provenance—calibrated, detected, or standard), and edit timing beyond a creation stamp. One linkage is deliberate and disclosed: shapes carry opaque, locally-minted IDs so a re-contribution after an addendum supersedes rather than duplicates.

  • The bundled capture server (capture/)—one stdlib-only Python file, no pip install—receives it on localhost and banks one training row per labeled shape, hash-gated so re-contributions never duplicate. v2 rows distinguish what the machine got right from what an expert had to fix, which is the signal that actually teaches a takeoff model. Point it at a synced folder with --mirror and the corpus rides existing company storage sync, atomically.

python3 capture/capture_server.py    # then, in the app's browser console:
# localStorage.opentakeoff_contribute_endpoint = "http://localhost:8787/contribute"

Run OpenTakeoff as-is and none of this exists for you—nothing is captured, nothing leaves your machine. Install it and every takeoff you choose to contribute compounds into an asset you own. This is the open edition of the capture layer inside Spline, the commercial Division 9 estimating system OpenTakeoff was carved from, where capture runs ambient on autosave and commit instead of behind a button. The row schema and the training angle are in capture/README.md.

The research program

OpenTakeoff is the open half of an applied-research program run by a working commercial flooring estimator who builds the AI his own department uses (Kentucky AI). The open-core boundary is the same one the better open scientific software draws: the measurement engine—rendering, scale, geometry, exports, the MCP server—is Apache-2.0 and stays open. The models trained on our own estimating archive are proprietary. You get a real tool with no seat licenses; the part only our data can build stays ours.

The research side is run as a lab, and the receipts are the point:

  • Parameter-efficient tuning, not pretraining. QLoRA adapters on open-weights bases (~0.1% of parameters trained), specialized from a verified bid archive—cheap enough to retrain when the data says retrain, small enough to ship. The flagship adapter predicts bid totals at 12.3% median absolute percentage error on a 51-project temporal holdout, against 62.8% for the untuned base; full method and honest caveats on the model card.

  • Verified labels in. Before a historical bid becomes training data it passes a dual-document verification gate: totals must reconcile between the bid workbook and the separately filed proposal, change orders only count when corroborated by an actual change-order document, and line-item arithmetic is recomputed and forensically checked. Unverifiable projects don't train.

  • Verifiable rulers out. Models are scored against temporally held-out projects—future bids, not a random split—with a geometry scorer whose own error floor is measured (0.4%), so a number can be attributed to model error versus measurement error.

  • Multi-seed replication. No result is promoted from a single training run; promotion requires seed replication with paired bootstrap confidence intervals, and the cross-seed spread gets published alongside the best seed.

  • Negative results are kept. The experiment ledger records what failed and why—an unfreeze recipe that destroyed detection, a vertical-specialist model that lost to the generalist's cross-vertical transfer—next to what worked.

  • Leak-audited before release. Identifiers are replaced before training, so the weights never see a real name, and every public artifact passes a differential red-team: adversarial extraction probes against the tuned model with the untuned base as control.

Sanitized artifacts—model cards, benchmark specs, papers—publish as they clear review: Hugging Face · kentucky-ai.com. The agent-side evaluation lives in OpenTakeoff Academy.


Run it / deploy it

To use it, all you need is a browser. To self-host, it's one static build you can drop anywhere—no backend, no database, no environment to stand up.

cd web
npm install
npm run build      # → web/dist/  (static; host it anywhere)

Deploy to Netlify

The repo ships a root netlify.toml, so the button is genuinely one-click. The same web/dist/ works on Vercel, GitHub Pages, Cloudflare Pages, S3—anywhere that serves static files. Running your own reverse proxy—nginx, Docker, Tailscale? Check docs/SELF_HOSTING.md first—there's one MIME-type gotcha worth knowing about. Deployment notes and the optional AI backend: docs/DEPLOYMENT.md.

Fork it

Apache-2.0: fork it, change it, ship it—for your own crew or as the base of your own product. A fork is the unit of ownership here, and it's the unit of contribution: the same three steps give you a private instance and a branch to send back.

  1. Fork on GitHub, then clone your fork.

  2. Run it: cd web && npm ci && npm run dev — the canvas is at localhost:5173, and npm run check is the exact CI gate (typecheck, lint, test, build).

  3. Put it on your own URL: the repo carries its netlify.toml (base web, publish dist), so importing your fork into Netlify deploys with no settings; any static host works, and docs/SELF_HOSTING.md names the one nginx gotcha. Your instance keeps every plan local exactly as the public one does.

Pull requests from a fork run the full CI with no secrets and a read-only token (docs/DEPLOYMENT.md), so a green check on your fork is a green check here. The codebase is deliberately small and readable, and the geometry libraries are pure so you can lift them straight out:

What

Where

Flood fill, face extraction, corner snap, raster fallback

web/src/lib/oneclick.ts—pure TS, tested

Scale detection, sheet helpers, polygon area

web/src/lib/sheets.ts—pure TS, tested

Waste, square-yard, coverage → order quantity

web/src/lib/totals.js

Roll-goods lane and seam layout

web/src/lib/rollgoods.js—pure, tested

Persistence (IndexedDB + localStorage)

web/src/lib/store.js

PDF / image / zip ingest

web/src/lib/ingest.js

The canvas (one large component, ~90% of the app)

web/src/pages/TakeoffCanvas.jsx

MCP server (imports the same libs)

mcp/src/

Design tokens—source of truth for color and spacing

web/src/styles/tokens.css

Third-party integrations and downstream forks run on this engine today.

cd web && npm run check is the exact CI gate—typecheck, lint, test, build. Keep oneclick.ts and sheets.ts free of React and DOM; that purity is what makes them reusable and testable. Never commit real construction plans. See CONTRIBUTING.md and AGENTS.md—the repo's own instructions for coding agents—plus the user manual.

Contributing

The open work is architectural, and it's posted as RFCs with a stated finish line rather than a manufactured chore list. Currently open:

  • RFC #60—make One-Click Area genuinely great: face extraction, gap tolerance, confidence. Partially landed—a first slice merged in #179, contributed by @knmurphy and credited in the release notes—and the accuracy ceiling is still open.

  • RFC #87—the sheet graph: resolve room tags, schedules, legends, and detail callouts into one queryable graph with a citation per answer. Two phases shipped; revision clouds and detail-callout chains are open.

  • Anything labeled rfc or flagship—a flagship is an open design-and-build challenge where multiple entries are welcome and the best one merges with credit.

  • Smaller, fully-specified entry points are labeled good first issue—they name the exact files. Claim one in a comment and go.

Ground rules are in CONTRIBUTING.md. The bar is a green npm run check plus a test for anything touching the geometry libraries; tested PRs merge fast. CI also holds two lines npm run check doesn't: every relative link and anchor in the docs must resolve (node scripts/check-doc-links.mjs runs it locally), and web/bench/results.json must match what the engine actually produces—an engine change carries its bench delta in the same PR. External contributions are credited by name in the commit and the release notes—and because opentakeoff-mcp publishes to npm off a mcp-v* tag, engine work you land ships to every agent that pulls the package.

Found something exploitable? Report it through private vulnerability reporting rather than a public issue. SECURITY.md states the threat model up front—worth a read before reporting, since it explains what the trust boundary actually is for a client-only app and a local stdio MCP server, and what that does and doesn't make a vulnerability.

Tech stack

  • Frontend: React 18 + Vite 6, plain JSX

  • Drawing: raw HTML5 Canvas + SVG—no charting or canvas frameworks

  • Geometry: TypeScript (oneclick.ts, sheets.ts), pure and unit-tested

  • PDF rendering: pdf.js

  • Plan-set ingest: fflate (zip) + pdf-lib (image → PDF), lazy-loaded

  • Speech: transformers.js, whisper-tiny.en (q8 encoder + uint8 decoder) in a Web Worker—benchmarked against the alternatives in docs/VOICE.md

  • MCP: TypeScript stdio server importing the web engine's own libraries

  • Storage: IndexedDB + localStorage—no backend required

  • Tests: node --test + tsx

  • No paid dependencies. See THIRD-PARTY-NOTICES.md.

Status

A working tool used on real commercial bids, not a preview. The measuring engine is the production engine carved out of a commercial estimating system, and the same engine answers to a person at the canvas or an agent over MCP with the same math, the same scale gate, and the same provenance record. What is measured about agents driving it, and what is still open, is in one table: the Phase 3 completion gate. Named limits, so you don't find them the hard way: Snap is beta, revision compare is quantity-level rather than geometric, and the translated READMEs lag the English one. Issues and pull requests are welcome.

Who's building this

I run estimating for a commercial flooring company and build the AI that runs my department. OpenTakeoff is the open half of that work: the measuring engine, given to anyone—human or agent—who needs to read a building. The models trained on our own estimating archive stay ours, and the boundary is drawn in public so it can be held to account.

What makes the data worth anything is that it comes from bids that were actually submitted, won or lost, and reconciled against a separately filed proposal. That's also why the engine had to be free: a corpus is only as good as the number of real takeoffs that flow through the instrument producing it.

— Michael · Kentucky AI

Contact: research collaborations, data questions, press, or anything that is not a bug — research@kentucky-ai.com. Bugs and feature requests go in issues; security reports follow SECURITY.md.

License

Apache License 2.0—use it, fork it, ship it, build on top of it. See NOTICE for attribution.

For an agent measurement workflow focused on accurate geometry, see Geometry from source to review.

Shared knowledge for people and agents

The wiki routes architecture, protocol, human/agent workflows, MCP tool selection and domain knowledge. MCP clients read the same packaged pages at takeoff://wiki and takeoff://wiki/{page} before loading a plan. AGENTS.md is the contributor router; detailed guidance lives in the wiki. CI checks packaged wiki content, tool counts/inventory, schema references and links against source.

Available Tools

53 tools
annotateA

Place an annotation on a sheet — a note ABOUT the work, never a measurement of it. Types: cloud and highlight take rect:[[x0,y0],[x1,y1]] (a revision cloud around an area, a highlight box over it), text takes at:[x,y], callout takes at:[x,y] plus target:[x,y] (the point its leader aims at), arrow takes from:[x,y] and to:[x,y] (tail and head — plank/seam direction, the markup flooring drawings use most; #150), bubble takes at:[x,y] plus optional r (a keynote/detail circle carrying centered text), dimension takes from:[x,y] and to:[x,y] (its two measured endpoints) and labels itself with the length between them at the sheet's scale — drawn as a dimension line with end ticks and the measurement centered. A dimension states a REAL length, so it is the one annotation the scale gate applies to: on an unscaled sheet it refuses exactly like the measure tools (set_scale first) rather than dressing a px figure up as feet. It still touches no quantity — a dimension is a note about a distance, not a takeoff line item.

Pass condition to attach the note to a finish tag, which is what makes it part of that SCOPE rather than a floating remark: it then wears the condition's colour on the canvas and in the marked-set PDF, and travels with it into the report. The tag is minted on first touch like measure_polygon, so you can annotate CPT-1 before anything is traced for it. Omit condition for a note about the sheet itself.

No review gate: the pencil-not-ink rule exists to stop an agent inventing geometry, and a cloud reading "verify substrate" is not geometry. It touches no quantity. Coordinates are image px at render scale 2.0: PDF pt × 2, origin top-left, y down (the browser canvas's native space). Sheet payloads carry dims in both px and pt.

ParametersJSON Schema
NameRequiredDescriptionDefault
rNoBubble radius (image px); omitted → the canvas default (2% of sheet width)
atNoAnchor point (image px) — text, callout, and bubble (the circle's center)
toNoArrow head / dimension end (image px)
fromNoArrow tail / dimension start (image px)
rectNoCorners (image px) — cloud and highlight
textNoThe note. A cloud with no text still reads as 'look here'; a bubble's text draws centered in the circle; a dimension appends it after the measured length
typeYescloud/highlight need rect; text/callout/bubble need at; callout also needs target; arrow and dimension need from + to
sheetYesSheet name or number, as sheet_info reports it
targetNoWhat a callout's leader line points at (image px)
conditionNoFinish tag to attach this note to, e.g. 'CPT-1' (minted on first use). Omit for an unattached sheet note

Output Schema

ParametersJSON Schema
NameRequiredDescription
idYes
noteYes
textYes
typeYes
sheetYes
conditionYes
length_lfNoDimension only: the measured length (real feet) the annotation will label itself with
condition_idYes

TDQS

A4.5/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations provided, the description carries the full burden and does so well: it discloses the dimension scale gate and its refusal behavior, the tag-minting-on-first-touch behavior, the no-review-gate rule, coordinate space (image px at 2.0 scale, PDF pt x2, origin top-left), and colour/PDF/report propagation. The main omission is any statement about what happens on failure for non-dimension types or whether annotations can be re-edited, so it is not quite exhaustive.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness3/5

Is the description appropriately sized, front-loaded, and free of redundancy?

It is front-loaded with the core purpose, but the paragraph is dense, parenthesis-heavy and carries a reference marker (#150) plus redundant restatement ('it touches no quantity' appears twice in different words). For a 10-parameter, 7-type tool some length is warranted, but several clauses do not earn their place.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

An output schema exists, so return values need not be documented, and the description covers everything else an agent needs: type-to-parameter requirements, coordinate conventions, gate behavior, and condition/scope semantics. Nothing material to calling this correctly is missing.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is already 100%, so the baseline would be 3; however the description adds genuine meaning beyond the schema by explaining the dimension's measured-endpoint semantics and the scale gate, the bubble radius default, and the condition tag's scope consequences. The per-type parameter mapping largely restates what the enum description already says, keeping this short of a 5.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The first sentence states a specific verb and resource ('Place an annotation on a sheet') and immediately differentiates it from the measurement tools ('a note ABOUT the work, never a measurement of it'). It then enumerates all seven annotation types and their geometry, so an agent can tell exactly what this does versus nearby siblings like measure_polygon or edit_annotation.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

It states explicitly when to attach a note to a condition tag ('what makes it part of that SCOPE rather than a floating remark') and when to omit it ('for a note about the sheet itself'). It also names the alternative route for dimensions ('set_scale first') and clarifies there is no review gate, covering when-not as well as when.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

apply_rulesA

Re-run the correction rules the takeoff arrived with (#207) — the lessons an estimator TAUGHT the canvas (#88): "every room like this loses the mechanical chase." A rule is a deterministic predicate (enclosed linework islands under a size cap, inside the rule's condition's rooms), never a re-prompt. Evaluation is the same pure rules.ts engine the canvas Preview runs; the commit is the one batch the canvas's Apply makes — ONE journal entry, undo_last takes the whole batch back. Everything lands reviewed: false (this server has no review gate), and the reply's per-rule disclosure — what each rule produced, what was skipped, with ids — IS your preview: read it, then view_sheet overlay:true. Idempotent by construction: any candidate an existing deduct already covers is dropped by the engine, so re-running after new rooms commit is the intended workflow and never double-deducts. Rules arrive ONLY via import_takeoff (minting a new rule is an estimator's correction and stays behind the canvas's human Preview→Apply gate); with none imported this refuses. Pass sheet to scan one sheet; omit it to scan every sheet holding the rules' rooms. Uncalibrated and scanned-raster sheets come back in skipped_sheets, named.

ParametersJSON Schema
NameRequiredDescriptionDefault
sheetNoScan only this sheet (default: every sheet holding the rules' conditions' rooms)

Output Schema

ParametersJSON Schema
NameRequiredDescription
noteYes
rulesYes
committedYesDeducts committed across all rules — 0 is a result (idempotence)
skipped_rulesYesRules not evaluated, named — never silently dropped
skipped_sheetsYesSheets that could not be scanned (uncalibrated, or scanned raster with no linework mask)
total_deduct_sfYes

TDQS

A4.6/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

No annotations exist, so the description carries full burden. It discloses key behavioral traits: deterministic (not a re-prompt), uses the same rules.ts engine as Preview, commits as a single batch reversible by undo_last, marked reviewed:false, includes per-rule disclosure in the reply, and is idempotent. It also notes refusal when no rules are imported and skipped_sheets for uncalibrated/raster sheets.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is long but every sentence contributes essential behavioral context for a tool with no annotations and nuanced semantics. It is front-loaded with the core purpose then layers details about evaluation, commit, idempotency, import gate, and skipped sheets, making it dense but not wasteful.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

The description fully covers the tool's behavior for its single-parameter interface: it explains the default scope, output behavior (per-rule disclosure serving as preview), error conditions (refuses without imports), and edge cases (skipped sheets). An output schema exists, and the description complements it by describing the meaning of skipped_sheets and the preview nature of the reply.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 100% and the tool has only one optional 'sheet' parameter. The description restates the schema ('Pass sheet to scan one sheet; omit it to scan every sheet holding the rules' rooms') without adding new meaning, so it stays at the baseline for high schema coverage.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states the tool re-runs correction rules, identifying them as deterministic predicates ('enclosed linework islands under a size cap') and ties them to the canvas's Apply action. It distinguishes from siblings by noting rules arrive only via import_takeoff and that undo_last can reverse the batch.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Explicit when-to-use guidance is provided: re-running after new rooms commit is the intended workflow, and it is safe due to idempotency. The description also contrasts with alternatives: rules only via import_takeoff, preview via view_sheet, and undo via undo_last.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

count_marksA

The COUNT TAKEOFF in one deterministic call — no seeds, no model, seconds: census every VALUE-ANNOTATED mark tag on the plan-role sheets, counted per schedule mark, committed as EA markers when asked. The identity rule is the annotated-device drafting pattern: a device is drawn as its mark tag with a value under it ("S1" over "200" — CFM on air devices, GPM on fixtures, a rating on equipment), so a tag WITH a paired value counts, a tag inside a schedule table's own region is a row label (excluded, tallied), and every other occurrence is WITHHELD with a reason and coordinates — a tag amid linework but unvalued may be a real device (view_sheet it), a bare tag is probably a note mention. Marks default to the set's schedule row keys (a compound row "R1 / E1" answers for R1 AND E1; each mark cites its row), or state them: {marks: ["S1","R1"]}. The complement to sweep_schedule_row: THAT tool is for marks drawn ON their marker with no value (finish tags in bubbles) and matches geometry; this one is for annotated devices and needs no fingerprint at all. Refusal-honest: scans refuse (no text layer), a set with no mark-shaped rows refuses unless marks are stated, non-plan sheets are skipped with the role that excused them. commit: true commits every counted occurrence under its mark's own tag — ONE undo step for the whole census, schedule citation on origin. Counts are scale-free (EA) — no set_scale needed. Then AUDIT: view_sheet {overlay: true} where the markers landed, and read every withheld entry — a withheld item you ignore is a hole in the bid. Coordinates are image px at render scale 2.0: PDF pt × 2, origin top-left, y down (the browser canvas's native space). Sheet payloads carry dims in both px and pt. EQUIPMENT marks (a row in an equipment/device schedule — fans, pumps, heaters, fixtures, panels) follow the leader-tag convention instead: a scheduled mark drawn amid linework with no value under it is counted BY LABEL (occurrence by: "label", counted_by_label on the mark); a bare mention in a note still withholds.

ParametersJSON Schema
NameRequiredDescriptionDefault
marksNoThe marks to census, e.g. ["S1", "R1"] — omit to take them from the schedule tables' row keys
commitNoCommit every counted occurrence as one EA count marker under its mark (withheld/excluded never commit)

Output Schema

ParametersJSON Schema
NameRequiredDescription
marksYes
totalYesAll counted instances across every mark
skippedYes
completeYes
per_sheetYes
excluded_in_tablesNoTag occurrences inside a schedule table's own region — row labels, never instances

TDQS

A4.7/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

No annotations exist, so the description carries the full burden and does so richly: deterministic/no-seed behavior, the identity rule for what counts vs. is withheld, commit producing ONE undo step, refusal-honest scan/sheet behavior, and the coordinate space. This is far beyond what structured fields provide.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness3/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Front-loaded with the core action, but the body is a dense run-on paragraph packed with jargon, heavy em-dash nesting, and a late equipment-marks addendum. Most sentences carry information, yet the structure makes it hard to parse and is longer than it needs to be.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

With an output schema present, return values needn't be explained, and the description still covers the counting rule, exclusions, refusals, commit/undo, coordinate frame, and the special equipment-mark case. Nothing an agent needs to call this correctly is missing.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 100%, so the baseline is 3, but the description adds real semantics: how marks default to schedule row keys, compound-row behavior ('R1 / E1' answers for both), the citation of a source row, and the commit-per-mark semantics. This meaningfully exceeds the schema's own strings.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb (census/count) and resource (VALUE-ANNOTATED mark tags per schedule mark) and names the sibling it is not (sweep_schedule_row). An agent can distinguish it from the geometry-matching alternative without opening either schema.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Explicitly contrasts with sweep_schedule_row ('THAT tool is for marks drawn ON their marker with no value... this one is for annotated devices'), and states when marks should be stated vs. defaulted. Refusal conditions (scans, no mark-shaped rows, non-plan sheets) are spelled out.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

create_rfiA

Raise an RFI — a Request For Information — when the drawing set contradicts itself or cannot answer a question you need answered to take the work off: a room-finish schedule row that names a tag the plan never draws, a room label the schedule has no row for, a finish called out two ways, a scale that disagrees with a stated dimension. It lands in the estimator's RFI register (the canvas's RFI panel) with the next number in that register's own sequence (RFI-001, RFI-002, …), status open, dated today, on the sheet you name. You raise it as the agent: the record carries origin {actor: "agent", reviewed: false} and is PENDING — pencil — until the estimator accepts it in the register, because an RFI goes to the architect and nothing sends without a human. It still prints in the marked set's RFI schedule like any other RFI, so the question is on the deliverable. Pass markup_ids to pin it to annotations already on the sheet (annotate a cloud or callout at the conflict first, then link it here) — a linked markup carries the RFI number on the canvas and in the marked set, and list_rfis reports which finish tags the question touches through those links. Prefer this to describing the conflict in prose: a question in the register is tracked, numbered, and answered; a sentence in a reply is lost. Journaled; undo_last takes it back.

ParametersJSON Schema
NameRequiredDescriptionDefault
sheetYesSheet name or number the question is about, as sheet_info reports it
titleYesThe one-line subject the register and the RFI schedule print — what the question is about
questionYesWhat you are asking the architect to answer, stated so a reply can settle it
markup_idsNoAnnotation ids (annotate / list_annotations) to link — they carry this RFI's number on the sheet

Output Schema

ParametersJSON Schema
NameRequiredDescription
idYesThe record id ("rfi-…")
dateYesYYYY-MM-DD opened
noteYes
actorYesWho raised it — "agent" for every RFI minted over MCP, "estimator" for a panel-raised one
sheetYesThe sheet the question is about
numberYesThe register number, "RFI-001" — next in the panel's own sequence, never reissued
statusYesThe panel's lifecycle: open → answered → closed; void = withdrawn
pendingYestrue = agent-raised and not yet accepted by an estimator in the register (origin.reviewed false) — pencil, not sent
subjectYes
questionYes
responseYes
conditionsYesFinish tags the linked markups are attached to — the scopes this question touches
response_dateYesYYYY-MM-DD answered, '' while open
linked_markupsYesAnnotation ids carrying this RFI's number on the sheet (markup.rfi_id) — derived, never stored twice

TDQS

A4.7/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations, the description fully carries the behavioral burden: it discloses the register sequence (RFI-001, …), default status ('open'), date handling, the agent origin record and 'PENDING' state pending human acceptance, printing behavior in the marked set, markup linking effects, and that the action is journaled and undoable. This is exceptionally thorough for a mutation tool.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is dense but every sentence earns its place: it front-loads purpose and conditions, then systematically covers behavior, linking, and journaling. There is no filler or repetition, and the information is logically ordered for an agent.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given the absence of annotations and the presence of an output schema (which handles return values), the description provides all necessary context for correct invocation: when to use it, how it behaves, what state it creates, and how linked markups affect downstream reporting. Nothing critical is missing.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 100%, so the baseline is 3. The description adds significant meaning for markup_ids (explaining how to link annotations and the resulting RFI number propagation) but adds little beyond the schema for title, question, and sheet. The net value beyond structured data is moderate.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description opens with a precise verb+resource ('Raise an RFI') and immediately defines the specific conditions for its use, distinguishing it from prose or other tracking methods. The detailed scenario examples make the purpose unmistakable without opening the schema.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

It clearly states when to use the tool ('when the drawing set contradicts itself or cannot answer a question') and explicitly prefers it over describing the conflict in prose. However, it does not name an alternative sibling tool (e.g., resolve_rfi or list_rfis) for related tasks, so the routing guidance is strong but not exhaustive.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

cut_outA

Cut a REAL hole in a committed floor_area shape (#206) — the way the canvas cuts one (#137): the same lib/cutout.js boolean subtract, so the two surfaces can never disagree about what a hole holds. The parent keeps its outer ring plus the reconciled hole(s) (verts_norm_holes), its computed nets for real — N cuts compose, overlap between cuts never double-deducts (set subtraction), a hole ADDS perimeter — and the deduct commits carrying cuts_shape_id so the report and legend read the reconciled number, never a second arithmetic pass. This is the verb for a column, a floor drain, an island of casework INSIDE a room; an independent measure_polygon role:"deduct" stays the tool for a deduction that isn't a hole in one parent. Refusal over guessing: the ring must sit FULLY inside the parent's outer ring (an edge-crossing cut is a boundary correction — edit_shape the parent instead), and a cut that would erase the parent or split it in two refuses whole (trace the pieces as rooms). One journal entry — undo_last restores parent and hole together; delete_shape on the deduct later reverts the cut too (a multi-cut parent rebuilds from the chain's pristine snapshot minus the survivors). AN OPEN RUN IS CLIPPED, NOT SUBTRACTED: wall tile (surface_area) and base/transitions (linear) are polylines traced in plan, so the ring removes the stretch it covers, the run keeps its id and takes what survives, and a cut through the MIDDLE leaves the far side as its own shape (same condition, same height) — quantities ride the surviving length, which is exact, since wall SF is LF × height and a border's SF is LF × thickness. No deduct is minted for a run: there is no area for one to sit on, and a deduct's SF counts against the FLOOR total a run never fills. A ring that misses the run, one that swallows it whole (delete_shape it), and a curved run (its verts are control points) all refuse. A derived base with numeric openings also refuses: those deductions have no stored location; use measure_line for installed runs so a geometric cut cannot erase the numeric allowance. Coordinates are image px at render scale 2.0: PDF pt × 2, origin top-left, y down (the browser canvas's native space). Sheet payloads carry dims in both px and pt.

ParametersJSON Schema
NameRequiredDescriptionDefault
vertsYesThe ring, image px — fully inside the parent for an area; over the stretch to remove for a run
parent_shape_idYesA committed floor_area shape id, or an open run (surface_area / linear) to clip (list_shapes)

Output Schema

ParametersJSON Schema
NameRequiredDescription
noteYes
holesNoArea parent: holes the parent now carries
piecesNoRun parent: every stretch that survived the cut — more than one when the ring fell in the middle
hole_sfNoArea parent: what this cut actually removed from the parent's net — 0 when the ring fell entirely inside an existing hole
shape_idNoRun parent: the run that was clipped — it keeps its id and takes the first surviving stretch
parent_netNo
removed_lfNoRun parent: length the cut took out
removed_sfNoRun parent: the SF that rode on that length
measure_roleNoRun parent: surface_area or linear
deduct_shape_idNoArea parent: the reconciled deduct — carries cuts_shape_id; totals skip it (the parent nets the hole)
parent_shape_idNo

TDQS

A4.7/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

No annotations are provided, so the description carries the full burden; it discloses mutation semantics, one journal entry, undo_last behavior, delete_shape reverting the cut, multi-cut rebuild from the pristine snapshot, and multiple refusal rules. It also explains clipping versus subtracting for open runs and exact quantity effects, which is far beyond what structured fields provide.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness3/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Front-loads the core purpose, but the description is far longer than typical and packed with parenthetical references, asides, and repeated framing. Much content is substantive for a complex tool, yet the density and length hurt scannability, making it only adequately concise.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given the absence of annotations and the tool's complexity, the description covers behavior, refusals, undo semantics, alternatives, and coordinate space exhaustively. An output schema exists, so return values need not be described; nothing critical seems missing.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 100%, so the schema already documents verts and parent_shape_id. The description adds valuable coordinate-frame semantics (image px at render scale 2.0, PDF pt × 2, origin top-left, y down) and distinguishes area cuts from run clipping, going meaningfully beyond the schema.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb and resource: cutting a real hole in a committed floor_area shape or clipping an open run. It explicitly differentiates from measure_polygon role:'deduct' and edit_shape for boundary corrections, so an agent can identify the tool without opening sibling schemas.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Explicit when-to-use guidance: column, floor drain, island of casework inside a room; names measure_polygon as the alternative for independent deductions. It also lists refusal conditions and alternative tools (edit_shape the parent, trace pieces as rooms, use measure_line), giving clear routing.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

delete_rfiA

Withdraw an RFI. A TOMBSTONE, never a renumber: the record stays with its number reserved, so the register and the marked set keep printing a gap where it was and the next RFI takes the next number — an RFI number that went out and then meant something else would be a lie. Every markup linked to it keeps its note and loses the link (the canvas's own delete rule). Withdraw a question you raised in error; a question the architect answered is closed in the register, not deleted. Journaled; undo_last puts the record and its links back.

ParametersJSON Schema
NameRequiredDescriptionDefault
rfi_idYesRecord id from create_rfi or list_rfis

Output Schema

ParametersJSON Schema
NameRequiredDescription
noteYes
numberYesIts number — stays reserved; the register and the marked set keep the gap
deletedYesThe withdrawn record's id
rfis_remainingYesLive RFIs after the withdrawal
unlinked_markupsYesMarkups that kept their note and lost the link

TDQS

A4.6/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations, the description carries the full behavioral burden and does so richly: tombstone semantics (record stays, number reserved, register prints a gap), link handling (markups keep note, lose link), journaling, and reversibility via undo_last. This is well beyond what structured fields provide.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Front-loads the core action and scoping within the first clause, and most sentences earn their place by disclosing behavior. Slightly verbose with rhetorical flourish ('would be a lie'), but not wasteful enough to hurt.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Output schema exists so return values need not be explained, and annotations are absent but fully compensated by the description's coverage of tombstoning, links, journaling, and undo. An agent has everything needed to call it correctly.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Only one parameter with 100% schema description coverage ('Record id from create_rfi or list_rfis'), so the schema already documents it fully. The description adds no syntax or format beyond what the schema states, so baseline 3 applies.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb+resource ('Withdraw an RFI') and immediately distinguishes the operation from related siblings by noting that an answered question is 'closed in the register, not deleted' (i.e., resolve_rfi vs delete_rfi). An agent can route correctly without opening any schema.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Explicitly states when to use ('a question you raised in error') and when not ('a question the architect answered is closed in the register'), effectively naming the alternative behavior. Nothing is left to inference.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

delete_shapeB

Remove a committed shape by the id returned when it was committed. Coordinates are image px at render scale 2.0: PDF pt × 2, origin top-left, y down (the browser canvas's native space). Sheet payloads carry dims in both px and pt.

ParametersJSON Schema
NameRequiredDescriptionDefault
shape_idYes

Output Schema

ParametersJSON Schema
NameRequiredDescription
noteNoCutout interplay (#206), when it applies: the parent's cut was reverted, could not be rebuilt, or reconciled deducts were orphaned by a parent delete
deletedYesThe removed shape's id
shape_countYesCommitted shapes remaining

TDQS

B3.1/5.0
Behavior2/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations, the description carries full burden. It does not disclose destructive nature beyond 'Remove', nor does it mention reversibility, permissions, or error behavior. The unrelated coordinate information does not contribute to behavioral transparency.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness2/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The first sentence is concise and effective, but the second sentence about coordinates is unrelated to the tool's parameters and adds noise, violating the 'every sentence earns its place' principle.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness2/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Despite having an output schema, the description doesn't explain operation outcomes, idempotency, or side effects. The tool is simple but the description is incomplete and includes irrelevant coordinate context that distracts from core functionality.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

The description adds meaning to shape_id by specifying it is the id returned at commit time, which is not in the schema. However, the coordinate sentence is irrelevant and could confuse parameter expectations.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

Description clearly states the verb 'Remove', the resource 'committed shape', and the identifier 'id returned when it was committed'. It distinguishes from siblings like edit_shape by focusing on deletion of committed shapes.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines2/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

No guidance on when to use this tool versus alternatives. The description provides no context about when deletion is appropriate, prerequisites, or exclusions relative to sibling tools like cut_out or edit_shape.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

delete_verdictA

Lift an agent verdict mark by id (mark_verdict's reply, or list_annotations verdicts[]). Agent marks only: the estimator's APPROVED seal is human ink and is refused — the same line edit_shape holds on reviewed shapes. Journaled like every mutation, so undo_last re-seats a lifted mark exactly where it was.

ParametersJSON Schema
NameRequiredDescriptionDefault
verdict_idYesRecord id from mark_verdict or list_annotations verdicts[]

Output Schema

ParametersJSON Schema
NameRequiredDescription
deletedYesThe lifted record's id
verdicts_remainingYesApproval-family records still on the takeoff (both actors)

TDQS

A4.5/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations provided, the description carries the burden of behavioral disclosure. It states the operation is a mutation ('Journaled like every mutation'), reveals that it refuses human APPROVED seals, and explains that undo_last can re-seat a lifted mark. This is substantial and goes beyond the basic schema.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is three sentences with no filler. It front-loads the action, then packs in the essential details: source of the ID, restriction on human seals, and undo/redo behavior. Every sentence earns its place.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a single-parameter tool with an output schema and no annotations, the description is remarkably complete. It covers purpose, valid ID sources, exclusivity of agent marks, the mutation/journaling behavior, and reversibility via undo_last. Nothing critical is missing for correct invocation.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

The schema already fully documents verdict_id with the same source reference, but the description adds important semantic context by clarifying that only agent marks are valid and that human APPROVED seals are refused. This helps the agent understand which IDs are acceptable.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description uses a specific verb and resource: 'Lift an agent verdict mark by id', and immediately tells the reader where to find valid IDs ('mark_verdict's reply, or list_annotations verdicts[]'). This clearly distinguishes it from sibling tools like delete_shape or mark_verdict.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description provides clear usage context: only agent verdict marks can be lifted, while the estimator's APPROVED seal is refused. It also mentions the undo_last journaling behavior, giving the reader practical when-to-use and what-to-expect guidance, though it does not explicitly name alternative tools for comparison.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

derive_baseA

Mint the wall base from committed rooms (#148) — the estimator's most mechanical derivation: base LF = room perimeter − stated door openings. For every floor_area shape of source_condition, commits ONE linear shape under condition (e.g. 'RB-1') tracing that room's boundary, quantified NET of the openings you state per room. The openings are YOUR claim to make — look at the doors with view_sheet, state {shape_id, lf} per room (repeat a shape_id to stack openings); the tool never guesses, and your claim is recorded on origin.derived (from_shape_id, gross_lf, openings_lf). The output geometry remains the whole perimeter: deducted openings are numerical, not visible gaps. For a drawing of the actual installed base, use measure_line on the physical runs after checking door jambs, alcoves and open finish splits. All-or-nothing: an unknown shape_id, a negative lf, or openings meeting a room's whole perimeter refuses the call before anything commits. The whole derivation is ONE undo step. Deriving onto the source condition is refused — base lands on its own tag.

ParametersJSON Schema
NameRequiredDescriptionDefault
openingsNoStated openings per room — omit for gross perimeters
conditionYesFinish tag the base commits under (minted on first use), e.g. 'RB-1'
source_conditionYesFinish tag whose floor_area rooms the base derives from, e.g. 'CPT-1'

Output Schema

ParametersJSON Schema
NameRequiredDescription
noteYes
roomsYes
total_lfYesSum of net_lf across rooms
committedYes
conditionYesThe tag the base committed under
source_conditionYes

TDQS

A4.8/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations, the description carries the full burden and delivers: all-or-nothing refusal on unknown shape_id, negative lf, or openings meeting a full perimeter; the whole derivation is one undo step; deriving onto the source condition is refused; deducted openings are numerical not visible geometry. It also states where the claim is recorded (origin.derived).

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Front-loaded with purpose and formula before constraints, and nearly every sentence earns its place by adding a rule or failure mode. It is dense and long, but for a tool with this many invariants the length is largely justified rather than padding.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given an output schema exists, return-value explanation is not required, and the description covers everything else an agent needs: the derivation model, refusal conditions, undo granularity, and the condition-tag constraint. Nothing material for correct invocation is missing.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 100%, so the baseline is 3, but the description adds genuine meaning beyond the schema: openings are the caller's own claim and are recorded, shape_id can be repeated to stack openings, and omitting openings yields gross perimeters. The stacking and claim-recording semantics are not obvious from the schema alone.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb and resource — minting a wall base from committed rooms — and gives the exact formula (base LF = room perimeter − stated door openings). It also distinguishes itself from siblings by scoping to floor_area shapes of source_condition and naming measure_line for the alternative 'installed base' case.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Explicit when-to-use (derive base from committed rooms of source_condition), when-to-use-the-other (drawing of actual installed base → measure_line on physical runs), and the prerequisite workflow (look at doors with view_sheet, state openings per room). Alternatives and conditions are spelled out.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

derive_transitionsA

Mint the transition where two finishes MEET (#202) — the derivation that follows derive_base, and the line an estimator draws by hand on every job. Pass the two finish tags and the tag the transition commits under (e.g. condition_a 'CPT-1', condition_b 'PT-1', condition 'T-1'), and every committed room of each is compared against every committed room of the other.

WHAT THE GEOMETRY ACTUALLY IS, because it decides what you get back: flood-traced rooms DO NOT SHARE EDGES. A trace fills to the wall linework, so two rooms across a partition are separated by four to eight inches of nothing — testing for a shared edge finds zero transitions on a real planset. What is there is proximity, in two flavours that mean completely different things:

• BUTT JOINT — the two rings run together inside ONE open space (a lobby that changes from carpet to tile with no wall between). The transition IS that run, and it commits as a linear shape under your tag, origin.derived naming both parent shapes and the measured gap.

• WALL-SEPARATED — the rings run parallel across a partition. The rooms are adjacent, but the transition is NOT the shared wall: it is a threshold, in the doorway, and NOTHING in the trace record says where the doorway is (the flood engine seals openings and reports how MUCH boundary it synthesised, never where). Committing 34 LF of threshold because two rooms share 34 LF of wall would be a wrong bid with a machine's confidence behind it. These come back in withheld — measured, with their length, their gap in inches, and an at point — as questions you answer by LOOKING (view_sheet at at, then measure_line or place_count the threshold yourself). The symbol_sweep doctrine: a near-match is never a silent commit and never a silent drop.

Tuning: max_gap_in (default 12) is how far apart two rings can be and still count as adjacent at all — raise it for thick walls, and every extra inch turns more of the plan into wall_separated questions, never into committed LF. min_run_in (default 12) drops corner artifacts. The butt-joint threshold is fixed at one inch and is not a knob: "these two finishes touch" is not a judgement call.

All-or-nothing, like derive_base: an unknown tag, a transition landing on either source tag, the same tag twice, or a sheet without a scale refuses the whole call before anything commits. The whole sweep is ONE undo step. After it, LOOK — view_sheet {overlay: true} over each run — before trusting total_lf. Coordinates are image px at render scale 2.0: PDF pt × 2, origin top-left, y down (the browser canvas's native space). Sheet payloads carry dims in both px and pt.

ParametersJSON Schema
NameRequiredDescriptionDefault
conditionYesFinish tag the transitions commit under (minted on first use), e.g. 'T-1'. Must differ from both sources
max_gap_inNoHow far apart two rings can be and still count as adjacent, in inches (default 12 — a thick partition). Wider only produces more wall_separated QUESTIONS, never more committed LF
min_run_inNoShortest run worth reporting, in inches (default 12) — below this is a corner where two rooms clip, not a transition
condition_aYesFirst finish tag, e.g. 'CPT-1' — its committed rooms are walked, and runs are traced along their boundaries
condition_bYesSecond finish tag, e.g. 'PT-1'

Output Schema

ParametersJSON Schema
NameRequiredDescription
noteYes
runsYes
betweenYesThe two finish tags
total_lfYesSum of committed run lengths — butt joints only
withheldYesAdjacency across a wall: real, measured, and NOT committed — the transition there is a threshold at a doorway this cannot locate
committedYes
conditionYesThe tag the transitions committed under
withheld_lfYesShared-wall length held back — never part of total_lf

TDQS

A4.8/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations provided, the description carries the full burden and goes far beyond it: it discloses all-or-nothing atomicity ('refuses the whole call before anything commits'), the single-undo-step behavior, the committed vs. withheld output split, coordinate system specifics (image px at scale 2.0, origin top-left, y down), and failure conditions. It also explains the critical geometric nuance that flood-traced rooms do not share edges, which materially affects what the tool returns.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is long, but every paragraph earns its place for a tool with complex geometric behavior. It opens with a one-sentence purpose, then systematically covers geometry, tuning, failure modes, and post-conditions. The structure uses clear signposts ('WHAT THE GEOMETRY ACTUALLY IS', 'Tuning:', 'All-or-nothing') to make the length navigable.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

The description is remarkably complete for a complex tool, covering input semantics, output semantics (committed vs. withheld, total_lf), coordinate system, failure conditions, undo behavior, and recommended follow-up actions. Since an output schema exists, it doesn't need to enumerate return fields, but it still explains the meaning of those fields and the operational context, exceeding the minimum needed.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters5/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 100%, so the baseline is 3, but the description adds substantial meaning beyond the schema: it explains that condition is 'minted on first use', elaborates on max_gap_in's trade-off ('Wider only produces more wall_separated QUESTIONS, never more committed LF'), clarifies min_run_in as a corner artifact filter, and contextualizes condition_a/condition_b with examples. This is exactly the kind of semantic enrichment that helps an agent choose correct parameter values.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description opens by naming the exact operation: 'Mint the transition where two finishes MEET (#202)', which is specific and action-oriented. It also distinguishes itself from siblings by explicitly calling out that it 'follows derive_base' and by referencing the 'symbol_sweep doctrine' to differentiate its behavior from symbol_sweep.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description clearly establishes when to use this tool—after derive_base, when two finish tags require a transition—and gives practical tuning guidance ('raise it for thick walls'). It also directs users to alternatives for follow-up actions ('view_sheet at `at`, then measure_line or place_count the threshold yourself'), but it does not provide explicit 'when not to use' exclusions, so it falls short of a 5.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

duplicate_conditionA

Twin a condition — the same finish measured somewhere else, with its own supporting materials. One finish in two areas is not two conditions and it is not one either: the same sheet goods over a slab and over a raised deck take the same field material and different preparation underneath (one wants a moisture barrier, the other a primer and a different adhesive). The twin arrives carrying the original's whole materials list and keeps FOLLOWING it — change a coverage rate on the original and every twin that has not touched that row gets it; edit a row on the twin and only THAT row stops following. label is REQUIRED and becomes the tag suffix ('CPT-1' + 'Level 2' → 'CPT-1 – Level 2'), because every tool in this server resolves a condition by finish tag and takes the FIRST match: two conditions sharing a tag would make one permanently unreachable, and a takeoff re-import collapses them last-wins. A label already in use is refused rather than de-collided. No takeoffs come along — measure the new area against the returned condition_id. Reversible with undo_last; use split_condition to end the inheritance permanently.

ParametersJSON Schema
NameRequiredDescriptionDefault
labelYesWhat makes this one different, usually the area: 'Level 2', 'Building B', 'Phase 2'
conditionYesFinish tag of the condition to twin, e.g. 'CPT-1'

Output Schema

ParametersJSON Schema
NameRequiredDescription
noteYes
conditionYesThe twin's finish tag — base tag + the label, e.g. 'CPT-1 – Level 2'
family_idYesShared by every variant of this finish — survives a split
variant_ofYesThe condition whose material rows this one follows
condition_idYesThe TWIN — measure the new area against this
variant_labelYes
inherited_rowsYesMaterial rows copied, all still following the original

TDQS

A5/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Despite no annotations, the description thoroughly discloses behavioral traits: the inheritance propagation rule ('change a coverage rate on the original and every twin that has not touched that row gets it'), the label uniqueness requirement with rationale (first match wins, duplicate tags cause unreachable conditions), refusal of duplicate labels, and reversibility. This far exceeds a simple 'duplicates a condition'.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is lengthy but every sentence provides valuable information, covering concept, examples, constraints, and alternatives. It is logically structured, starting with core purpose and layering detail, with no redundant or filler content.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

It addresses why the tool exists, the inheritance behavior, labeling pitfalls, what is not carried over ('No takeoffs come along'), and how to reverse the action. It also indicates the return value (condition_id) and is complemented by an output schema, making it complete for a complex tool.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters5/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

While the schema defines 'label' and 'condition' with brief descriptions, the description adds critical semantics: label becomes a tag suffix with a concrete example ('CPT-1' + 'Level 2' → 'CPT-1 – Level 2') and must be unique. It also clarifies that condition is the finish tag the twin follows, providing much deeper meaning than the schema alone.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description opens with 'Twin a condition — the same finish measured somewhere else, with its own supporting materials,' providing a specific verb and resource. It clearly distinguishes from siblings like split_condition, which ends inheritance, and edit_condition, which modifies a condition.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

It gives a concrete scenario for when to use the tool ('One finish in two areas... same sheet goods over a slab and over a raised deck...') and explicitly names alternatives: 'Reversible with undo_last; use split_condition to end the inheritance permanently.' It also instructs the user to 'measure the new area against the returned condition_id,' guiding post-invocation steps.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

edit_annotationA

Shorten, replace or clear the text of an existing annotation. Get annotation_id from list_annotations (annotations, not verdicts). Changes only text: position, shape, dimension length, condition links, quantities and review records stay unchanged. Empty text clears the note; a dimension still prints its measured length. Refuses an RFI-linked note: review that question's context in the browser RFI register. One undo_last step restores the previous text. Does not create a verdict or human approval.

ParametersJSON Schema
NameRequiredDescriptionDefault
textYesReplacement text; empty string clears it
annotation_idYesAn annotation id from list_annotations

Output Schema

ParametersJSON Schema
NameRequiredDescription
idYes
noteYes
textYes

TDQS

A4.7/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

No annotations are supplied, so the description carries the full burden and does so: it limits the blast radius (position, shape, dimension length, condition links, quantities and review records unchanged), explains the empty-string clearing behavior, states the RFI-link refusal, and confirms reversibility via one undo_last step. It also explicitly disclaims verdict or human-approval creation, heading off a likely confusion with mark_verdict.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Front-loads the core action, then layers scope limits, refusal condition, and undo semantics in short declarative sentences. No sentence is filler and no information is repeated.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

An output schema exists, so return values need not be described. Combined with the disclosed side-effect scope, refusal rule, and undo path, an agent has everything needed to invoke this correctly and to recover from it.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 100% and both parameters are documented in the schema itself, so the baseline is 3. The description's note that empty text clears the annotation largely restates the schema's 'empty string clears it' and adds no new syntax or format detail.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

States specific verbs (shorten, replace, clear) against a specific resource (text of an existing annotation), and explicitly scopes itself away from verdicts and from non-text properties. An agent can distinguish it from edit_condition, link_annotation, and mark_verdict without opening any schema.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Tells the agent where to source the id (list_annotations, not verdicts) and names an explicit when-not case: RFI-linked notes are refused and must be handled in the browser RFI register. That is both a precondition and a routing rule, not implied guidance.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

edit_conditionA

Set a condition's quantity knobs — waste %, multiplier, height_ft (the H knob measure_surface quantifies against), and/or roll_setup (the roll-goods opt-in: seams and order footage figured from the committed rooms, #147). takeoff_summary emits waste-adjusted *_net order quantities and a per-condition multiplier, and every export carries both, but conditions minted through the measure tools start at waste 0 / multiplier 1 — without this tool an agent's takeoff always ships net === gross (#131). waste_pct is the estimator's cut-waste percentage (carpet commonly 5–10); multiplier scales every quantity on the condition (×N identical floors — takeoff_summary applies it before waste). condition must resolve to an EXISTING finish tag — a typo'd tag errors rather than minting an empty condition (the edit_materials remove/patch rule, not its add rule: these knobs mean nothing on a condition that doesn't exist yet). No review gate — quantity config, not traced geometry; undo_last reverses a call in one step (both knobs snapshotted together, restored verbatim).

ParametersJSON Schema
NameRequiredDescriptionDefault
conditionYesFinish tag of an existing condition, e.g. 'CPT-1'
height_ftNoWall height in feet — the canvas's H knob; measure_surface quantifies traced LF × this
waste_pctNoWaste percentage applied to net order quantities, e.g. 10 for 10%
multiplierNoQuantity multiplier (×N identical areas). Note: the canvas treats 0 as 1, so 0 is rejected here rather than silently meaning 'off'
roll_setupNoRoll-goods opt-in (#147): presence of a setup is what makes the condition roll goods — seams figured, cuts packed, order footage beside the measured quantities. Same-material partial edits patch the existing setup; null opts out. The reply echoes the figured order (cuts, order_lf, rolls, order_qty) whenever floor shapes exist on scaled sheets, and export_report's roll_goods block carries the same rows

Output Schema

ParametersJSON Schema
NameRequiredDescription
rollNoThe figured order (same row export_report's roll_goods carries) — present when the roll-goods condition has floor shapes on scaled sheets
conditionYesThe finish tag passed in
height_ftNoThe condition's wall height after this write — present once set (measure_surface multiplies traced LF by it)
waste_pctYesThe condition's waste % after this write
multiplierYesThe condition's quantity multiplier after this write
roll_setupNoThe condition's roll-goods setup after this write — present while opted in
condition_idYes

TDQS

A5/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations, the description fully carries the behavioral transparency burden. It discloses downstream effects (takeoff_summary emits waste-adjusted *_net quantities, exports carry both), error behavior on typo'd tags, roll_setup presence as opt-in with null opt-out, reply echoing figured orders, and undo_last reversing with a snapshot. No contradiction with structured data.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is long but every sentence carries value. It is front-loaded with the primary purpose, then elaborates on each parameter, edge cases, and interactions. Examples like 'carpet commonly 5–10' and issue references add specificity without padding.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

It covers prerequisites, side effects, output behavior (reply echoes figured order), interaction with undo_last, and roll_setup nested options. Even with an output schema present, the description provides essential context about when and how quantities are applied, making it comprehensive for an agent.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters5/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 100%, but the description adds significant semantic meaning: waste_pct is the estimator's cut-waste percentage (carpet 5-10), multiplier scales before waste, height_ft is the H knob measure_surface quantifies against, and roll_setup presence is opt-in with partial edits patching the existing setup. This goes far beyond the raw schema descriptions.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description opens with 'Set a condition's quantity knobs' and enumerates the specific knobs (waste %, multiplier, height_ft, roll_setup), clearly identifying the resource and action. It also distinguishes from siblings like edit_materials by referencing its remove/patch rule and from measure tools by noting the default starting values.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

It explicitly states when to use the tool: after measuring, to adjust quantity knobs that otherwise default to waste 0/multiplier 1, making takeoff net===gross. It also provides prerequisites (condition must resolve to an existing finish tag) and mentions alternatives like undo_last for reversal and contrasts with edit_materials add rule.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

edit_materialsA

Add, remove, or patch supporting-materials rows on a condition — the coverage-rate lines that turn a measured area/length/count into an order quantity (adhesive at N sf/gal, grout at N lf/bag, …), matching the canvas's per-condition Supporting Materials panel. Each row is {name, per, basis, unit, round, note}: quantity = the condition's basis total (area/linear/count/seam_lf) ÷ per, rounded up to whole purchase units unless round:false. basis "seam_lf" is the one basis that is FIGURED rather than measured: it is the length where two cuts meet on the floor, read off the condition's roll layout (set roll_setup with edit_condition), which is what a heat-weld rod or a carpet seam tape is bought by. A 20-ft-wide room off a 12-ft roll seams once down its length; the same square footage as two 10-ft rooms seams not at all, and no percentage of the area or the perimeter can tell those two jobs apart. Without a roll_setup — or with no committed floor shapes to lay out — a seam_lf row reads 0, which is the honest state rather than a guess. condition names an existing OR NEW finish tag (minted on first touch, same as measure_polygon) — add alone is enough to seed materials on a condition before you've traced anything. remove/patch target existing row ids from this reply or export_takeoff (takeoff_summary strips materials for a compact quantities-only reply); a bad id 404s the WHOLE call before anything is written, and referencing an id on a tag with no condition yet errors rather than silently minting an empty one. No review gate here — materials rows are quantity config, not traced geometry, so this edits directly; undo_last reverses a call in one step (the condition's whole materials array, snapshotted before the write, restored verbatim).

ParametersJSON Schema
NameRequiredDescriptionDefault
addNoNew rows to add
patchNoField changes on existing rows
removeNoExisting row ids to remove
conditionYesFinish tag, e.g. 'CPT-1'

Output Schema

ParametersJSON Schema
NameRequiredDescription
changedYes
conditionYesThe finish tag passed in
materialsYesThe condition's full materials array after this write
condition_idYes

TDQS

A4.7/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations, the description carries the full behavioral burden and does so: no review gate (direct edit), a 404 aborts the whole call before any write, referencing an id on a tag with no condition errors rather than silently minting, seam_lf reads 0 without a roll_setup, and undo_last restores the snapshotted materials array verbatim in one step.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness3/5

Is the description appropriately sized, front-loaded, and free of redundancy?

It is front-loaded with the core action, but the ~250-word body includes an extended digression on seam geometry ('a 20-ft-wide room off a 12-ft roll seams once down its length...') that reads as documentation rather than crisp tool guidance. Informative sentences, but the volume costs discoverability/scannability.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

An output schema exists, so return values need not be explained. For a 4-param mutation tool the description covers row semantics, the seam_lf edge case, seeding behavior, id resolution sources, error semantics, and undo — nothing an agent needs before calling it is missing.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 100%, so the baseline is 3, but the description adds real meaning: the row shape {name, per, basis, unit, round, note}, the quantity formula (basis total ÷ per, rounded up unless round:false), and the conceptual distinction that seam_lf is figured rather than measured. This goes beyond restating the schema's field docs.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description opens with a specific verb+resource: 'Add, remove, or patch supporting-materials rows on a condition'. It defines what those rows are (coverage-rate lines that turn a measured quantity into an order quantity) and routes to siblings like edit_condition and takeoff_summary by name, so an agent can distinguish it without opening a schema.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

It states explicitly that 'add' alone can seed materials on a condition before anything is traced, that remove/patch consume existing row ids from this reply or export_takeoff, and that takeoff_summary strips materials for a compact reply. Alternatives (takeoff_summary, export_takeoff, edit_condition for roll_setup) and their selection conditions are named.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

edit_shapeA

REVISE a shape you already committed, instead of deleting it and starting over: pass new verts to move the geometry, condition to reassign it to a different finish tag, role to switch between floor_area / deduct / linear, label to name the room it belongs to, or any combination. Quantities are recomputed from the result — a role flip alone re-measures (closed area vs open length). The loop this is for: measure_polygon to commit, view_sheet with overlay:true to LOOK at what landed, then edit_shape to fix the two vertices that overshot into the corridor. label is the per-room reporting seam: this is how a shape traced by hand — or one whose room number the sweep read wrong — joins the same per-room breakdown the Report and the workbook's floor × room tab group by. Shapes a human affirmed (origin.reviewed) are ink and are refused — an agent revises its own pencil and nothing else. Agent self-revision is tallied on origin.agent_edits, kept deliberately separate from the human-correction fields. Coordinates are image px at render scale 2.0: PDF pt × 2, origin top-left, y down (the browser canvas's native space). Sheet payloads carry dims in both px and pt.

ParametersJSON Schema
NameRequiredDescriptionDefault
roleNoSwitch what the shape measures — flipping INTO surface_area needs a height on the shape or its condition
labelNoThe room (or phase/area) this shape belongs to, e.g. "134" or "OFFICE 101" — what per-room reporting groups by. Pass "" to clear it
vertsNoReplacement geometry (image px): ≥3 vertices for an area shape, ≥2 points for a linear/surface run, ≥1 for a count marker
shape_idYesId returned when the shape was committed
conditionNoReassign to this finish tag (minted on first use)

Output Schema

ParametersJSON Schema
NameRequiredDescription
countNocount shapes only — the marker's EA (preserved across the edit)
labelNoThe shape's room/phase label after this call — absent when it carries none (a cleared label reports as absent, not as an empty string)
nvertsYes
area_sfNo0 for linear shapes; LF × height for surface_area; absent for count
changedYesWhich fields this call actually changed
shape_idYes
agent_editsYesHow many times the agent has revised this shape — separate from the human-correction tally
measure_roleYes
perimeter_lfNoLength for linear/surface runs, perimeter for closed ones; absent for count

TDQS

A4.8/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations, the description carries the full burden and does: it discloses that quantities are recomputed on every edit, that a role flip alone re-measures (closed area vs open length), that origin.reviewed shapes are rejected, that agent edits are tallied on origin.agent_edits, and the exact coordinate space (image px, scale 2.0, top-left origin, y down). This is exactly the behavioral context annotations would otherwise provide.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Purpose and the refusing-rule are front-loaded and every sentence carries operational content, but the 'ink vs pencil' metaphor and the density of the run-on middle sentence cost some clarity. Efficient overall, small flourish.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

A complex mutation tool with an output schema (so return values need no explanation) and full schema coverage; the description still fills the gaps that matter — recompute behavior, refusal conditions, edit accounting, and coordinate conventions. Nothing an agent needs to call it correctly is missing.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 100%, so baseline is 3, but the description adds genuine meaning beyond the schema: it frames each parameter by consequence (verts move geometry, condition reassigns finish, role switches measurement mode, label is the per-room reporting seam) and explains the downstream effect of a role flip. It stops short of adding syntax details the schema already provides.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb (REVISE) and resource (a committed shape), and explicitly distinguishes it from the delete-and-recreate path plus the sibling delete_shape. An agent can tell exactly what this does from the first clause.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Names the exact workflow loop — measure_polygon to commit, view_sheet overlay:true to inspect, then edit_shape to fix overshooting vertices — and states the boundary condition that reviewed (human-affirmed) shapes are refused. Usage and exclusions are both explicit.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

export_dxfA

The takeoff as a CAD drawing — a DXF (R2000) AutoCAD, BricsCAD, LibreCAD and Revit import as native geometry, not a picture. ONE sheet per file, like a DWG: every committed shape on that sheet becomes an LWPOLYLINE (floor rings CLOSED, walls and linear runs open, count marks a 1-ft circle), on a layer named for its finish — OT-, with -DEDUCT / -HOLE / -WALL / -LINEAR / -COUNT suffix layers so a CAD user isolates any bucket with one layer filter, and room labels as TEXT on OT-LABELS. Coordinates are real units in the sheet's own frame: origin at the sheet's BOTTOM-left, Y up (CAD convention), feet by default ($INSUNITS 2) or metres with units:"m"; a ring's area in CAD equals its area in export_report to rounding, so the drawing IS the audit. Requires the sheet's scale (refuses otherwise — pixels in a DXF are worse than nothing); with several sheets carrying shapes, pass sheet to choose the drawing (the refusal lists them). The reply names every shape left out and why — a reconciled deduct ships as its parent's -HOLE ring, never twice. Writes to path (required — a DXF lives on disk, next to the DWG it aligns to); pair with export_marked_pdf for the reviewed planset.

ParametersJSON Schema
NameRequiredDescriptionDefault
pathYesFile path to write the .dxf to
sheetNoSheet key ("plan.pdf", "plan.pdf#2") or title-block number ("A-101"). Optional only when exactly one calibrated sheet carries shapes
unitsNoOutput units — "ft" (default) or "m"
overwriteNoReplace the file at path even when it is not an OpenTakeoff export. Off by default: re-exporting over a previous export of your own already overwrites without this, so you only need it to deliberately destroy an unrelated file.

Output Schema

ParametersJSON Schema
NameRequiredDescription
pathYesThe DXF written
bytesYes
sheetYesSheet key the drawing was cut from
unitsYes
layersYesLayer names in table order — OT-<TAG>, plus -DEDUCT/-HOLE/-WALL/-LINEAR/-COUNT suffix layers and OT-LABELS
shapesYesCommitted shapes that produced geometry
extentsYesModel-space bounding box in output units; origin = sheet's bottom-left, Y up
skippedYesShapes on this sheet left out, each with why — never silent
entitiesYesLWPOLYLINE + CIRCLE + TEXT entities in model space
sheet_numberYes

TDQS

A4.7/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations, the description carries the full behavioral burden and succeeds thoroughly. It discloses output geometry, layer naming, coordinate conventions, units, scale requirements, refusal behavior, handling of missing shapes, and overwrite semantics — far beyond what an annotation could capture.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is front-loaded with the core purpose, and every sentence adds technical or behavioral value. It is dense and somewhat run-on, but for a CAD export tool with many edge cases, the length is justified. It could be tightened with clearer sentence breaks.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given the tool's complexity, the description covers all essential operational concerns: output format, layer organization, coordinate system, units, scale prerequisite, multi-sheet selection, exclusions, overwrite safety, and a companion PDF tool. The presence of an output schema means return-value details are not needed here.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters5/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Although schema coverage is 100%, the description adds meaningful meaning: path is required because DXF is a disk file aligned to the DWG; sheet is optional only when one calibrated sheet carries shapes; units affect INSUNITS; and overwrite has a nuanced 'your own previous exports already overwrite' behavior. This is more than baseline schema documentation.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description states the tool exports the takeoff as a CAD drawing in DXF R2000 format, with native geometry rather than an image. It clearly distinguishes this from export_marked_pdf by emphasizing CAD interoperability and from export_report by noting that CAD areas match the report's areas.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

It provides strong context: requires a calibrated sheet scale, requires a path, and explains when to pass the sheet parameter. It also names export_marked_pdf as a companion for a reviewed planset. However, it never explicitly states 'use this instead of X when Y' or lists exclusion conditions beyond the scale requirement.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

export_marked_pdfA

The MARKED-UP PLANSET — the deliverable of every takeoff. Writes a distribution-ready PDF to disk: a legend cover (per-condition totals, swatches, a by-sheet breakdown) followed by every sheet that carries takeoff shapes or annotations, vector-copied from the source plan with the work burned in as drawn — condition colors and hatches, a quantity chip on every shape, annotation clouds/callouts/highlights, and approval marks (the estimator's APPROVED rings, the agent's AGENT diamonds — the cover tallies the split). Built by the same module as the canvas's MARKED SET button, so agent output and app output are one implementation. A construction takeoff is no good without markup: finish EVERY takeoff by writing this file and giving the user its path (export_report carries the numbers for pricing; this carries the evidence). When the shapes were machine-traced and unreviewed, the document says so on its last page — the review path is importing the export_takeoff payload into the app, where agent shapes arrive as pencil proposals. Default path: next to the loaded plan as " - marked set.pdf". Needs no native canvas — pure vector copy, so it works even where view_sheet cannot render. The one source it refuses: an ENCRYPTED plan PDF (owner password, empty user password — it opens everywhere, but its pages cannot be vector-copied and there is no canvas here to render them); the refusal names the sheet — export the marked set from the app, or supply an unencrypted PDF.

ParametersJSON Schema
NameRequiredDescriptionDefault
pathNoWhere to write the PDF (default: "<plan dir>/<plan> - marked set.pdf")
overwriteNoReplace the file at path even when it is not an OpenTakeoff export. Off by default: re-exporting over a previous export of your own already overwrites without this, so you only need it to deliberately destroy an unrelated file.
project_nameNoCover-page project name (default: the plan file's name)

Output Schema

ParametersJSON Schema
NameRequiredDescription
noteYes
pathYesAbsolute path of the written marked-set PDF — hand this to the user
pagesYesLegend cover + the RFI schedule page(s) when any RFI is live + one page per marked sheet
rfis_printedYesLive RFIs printed on the RFI schedule page (#364) — agent-raised and panel-raised alike; withdrawn ones leave a numbering gap
shapes_drawnYes
sheets_markedYesSheets carrying shapes, annotations, or approval marks — unmarked sheets are omitted
approvals_drawnYesApproval-family glyphs burned in (#176) — estimator APPROVED rings + agent AGENT diamonds; the cover tallies the split when any exist
annotations_drawnYes

TDQS

A4.6/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations, the description carries the full burden and does so: it discloses the output structure, that unreviewed shapes are flagged on the last page, the default output path, that it needs no native canvas, and a specific refusal case (encrypted owner-password PDFs) with the reason and remedies. This is far beyond what structured fields provide.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Front-loaded with the core deliverable identity, then layers usage, review-path, default path, and refusal cases. It is dense and long but nearly every clause adds operational value; only minor tightening is possible.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

An output schema exists, so return values need no explanation. The description covers behavior, refusal conditions, fallbacks, and the finish-the-takeoff workflow, leaving nothing an agent needs to call it correctly unstated.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 100%, so all three parameters are already documented in the schema (including default path and overwrite semantics). The description reinforces the default path but adds no syntax or meaning beyond the schema, so the baseline 3 applies.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb (writes a PDF to disk) and resource (the MARKED-UP PLANSET deliverable), enumerating its contents: legend cover, per-condition totals, marked sheets. It explicitly distinguishes itself from siblings export_report ('carries the numbers for pricing; this carries the evidence') and export_dxf.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Gives explicit when-to-use ('finish EVERY takeoff by writing this file and giving the user its path') and names the contrasting alternative (export_report). It also states the review-path alternative for unreviewed machine-traced shapes (import_takeoff payload) and the fallback for encrypted plans (export from the app).

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

export_reportA

The computed Report document — "opentakeoff.report.v1", the same schema the canvas Report's JSON export writes. Everything a pricing consumer needs without re-implementing the app's math: per-condition quantities with waste and multiplier applied (gross and *_net), the computed materials BUY LIST per condition (order quantity = basis ÷ coverage rate, rounded up to whole purchase units) plus the project-wide roll-up summed by (name, unit), per-sheet BASE subtotals, scale provenance per sheet, and annotations. Contrast: export_takeoff is the raw canvas payload (materials as CONFIG rows, no computed quantities) and takeoff_summary strips materials for a compact reply — when the numbers are leaving for pricing, consume this. A report alone is HALF the deliverable: pair it with export_marked_pdf, because a takeoff is reviewed on marked drawings, not on numbers. Returned inline; pass path to also write it to disk as JSON.

ParametersJSON Schema
NameRequiredDescriptionDefault
pathNoFile path to write the document to
overwriteNoReplace the file at path even when it is not an OpenTakeoff export. Off by default: re-exporting over a previous export of your own already overwrites without this, so you only need it to deliberately destroy an unrelated file.
project_nameNoLabel for the document's project_name field (a headless session has no project of its own; omitted → null)

Output Schema

ParametersJSON Schema
NameRequiredDescription
rfisYes
unitsYes
schemaYes
sheetsYesScale provenance per sheet — how each scale was set
totalsYes
markupsYes
by_labelYes
by_sheetYesBASE per-sheet subtotals — multiplier NOT applied, no waste, no materials
materialsYesProject-wide buy list — condition rows summed by (name, unit)
conditionsYesconditionTotals rows: gross + *_net quantities AND the computed materials buy list
roll_goodsYesRoll-goods order rows (#136) — order_lf / rolls / order_qty per roll-goods condition, ×N applied; empty when no condition carries a roll_setup (always the case for a headless session today)
project_nameYes
shape_labelsYes
display_unitsYes
generated_withYes
condition_columnsYes
proposed_condition_editsNoPending condition-edit proposals (#365) — the rows above print the CURRENT knobs; each entry here carries the proposed values beside them. Present only when any are pending, so a proposal-free report is byte-identical

TDQS

A4.6/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations, the description carries the full burden and does well: it describes what is computed, what is returned inline, and that passing path writes the document to disk. It omits practical traits such as output size/limits and does not explicitly label the operation as read-only against project state, but the mutation side effect (file write) is disclosed.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Front-loaded with the resource identity, then contents, then sibling contrast, then workflow pairing, then return behavior. It is dense and long, and the 'A report alone is HALF the deliverable' framing is editorial, but nearly every sentence carries routing or payload information.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

An output schema exists so return values need not be spelled out, yet the description still summarizes the document's shape. Combined with explicit sibling contrasts, workflow pairing, and file-write behavior, nothing an agent needs to call this correctly is missing.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 100%, so the baseline is 3, but the description adds real meaning: 'Returned inline; pass path to also write it to disk as JSON' clarifies the inline-vs-file duality that the schema's path description does not state on its own. project_name and overwrite semantics are left to the schema.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb/resource ('the computed Report document — "opentakeoff.report.v1"') and enumerates the payload contents (quantities with waste/multiplier, buy list, roll-up, base subtotals, scale provenance, annotations). It also explicitly distinguishes itself from two named siblings, export_takeoff and takeoff_summary, so an agent can route without opening schemas.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Gives an explicit decision rule: 'when the numbers are leaving for pricing, consume this,' contrasted against export_takeoff (raw config rows) and takeoff_summary (materials stripped). It further prescribes a companion workflow ('pair it with export_marked_pdf'), which is actionable routing guidance beyond mere context.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

export_takeoffA

The full "opentakeoff.takeoff_canvas.v1" annotations payload — exactly what the app autosaves, importable by it. Returned inline; pass path to also write it to disk as JSON. Coordinates are image px at render scale 2.0: PDF pt × 2, origin top-left, y down (the browser canvas's native space). Sheet payloads carry dims in both px and pt.

ParametersJSON Schema
NameRequiredDescriptionDefault
pathNoFile path to write the payload to
overwriteNoReplace the file at path even when it is not an OpenTakeoff export. Off by default: re-exporting over a previous export of your own already overwrites without this, so you only need it to deliberately destroy an unrelated file.

Output Schema

ParametersJSON Schema
NameRequiredDescription
rfisNoLive RFI records; withdrawn tombstones are omitted
unitsNoPresent only for a metric project; absent means imperial — the app's own diff-only convention
schemaYes
shapesYes
sheetsYes
markupsYes
approvalsNoApproval-family records (#176) — the estimator's APPROVED seals and the agent's verdict marks {id, actor, ts, sheet_id, at:[nx,ny], shape_id?, text?}. Present only when any exist (the canvas payload's own convention), so a verdict-free export stays byte-identical
proposalsNoProposal batches (#365) — present only when any exist. Shapes reference them by origin.proposal_id; the canvas shows one Accept per batch
conditionsYes
last_groupYes
sheet_tabsYes
sheet_groupYes
project_nameYes
sheet_levelsNoPresent only when a sheet carries a level label (the app omits it when empty)
condition_edit_proposalsNoPending condition-edit diffs (#365) — present only when any exist. Nothing on the condition changes until the estimator accepts in the canvas

TDQS

A3.8/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

No annotations are provided, so the description carries the full burden, and it does well: it discloses that the payload is returned inline by default, that passing path additionally writes JSON to disk, and it documents the exact coordinate conventions (image px at scale 2.0, PDF pt × 2, top-left origin, y down) and that sheet payloads carry px and pt dims. It does not explicitly frame the operation as read-only versus file-writing in safety terms, which keeps it from a 5.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The definition is front-loaded with what the payload is, then adds invocation and coordinate details in compact clauses. Every sentence earns its place, though the dense coordinate-system sentence is somewhat tangential to tool selection.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

An output schema exists, so return values need not be described, yet the description still explains the payload's identity and coordinate space. With 100% param coverage and no annotations, only the absence of explicit safety framing for the on-disk write leaves a minor gap.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 100%, so baseline is 3, but the description adds real meaning: 'Returned inline; pass path to also write it to disk as JSON' clarifies that path is an additive side-effect flag rather than a mode selector, which the schema alone does not convey. The overwrite semantics are fully covered by the schema itself and not restated.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose4/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description names a precise verb+resource — the full 'opentakeoff.takeoff_canvas.v1' annotations payload, exactly what the app autosaves and can re-import. This distinguishes it from the other export tools (export_dxf, export_report, export_marked_pdf) by making clear it is the native, round-trippable format. It stops short of explicitly naming those siblings, so 4 rather than 5.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines3/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description implies usage ('Returned inline; pass path to also write it to disk as JSON') but gives no explicit when-to-use or when-not-to-use guidance relative to the other export siblings. The condition for writing to disk is clear, but routing between export tools is left to inference.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

find_scheduleA

Locate a schedule table in the set (#87): pass a kind ("room finish", "material"/"finish") and get every matching table's sheet, title, headers, TOTAL row count, and REGION — sized for a view_sheet look or a read_sheet_text pull of exactly the table. A schedule continued across sheets is ONE match whose "parts" list every fragment (base first) with its own viewable region; tables read through rotated headers say so; a table answering for one building carries "building"; a table with delta/REV-marked rows says how many in "revised_rows". Errors with what WAS found when the asked-for kind isn't in the set. Coordinates are image px at render scale 2.0: PDF pt × 2, origin top-left, y down (the browser canvas's native space). Sheet payloads carry dims in both px and pt.

ParametersJSON Schema
NameRequiredDescriptionDefault
kindYes"room finish" (rooms → surface finishes), "finish"/"material" (codes → products), or "equipment" (MEP device schedules — fans, pumps, heaters, AHUs, VAVs, diffusers/grilles/registers — keyed by mark, proven by a powered or air-device column)

Output Schema

ParametersJSON Schema
NameRequiredDescription
matchesYes

TDQS

A4.2/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations, the description carries the full burden and delivers rich behavioral detail: continued schedules are one match with a parts list, rotated headers are flagged, building-specific tables carry a 'building' attribute, delta/REV-marked tables report revised_rows, errors include what was found, and the coordinate system is fully specified (image px at scale 2.0, origin top-left, y down). This gives the agent a clear mental model of edge cases and output structure.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is a single, dense paragraph that front-loads the tool's purpose and then layers on critical edge-case behavior and coordinate conventions. Every sentence contributes useful information, though the length and lack of structural breaks (e.g., bullets) slightly reduce readability.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given the complexity of schedule finding and the absence of annotations, the description covers purpose, return shape, edge cases (continuations, rotations, building, revised rows), error behavior, and coordinate system. An output schema exists, so return values need not be fully spelled out, but the description still provides sufficient context for correct invocation.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 100%, so the schema already defines the 'kind' parameter and its allowed values. The description repeats some of these values but omits 'equipment' (which is detailed in the schema), adding no meaningful semantics beyond what the schema provides. Baseline 3 is appropriate.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description states a specific verb (Locate) and resource (schedule table), and details exactly what is returned (sheet, title, headers, TOTAL row count, REGION). It distinguishes from siblings by positioning the output for subsequent view_sheet or read_sheet_text calls.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines3/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

It implies usage by saying results are 'sized for a view_sheet look or a read_sheet_text pull of exactly the table,' which suggests it's a preparatory step. However, it does not explicitly state when to use this tool versus alternatives like find_text or sweep_schedule_row, leaving the agent to infer the context.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

find_textA

LOCATE a known string on a sheet — the complement to read_sheet_text (which returns what a region SAYS; this finds WHERE a string you already know sits). Case-insensitive substring match against each pdf.js text run, so a room label split across runs ("OFFICE" then "134" as separate items) needs a find_text call per fragment, or read_sheet_text over a region to see the whole thing joined. Every hit tells you which room a polygon belongs to — the locate-then-trace workflow: find_text the room number, then measure_polygon (One-Click is temporarily gated on this server) on that room's wall faces. Optionally restrict to a region {x0, y0, x1, y1}; results cap at limit (default 200), with count/truncated telling you exactly how much a tighter region or higher limit would recover. Coordinates are image px at render scale 2.0: PDF pt × 2, origin top-left, y down (the browser canvas's native space). Sheet payloads carry dims in both px and pt.

ParametersJSON Schema
NameRequiredDescriptionDefault
qYesText to find — a room number ('134'), a label fragment ('RECEPTION'), a schedule tag ('CPT-1')
limitNoMax hits returned
sheetYes
regionNoRect in image px (origin top-left, y down); omit for the full sheet

Output Schema

ParametersJSON Schema
NameRequiredDescription
qYes
hitsYes
countYesTotal matches before the limit cap
sheetYes
truncatedYestrue = count exceeds hits.length; narrow the region or raise limit

TDQS

A4.6/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

No annotations are provided, so the description carries the full burden, and it does well: it discloses case-insensitivity, substring matching against pdf.js text runs, the room-label-split-across-runs caveat, the limit default of 200 and truncation/count behavior, and the coordinate system (image px, scale 2.0, PDF pt × 2, top-left origin, y down). It also flags that measure_polygon's One-Click is temporarily gated. Minor remaining gap: no explicit statement of auth/permission requirements or whether it mutates anything (though 'find' strongly implies read-only).

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Front-loads purpose with the complement contrast first, then behavior, then the workflow, then coordinates. Sized appropriately for a tool with four parameters and non-obvious coordinate semantics, though it is dense and slightly long-winded in places (the room-label split example runs long).

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given the tool's complexity (nested region object, coordinate scale, truncation semantics) and that an output schema exists so return values need not be re-explained, the description covers everything an agent needs: what it finds, how matching works, how to scope, how the cap works, and the coordinate frame. Nothing material is missing.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 75%, high enough that the schema carries the heavy lifting (q, limit, region bounds all documented). The description still adds value: it clarifies the optional region syntax idea and the meaning of count/truncated in relation to limit, and reiterates the coordinate space. Not a full 5 because the region's numeric bounds are only in the schema, not elaborated in the description.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb (LOCATE) and resource (a known string on a sheet), and explicitly positions it as the complement to the sibling read_sheet_text with a parenthetical contrast. An agent can immediately distinguish the two tools.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Gives explicit when-to-use (you already know the string and want its location), when-not (read_sheet_text returns what a region SAYS), and even the locate-then-trace workflow chaining into measure_polygon. No inference required.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

get_sheet_vectorsA

The STROKES — the sheet's vector layer exactly as the engine is fed it, so you can run your own geometry against what the app sees (#367). view_sheet lets you look, read_sheet_text lets you read, sheet_context classifies a region; this returns the raw extractor output that all of them and every shape verb (the wall network's pen weights, symbol_sweep's matching) work from: flat points [x1, y1, x2, y2, …] in image px, one meta byte per segment (low nibble flags: 1 curve chord, 2 clip-only, 4 fill-only, 8 polyline arc; pen width = meta >> 4), per-segment stroke luminance, the drawn figure each segment belongs to (subpath ordinal), the sheet's placed-image area, and its PDF layer table with a per-segment layer index (sheet_info.layers names and classifies the same ids). Nothing is classified, decimated, or merged here — segments arrive whole, in extraction order, undecimated, which is the point: a reader can build its own room finder, symbol matcher, or wall classifier on the same array and commit through the existing verbs with provenance intact. Paged, never clipped silently: a dense sheet runs to hundreds of thousands of segments, so the reply carries limit (default 20000 segments, ceiling 100000) and the ledger offset + returned + dropped === total on every page; dropped is exactly what passing next_cursor as cursor recovers. region keeps every segment that intersects the rect (endpoints untouched — the same keep test sheet_context uses, so total here equals sheet_context's total_in_region) and echoes it post-clamp. Read-only and stateless — no shape, condition, or scale is touched. A scan has no strokes: the verb refuses and names view_sheet as the path. Coordinates are image px at render scale 2.0: PDF pt × 2, origin top-left, y down (the browser canvas's native space). Sheet payloads carry dims in both px and pt.

ParametersJSON Schema
NameRequiredDescriptionDefault
limitNoPage size in segments (default 20000, max 100000) — about 1 MB of JSON per 20000
sheetYesSheet key ("plan.pdf", "plan.pdf#2") or title-block number ("A-101")
cursorNoA previous reply's next_cursor — resume paging there. Omit for the first page
regionNoRect in image px (origin top-left, y down); omit for the full sheet. A segment is kept when it intersects the rect

Output Schema

ParametersJSON Schema
NameRequiredDescription
lumYesAligned with points: stroke luminance 0 (black) – 255 (white), Rec. 709 over the stroke colour in force when the path was built. Empty only when the geometry carries no luminance channel
metaYesOne byte per segment, aligned with points: low nibble flags — 1 curve chord (bezier tessellation or detected polyline arc), 2 clip-only path (invisible ink), 4 filled-not-stroked, 8 polyline-arc provenance; high nibble (meta >> 4) = device pen width in px
noteNoPresent when the sheet is a scan wrapper — the few segments here are a frame, not the drawing
pageYes
limitYesThe page size that applied
sheetYes
totalYesSegments on the sheet intersecting the region — the whole set, before paging
offsetYesMatching segments BEFORE this page (skipped by cursor)
pointsYesFlat [x1, y1, x2, y2, …] — four numbers per segment, image px to 0.01, endpoints exactly as extracted (never clipped to the region, never merged); segments arrive in extraction order, the order the engine sees
regionYesThe region actually resolved, post-clamp [x0, y0, x1, y1] image px — the same rect sheet_context and view_sheet take, so all three verbs answer in one frame
droppedYesMatching segments AFTER this page that limit cut — exactly what next_cursor recovers. offset + returned + dropped === total on every reply; 0 means this page ends the set
subpathYesAligned with points: ordinal of the drawn FIGURE each segment belongs to (each moveTo starts one, each rectangle is one) — segments sharing a value are one path; −1 = outside every figure
layer_ofYesAligned with points: index into layer_ids, or −1 for a segment outside every layer. Empty only when the geometry carries no layer channel
returnedYesSegments in this reply
sheet_pxYes
layer_idsYesThe sheet's PDF Optional Content Group ids in first-seen order — the same ids sheet_info.layers reports (with names and roles); [] on an unlayered sheet
image_areaYesTotal placed-image area on the sheet, image px² — a value near the sheet area means a scan or photo underlay sits under whatever linework there is
next_cursorNoPass as cursor to fetch the next page; absent when dropped is 0

TDQS

A4.7/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations, the description carries the full burden and does so thoroughly: paging semantics (default 20000, ceiling 100000, ledger offset + returned + dropped === total, dropped recoverable via next_cursor), region intersection behavior with post-clamp echo and alignment to sheet_context's total_in_region, read-only and stateless operation, and refusal behavior for scans. It also specifies coordinate space and origin.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness3/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The first sentence is front-loaded and clear, but the rest is a single massive paragraph dense with technical detail. While most sentences are informative, some repetition (e.g., 'undecimated') and lack of bullet-point structure reduce economy. It is adequate but could be more scannable for an agent.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given the tool's complexity, the absence of annotations, and the presence of an output schema, the description is complete enough for an agent to call the tool correctly. It covers paging, region filtering, refusal cases, coordinate system, and output structure, leaving no critical gap for correct invocation.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 100%, so the baseline is 3. The description adds meaning beyond the schema for the region parameter by explaining the exact keep test (intersects rect, endpoints untouched) and that total equals sheet_context's total_in_region, and clarifies the paging contract for limit/cursor. It does not add much for the sheet parameter, but the added region semantics lift it above baseline.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb and resource: returns the sheet's raw vector stroke layer exactly as the engine sees it, and explicitly distinguishes itself from view_sheet, read_sheet_text, sheet_context, and shape verbs by naming what each does instead. The agent can identify this tool's unique output without opening any schema.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Explicitly names the sibling alternatives and their roles (view_sheet looks, read_sheet_text reads, sheet_context classifies a region) and states the use case: to run your own geometry against the app's raw extractor output. It also provides a when-not case: a scan has no strokes and the verb refuses, naming view_sheet as the path.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

import_takeoffA

The way BACK IN (#151): load an "opentakeoff.takeoff_canvas.v1" file — a prior export_takeoff, or the app's own save — into this session, through the SAME tested merge rules as the app's Sheet-menu import: finish-tag identity joins imported conditions onto this session's own (their knobs win), new ids append, duplicate ids skip (re-import is idempotent), and THIS session's calibration wins per sheet. An uncalibrated empty session adopts the file wholesale. New dimensional shapes refuse atomically if their source scale differs from the session calibration or is missing; align scales and re-export, or use a fresh session. Counts and duplicate IDs are exempt. Legacy agent traces without review flags arrive reviewed:false. Resume yesterday's work, extend a takeoff a human already reviewed (their ink stays ink — reviewed shapes arrive untouchable by agent verbs), or audit someone else's export with list_shapes/takeoff_summary. Requires a loaded plan; shapes referencing OTHER files ride along and count in totals but can't be viewed against this document — the reply's unknown_files names them. Approval marks ride the file too — transport, not minting: an estimator seal arriving by import stays estimator ink, listable but untouchable here. undo_last removes the imported SHAPES as one step; adopted conditions, scales, annotations, and approval marks stay.

ParametersJSON Schema
NameRequiredDescriptionDefault
pathYesPath to a takeoff_canvas.v1 JSON file on disk

Output Schema

ParametersJSON Schema
NameRequiredDescription
fileYesBasename of the imported file
noteYes
replacedYestrue = the session was empty and adopted the file wholesale
shapes_addedYes
shapes_totalYes
unknown_filesYesFiles referenced by imported shapes that this document doesn't have — they count in totals but can't be viewed here
rules_importedYesCorrection rules (#88) that arrived with the file — apply_rules re-runs them
scales_adoptedYesSheets whose calibration came from the file (this session's own always wins)
shapes_pendingYesOf the added shapes, how many are unreviewed machine pencil
conditions_addedYes
conditions_mergedYesImported conditions that joined an existing finish tag (its knobs won)

TDQS

A4.5/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

No annotations are provided, so the description carries the full behavioral burden, and it does: merge identity rules, idempotency of re-import, this-session-calibration precedence, atomic refusal on scale mismatch, reviewed:false default for legacy traces, untouchability of imported reviewed shapes, unknown_files behavior, and undo_last semantics. This is rich, non-obvious behavioral disclosure beyond any structured field.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness2/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Front-loaded with confusing flavor text referencing a ticket '#151' and 'The way BACK IN', then one extremely dense run-on sentence packing many distinct behaviors. It is substantially overlong for a one-parameter tool and hard to parse, though the content is nearly all relevant, so not a 1.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Complete enough: since an output schema exists, it needn't explain return values, and it still correctly mentions the reply's unknown_files field. Covers prerequisites, merge semantics, refusal behavior, and downstream effects (undo_last, approval marks). Minor gap: it doesn't plainly restate what the required 'path' refers to at the top.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 100% and the single parameter is already documented in the schema, so baseline is 3. The description adds meaning by specifying the file format/version ('opentakeoff.takeoff_canvas.v1') and that a prior export_takeoff or app save is acceptable, which clarifies what path should point to beyond the schema's generic 'JSON file'.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb (load/import) and resource (an opentakeoff.takeoff_canvas.v1 file), and explicitly distinguishes itself from export_takeoff (its counterpart) and from list_shapes/takeoff_summary which are named as audit alternatives. The merge-rule detail makes the tool's exact role unambiguous.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Gives explicit when-to-use scenarios (resume yesterday's work, extend a human-reviewed takeoff, audit someone else's export) and names the alternative tools for the audit case (list_shapes/takeoff_summary). It also states a prerequisite ('Requires a loaded plan') and remediation paths (align scales and re-export, or use a fresh session) for refusal.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

list_annotationsA

Every annotation on the takeoff, with condition_id RESOLVED to its finish tag so you can act on the reply without joining against conditions[]. Filter by sheet, by condition, or both. Coordinates come back in image px (the same frame you passed in), not the normalized form they're stored as. unattached counts the notes carrying no condition — the candidates for link_annotation. verdicts is the approval family's inventory (mark_verdict/delete_verdict): every mark with its actor stated — the estimator's APPROVED ring or the agent's AGENT diamond — under the same filters, a condition filter reaching a verdict through its target shape. Coordinates are image px at render scale 2.0: PDF pt × 2, origin top-left, y down (the browser canvas's native space). Sheet payloads carry dims in both px and pt.

ParametersJSON Schema
NameRequiredDescriptionDefault
sheetNoOnly annotations on this sheet
conditionNoOnly annotations attached to this finish tag

Output Schema

ParametersJSON Schema
NameRequiredDescription
countYes
verdictsYesApproval-family records (#176) under the same filters: sheet applies directly; a condition filter reaches a verdict THROUGH its target shape (a sheet-point mark carries no scope and drops out)
unattachedYesHow many carry no condition — candidates for link_annotation
annotationsYes
verdict_countYes

TDQS

A4.8/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations provided, the description fully discloses critical behavior: coordinate transformation (image px at render scale 2.0, PDF pt × 2, origin top-left, y down), resolution of condition_id to finish tags, and the semantics of 'unattached' and 'verdicts'. This goes far beyond basic expectations.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is dense but every sentence carries necessary behavioral detail. It efficiently covers output fields, filters, and coordinate system without redundancy. Slightly long, but the complexity of information justifies the length.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a tool with no annotations and complex output behavior, the description covers all essential aspects: filters, coordinate system, resolved condition semantics, and the purpose of special fields. An output schema exists, so not explaining every return field is acceptable; the description still adds enough context to use the tool correctly.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema descriptions already cover both parameters (100% coverage). The description adds meaning by clarifying that filters can be combined ('or both'), and explicitly maps the 'condition' parameter to the resolved finish tag concept, which is not fully apparent from the schema alone.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states the tool lists every annotation on the takeoff with condition_id resolved to its finish tag. It distinguishes itself from siblings by mentioning resolved conditions, unattached counts, and verdicts, which positions it as the comprehensive annotation inventory tool.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Explicitly explains filtering by sheet, condition, or both, and ties output fields to concrete actions: 'unattached' identifies candidates for link_annotation, and 'verdicts' serves the mark_verdict/delete_verdict family. This provides clear when-to-use guidance versus related tools.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

list_rfisA

Every RFI in the register with its status, sheet, who raised it (actor) and whether an agent-raised one is still pending the estimator's acceptance, its linked markup ids, and the finish tags those markups are attached to — the scopes the question touches. withdrawn[] lists the numbers delete_rfi tombstoned, so a gap in the sequence is explained rather than silent. Read this before raising a question the register already holds.

ParametersJSON Schema
NameRequiredDescriptionDefault

No parameters

Output Schema

ParametersJSON Schema
NameRequiredDescription
openYesStill awaiting an answer
rfisYesEvery live RFI, register order
countYes
pendingYesAgent-raised and not yet accepted by an estimator
withdrawnYesNumbers of withdrawn RFIs (delete_rfi tombstones) — the gaps in the sequence, explained

TDQS

A4.3/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations, the description carries the full behavioral burden. It discloses the returned fields, the meaning of withdrawn[] (tombstoned by delete_rfi), and the pending-acceptance state of agent-raised RFIs. It does not state read-only/authorization behavior explicitly, but the rich semantic disclosure is strong.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness3/5

Is the description appropriately sized, front-loaded, and free of redundancy?

It is front-loaded and every clause carries information, but it is a single dense sentence with multiple em dashes and parenthetical asides. The lack of sentence breaks makes it harder to parse than necessary, and some return-field detail is redundant with the output schema.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given a zero-parameter tool with an output schema and no annotations, the description supplies the semantic context an agent needs: what an RFI listing contains, what the withdrawn[] gap means, and when to consult it before creating a new RFI. Nothing essential is missing.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

The tool has zero input parameters, so there is no parameter semantics to explain; the empty schema's 100% coverage makes the baseline 4. The description appropriately does not invent parameter guidance.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description states a specific verb-and-resource pairing: listing every RFI in the register with explicitly enumerated attributes. It distinguishes the tool from siblings like create_rfi and delete_rfi by scoping it as the read-before-you-raise view and by explaining that delete_rfi tombstones entries.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

It gives a clear trigger — 'Read this before raising a question the register already holds' — which routes the agent away from create_rfi when the register may already contain the answer. It does not name any explicit exclusion or alternative beyond that implication, so it falls short of full when/when-not guidance.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

list_shapesA

The mid-session shape inventory (#149): every committed shape's id, sheet, condition tag, role, quantities, room label, vertex count, and review state in one compact read — the ids edit_shape and delete_shape assume you have, without pulling the whole export_takeoff payload to find one shape. Filter by sheet, by condition, or both; filters narrow, an empty list is a result, not an error.

ParametersJSON Schema
NameRequiredDescriptionDefault
sheetNoOnly shapes on this sheet
conditionNoOnly shapes under this finish tag (must exist)

Output Schema

ParametersJSON Schema
NameRequiredDescription
countYes
shapesYes

TDQS

A4.4/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations, the description carries the behavioral burden and does disclose the key traits: read scope is limited to 'committed' shapes, filters only narrow (never broaden), and an empty result is normal rather than a failure. It does not cover auth/permission needs or pagination limits, which would be relevant for a large inventory read.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Front-loaded with the core purpose and field list, and the operational caveats come last. It is a single dense sentence with some incidental noise (the '#149' issue reference) but each clause largely earns its place.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a two-param, optional-filter read whose output schema already defines the return payload, the description covers scope, filter behavior, and empty-result handling completely. Nothing needed to invoke it correctly is missing.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 100%, so both parameters are already documented (sheet = 'Only shapes on this sheet', condition = 'Only shapes under this finish tag'). The description adds only the semantic that filters narrow rather than expand, which is useful but minimal beyond the schema.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb+resource ('shape inventory') and enumerates the exact fields returned (id, sheet, condition tag, role, quantities, room label, vertex count, review state). It explicitly distinguishes itself from sibling export_takeoff and positions itself as the id source that edit_shape and delete_shape require.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Names the alternative it replaces ('without pulling the whole export_takeoff payload'), states the filtering options (sheet, condition, or both), and clarifies result semantics ('filters narrow, an empty list is a result, not an error'). An agent knows exactly when to reach for this vs. the bulk export.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

load_planA

Open a plan PDF from disk. Default: replace the whole session (previous documents, scales, conditions, and shapes are cleared). merge: true ADDS the document to the working set instead (#152) — a bid set is plans + schedule + addenda, not one PDF — keeping every scale, condition, and shape; sheet keys carry file names so documents never collide, the sheet graph spans the whole set (resolve_tag can chain a plan tag on one file to a schedule row in another), and the marked set covers every worked sheet. Re-loading an already-merged file is refused — reload = replace, deliberately. Returns file, files, page_count, and one entry per sheet. The loaded sheets also become browsable resources (takeoff://sheets). Coordinates are image px at render scale 2.0: PDF pt × 2, origin top-left, y down (the browser canvas's native space). Sheet payloads carry dims in both px and pt.

ParametersJSON Schema
NameRequiredDescriptionDefault
pathYesPath to a plan PDF on disk
mergeNotrue = ADD this document to the working set, keeping all existing work (merge into an empty session is just a load)

Output Schema

ParametersJSON Schema
NameRequiredDescription
fileYesThe document just loaded (basename)
noteYes
filesYesEvery document in the working set, load order (#152 — one entry unless merge was used)
sheetsYesEVERY sheet in the working set, not just the file loaded by this call
page_countYesTotal sheets across the working set

TDQS

A4.9/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations provided, the description carries the full burden and excels: it discloses the destructive default (clears session), the merge behavior, the refusal condition, the return payload, coordinate system details, and the conversion to browsable resources. This is a comprehensive safety/behavioral profile.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is a single dense paragraph but logically organized: action, default behavior, merge variant, edge case, return values, resources, and coordinate system. Every sentence adds necessary information with no filler, achieving conciseness through purposeful density.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given the tool's moderate complexity and absence of annotations, the description covers everything an agent needs: the two modes, re-load restriction, return fields, coordinate transformation, and resource availability. It is fully sufficient for correct invocation and interpretation.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

The schema already documents both parameters with 100% coverage, so the baseline is 3. The description adds meaningful context beyond the schema by elaborating on the default behavior, the meaning of merge:true, and the edge case of merging into an empty session, increasing clarity for parameter usage.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description opens with a clear verb+resource ('Open a plan PDF from disk') and immediately clarifies the default replacement behavior versus merge mode. This distinguishes it from sibling tools that operate on already-loaded sheets or produce analyses.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

It explicitly contrasts the default replace-all behavior with merge:true for adding to a working set, explains the bid-set use case, and states that re-loading an already-merged file is refused. This gives clear when-to-use and when-not-to-use guidance, including a concrete scenario.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

mark_verdictA

Mark the agent's VERDICT on work — the pencil half of the approval family, and the only half an agent can mint. Two actors exist on the record: the estimator's APPROVED ring is ink, minted solely by a human's click at the canvas's Approve tool; this tool mints the AGENT diamond and structurally nothing else — it takes no actor input to misuse. Target the work either way: shape_id anchors the mark ON a committed shape (a room at its area centroid, a run at its on-path midpoint, a count marker at its point) and records WHAT was marked — the shape_id stays on the record as provenance, and the glyph keeps its own anchor even if the shape is later deleted; or sheet + at drops the mark at a sheet point (image px). Exactly one target. Optional text rides the record through every export; the glyph itself always reads AGENT. A verdict touches no quantity and gates nothing: it is the agent's signed claim that it checked this work — pencil beside the estimator's ink, never in its place. The mark renders as the graphite AGENT diamond on the canvas and in the marked set, the marked-set cover tallies the split ("Approval stamps: N estimator-approved · M agent-marked"), and the record rides the annotations payload through export_takeoff / import_takeoff and the app's own saves. One mark per shape (re-mark = delete_verdict, then mark again); list_annotations returns the inventory in verdicts[]; undo_last steps over a mark exactly like any other mutation. Coordinates are image px at render scale 2.0: PDF pt × 2, origin top-left, y down (the browser canvas's native space). Sheet payloads carry dims in both px and pt.

ParametersJSON Schema
NameRequiredDescriptionDefault
atNoSheet-point mode: where the AGENT diamond renders (image px)
textNoOptional short note riding the record and every export — the glyph always reads AGENT
sheetNoSheet-point mode: the sheet, together with at
shape_idNoMark a committed shape (list_shapes has the ids) — anchored on the shape, recorded as provenance. Exactly one target: this OR sheet + at

Output Schema

ParametersJSON Schema
NameRequiredDescription
atNoWhere the AGENT diamond renders (image px) — absent only when the marked shape rides a sheet from a file this session hasn't loaded (#152)
idYesThe minted record id ("apr-…")
tsYesISO-8601 mint time
noteYes
textNo
actorYesAlways agent — this tool is structurally incapable of minting the estimator's seal
sheetYes
shape_idNoShape mode: the committed shape this verdict is about
conditionNoShape mode: the marked shape's finish tag, resolved

TDQS

A5/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations, the description carries full responsibility for behavioral disclosure. It details side effects (provenance recording, glyph anchor persists even if shape deleted), non-effects (touches no quantity, gates nothing), rendering behavior, tally in marked-set, payload propagation, and coordinate conventions. It also explains the one-mark-per-shape rule and interaction with undo_last. This is exemplary transparency.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is long but every sentence contributes unique, essential information. It is front-loaded with purpose, then systematically covers targeting, behavior, rendering, and coordinates. No filler or repetition; efficient for the tool's complexity.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given the tool's moderate complexity, the description is remarkably complete. It covers all parameters, behavioral nuances, sibling relationships, coordinate systems, and lifecycle interactions (re-mark, undo, list). The output schema exists, so not describing return values is acceptable. There are no significant gaps.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters5/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Although schema coverage is 100%, the description adds substantial meaning beyond the schema. It clarifies the mutually exclusive targeting modes (shape_id vs sheet+at), the coordinate system (image px at render scale 2.0, top-left origin, y down), and the exact role of text. This significantly enriches parameter understanding.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description opens with a clear, specific verb+resource ('Mark the agent's VERDICT on work') and immediately distinguishes this tool from the estimator's approval ring, identifying it as the only half an agent can mint. It explicitly states what the tool does and how it differs from siblings like delete_verdict and annotate.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description provides explicit when-to-use guidance: it distinguishes the two targeting modes (shape_id vs sheet+at) and states 'Exactly one target.' It also mentions alternatives such as delete_verdict for re-marking and list_annotations for inventory, and clarifies that this is the agent's tool distinct from the estimator's approval. This exceeds basic usage guidance.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

measure_lineA

Measure an open polyline (min 2 points, image px): length_lf at the sheet's scale. Requires the scale to be set. Pass condition to commit it as a linear shape (base, transitions, feature strips). A curved run (base along a radius wall, a curved feature strip) takes arc_through: one point on the bow, marked. Coordinates are image px at render scale 2.0: PDF pt × 2, origin top-left, y down (the browser canvas's native space). Sheet payloads carry dims in both px and pt.

ParametersJSON Schema
NameRequiredDescriptionDefault
ptsYes
sheetYes
conditionNo
arc_throughNoIndices of points that are the MIDDLE of an arc: the trace runs the point before → this point → the point after as the unique circle through the three (the canvas's Curve mode). For a curved wall put one point anywhere ON the bow between its two ends and mark it. The arc is baked to ordinary vertices on commit and origin.curved is stamped; a mark on an end of an open run, or two marks in a row, refuses.

Output Schema

ParametersJSON Schema
NameRequiredDescription
arcsNoHow many arc_through bows were laid — present only when the trace was bent; the vertices reported are the baked arc, not the three points you gave
nptsYes
shape_idNoPresent when condition was passed and the shape committed
length_lfYes

TDQS

A4/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations, the description carries the burden and does well: it discloses the scale prerequisite, that condition commits the trace as a persistent linear shape, that arc_through bakes arcs on commit, and precisely defines the coordinate space (render scale 2.0, pt×2, origin top-left, y down). It does not state permissions, rate limits, or exactly what a non-committing call returns, so not a 5.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Front-loaded with the core action and unit, followed by prerequisites, commit behavior, curved-run handling, and the coordinate frame. Every sentence carries information, though the coordinate-space sentence is dense and could be tightened.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

An output schema exists, so return values need not be explained. The description covers the scale precondition, commit semantics, arc behavior, and the drawing coordinate system — enough to call it correctly. Missing only edge behavior for a measure-only (no condition) call and any mutation/permission caveats.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is only 25%, so the description must compensate, and it largely does: it explains pts (min 2 image-px points and the coordinate frame), condition (commit semantics with example categories base/transitions/feature strips), and arc_through (one point on the bow, marked). Only 'sheet' is left undocumented, so the added meaning is substantial despite the coverage gap.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose4/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb and resource ('Measure an open polyline'), the minimum input, and the output unit ('length_lf at the sheet's scale'). The 'open polyline'/'linear shape' framing implicitly separates it from measure_polygon/measure_surface, but no sibling is named explicitly, so it stops short of full 5-level differentiation.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Gives clear context and a precondition: 'Requires the scale to be set', plus the conditional rule that condition commits it as a linear shape and arc_through is used for curved runs. It lacks an explicit 'when not to use this / use measure_polygon instead' exclusion, so it is clear context rather than full routing guidance.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

measure_polygonA

Measure a closed polygon you supply (min 3 vertices, image px): area_sf and perimeter_lf at the sheet's scale. Requires the scale to be set. Pass condition to commit it; role "deduct" subtracts. A room ring belongs on the innermost wall-face strokes from get_sheet_vectors, crossing each door opening on the wall centerline and wrapping columns and stubs; never on a hatch edge, casework or a door leaf. Check it with view_sheet overlay:true on a tight crop and fix it with edit_shape. A CURVED wall is a circle: do not chord it and do not hand-tessellate it — give the bow one point on the wall and list its index in arc_through. Coordinates are image px at render scale 2.0: PDF pt × 2, origin top-left, y down (the browser canvas's native space). Sheet payloads carry dims in both px and pt.

ParametersJSON Schema
NameRequiredDescriptionDefault
roleNofloor_area
sheetYes
vertsYes
conditionNo
arc_throughNoIndices of points that are the MIDDLE of an arc: the trace runs the point before → this point → the point after as the unique circle through the three (the canvas's Curve mode). For a curved wall put one point anywhere ON the bow between its two ends and mark it. The arc is baked to ordinary vertices on commit and origin.curved is stamped; a mark on an end of an open run, or two marks in a row, refuses.

Output Schema

ParametersJSON Schema
NameRequiredDescription
arcsNoHow many arc_through bows were laid — present only when the trace was bent; the vertices reported are the baked arc, not the three points you gave
nvertsYes
area_sfYes
warningNoMixed-scale warning (#153): a scale note disagreeing with the sheet's sits in the measured region — verify before trusting these numbers
shape_idNoPresent when condition was passed and the shape committed
perimeter_lfYes

TDQS

A4.4/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations, the description carries the full burden and does well: it discloses the scale prerequisite, that condition commits vs a bare measurement, that role 'deduct' subtracts, and that arcs are baked to vertices on commit with origin.curved stamped. It does not cover failure handling beyond the arc refusal note (which lives in the schema) or idempotency, so it is strong but not exhaustive.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Front-loads purpose and units before the tracing guidance, and every clause is task-relevant for a takeoff tool. It is dense and long - the room-ring and curved-wall paragraphs could be tighter - but nothing is filler.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Complete for a complex geometry tool: coordinate convention (image px, render scale 2.0, origin top-left, y down), units in both px and pt, prerequisites, arc semantics, and a verify-then-fix workflow. An output schema exists, so return values need not be restated.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is only 20%, so the description must compensate and largely does: it explains verts (min 3, image px), role ('deduct' subtracts, default floor_area), condition (commit), and the arc_through curved-wall pattern including where to place the point. Only 'sheet' is left to inference, which is low risk given the surrounding context.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

Names a specific verb and resource (measure a closed polygon you supply) and states the concrete outputs (area_sf and perimeter_lf at the sheet's scale). This clearly separates it from measure_line and measure_surface without needing to open any schema.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Gives real usage context: state must be set first, pass condition to commit, use role 'deduct' to subtract, verify with view_sheet overlay:true and fix with edit_shape. It stops short of naming measure_line/measure_surface as the alternatives for non-polygon work, so selection vs those siblings is left implicit.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

measure_surfaceA

Surface Area — wall SF (#146): trace an OPEN run along the wall in plan view (min 2 points, image px) and the quantity is traced LF × height. This is how wall tile, wainscot, and wall systems are taken off — the quantity family measure_polygon cannot produce. Height lives on the CONDITION (the canvas's H knob): pass height_ft to set it on this call (journals as its own undo step, like typing H before tracing), or set it once with edit_condition; with neither, this refuses and mints nothing. The shape snapshots the height it was quantified at. Requires the sheet's scale. Coordinates are image px at render scale 2.0: PDF pt × 2, origin top-left, y down (the browser canvas's native space). Sheet payloads carry dims in both px and pt.

ParametersJSON Schema
NameRequiredDescriptionDefault
ptsYesThe wall run, an open polyline (image px)
sheetYes
conditionYesFinish tag to commit under (minted on first use), e.g. 'CT-W1'
height_ftNoWall height in feet — written to the condition's H knob first, then used
arc_throughNoIndices of points that are the MIDDLE of an arc: the trace runs the point before → this point → the point after as the unique circle through the three (the canvas's Curve mode). For a curved wall put one point anywhere ON the bow between its two ends and mark it. The arc is baked to ordinary vertices on commit and origin.curved is stamped; a mark on an end of an open run, or two marks in a row, refuses.

Output Schema

ParametersJSON Schema
NameRequiredDescription
arcsNoHow many arc_through bows were laid — present only when the trace was bent; the vertices reported are the baked arc, not the three points you gave
nptsYes
area_sfYeslength_lf × height_ft — the wall SF committed
shape_idYes
conditionYes
height_ftYesThe height this shape was quantified at (snapshotted on the shape)
length_lfYesThe traced run's open length

TDQS

A4.7/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations, the description carries the full burden and delivers unusual detail: the refusal-on-missing-height behavior, the separate undo journal entry for height_ft, the height snapshot on the committed shape, and the arc baking to ordinary vertices with origin.curved stamped. It does not cover permissions/auth, and output schema covers returns, so the safety profile is thin but the mutation semantics are well disclosed.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Front-loaded with the quantity identity and use case, then the prerequisites and coordinate convention. It is dense and long, with some clause-heavy sentences, but nearly every sentence adds operative information rather than restating the name.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a mutation tool with no annotations, the description covers the failure mode, the side effect on the condition, undo journaling, scaling requirements, and the coordinate space; with an output schema present, return values need not be explained. Nothing an agent needs to invoke it correctly appears to be missing.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters5/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 80%, so the baseline would be 3, but the description adds real meaning beyond the schema: the coordinate system (image px at render scale 2.0 = PDF pt × 2, origin top-left, y down), arc_through's Curve-mode behavior and its refuse cases, and the ordering effect of height_ft on the condition. These are semantics the schema strings do not convey.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb and resource ('Surface Area — wall SF: trace an OPEN run along the wall in plan view') and contrasts it with the sibling it cannot replace ('the quantity family measure_polygon cannot produce'). An agent can distinguish it from measure_line and measure_polygon without opening the schema.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Explicitly names the use cases (wall tile, wainscot, wall systems) and the alternative (measure_polygon), and spells out the preconditions: height must come from height_ft or edit_condition, 'with neither, this refuses and mints nothing', plus the sheet scale requirement. Both the when and the when-not are stated.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

place_countA

Count markers — EA (#146): one point, one each. Thresholds, stair nosings, floor boxes, entrance mats — the scale-free quantity family. Commits one count shape per point (computed {count: 1}, exactly the canvas's Count tool), NO scale required, and the whole call is ONE undo step. takeoff_summary reports them as ea; the marked set draws each marker. Coordinates are image px at render scale 2.0: PDF pt × 2, origin top-left, y down (the browser canvas's native space). Sheet payloads carry dims in both px and pt.

ParametersJSON Schema
NameRequiredDescriptionDefault
sheetYes
pointsYesMarker positions (image px), one committed count shape each
conditionYesFinish tag to commit under (minted on first use), e.g. 'TR-1'

Output Schema

ParametersJSON Schema
NameRequiredDescription
ea_totalYesThe condition's total EA after this call
committedYesCount shapes committed by this call — one per point
conditionYes
shape_idsYes

TDQS

A3.8/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations, the description does real work: it discloses atomic undo ('the whole call is ONE undo step'), that each point yields a computed {count: 1} shape, that no scale is required, and how takeoff_summary will report the result. Auth/permission needs are not covered, but the behavioral payload is substantial.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Opens with the core verb and unit, then packs in coordinate, undo, and reporting detail. Dense but front-loaded and each sentence carries information; slightly rambling with parenthetical asides.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a 3-required-param mutation with an output schema (so returns needn't be explained), the description covers coordinate space, atomicity, condition minting, and downstream reporting. Nothing critical is missing, though permission/error behavior is absent.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 67% and the description adds meaning beyond it: it defines the coordinate space precisely (image px at render scale 2.0, PDF pt × 2, origin top-left, y down) and notes the condition tag is minted on first use. This meaningfully supplements the points/condition schema entries.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose4/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb and resource ('Count markers', committing one count shape per point) and clarifies it is the scale-free EA quantity family. It does not explicitly name or differentiate from the sibling count_marks or symbol_sweep, so the reader must infer the boundary.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines3/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The examples (thresholds, stair nosings, floor boxes, entrance mats) and 'NO scale required' imply the right context for scale-free counting, but there is no explicit when-to-use vs count_marks/symbol_sweep guidance or exclusion.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

propose_condition_editA

PROPOSE a change to a condition instead of making it (#365): a diff — a new finish tag (rename), waste %, ×N multiplier, height_ft, roll_setup — held PENDING until the estimator accepts it from the panel. edit_condition is the wrong power for "I think this condition is wrong": a tag rename or a knob change should be a decision the estimator makes, not one they discover. Until acceptance NOTHING changes — takeoff_summary and export_report keep computing from the current values and carry the diff beside them (proposed_condition_edits), and once accepted the report is byte-for-byte what a direct edit_condition would have produced (the same write path). Only fields that differ from the current value are recorded; a proposal that changes nothing is refused, and a rename onto a tag another condition already carries is refused (two conditions on one tag would make one unreachable). One pending diff per condition — proposing again replaces the earlier one (undo_last restores it). rationale is required: the estimator accepts a reason.

ParametersJSON Schema
NameRequiredDescriptionDefault
conditionYesFinish tag of an EXISTING condition, e.g. 'CPT-1'
height_ftNo
rationaleYesWhy — the schedule row, the spec section, the sheet note that decided it
waste_pctNo
finish_tagNoProposed new tag (a rename)
multiplierNo
roll_setupNoProposed roll-goods setup, or null to propose opting out

Output Schema

ParametersJSON Schema
NameRequiredDescription
noteYes
currentYes
proposedYes
conditionYes
rationaleYes
proposal_idYes
condition_idYes
replaced_proposal_idNoPresent when this proposal replaced an earlier pending one on the same condition

TDQS

A4.8/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations, the description carries the full burden and delivers: the diff is held PENDING until accepted, nothing changes until then, takeoff_summary and export_report keep computing current values and carry the diff as proposed_condition_edits, and acceptance is byte-for-byte identical to a direct edit_condition. It also discloses four distinct refusal/failure modes (no-op proposal, tag collision, one-pending-diff-replaces-earlier, required rationale), which is unusually complete for a mutation tool with zero annotation coverage.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Purpose and the edit_condition contrast are front-loaded, and each clause carries real information (pending semantics, refusals, replacement rule). It is on the dense/long side with several parentheticals and em-dash asides, so it is efficient rather than maximally tight.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

An output schema exists, so return values need not be described, and the description still covers pending state, the side-channel field (proposed_condition_edits), the acceptance write path, refusal conditions, and one-pending-diff/undo behavior. Nothing an agent needs to call this correctly or predict its effects is missing.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 57%, so the description should compensate. It does list the meaningful parameter set (finish tag, waste %, multiplier, height_ft, roll_setup) and adds a semantic constraint the schema lacks: 'Only fields that differ from the current value are recorded.' Rationale's meaning is elaborated ('Why — the schedule row, the spec section, the sheet note that decided it') matching the schema description, and finish_tag is flagged as a rename. It stops short of per-field units/format detail but covers the semantics well.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb+resource ('PROPOSE a change to a condition instead of making it') and immediately contrasts it with the sibling edit_condition, so an agent can distinguish the two tools without opening a schema. The parenthetical lists the exact field classes the proposal can carry (tag rename, waste %, ×N multiplier, height_ft, roll_setup).

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Explicitly routes: edit_condition is 'the wrong power for "I think this condition is wrong"', and the reason a proposal should instead be a decision the estimator makes is spelled out. It also names undo_last as the recovery path and withdraw_condition_edit exists as a sibling, giving clear when/when-not framing.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

propose_takeoffA

Open a PROPOSAL — a named batch of the shapes you are about to commit, with one identity (#365). Every shape you commit from here on (measure_polygon, measure_line, measure_surface, place_count, the sweeps, the derives, cut_out) attaches to it until you open another proposal or withdraw this one; the estimator then sees ONE Accept pill for the whole batch instead of one per shape — a forty-room pass becomes one decision, not forty. Use it BEFORE the work, the way an estimator titles a takeoff before tracing: "Level 2 rooms per finish schedule A-601", "Base derived from CPT-1 rooms". label is what the estimator reads on the pill; rationale is what decided the batch (the schedule row, the sheet, the rule) — both required, neither is a comment. Nothing here commits geometry or changes a total: an empty proposal is just a heading. The batch is what revise_proposal replaces and withdraw_proposal removes; shapes the estimator has already accepted leave the batch and no agent verb reaches them. takeoff_summary carries the ledger (pending / accepted / withdrawn per batch).

ParametersJSON Schema
NameRequiredDescriptionDefault
labelYesThe batch's title, as the estimator will read it on the Accept pill
rationaleYesWhat decided the batch — cite the schedule row, sheet, or rule

Output Schema

ParametersJSON Schema
NameRequiredDescription
noteYes
labelYes
rationaleYes
proposal_idYes

TDQS

A4.6/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations, the description carries the full burden and does so well: it states nothing commits geometry or changes a total, an empty proposal is just a heading, attached verbs (measure_polygon, measure_line, etc.) bind to it until superseded, and accepted shapes leave the batch. It also points to takeoff_summary for ledger state — rich behavioral context beyond the schema.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Front-loaded with the purpose and the key constraint ('nothing here commits geometry'), and every clause is informative. It is on the long side and the label/rationale explanation partly repeats the schema descriptions, costing it a point.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

An output schema exists, so return values need no explanation, yet the description still usefully points to takeoff_summary for the pending/accepted/withdrawn ledger. For a two-required-param creation tool with no annotations, the coverage of intent, timing, and side effects is complete.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 100%, and the parameter descriptions in the schema are nearly verbatim what the description says (label = what the estimator reads on the pill; rationale = what decided the batch). The description adds framing (neither is a comment) but little syntactic or format detail beyond the schema, so the baseline 3 applies.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb and resource ('Open a PROPOSAL — a named batch of the shapes you are about to commit') and gives it a concrete identity concept (#365). It explicitly distinguishes itself from revise_proposal (replaces) and withdraw_proposal (removes), so an agent can route correctly without opening other schemas.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Gives explicit timing ('Use it BEFORE the work') and a vivid workflow analogy for the estimator. It names the alternatives by function — revise_proposal replaces the batch, withdraw_proposal removes it — and notes that already-accepted shapes are out of scope, covering when-not as well as when.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

read_sheet_textA

The sheet's text with positions — items [{str, x, y}] in image px plus the joined text. Optionally restrict to a region {x0, y0, x1, y1}. Use it to read title blocks, room labels, finish schedules, and scale notes. Coordinates are image px at render scale 2.0: PDF pt × 2, origin top-left, y down (the browser canvas's native space). Sheet payloads carry dims in both px and pt.

ParametersJSON Schema
NameRequiredDescriptionDefault
sheetYes
regionNo

Output Schema

ParametersJSON Schema
NameRequiredDescription
textYesThe items joined with spaces
itemsYesPositioned text items (image px)
sheetYes

TDQS

A4.7/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Since no annotations are provided, the description fully carries the transparency burden. It discloses the coordinate system (image px, origin top-left, y down, render scale 2.0), explains the return format, and notes how sheet payloads carry dimensions in both px and pt. No behavioral surprises are left unmentioned.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is five sentences long, front-loaded with the core functionality ('The sheet's text with positions'), followed by optional behavior, use cases, coordinate details, and dims note. Every sentence adds value without redundancy or verbosity.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given the tool's moderate complexity (2 params, one nested) and the presence of an output schema (so return values need not be reiterated), the description covers all essential aspects: purpose, parameter formats, coordinate system, and typical use cases. It is sufficiently complete for an agent to invoke correctly.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

The input schema has 2 parameters (sheet, region) with 0% description coverage, so the description must compensate. It explains the optional region parameter's format and coordinate significance, and it clarifies coordinate space for region values. The 'sheet' parameter is not elaborated, but its purpose is inferable from context. The description adds meaningful value beyond the raw schema.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states the tool reads sheet text with positions, including the structure '[{str, x, y}]' and joined text. It provides concrete use cases (title blocks, room labels, etc.) and distinguishes itself from sibling tools like 'sheet_info' (metadata) or 'measure_*' (measurements), making the purpose unambiguous.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description explicitly says 'Use it to read title blocks, room labels, finish schedules, and scale notes,' giving clear guidance on when to apply the tool. However, it does not explicitly state when not to use it or contrast with siblings, though the context of siblings (delete, measure, etc.) makes the use case distinct.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

resolve_rfiA

Answer an OPEN RFI: the answer lands as its response, status becomes answered (the register's own state for "response in"), and the response date stamps exactly as the panel's would, plus an ISO timestamp of the resolve. Only an open RFI resolves — an answered, closed, or void one is refused rather than re-answered or quietly revived (undo_last reverses your own resolve if the answer was wrong). Record the answer the drawings or the architect actually gave; an RFI is not resolved by guessing.

ParametersJSON Schema
NameRequiredDescriptionDefault
answerYesThe response — what settles the question
rfi_idYesRecord id from create_rfi or list_rfis

Output Schema

ParametersJSON Schema
NameRequiredDescription
idYesThe record id ("rfi-…")
dateYesYYYY-MM-DD opened
noteYes
actorYesWho raised it — "agent" for every RFI minted over MCP, "estimator" for a panel-raised one
sheetYesThe sheet the question is about
numberYesThe register number, "RFI-001" — next in the panel's own sequence, never reissued
statusYesThe panel's lifecycle: open → answered → closed; void = withdrawn
pendingYestrue = agent-raised and not yet accepted by an estimator in the register (origin.reviewed false) — pencil, not sent
subjectYes
questionYes
responseYes
conditionsYesFinish tags the linked markups are attached to — the scopes this question touches
resolved_atYesISO-8601 time of this resolve
response_dateYesYYYY-MM-DD answered, '' while open
linked_markupsYesAnnotation ids carrying this RFI's number on the sheet (markup.rfi_id) — derived, never stored twice

TDQS

A4.8/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations, the description carries the full burden and does so richly: it discloses the resulting status (answered), the date-stamping behavior, the added ISO timestamp, and the refusal semantics for non-open records. This is exactly the state-machine and side-effect information an agent needs for a mutation with zero annotation coverage.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Front-loaded with the core action and state constraint, then progressively adds refusal rules and the undo escape hatch. The parentheticals are dense but each carries real information; nothing is pure filler, though it is on the longer side for two fields.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

An output schema exists so return values need no explanation, and the description still covers preconditions, refusal behavior, timestamp effects, and reversal. Nothing an agent needs to invoke this correctly is missing.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 100%, so the schema already documents both parameters (baseline 3). The description adds semantic guidance beyond the schema for the answer param — "Record the answer the drawings or the architect actually gave; an RFI is not resolved by guessing" — clarifying what a valid answer is.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

Opens with a specific verb+resource: "Answer an OPEN RFI." It immediately distinguishes itself from the create_rfi/list_rfis/delete_rfi siblings by naming the exact state transition (open -> answered) it performs.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Explicitly states the precondition ("Only an open RFI resolves") and the exclusion ("an answered, closed, or void one is refused rather than re-answered"). It also names the alternative for reversing a mistake ("undo_last reverses your own resolve"), giving the agent a clear routing decision.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

resolve_tagA

Resolve ONE room tag across the set (#87): the plan tag → its room-finish schedule row → each finish code's definition in the finish/material schedule, EVERY edge carrying an evidence pointer (sheet + literal text + bbox — pass a bbox to view_sheet to look at the source). Rows carried by a continuation sheet ("… SCHEDULE — CONT'D") resolve exactly like base-sheet rows, citing the sheet the ink is on. The doctrine is refusal over guessing: a room that appears on the plan with no schedule row returns status "unresolved" with the reason (and still cites the plan tag); reused room numbers return "ambiguous" rather than picking one — on a multi-building set the refusal LISTS the candidate rows per building, and a building-qualified tag ("A-134") picks the building the set names. A delta triangle or REV tag on the answering row (or the plan bubble) rides the result as "revisions": the codes returned are the POST-revision answer, but the ink changed under that delta — view_sheet the marker's bbox and check the addendum before pricing. Coordinates are image px at render scale 2.0: PDF pt × 2, origin top-left, y down (the browser canvas's native space). Sheet payloads carry dims in both px and pt.

ParametersJSON Schema
NameRequiredDescriptionDefault
tagYesThe room tag as drawn, e.g. "134" or "139A" — or building-qualified on a multi-building set, e.g. "A-134" (building A, room 134)

Output Schema

ParametersJSON Schema
NameRequiredDescription
tagYes
roomYesThe plan tag, when the room appears on a plan sheet — cited even when resolution fails. null on a multi-building ambiguity: citing one building's tag would be quietly wrong
reasonNounresolved only — WHY (no schedule row / ambiguous / no schedule found). A room that appears on the plan with no row comes back here, never as a silent omission
statusYes
sourcesNoThe chain: plan tag → schedule row (the row cites the sheet that CARRIES it — under a continuation that is the CONT'D sheet)
buildingNoresolved only — the building whose schedule row answered, when the set names buildings
finishesNo
revisionsNoresolved only — delta/REV markers on the answering schedule row or the plan bubble. The finishes above are the POST-revision answer, but the ink changed: check the marker (view_sheet its bbox) and the addendum before pricing
candidatesNounresolved only — every schedule row that COULD have answered (an ambiguous multi-building tag lists one per building; qualify the tag, e.g. "A-134", to pick)

TDQS

A4.4/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations to fall back on, the description thoroughly discloses behavioral traits: refusal over guessing, 'unresolved' status on ambiguity, handling of reused numbers, building-qualified tags, delta/REV tags and their effect (post-revision answer), and the image coordinate system (px at render scale 2.0, top-left origin, y down). This is far beyond what annotations would provide.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is a single dense paragraph, but logically ordered: from plan tag to schedule rows to finish codes, then evidence and edge cases. Each sentence adds necessary information. It is long but appropriately packed with essential behavior for a complex tool.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given the tool's complexity (multi-step resolution, continuation sheets, ambiguity handling, building-qualified tags, revision markers, coordinate system), the description covers all crucial context. It explains edge cases, coordinate details, and the link to view_sheet. The existing output schema handles return values, so the description is complete for selection and invocation.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

The single parameter 'tag' is fully described in the schema (including examples and building-qualified syntax). The description adds little beyond restating the schema's parameter description, though it does reinforce the resolution context. Since schema coverage is 100%, baseline 3 is appropriate.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly identifies the tool's action: resolving a single room tag across the entire set, tracing through schedule rows and finish definitions, and outputting evidence pointers. It distinguishes itself from sibling tools like sweep or find_text by specifying the multi-step resolution chain, making its purpose unambiguous.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description provides strong contextual guidance: it explains when to use the tool (to resolve a room tag end-to-end), how to handle multi-building sets, and that view_sheet should be used to inspect evidence. While it doesn't explicitly name alternative tools for non-resolution tasks, it implicitly distinguishes itself by describing a unique workflow, and the mention of view_sheet for evidence checking gives practical usage direction.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

revise_proposalA

Replace EVERY still-pending shape in a proposal with a new set, as ONE journal step (#365) — the move for "I re-measured and got a better batch". The old pending shapes go, the replacements commit under the same proposal, and undo_last puts the previous batch back exactly. All-or-nothing: the whole replacement is validated (sheet, scale, vertex count, a height for surface_area) before the first pending shape is removed, so a malformed last shape leaves the batch untouched and the error says which entry and why. Shapes the estimator already accepted are ink — they stay, and they are not part of what this replaces. verts are image px like every other tool; roles and minimums match the measure tools (floor_area/deduct ≥3, linear/surface_area ≥2, count 1). An empty shapes list is refused — withdraw_proposal is the verb for that. Coordinates are image px at render scale 2.0: PDF pt × 2, origin top-left, y down (the browser canvas's native space). Sheet payloads carry dims in both px and pt.

ParametersJSON Schema
NameRequiredDescriptionDefault
shapesYes
proposal_idYesThe batch, from propose_takeoff

Output Schema

ParametersJSON Schema
NameRequiredDescription
noteYes
labelYes
replacedYesPending shapes removed from the batch
committedYesReplacement shapes committed, all attached to the same batch
shape_idsYes
proposal_idYes

TDQS

A4.8/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations, the description carries the full burden and delivers: atomic all-or-nothing validation before any removal, the exact validation checks (sheet, scale, vertex count, height for surface_area), error reporting ('which entry and why'), undo_last restoring the prior batch exactly, and that accepted shapes are untouched. This is unusually complete behavioral disclosure.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The core action is front-loaded in the first clause, and nearly every sentence earns its place. It is dense and long with stacked parentheticals, which slightly hurts scannability but not correctness.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

An output schema exists so return values need no explanation, and the description fully covers the mutation's transactional semantics, validation rules, coordinate space, and role constraints. Nothing an agent needs to call this correctly is missing.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 50%, so the description must compensate and does: it specifies verts are image px, role minimums per role type (floor_area/deduct >=3, linear/surface_area >=2, count 1), and the coordinate convention (scale 2.0, pt x 2, origin top-left, y down). This adds real meaning beyond the schema, though proposal_id is only lightly addressed.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a precise verb+resource+scope: 'Replace EVERY still-pending shape in a proposal with a new set, as ONE journal step.' It distinguishes itself from siblings by naming what it is not (accepted shapes stay; empty list routes to withdraw_proposal) so an agent can tell it apart from propose_takeoff and withdraw_proposal.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Gives the explicit condition for use ('I re-measured and got a better batch') and the explicit exclusion ('An empty shapes list is refused — withdraw_proposal is the verb for that'), which routes the agent to the correct alternative verb by name.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

scope_duplicatesA

Two conditions claiming the same floor, as a list (#366). Every pair of committed floor_area shapes on one sheet whose EXACT polygon intersection exceeds min_fraction of the smaller shape — with the shared SF, which condition each belongs to, whether the estimator already affirmed either, and a look region to pass to view_sheet {overlay: true}. Pairs on DIFFERENT conditions are collisions: every total downstream counts that floor twice. Pairs on the SAME condition are a double trace (a different bug) and come back in duplicates. shared_floor_sf is the whole compared set's Σ areas − union, counted once per cell no matter how many shapes pile on it — the number takeoff_summary carries and the one that has to read 0 before any total means anything. Machine-precision edge remnants are ignored; a real overlap below 0.01 SF stays listed with an explanatory note. Supporting materials belong in edit_materials coverage rows, not duplicate floor polygons. Deducts and runs are not claims. Read-only; a shape on an unscaled sheet or with a degenerate ring is listed in unmeasured, never counted as zero. Same rule as the room eval's shared-floor gate (iou ≥ 0.5 = the same space claimed twice). Coordinates are image px at render scale 2.0: PDF pt × 2, origin top-left, y down (the browser canvas's native space). Sheet payloads carry dims in both px and pt.

ParametersJSON Schema
NameRequiredDescriptionDefault
sheetNoRestrict to one sheet; default every sheet with floor shapes
min_fractionNoList a pair only when shared ÷ smaller ≥ this (default 0.05 — rings that merely kiss along a wall are not claims; 0 lists every positive overlap above machine-precision noise)

Output Schema

ParametersJSON Schema
NameRequiredDescription
noteYes
by_sheetYes
collisionsYesPairs on DIFFERENT conditions, biggest shared floor first
duplicatesYesPairs on the SAME condition — a double trace, a different bug
unmeasuredYesShapes left out of every number, with the reason — never silently counted as zero
floor_shapesYes
min_fractionYes
shared_floor_sfYesΣ areas − union over the compared floor shapes, counted once per cell

TDQS

A4.6/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations, the description carries the full burden and does so: it declares read-only behavior, describes the returned fields (shared SF, condition membership, estimator affirmation, look region for view_sheet), and discloses edge-case handling — machine-precision remnants ignored, real overlaps below 0.01 SF retained with a note, unscaled/degenerate shapes routed to 'unmeasured' and never counted as zero. That is unusually rich behavioral disclosure for a query tool.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The purpose is front-loaded in the first sentence, and nearly every following sentence carries load (collision vs double-trace semantics, shared_floor_sf definition, edge cases, coordinate space). It is dense and jargon-heavy for a two-parameter read-only tool, running long as a single block, which keeps it short of a 5.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

An output schema exists, yet the description still supplies the semantic glue an agent needs — what a pair means downstream, which field must read 0 before totals count, and how anomalies are surfaced. It also cross-references the room eval gate and view_sheet handoff, leaving no ambiguity about correct invocation or interpretation.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 100%, so both parameters are already fully documented in the schema (including min_fraction's default and the 0-case). The prose adds conceptual framing (shared ÷ smaller, the shared_floor_sf definition, coordinate space) but no syntax or format detail beyond what the schema provides, so the baseline 3 applies.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The opening clause names a specific resource and output ('Two conditions claiming the same floor, as a list') and immediately scopes it to committed floor_area shapes whose exact polygon intersection exceeds min_fraction. It explicitly separates itself from neighboring tools by referencing view_sheet, edit_materials, and takeoff_summary, so an agent can tell what this tool is and is not without opening any schema.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

It states when the tool applies (committed floor_area shapes, overlaps above min_fraction), how to interpret results (different conditions = collision/double-count; same condition = double trace/different bug), and where things should go instead ('Supporting materials belong in edit_materials coverage rows, not duplicate floor polygons'). Exclusions are explicit: deducts and runs are not claims.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

scope_mergeA

Resolve ONE collision (#366): given a pair of floor shapes and the winner, the loser gives up the shared floor — TRIMMED to its remainder by an exact boolean difference (the cut_out module's own arithmetic; its quantities re-measured from the result), or DELETED outright when the overlap is near-total (≥ 98% of the loser: the same space claimed twice, not a room with a sliver left). One journal step either way; undo_last restores the loser verbatim. Who wins: state winner; with it omitted the reviewed shape wins over a pending one, and the verb refuses when neither is reviewed (it does not guess which condition the floor belongs to) or when BOTH are (that is the estimator's call — the collision shows on both condition rows in the canvas). The ink rule is absolute: a loser the estimator affirmed is refused whoever you name. A trim that would split the loser into disjoint pieces refuses — that is a re-trace decision, not a merge — and a loser carrying reconciled cutouts refuses (delete the cuts first).

ParametersJSON Schema
NameRequiredDescriptionDefault
winnerNoWhich of the two keeps the shared floor; omit to let the reviewed one win
shape_aYesOne shape of the pair (from scope_duplicates)
shape_bYesThe other

Output Schema

ParametersJSON Schema
NameRequiredDescription
noteYes
loserYes
actionYes
winnerYes
shared_sfYes
loser_holesNotrimmed: holes the remainder carries (a winner inside the loser leaves one)
shape_countYes
loser_after_sfYes
loser_before_sfYes

TDQS

A4.8/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations, the description carries the full burden and does so: one journal step either way, undo_last restores the loser verbatim, the ≥98% near-total overlap deletion rule, the exact boolean-difference trim, and the full set of refusal conditions. This is unusually rich behavioral disclosure for a mutation tool.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The core action is front-loaded in the first clause and every sentence carries behavioral information, but the dense parentheticals, dashes and domain jargon ("the ink rule", "re-trace decision") make it heavy to parse for a single tool definition.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

An output schema exists, so return values need not be explained. Given that, the description covers the winner-resolution logic, journaling/undo, and all refusal paths—nothing an agent needs to invoke it correctly is missing.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 100%, so the baseline is 3, but the description adds real meaning: how the winner parameter defaults (reviewed beats pending), and how shape_a/shape_b tie to the pair from scope_duplicates. It goes beyond restating the schema fields.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a precise verb and object: resolve ONE collision between a pair of floor shapes, with the winner keeping the shared floor and the loser trimmed or deleted. It clearly distinguishes itself from siblings like scope_duplicates (which finds the pair) and cut_out (whose arithmetic it reuses).

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Extensively specifies when it applies and when it refuses: neither reviewed, both reviewed, an affirmed loser (the ink rule), a trim that would split the loser into disjoint pieces, and a loser carrying reconciled cutouts. It also routes to alternatives (delete the cuts first; a re-trace decision).

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

set_scaleA

Set a sheet's scale — exactly ONE of: label (a standard scale, e.g. '1/4" = 1'-0"'), upp (real feet per image px), calibrate (two points along a known dimension plus its real feet), or use_detected (adopt the drawn scale note read off the sheet). The detected scale is never applied automatically — setting it is always this explicit call. Changing an existing scale recomputes measurements and cutout restore quantities from geometry and records one undo_last step. Counts are unchanged. Human-reviewed dimensional work must be recalibrated in the canvas; scales must be finite and positive. Coordinates are image px at render scale 2.0: PDF pt × 2, origin top-left, y down (the browser canvas's native space). Sheet payloads carry dims in both px and pt.

ParametersJSON Schema
NameRequiredDescriptionDefault
uppNoReal feet per image px at render scale 2.0
labelNoA standard scale label, exactly as listed in the error on a miss
sheetYes
calibrateNoTwo points (image px) a known real distance apart, and that distance in feet
use_detectedNotrue = adopt the sheet's detected scale

Output Schema

ParametersJSON Schema
NameRequiredDescription
uppYesReal feet per image px at render scale 2.0
labelNoThe standard scale label, when set by label or detected note
sheetYes
sourceYes
warningNoPresent when the sheet carries MULTIPLE distinct scale notes (#153) — enlarged plans/details likely; region measurements under a disagreeing note will warn
confirmedYesAlways false here: set_scale is the agent surface, and an agent-set scale stays UNCONFIRMED until a human confirms it in the canvas — quantities still flow, wearing the caveat

TDQS

A4.6/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations present, the description carries the full burden and delivers: changing a scale recomputes measurements and cutout restore quantities from geometry, records exactly one undo_last step, and leaves counts unchanged. It also flags that human-reviewed dimensional work must be recalibrated in the canvas and that scales must be finite and positive. This is exactly the side-effect and constraint disclosure a mutation tool needs.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Dense and front-loaded — the mode enumeration comes first, followed by behavioral consequences. Every sentence carries information, though the closing note about sheet payloads carrying dims in both px and pt is peripheral to invoking the tool and slightly pads the ending.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

An output schema exists, so return values need not be described, and the description fills the remaining gaps: mode exclusivity, side effects on measurements and undo history, recalibration caveat, and the coordinate convention needed to build calibration points. For a 5-parameter mutation tool with a nested object, this is complete.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 80%, so the schema already documents most parameters, but the description adds real value: it explains the coordinate space for calibration points (image px at render scale 2.0, PDF pt × 2, origin top-left, y down) and states that label must match a standard label and use_detected adopts the drawn note. The p1/p2 geometry interpretation is meaningfully enriched beyond the bare schema.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description names a specific verb and resource ("Set a sheet's scale") and then enumerates the four mutually exclusive modes (label, upp, calibrate, use_detected), so an agent knows exactly what operation it is and which shape the call must take. It also implicitly separates this tool from the automatic scale detection path by stating the detected scale is never applied on its own.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

It gives a strong usage constraint — "exactly ONE of" the four modes — and clarifies when the detected-scale path is used (only via this explicit call, never automatically). It does not name a competing sibling tool or spell out a when-not condition, but no obvious alternative exists in the sibling list, so the guidance is clear without being exhaustive.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

sheet_contextA

The sheet's STRUCTURE in one call and one frame: the classified vector segments, the positioned text spans, and the hatch-family instances of a region — everything the engine itself floods against, exposed as data instead of pixels. Use it when you need to REASON about a region rather than look at it: which lines bound this space and at what pen weight, what the region says, and which periodic fill pattern covers it. The join is the point — all three arrive in image px with no reconciliation left to do, and the reply echoes the post-clamp region so passing that same rect to view_sheet gives you the matching render by construction. Hatch families carry a content-derived id (same pattern spec ⇒ same id, anywhere on the sheet), so matching a plan region to a legend swatch is comparing two ids, not guessing from a render — read the legend region, read the room region, match ids, and cite both bboxes as evidence. Decimation is declared, ordered, and counted on every reply: segments shorter than min_len_px drop first (invisible ink), then a max_segments cap applies LONGEST-FIRST so walls survive and hatch strokes go; kept + dropped always reconciles to total_in_region, and whole segments drop with their meta intact — nothing is ever simplified or merged, because these are classified segments and a merge would rewrite the classification. A scan returns has_vector_linework: false with empty vectors — absence of linework, never a claim the region is blank. Coordinates are image px at render scale 2.0: PDF pt × 2, origin top-left, y down (the browser canvas's native space). Sheet payloads carry dims in both px and pt.

ParametersJSON Schema
NameRequiredDescriptionDefault
sheetYes
regionNoRect in image px (origin top-left, y down); omit for the full sheet
min_len_pxNoDrop segments shorter than this (default 2 — one PDF point at render scale 2.0, below any pen width). 0 keeps everything
max_segmentsNoSegment cap, applied longest-first (default 4000). The reply's dropped.cap says exactly what a smaller region would recover

Output Schema

ParametersJSON Schema
NameRequiredDescription
pageYes
textYes
hatchYes
sheetYes
regionYesThe region actually resolved, post-clamp — pass this same rect to view_sheet and the render is in the same frame by construction
vectorsYes
sheet_pxYes
has_vector_lineworkYesfalse = a scan: vectors and hatch are empty because there are none, not because the region is blank

TDQS

A4.8/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations, the description carries the full burden and excels: it details decimation ordering (longest-first, whole segments drop), the meaning of has_vector_linework:false, coordinate space, and the non-mutating nature ('exposed as data instead of pixels'). It also accounts for reconciliation of kept/dropped counts, leaving no ambiguity about side effects or edge behavior.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is long but dense, with each sentence adding unique information about decimation, coordinate systems, or usage. It is slightly repetitive about image px and top-left origin, but this emphasis is justified given how often coordinate mistakes cause errors. No filler or tautology.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

The description fully covers what the tool returns, how decimation works, how the output relates to view_sheet, and the semantics of hatch IDs. Given the complexity (three data types, coordinate conversion, decimation policy), it leaves no significant gaps for an agent to select and invoke the tool correctly.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

The schema covers 75% of parameters, and the description adds semantic depth beyond the schema: it explains that min_len_px default corresponds to one point at render scale 2.0, and that max_segments cap is applied 'longest-first' so walls survive. This complements the schema's terse descriptions, though the schema already provides solid baseline coverage.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states the tool returns structural data (classified vector segments, positioned text spans, hatch-family instances) for a region, distinguishing it from rendering tools like view_sheet. The resource and scope are specific, and the emphasis on the integrated response makes the purpose unambiguous.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

It explicitly says 'Use it when you need to REASON about a region rather than look at it' and mentions view_sheet as the counterpart for visual rendering. It also provides a concrete usage pattern (matching plan regions to legend swatches via hatch IDs), leaving no doubt about when to apply this tool.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

sheet_graphA

The plan-set INDEX (#87): every sheet's role (plan / schedule / legend / …, with confidence and the title evidence), the schedule tables found (kind, row count, region — a schedule CONTINUED across sheets ("… SCHEDULE — CONT'D") reads as ONE table, the continuation fragment naming its base in "continues"; rotated column headers are read at their quarter-turn and flagged), every number CORROBORATED as a room (with the stacked room NAME when one exists, the room's BUILDING on multi-building sets, and "corroboration" saying why it counts as a room) plus "unmatched_tags" — the numbers that are NOT rooms (keynote hexagons, detail markers, dimension fragments, legend rows), each with a reason, listed and never dropped; READ those reasons, one of them may be a room the schedule left out, the detail callouts (3/A-601 → sheet edges), the set's building designators, every REVISION marker the set carries (text markers "Δ2"/"REV 2" AND drawn deltas — a bare digit inside a triangle of linework, proven from vector geometry and flagged drawn — in "revisions", and attached to the schedule row / room tag they sit on), and named indexing gaps in "notes". Built once per document from the text layer and cached. This is how an agent decides WHAT to measure without a human enumerating the rooms: list the rooms here, resolve each with resolve_tag, then measure with measure_polygon on its wall faces. A scanned set (no text layer) returns available: false — unavailable, never half-populated. Coordinates are image px at render scale 2.0: PDF pt × 2, origin top-left, y down (the browser canvas's native space). Sheet payloads carry dims in both px and pt.

ParametersJSON Schema
NameRequiredDescriptionDefault

No parameters

Output Schema

ParametersJSON Schema
NameRequiredDescription
notesNoNamed gaps found while indexing (e.g. a continuation whose rows could not be aligned) — the graph refuses silently dropping anything
roomsYesNumbers CORROBORATED as rooms — a room-finish row answers for them, or (where the set carries no room-finish schedule) a room name is drawn with them. Each says which in `corroboration`. Schedule sheets contribute rows, never phantom rooms
countsYes
sheetsYes
calloutsYesDetail callouts (3/A-601) — edges to their target sheets
availableYesfalse = the set has no text layer (a scan) — the graph degrades to unavailable, never half-populates
buildingsNoEvery building designator the set names (sorted) — present only on multi-building-aware sets. Room numbers reused across these need qualified tags ('A-134')
revisionsNoEvery delta-triangle / REV-tag marker the set carries — text markers ('Δ2', 'REV 2') and DRAWN deltas (a bare digit inside a triangle of linework, drawn: true) — where one sits, the ink changed under that revision. Markers on a schedule row or room bubble also attach there (and ride resolve_tag). A revision CLOUD is arc-chain linework these detectors do not read — absence here is not absence of revisions
unmatched_tagsNoNumbered tags on plan sheets that are NOT counted as rooms — keynote hexagons, detail markers, dimension fragments, legend rows. Listed with a reason, never dropped. A real finish plan is covered in 2–3 digit numbers that are not rooms; counting them as rooms makes every one come back "no schedule row", which reads exactly like the lost-bid case and buries it

TDQS

A4.2/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations, the description carries the full burden and discharges most of it: built once per document from the text layer and cached, never half-populated on scanned sets, and a precise coordinate convention (image px at render scale 2.0, pt × 2, origin top-left, y down). It does not cover permissions or refresh/invalidation behavior, so it stops short of a 5.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness3/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The content is dense but delivered as one sprawling, comma-chained sentence with heavy ALL-CAPS emphasis and nested parentheticals, which makes the primary purpose slow to extract. Since an output schema already exists, part of the lengthy return-value enumeration is arguably redundant. The information earns its place but the structure does not, and the front-loaded purpose is buried.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a complex whole-document aggregation tool with 0 params and an output schema, the description supplies everything an agent needs to decide whether and how to call it: what it aggregates, its caching model, its degradation mode, the coordinate frame, and the follow-on workflow. Nothing material is missing.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

The tool takes zero parameters, so there is no parameter semantics to document and the baseline is 4. The description correctly spends no space on arguments.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose4/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description names a concrete resource — the plan-set INDEX (#87) — and enumerates exactly what it holds (sheet roles, schedules, corroborated rooms, unmatched tags, revisions, indexing gaps). It is far more than a restatement of the name. However, it never cleanly separates itself from near-siblings like find_schedule or sheet_context, so an agent gets the content but not a crisp one-line 'this vs that'.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

It gives an explicit workflow — 'list the rooms here, resolve each with resolve_tag, then measure with measure_polygon on its wall faces' — naming the exact downstream alternatives. It also states the failure condition (scanned set returns available: false) so the agent knows when the tool is not usable. Nothing about when to reach for this is left to inference.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

sheet_infoB

Sheet detail: dims (px and pt), vector segment count, whether the sheet has vector linework (measure_polygon runs on it; get_sheet_vectors returns the strokes), scale status, the detected scale suggestion, and this sheet's committed shape count. Coordinates are image px at render scale 2.0: PDF pt × 2, origin top-left, y down (the browser canvas's native space). Sheet payloads carry dims in both px and pt.

ParametersJSON Schema
NameRequiredDescriptionDefault
sheetYesSheet key ("plan.pdf", "plan.pdf#2") or title-block number ("A-101")

Output Schema

ParametersJSON Schema
NameRequiredDescription
uppNoReal feet per image px at render scale 2.0 — present once the scale is set
pageYes1-based page number
sheetYesSheet key: page 1 is the bare file name ("plan.pdf"), pages 2+ are "plan.pdf#2"
layersYesThe sheet's PDF layer table (#85) — [] when no Optional Content survived export (every engine path then runs the heuristics unchanged)
width_ptYes
width_pxYesImage px at render scale 2.0 — the coordinate space every tool speaks
height_ptYes
height_pxYes
scale_setYes
seg_countYesVector segment count
shape_countYesCommitted shapes on this sheet
sheet_numberNoTitle-block sheet number ("A-101") where detected
detected_scaleNoDrawn scale note read off the sheet — a suggestion, never auto-applied
multiple_scalesNoSeveral DISTINCT scale notes on this sheet (#153) — enlarged plans/details likely
has_vector_lineworkYesone_click needs vector linework

TDQS

B3.2/5.0
Behavior3/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations provided, the description carries the full burden, but 'Sheet detail' implies a pure read and it discloses genuinely useful behavioral context: the pixel coordinate space, render scale 2.0, PDF pt × 2, top-left origin, y-down. It stops short of stating side-effect freedom, auth needs, or caching/perf traits.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Front-loaded with the return inventory, then the coordinate-space note. It is dense and largely earns its sentences, though the nested parentheticals (

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

An output schema exists, so the description does not strictly need to enumerate return fields, yet doing so helps an agent decide whether to call it. Combined with the coordinate-space note, it is complete enough for a single-parameter reader, with only the sibling-routing gap lingering.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 100% and the single 'sheet' parameter is fully documented in the schema with accepted key formats. The description adds nothing about the parameter, so the schema does all the work and the baseline 3 applies.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose4/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description enumerates exactly what this tool returns (dims in px/pt, vector segment count, vector-linework flag, scale status, scale suggestion, committed shape count), so an agent knows it is a per-sheet metadata reader. It is scoped to a single sheet, though it never names the sibling it is not (sheet_context, view_sheet, get_sheet_vectors) to sharpen the boundary.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines2/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

There is no when-to-use or when-not-to-use guidance. The description merely notes that measure_polygon runs on sheets with vector linework and get_sheet_vectors returns strokes, which is incidental context rather than a routing rule between these closely-related siblings.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

split_conditionA

Cut a twin loose from its family: every following material row freezes at its current values and edits to the original stop reaching it. It keeps its finish tag and still groups with its siblings — only the inheritance ends. Use when two variants have diverged far enough that following one another is wrong. A condition that already owns its materials returns split:false rather than erroring. Reversible with undo_last.

ParametersJSON Schema
NameRequiredDescriptionDefault
conditionYesFinish tag of the twin to split, e.g. 'CPT-1 – Level 2'

Output Schema

ParametersJSON Schema
NameRequiredDescription
noteYes
splitYesfalse = it already owned its materials; nothing was following
conditionYes
family_idNoKept — it still groups with its siblings
frozen_rowsYesFollowing rows frozen at their current values
condition_idYes

TDQS

A4.5/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations, the description carries the full burden and does excellently. It discloses key behavioral consequences: following rows freeze, edits stop propagating, finish tag is kept, grouping remains, and the operation is reversible with undo_last. It also explains the non-erroring split:false case, which is valuable safety information.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is compact yet information-dense, using four sentences to cover purpose, effects, usage guidance, edge-case behavior, and reversibility. Every sentence earns its place with no filler or repetition of schema details.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a single-parameter tool with an output schema, the description provides comprehensive context: what happens to the data, when to invoke it, what happens in a specific edge case, and how to undo it. The existence of an output schema means return-value details are not required in the description.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

The schema already provides 100% coverage for the single 'condition' parameter with a clear description and example. The tool description reinforces the concept by repeatedly referencing 'finish tag' and 'twin', adding some semantic context, but does not significantly exceed what the schema documents.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states the specific action ('Cut a twin loose from its family') and the resource (a condition/twin), while detailing the effect: inheritance ends, materials freeze. It distinguishes itself from sibling tools like duplicate_condition by explaining that this tool severs inheritance rather than duplicating.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

It provides explicit use case: 'Use when two variants have diverged far enough that following one another is wrong.' It also describes behavior for an edge case (already-owned condition returns split:false) and reversibility, but does not explicitly mention when not to use it or name alternative tools.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

sweep_schedule_rowA

Take off a schedule row's mark from the row itself — the estimator's own gesture: a transition type sometimes exists only as a schedule row plus tag markers scattered across the plan sheets, and this tool mints the condition FROM the row and finds every occurrence. Pass the row's key (e.g. 'T1') and the tool (1) reads the row from the set's schedule tables (the sheet_graph/find_schedule machinery — the row is the condition's cited source), (2) anchors a geometric fingerprint on the marker the tag is DRAWN as on a plan sheet (a deterministic pad ladder around the tag text; where the tag occurs more than once the fingerprint must recur at a second occurrence before it is trusted — anchor.corroborated), and (3) sweeps every PLAN-role sheet for it. The count is geometry AND text agreeing: drafting reuses one bubble shape across many marks, so a match counts ONLY when the row's own tag sits within the marker footprint (its bbox rides the match as tag_at evidence); a match labeled with a SIBLING row's tag is excluded and says whose it is, an unlabeled match is withheld as a question, and a tag drawn with no matching marker is disclosed as text_only. REFUSAL over guessing, with the reason and the fix: no such row; the same key in two tables (ambiguous); a tag drawn on no plan sheet; no repeatable marker linework around the tag — a fingerprint is never guessed from text alone (the fallback is always: marquee one instance with symbol_sweep). commit: true commits the counted matches as EA markers under the row's own key — one undo step for the whole set-wide sweep, every marker carrying origin.assignment {source: "schedule"} plus the anchor and row citation on origin.symbol.seed. The COUNT is scale-free (EA), but matching is not: where the anchor sheet and a target sheet both carry a scale, the marker is resized by their exact ratio before matching (scaled per sheet), and where one does not, the sweep runs at 1:1 and discloses it (scale_assumed) rather than reporting a confident zero. After committing, LOOK: view_sheet {overlay: true} over each swept sheet. Coordinates are image px at render scale 2.0: PDF pt × 2, origin top-left, y down (the browser canvas's native space). Sheet payloads carry dims in both px and pt. LABEL-FIRST for devices (any trade): when the marker cannot be fingerprinted or does not reach a drawn tag amid linework — a heater bar or fan drawn to its own size, tagged by a leader — every such tag on a plan sheet counts as ONE instance BY LABEL, disclosed in found_by_label / label_only / counted_by; a bare mention in a note (no linework near it) is text_only, never a count. Rows come from every schedule family the sheet graph reads: room-finish, finish/material, and equipment (mechanical, electrical, plumbing, fire).

ParametersJSON Schema
NameRequiredDescriptionDefault
tagYesThe schedule row's key exactly as drawn, e.g. 'T1', 'TR-2' — it becomes the condition tag on commit
commitNoCommit every counted match as one EA count marker (excluded/withheld/text_only never commit)
mirrorNoAlso match mirrored markers
rotationsNoAlso match 90/180/270-rotated markers
tolerance_pxNoEndpoint match tolerance in image px (default 2 — CAD jitter, not drift)

Output Schema

ParametersJSON Schema
NameRequiredDescription
rowYesThe schedule row the sweep was seeded from — the condition's source
tagYesThe row key as normalized (the tag as drawn)
noteNo
foundYesInstances counted across every plan sheet: geometry matches carrying the row's own tag PLUS drawn tags counted by label — see counted_by
anchorYesnull when the sweep counted BY LABEL: no repeatable marker geometry sits around the drawn tag (the equipment convention — a device drawn to its own size, tagged by a leader), so nothing was fingerprinted
sheetsYesOne entry per swept PLAN-role sheet, load order
skippedYesSheets excluded from counting (schedule/detail/legend/unknown), each with its reason
warningNoPresent when the per-sheet work cap dropped candidates
completeYesTrue when every proposed placement was scored on every swept sheet — false means at least one sheet's count is a FLOOR, not a total (#261)
ea_totalNo
committedNocommit mode: count shapes committed — one per counted match, the whole sweep ONE undo step
conditionNocommit mode: the condition minted FROM the row — its key is the tag
shape_idsNo
counted_byYesHow the count was made — "label" means no marker geometry was matched at all; look at each label_only placement before pricing
found_by_labelYes…of which drawn tags amid linework the fingerprint did not (or could not) reach — an installed instance by the equipment convention, disclosed as label-counted
found_by_geometryYes…of which marker-fingerprint matches corroborated by the tag

TDQS

A4.7/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations, the description carries the full burden and delivers: refusal-over-guessing with reasons, commit semantics (one undo step for the whole sweep, markers carrying origin.assignment/origin.symbol.seed), scale handling (scaled vs scale_assumed rather than a confident zero), corroboration requirements for fingerprints, and the label-first fallback for non-fingerprintable markers. This is unusually rich behavioral disclosure.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness3/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The purpose is front-loaded, and for a genuinely complex geometric sweep much of the content earns its place. However, the body is a single sprawling run-on paragraph stuffed with backticked field names and compressed labels like 'REFUSAL over guessing, with the reason and the fix', which harms scanability despite the density of useful information.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

An output schema exists, so return values need not be restated. The description still covers what an agent must know to call it correctly: coordinate space (image px at render scale 2.0), scale-ratio matching behavior, refusal cases, and required follow-up verification. Nothing material is missing.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 100%, so the baseline is 3, but the description adds real meaning beyond the schema: it explains that the tag becomes the condition tag on commit, that commit produces one EA marker carrying cited provenance, and that counted vs excluded/withheld/text_only matches differ in whether they commit. mirror/rotations/tolerance_px are left to the schema, so it is not a full 5.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description states a specific verb and resource ('Take off a schedule row's mark from the row itself'), explains the mechanism (mint the condition from the row and sweep for every occurrence), and explicitly distinguishes itself from the sibling fallback symbol_sweep. An agent can tell it apart from count_marks, symbol_sweep, and find_schedule without opening a schema.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

It says when to use it (a transition type exists only as a schedule row plus scattered tag markers), names the alternative (marquee one instance with symbol_sweep) and the post-commit verification step (view_sheet with overlay per swept sheet), and enumerates refusal conditions with their fixes. The usage boundary is fully drawn.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

symbol_sweepA

Find EVERY instance of a repeated plan symbol from ONE example — drains, thresholds, fixtures, transition markers: marquee a tight seed_rect around a single instance and the vector linework is searched for every other placement of that same segment cluster. Deterministic geometry, not vision: each placement scores as the length-weighted fraction of the seed's segments reproduced within tolerance_px, under translation plus 0/90/180/270 rotation and mirroring (symbols rotate on plans — both ON by default; turn them off to pin orientation). Score ≥ 0.92 is a match; the 0.75–0.92 band comes back in withheld with a reason — a near-match is a question you answer by LOOKING (view_sheet at its at), never a silent commit and never a silent drop. RICHER VARIANTS are named, never silent: a placement that reproduces the whole seed but carries >30% extra linework fully inside its footprint (a register against a grille seed — the same outline plus louvers) comes back with its measured extra fraction on the row — LOOK at those first, they are the classic mislabel; background lines CROSSING the symbol and coincident duplicate ink never trip this. By default such placements still COUNT, because the contained-seed workflow below depends on supersets matching (seed a bare sub-shape, count the richer symbols that contain it, exclude what you don't mean). Pass variant_guard: true when your seed is the WHOLE symbol — grilles, drains, fixtures marqueed complete — and extra-ink placements demote to withheld as questions instead of counting; the guard stands down automatically when exclude counter-examples are in play, since supplying negatives is manual variant discrimination. The seed's own location is reported in seed and never double-committed. Every proposed placement is scored up to a hard work ceiling sized for pathological sheets, and the reply says which it was: complete true means the count is a total; complete false (with candidates.dropped > 0) means the count is a FLOOR — some placements were never scored — so tighten the seed rect around more distinctive geometry rather than trusting it as a total. Marquee discipline: the rect must hug ONE instance — only segments FULLY inside it define the symbol, so a loose rect that swallows wall linework fingerprints the wall, not the symbol. scope "set" sweeps the WHOLE working set, counting on PLAN-role sheets only (the sheet graph decides): a symbol drawn in a detail, legend, or schedule is a reference drawing and never counts itself — which is also how you seed from one: marquee the assembly on the detail sheet and its plan-sheet occurrences are counted while the detail stays excluded (the exclusion disclosed in skipped, per-sheet results with per-sheet caps and wall-clock in sheets). Scale across sheets: the fingerprint is size-true and is never scale-SEARCHED, so a detail drawn at 1-1/2" = 1'-0" is 12× the size of the same mark on a 1/8" plan — when BOTH sheets have a scale set, the exact ratio is computed from them and the seed is resized before matching (reported per sheet as scaled); when a scale is missing, the sweep runs at 1:1 and SAYS so (scale_assumed), because an unknown ratio plus a zero count is not evidence of absence. Seeding from a detail/legend/schedule sheet REFUSES outright until both scales are set — that is the case where an unstated ratio silently finds nothing. commit: true (requires condition) commits every match center as an EA count marker through the same path as place_count — the whole sweep (set-wide included) is ONE undo step, each marker carries origin.method "symbol_sweep" with its score, transform, and seed source, and withheld placements are NEVER committed. The SEED instance is not in that count (#296) — in sheet scope it is almost always installed work, so pass commit_seed: true to mint it into the same batch (the reply reminds you whenever a sheet-scope commit leaves it out; ea_total one short of the hand tally is exactly this). The COUNT is scale-free (EA), but matching across sheets of different scales is not — set_scale on the sheets involved is what turns the ratio from an assumption into arithmetic. Counter-examples (#259): drafting reuses one generic shape for different devices — a wall-mounted data outlet drawn as a plain triangle, the flush-floor variant the SAME triangle inside a square, keynote callouts a triangle with a letter in it — so the seed legitimately matches things you do not mean, and seeding more geometry only works where the drawing offers more to capture. exclude takes rects around instances you do NOT mean, marqueed exactly like the seed. You never choose a mechanism; the rect's contents decide, because both are the same gesture: a rect holding EXTRA linework beyond the seed rejects placements where that extra linework is present too (the box, the letter), and a rect holding no extra linework of its own is read as the line running THROUGH it — a bare ceiling-grid tile whose grid line a real fixture, drawn over it, would BREAK. That second mechanic is not expressible as a seed: only segments fully INSIDE a rect define a symbol, and background structure is long by nature. Every rejection is disclosed in rejected[] — which negative, what fraction of its evidence was found, and the placement — and NEVER counted in found: an exclusion is a judgement, so look at it and reinstate any you disagree with using place_count at its at. A counter-example that holds no instance of the seed, or holds the seed with nothing extra, is REFUSED rather than silently doing nothing. Stroke luminance (#260): a flattened export strips the layer tree and flattens every pen, but the file still STATES stroke color — a black fixture outline over a grey ceiling grid is unambiguous there even when the geometry is identical (two empty 2 ft grid tiles reproduce a 2×4 fixture's outline exactly). luminance_tolerance (0–254) gates on it: a sheet segment only answers for a seed segment when their stroke luminances are within the stated tolerance (Rec. 709, 0 = black, 255 = white; 32–64 separates black from grey without touching anti-aliasing wobble). OPT-IN and disclosed, in the spirit of tolerance_px — omitted, sweeps score exactly as before; stated, the reply's lum_gate says the seed's own luminance band and names every placement the geometry would have committed and the pen did not, so you can LOOK at what a stated gate cost. Prefer geometry (a counter-example, a tighter seed) where the drawing offers it — color is the fallback for exports where nothing else survived. Labels (#308): for a LABELED family — fixtures, tagged equipment, keyed devices — the drawing already names every instance, and the sweep reads those names: a fixture token written beside a placement, or connected to it by a drawn leader line (leader-following arms only on multi-pen sheets, where the annotation pen separates from the work), comes back as label + label_via on the row, and the seed's own tag rides seed.label. Disclosure in both directions, never a recount: a committed match with NO label while the family is labeled was counted on shape alone (measured case: two 0.97 matches that were valve internals, not drains — LOOK at those first), a withheld row carrying the seed's own tag is the drawing vouching for a near-miss (look, then place_count), and a withheld row named a DIFFERENT tag is a sibling fixture answered, not a missed count. After any batch commit, LOOK at what landed — view_sheet {overlay: true} over the swept area — and audit the markers against the drawing before trusting the EA total. Coordinates are image px at render scale 2.0: PDF pt × 2, origin top-left, y down (the browser canvas's native space). Sheet payloads carry dims in both px and pt.

ParametersJSON Schema
NameRequiredDescriptionDefault
scopeNo"sheet" = this sheet only; "set" = every PLAN-role sheet in the working set (needs a text layer for the sheet graph; non-plan sheets are excluded and disclosed)sheet
sheetYesThe sheet the seed rect sits on — in scope 'set' it may be ANY sheet (a detail/legend seed sheet is fingerprint source only, never counted)
commitNoCommit every MATCH center as one EA count marker (withheld placements never commit)
mirrorNoAlso match mirrored placements
excludeNoCounter-examples: rects around instances you do NOT mean, same gesture as seed_rect — 'count the triangles, not the keynote ones'. Marquee the LOOKALIKE ITSELF (the flush-floor variant with its box, the keynote triangle with its letter) or an EMPTY position whose background line a real instance would break (a bare ceiling grid tile). You never say which kind it is: the rect's own contents decide. Every rejection comes back in rejected[] with which negative did it and what it saw
conditionNoFinish tag to commit match markers under (minted on first use), e.g. 'FD-1'. Required when commit is true
rotationsNoAlso match 90/180/270-rotated placements
seed_rectYesMarquee around ONE example instance, [[x0,y0],[x1,y1]] in image px — tight: segments fully inside define the symbol
commit_seedNoSheet scope + commit only (#296): also commit the SEED instance — in sheet scope the seed is almost always installed work, and a count that excludes it bids one short. Joins the same one-undo-step batch, origin score 1. Refused in set scope, where a detail/legend seed is a reference drawing
tolerance_pxNoEndpoint match tolerance in image px (default 2 — CAD jitter, not drift)
variant_guardNoWhole-symbol mode: demote richer-variant placements (>30% extra linework inside the footprint) to withheld instead of counting them with an `extra` disclosure. Use when the seed is a COMPLETE symbol (a grille, a drain); leave off when seeding a contained sub-shape. Stands down when exclude counter-examples are passed
luminance_toleranceNoStroke-luminance gate, 0–254 (#260): a sheet segment only answers for a seed segment when their stroke luminances (Rec. 709, 0 black – 255 white) are within this. For flattened exports where a black device and its grey background twin are geometrically identical — 32–64 separates black from grey. Omit to score on geometry alone; stated, the reply's lum_gate discloses the seed's luminance band and every placement the gate pulled under the commit bar

Output Schema

ParametersJSON Schema
NameRequiredDescription
noteNo
seedYes
foundYesPlacements that cleared the commit bar — across every swept sheet in set scope
scopeYes"sheet" = the swept sheet alone (matches/withheld/candidates at top level); "set" = every PLAN-role sheet in the working set (per-sheet results in sheets[], exclusions in skipped[])
sheetsNoSet scope only: one entry per swept PLAN-role sheet, load order
matchesNoSheet scope only. Deterministic reading order (y, then x). The seed's own location is never listed here
skippedNoSet scope only: every sheet excluded from counting, with role and reason — including the seed's own sheet when it is not a plan
warningNoPresent when the work cap dropped candidates — what a tighter seed rect would recover
completeYesTrue when every proposed placement was scored (every swept sheet, in set scope) and the count is a total. FALSE MEANS THE COUNT IS A FLOOR — acknowledge it before trusting found (#261)
ea_totalNocommit mode: the condition's total EA after this call
lum_gateNoSheet scope only. The stated stroke-luminance gate's accounting (#260): the tolerance, the seed's own luminance band, and every placement the geometry would have committed that the pen pulled under the bar — NEVER counted in found, never silent. Set scope accounts per sheet in sheets[]
rejectedNoSheet scope only. Placements the geometry accepted and a counter-example refused (#259) — NEVER counted in found, and never silent: each says which negative did it and what it saw. Reinstate one by hand with place_count at its `at` if you disagree
withheldNoSheet scope only. Near-matches in the [0.75, 0.92) band — reported with a reason, NEVER committed. A withheld placement is a question you can answer with view_sheet; a hidden one is a miscount
committedNocommit mode: count shapes committed — one per match (0 when commit_refused is present)
conditionNocommit mode: the finish tag the markers counted under
negativesNoWhat each `exclude` rect was read as, in the order you passed them (#259)
shape_idsNo
candidatesNoSheet scope only — set scope accounts per sheet in sheets[]
commit_refusedNocommit mode (#376): present when the seed was too small and too common to commit on shape alone — fewer than 40 segments of seed linework and more than 50 placements cleared the bar. NOTHING was committed; the placements are still listed in matches (or per sheet in sheets[]) so you can look, and the text says what stands the guard down: variant_guard: true, exclude counter-examples, or a seed rect that captures more of the symbol
rejected_totalNoSet scope: placements counter-examples rejected across every swept sheet
seed_committedNoPresent when commit_seed: true minted the seed instance into the batch (#296) — ea_total then includes it

TDQS

A4.8/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations provided, the description carries the full behavioral burden and does so extensively: it discloses deterministic scoring, withheld bands, richer-variant handling, scope behavior, commit side effects as one undo step, seed exclusion, scale-assumption limits, refusal conditions, and luminance/label disclosures.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness3/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is front-loaded with purpose and key mechanics, but it is extraordinarily long and dense with parenthetical asides and repeated warnings. Many details earn their place for a 12-parameter tool, yet the overall size is not concise and risks overwhelming an agent trying to extract invocation parameters.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given the tool's complexity, 12 parameters, full schema coverage, no annotations, and an output schema, the description is complete enough to call the tool correctly. It explains edge cases, refusals, scale behavior, commit semantics, and audit steps without needing to explain return values because an output schema exists.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters5/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 100%, so the baseline would be 3, but the description adds substantial meaning beyond the schema for seed_rect marquee discipline, exclude mechanics, variant_guard demotion, luminance_tolerance gating, commit_seed minting, and scale-dependent matching semantics.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description states a specific verb and resource: finding every instance of a repeated plan symbol from one example rectangle. It distinguishes itself from siblings by naming place_count, view_sheet, and set_scale, and by explaining the deterministic geometry approach versus vision.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

It explicitly covers when to use the tool, how to seed, when to pass variant_guard, how counter-examples interact, and what to do with withheld matches. It also names alternatives and the conditions that select them, such as using place_count to reinstate rejected placements and view_sheet to audit committed markers.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

takeoff_summaryA

Per-condition totals (floor/wall/border SF, LF, EA, SY, with and without waste) plus grand totals — the Report's numbers, computed by the same rules. Numbers only: the deliverable that SHOWS the work on the drawings is export_marked_pdf. Coordinates are image px at render scale 2.0: PDF pt × 2, origin top-left, y down (the browser canvas's native space). Sheet payloads carry dims in both px and pt.

ParametersJSON Schema
NameRequiredDescriptionDefault

No parameters

Output Schema

ParametersJSON Schema
NameRequiredDescription
totalsYes
proposalsNoThe proposal ledger (#365): per batch, how many shapes are still pending, how many the estimator accepted, whether it was withdrawn, and which batch new commits attach to (current). Present only when a proposal exists
conditionsYes
shared_floor_sfYesScope collision (#366): floor claimed by more than one shape across the whole takeoff, counted once per cell (Σ areas − union), in SF through each sheet's scale. Has to read 0 before a total means anything — scope_duplicates names the pairs
scale_unconfirmedNoSheets whose scale is agent-set and no human has confirmed — these totals stand on an unverified scale; verify against a stated dimension or confirm in the canvas
shared_floor_unmeasuredNoFloor shapes the collision check could not measure (unscaled sheet, degenerate ring) — left OUT of shared_floor_sf rather than counted as zero; present only when any
proposed_condition_editsNoPending condition-edit diffs (#365) beside the current knobs. The rows above are the CURRENT values — nothing changes until the estimator accepts. Present only when any are pending

TDQS

A4.2/5.0
Behavior3/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations, the description carries the full burden. It usefully discloses that the values are computed by the same rules as the Report (i.e., consistent/deterministic) and that output is numbers only, but says nothing about read-only safety, permissions, or whether the computation is expensive/cached.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Dense and mostly front-loaded: content first, then the sibling contrast, then coordinate conventions. The trailing coordinate/px-vs-pt sentence is somewhat tangential to a numbers-only summary and slightly dilutes focus, keeping it from a 5.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Output schema exists so return values need no explanation, and the tool has no inputs. The description covers what is returned, how it relates to the alternate deliverable, and the coordinate conventions of embedded sheet data — enough for an agent to call it correctly with no risk.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

The tool takes zero parameters, so there is nothing to disambiguate; baseline 4 applies. The coordinate-system sentence is aimed at interpreting returned sheet payloads rather than inputs.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description states exactly what the tool returns: per-condition totals broken out by measurement type (floor/wall/border SF, LF, EA, SY) with and without waste, plus grand totals. It also explicitly distinguishes itself from the sibling export_marked_pdf, telling the agent this is the numbers-only view versus the drawing-markup deliverable.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

It gives a clear routing rule against the natural alternative: if you want numbers use this, if you want to SHOW the work on the drawings use export_marked_pdf. That is a concrete when/when-not condition, though it stops short of naming a broader set of alternatives or stating prerequisites.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

undo_lastA

Step back over your OWN last n mutations, newest first — a committed measure_polygon, an edit_shape, a delete_shape, an edit_materials call, an edit_condition call, or an RFI verb (create_rfi / resolve_rfi / delete_rfi). Each step is reversed exactly (a commit is removed, an edit is restored verbatim, a delete is re-inserted where it was, a materials edit's whole array is restored, a condition edit's waste/multiplier pair is restored), so this restores state rather than approximating it. Reads are never journaled, so n counts gestures that changed something, not tool calls you made. Use it when a sweep committed against the wrong condition or a batch went in on the wrong sheet — one call instead of N deletes. Scope: this session's own history only. It is not the browser canvas's undo stack, and load_plan clears it along with the shapes it refers to.

ParametersJSON Schema
NameRequiredDescriptionDefault
nNoHow many steps to reverse (1–100)

Output Schema

ParametersJSON Schema
NameRequiredDescription
noteNo
stepsYesNewest first
undoneYesSteps actually reversed
remainingYesSteps still available to undo
shape_countYesCommitted shapes after the undo

TDQS

A4.8/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

No annotations exist, so the description carries the full burden and does so: it explains that each step is reversed exactly (commit removed, edit restored verbatim, delete re-inserted in place, arrays and waste/multiplier pairs restored whole), that reads are never journaled so n counts mutations not calls, and how load_plan interacts with the history.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Front-loaded with the action, and nearly every clause is load-bearing (reversal fidelity, journaling rule, scope caveat). The mid-sentence enumeration of reversal semantics is dense but informative rather than repetitive, so the length is justified with only minor tightening possible.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a one-parameter mutation-reversal tool with no annotations, the description supplies everything an agent needs: what is reversible, how precisely, the counting rule, scope limits, and interaction with load_plan. The output schema covers return values, so nothing material is missing.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 100% and the single n parameter is documented in-schema, so the baseline is 3. The description earns an extra point by clarifying that n counts state-changing gestures rather than tool calls made, which is a semantic distinction the schema's '1–100 steps' wording does not capture.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb (undo) and resource (last n mutations) and enumerates exactly which gestures count: measure_polygon commits, edit_shape, delete_shape, edit_materials, edit_condition, and RFI verbs. An agent can distinguish this from delete_shape or the RFI delete siblings without opening another schema.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Gives concrete triggering scenarios (a sweep committed against the wrong condition, a batch on the wrong sheet) and states the payoff (one call instead of N deletes). It also draws explicit boundaries: session history only, not the browser canvas undo stack, and cleared by load_plan.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

view_sheetA

SEE the sheet — render the page (or a crop of it) to a PNG image. This is your eyes on the plan, so CROP, DON'T SQUINT: the render downsamples to the px budget (≤2000 long side), which on an E-size sheet is ~4 sheet pixels per returned pixel — a full-sheet render finds WHERE things are, and only a tight region crop can tell you what the linework and labels actually say. Never audit a trace or read a dimension off a full-sheet render. region is in image px — the same space as every other tool — so a feature at pixel (ix, iy) of the returned image sits at x = region_x0 + ix × (region_x1 − region_x0) / img_w (same for y), and those coordinates go straight into measure_polygon, or read_sheet_text. overlay:true burns the session's committed shapes into the render (human-affirmed ink solid red, unreviewed machine shapes dashed blue) — render again after committing to verify your geometry landed where you intended, and sanity-check what you see: a fixture-sized ring where a room should be means the seed landed inside a stall or casework; an outsized ring means the flood escaped through an opening. To MEASURE rather than guess, pass grid: a calibrated measuring grid is burned in — thin lines every 1 ft, heavy blue every 5 ft, foot labels along the crop edges, feet counted from the crop's top-left corner. Count grid cells between walls exactly like an estimator scaling a plan; never derive a dimension by eye when the grid can give it to you. grid "auto" uses the sheet's set scale; before set_scale, pass the drawing scale read off the title block as inches-per-foot — "1/4" for a 1/4" = 1'-0" plan, "3/16", "0.25". marks (#297) burns DISCLOSURE layers into the render, so what a reply names, the picture shows: pass the coordinate lists a tool disclosed — question: withheld placements (orange ?-circles), struck: rejections a counter-example or luminance gate refused (magenta struck ×), ring: reference points like the sweep's own seed (violet double ring). The colors sit deliberately off the common CAD pens so they cannot vanish into color-plotted work. An overlay audit without marks shows only committed ink — the validation trap where 37 disclosed near-misses read as "it missed them". Rendering needs the optional native canvas (@napi-rs/canvas); where it isn't installed this tool errors cleanly and every other tool still works. Coordinates are image px at render scale 2.0: PDF pt × 2, origin top-left, y down (the browser canvas's native space). Sheet payloads carry dims in both px and pt.

ParametersJSON Schema
NameRequiredDescriptionDefault
pxNoLong-side pixel budget of the returned image (default 1400) — small region + high px = readable dimension strings
gridNoBurn in a calibrated 1-ft/5-ft measuring grid: "auto" = the sheet's set scale; otherwise the drawing scale as inches-per-foot, e.g. "1/4", "3/16", "0.25"
marksNoDisclosure marks to burn into the render (#297): what the reply names, the picture shows. Coordinates in image px
sheetYes
regionNoCrop rect in image px (origin top-left, y down); omit for the full sheet
overlayNoBurn committed shapes into the render (solid = human-affirmed, dashed = unreviewed)

TDQS

A4.6/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations, the description carries the full burden and does so richly: it discloses the ≤2000px downsample budget and the ~4 sheet-pixels-per-returned-pixel tradeoff on E-size sheets, the coordinate space (render scale 2.0, PDF pt × 2, top-left origin, y down), the color semantics of committed vs unreviewed overlay ink, and the graceful failure path when @napi-rs/canvas is absent.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness3/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Front-loading is good — 'SEE the sheet' leads. But the body is a single dense run-on with colon-chained clauses, caps emphasis, and parentheticals that make it hard to scan; most sentences carry real information, yet the density crosses from thorough into unwieldy.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a complex 6-parameter render tool with no output schema and nested marks/region objects, it covers nearly everything an agent needs: coordinate spaces, pixel budget effects, grid/marks/overlay semantics, return-scale details, and the native-canvas dependency. The only gap is that the required 'sheet' identifier is left unexplained.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is already 83%, so the baseline is 3, and the description exceeds it: it gives the region→sheet coordinate mapping formula (region_x0 + ix × (region_x1 − region_x0) / img_w), the accepted grid values ('auto' or inches-per-foot like '1/4'/'3/16'/'0.25'), and the off-CAD-pen rationale for each marks color. The required 'sheet' parameter, however, is never explained in the description.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

Opens with a specific verb+resource: 'SEE the sheet — render the page (or a crop of it) to a PNG image.' It clearly distinguishes itself from siblings that extract data (read_sheet_text, get_sheet_vectors) by being the visual render path, and the crop-vs-full-sheet framing makes the tool's role unambiguous.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Gives explicit when-to-use rules: full-sheet render 'finds WHERE things are', tight crops tell 'what the linework and labels actually say', and 'Never audit a trace or read a dimension off a full-sheet render.' It routes the agent onward — 'those coordinates go straight into measure_polygon, or read_sheet_text' — and specifies grid for measuring, overlay for post-commit verification, and marks to avoid the 'validation trap'.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

withdraw_condition_editA

Drop a pending condition-edit proposal (#365) without touching the condition. undo_last re-seats it.

ParametersJSON Schema
NameRequiredDescriptionDefault
proposal_idYesFrom propose_condition_edit, or takeoff_summary's proposed_condition_edits

Output Schema

ParametersJSON Schema
NameRequiredDescription
conditionYes
withdrawnYes
proposal_idYes

TDQS

A3.6/5.0
Behavior3/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations, the description carries the full burden. It usefully discloses that the underlying condition is left untouched and that the action can be re-seated via undo_last, which is genuine reversibility context. It omits permission requirements, error behavior for non-pending proposals, and whether the drop is permanent.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Two terse sentences, zero filler, and the core action is front-loaded ahead of the undo_last recovery note. The stray issue reference (#365) is the only noise.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

An output schema exists, so return values need not be described, and the single parameter is fully covered by the schema. The remaining gap is behavioral: what states the proposal must be in and what happens on failure.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 100% and the single proposal_id parameter is already documented in the schema (including its source in propose_condition_edit or takeoff_summary). The description adds no parameter detail, so the baseline of 3 applies.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose4/5

Does the description clearly state what the tool does and how it differs from similar tools?

Specific verb ('drop') plus a precise resource ('a pending condition-edit proposal') that an agent can distinguish from propose_condition_edit and revise_proposal. It does not, however, differentiate itself from the sibling withdraw_proposal, which sounds like it could overlap.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines3/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The word 'pending' implies the applicable state, and it names undo_last as the recovery path, so usage is implied rather than spelled out. It never states when to prefer this over withdraw_proposal or what happens if the proposal was already applied.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

withdraw_proposalA

Take a proposal back (#365): every still-pending shape in the batch is removed in ONE journal step, the record stays marked withdrawn (its label is history the estimator may still read), and new commits stop attaching to it. Shapes the estimator already accepted are ink and stay — the reply counts them. This is the honest exit for "that batch was wrong" — one call instead of N delete_shape calls, and undo_last restores the whole batch.

ParametersJSON Schema
NameRequiredDescriptionDefault
proposal_idYesThe batch, from propose_takeoff

Output Schema

ParametersJSON Schema
NameRequiredDescription
noteYes
labelYes
withdrawnYesPending shapes removed
proposal_idYes
accepted_keptYesShapes from the batch the estimator had accepted — untouched

TDQS

A4.7/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations, the description carries the full behavioral burden and does so well. It discloses atomic journaling ('ONE journal step'), what is removed (still-pending shapes), what persists (the record stays marked withdrawn, accepted shapes stay), what happens to future commits (new commits stop attaching), and reversibility via undo_last.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is three sentences, front-loaded with the core action and followed by relevant behavioral and usage details. Every sentence earns its place by clarifying scope, effect, or alternatives, without padding.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a mutation tool with no annotations but an output schema, the description gives the agent everything needed to call it correctly: the batch parameter, what gets removed, what remains, the atomicity guarantee, and how it relates to delete_shape and undo_last.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 100%, so the parameter's meaning is already fully documented. The description reinforces the concept of a batch/proposal but does not add syntax, format, or constraints beyond what the schema provides, so the baseline of 3 applies.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description states a specific verb ('Take a proposal back') and resource (proposal/batch), and clearly scopes the action to pending shapes in the batch. It distinguishes the operation from delete_shape by noting that one call replaces N delete_shape calls, and from undo_last by noting that undo_last restores the whole batch.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

It explicitly identifies the situation for use ('that batch was wrong') and names alternatives: one call instead of N delete_shape calls, and undo_last restores the whole batch. This gives the agent enough to choose this tool over deleting individual shapes or reversing work.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Tool Schema Changelog

Recent tool additions, removals, and schema changes observed during successful MCP inspections.

  1. 3 tool updatesv0.1.24
    • Changedmeasure_line2 fields changed
      • addedInput schema / properties / arc_through
        Added value: +{
        +  "description": "Indices of points that are the MIDDLE of an arc: the trace runs the point before → this point → the point after as the unique circle through the three (the canvas's Curve mode). For a curved wall put one point anywhere ON the bow between its two ends and mark it. The arc is baked to ordinary vertices on commit and origin.curved is stamped; a mark on an end of an open run, or two marks in a row, refuses.",
        +  "items": {
        +    "minimum": 0,
        +    "type": "integer"
        +  },
        +  "type": "array"
        +}
      • addedOutput schema / properties / arcs
        Added value: +{
        +  "description": "How many arc_through bows were laid — present only when the trace was bent; the vertices reported are the baked arc, not the three points you gave",
        +  "type": "integer"
        +}
    • Changedmeasure_polygon2 fields changed
      • addedInput schema / properties / arc_through
        Added value: +{
        +  "description": "Indices of points that are the MIDDLE of an arc: the trace runs the point before → this point → the point after as the unique circle through the three (the canvas's Curve mode). For a curved wall put one point anywhere ON the bow between its two ends and mark it. The arc is baked to ordinary vertices on commit and origin.curved is stamped; a mark on an end of an open run, or two marks in a row, refuses.",
        +  "items": {
        +    "minimum": 0,
        +    "type": "integer"
        +  },
        +  "type": "array"
        +}
      • addedOutput schema / properties / arcs
        Added value: +{
        +  "description": "How many arc_through bows were laid — present only when the trace was bent; the vertices reported are the baked arc, not the three points you gave",
        +  "type": "integer"
        +}
    • Changedmeasure_surface2 fields changed
      • addedInput schema / properties / arc_through
        Added value: +{
        +  "description": "Indices of points that are the MIDDLE of an arc: the trace runs the point before → this point → the point after as the unique circle through the three (the canvas's Curve mode). For a curved wall put one point anywhere ON the bow between its two ends and mark it. The arc is baked to ordinary vertices on commit and origin.curved is stamped; a mark on an end of an open run, or two marks in a row, refuses.",
        +  "items": {
        +    "minimum": 0,
        +    "type": "integer"
        +  },
        +  "type": "array"
        +}
      • addedOutput schema / properties / arcs
        Added value: +{
        +  "description": "How many arc_through bows were laid — present only when the trace was bent; the vertices reported are the baked arc, not the three points you gave",
        +  "type": "integer"
        +}
  2. 1 tool updatev0.1.23
    • Changedexport_takeoff3 fields changed
      • addedOutput schema / properties / sheet_levels / description
        Added value: +"Present only when a sheet carries a level label (the app omits it when empty)"
      • addedOutput schema / properties / units / description
        Added value: +"Present only for a metric project; absent means imperial — the app's own diff-only convention"
      • changedOutput schema / required
        Previous value: -[
        -  "schema",
        -  "project_name",
        -  "units",
        -  "sheets",
        -  "conditions",
        -  "shapes",
        -  "markups",
        -  "sheet_group",
        -  "last_group",
        -  "sheet_tabs",
        -  "sheet_levels"
        -]New value: +[
        +  "schema",
        +  "project_name",
        +  "sheets",
        +  "conditions",
        +  "shapes",
        +  "markups",
        +  "sheet_group",
        +  "last_group",
        +  "sheet_tabs"
        +]
  3. 25 tool updatesv0.1.21
    • Changedcount_marks5 fields changed
      • changedOutput schema / properties / marks / items / properties / count / description
        Previous value: -"Value-paired instances counted on plan-role sheets"New value: +"Instances counted on plan-role sheets — value-paired tags, plus (for an equipment-schedule mark) tags drawn amid linework, see counted_by_label"
      • addedOutput schema / properties / marks / items / properties / counted_by_label
        Added value: +{
        +  "description": "…of which counted by label (an equipment mark with a leader, no value under it)",
        +  "type": "integer"
        +}
      • addedOutput schema / properties / marks / items / properties / occurrences / items / properties / by
        Added value: +{
        +  "description": "\"value\" = tag-over-value (air devices, fixtures); \"label\" = an EQUIPMENT-schedule mark drawn amid linework with no value — the leader-tag convention, counted as one instance and said so",
        +  "enum": [
        +    "value",
        +    "label"
        +  ],
        +  "type": "string"
        +}
      • changedOutput schema / properties / marks / items / properties / occurrences / items / properties / value / description
        Previous value: -"The paired value drawn under the tag (CFM, GPM, a count — the annotation that makes it an instance)"New value: +"The paired value drawn under the tag (CFM, GPM, a count — the annotation that makes it an instance); absent when counted by label"
      • changedOutput schema / properties / marks / items / properties / occurrences / items / required
        Previous value: -[
        -  "at",
        -  "value",
        -  "sheet"
        -]New value: +[
        +  "at",
        +  "sheet",
        +  "by"
        +]
    • Addedcreate_rfi
    • Addeddelete_rfi
    • Removeddetect_rooms
    • Addededit_annotation
    • Changedexport_marked_pdf3 fields changed
      • changedOutput schema / properties / pages / description
        Previous value: -"Legend cover + one page per marked sheet"New value: +"Legend cover + the RFI schedule page(s) when any RFI is live + one page per marked sheet"
      • addedOutput schema / properties / rfis_printed
        Added value: +{
        +  "description": "Live RFIs printed on the RFI schedule page (#364) — agent-raised and panel-raised alike; withdrawn ones leave a numbering gap",
        +  "type": "integer"
        +}
      • changedOutput schema / required
        Previous value: -[
        -  "path",
        -  "pages",
        -  "sheets_marked",
        -  "shapes_drawn",
        -  "annotations_drawn",
        -  "approvals_drawn",
        -  "note"
        -]New value: +[
        +  "path",
        +  "pages",
        +  "sheets_marked",
        +  "shapes_drawn",
        +  "annotations_drawn",
        +  "approvals_drawn",
        +  "rfis_printed",
        +  "note"
        +]
    • Changedexport_report1 field changed
      • addedOutput schema / properties / proposed_condition_edits
        Added value: +{
        +  "description": "Pending condition-edit proposals (#365) — the rows above print the CURRENT knobs; each entry here carries the proposed values beside them. Present only when any are pending, so a proposal-free report is byte-identical",
        +  "items": {
        +    "additionalProperties": false,
        +    "properties": {
        +      "condition": {
        +        "type": "string"
        +      },
        +      "condition_id": {
        +        "type": "string"
        +      },
        +      "current": {
        +        "additionalProperties": true,
        +        "description": "The condition's knobs as they stand — what every total above is computed from",
        +        "properties": {
        +          "finish_tag": {
        +            "type": "string"
        +          },
        +          "height_ft": {
        +            "type": "number"
        +          },
        +          "multiplier": {
        +            "type": "number"
        +          },
        +          "roll_setup": {
        +            "anyOf": [
        +              {
        +                "additionalProperties": true,
        +                "properties": {},
        +                "type": "object"
        +              },
        +              {
        +                "type": "null"
        +              }
        +            ]
        +          },
        +          "waste_pct": {
        +            "type": "number"
        +          }
        +        },
        +        "type": "object"
        +      },
        +      "proposal_id": {
        +        "type": "string"
        +      },
        +      "proposed": {
        +        "additionalProperties": true,
        +        "description": "Only the fields that would change",
        +        "properties": {
        +          "finish_tag": {
        +            "$ref": "#/properties/proposed_condition_edits/items/properties/current/properties/finish_tag"
        +          },
        +          "height_ft": {
        +            "$ref": "#/properties/proposed_condition_edits/items/properties/current/properties/height_ft"
        +          },
        +          "multiplier": {
        +            "$ref": "#/properties/proposed_condition_edits/items/properties/current/properties/multiplier"
        +          },
        +          "roll_setup": {
        +            "$ref": "#/properties/proposed_condition_edits/items/properties/current/properties/roll_setup"
        +          },
        +          "waste_pct": {
        +            "$ref": "#/properties/proposed_condition_edits/items/properties/current/properties/waste_pct"
        +          }
        +        },
        +        "type": "object"
        +      },
        +      "proposed_at": {
        +        "type": "string"
        +      },
        +      "rationale": {
        +        "type": "string"
        +      }
        +    },
        +    "required": [
        +      "proposal_id",
        +      "condition",
        +      "condition_id",
        +      "current",
        +      "proposed",
        +      "rationale",
        +      "proposed_at"
        +    ],
        +    "type": "object"
        +  },
        +  "type": "array"
        +}
    • Changedexport_takeoff5 fields changed
      • addedOutput schema / properties / condition_edit_proposals
        Added value: +{
        +  "description": "Pending condition-edit diffs (#365) — present only when any exist. Nothing on the condition changes until the estimator accepts in the canvas",
        +  "items": {
        +    "additionalProperties": true,
        +    "properties": {
        +      "condition_id": {
        +        "type": "string"
        +      },
        +      "id": {
        +        "type": "string"
        +      },
        +      "proposed": {
        +        "additionalProperties": true,
        +        "properties": {},
        +        "type": "object"
        +      },
        +      "proposed_at": {
        +        "type": "string"
        +      },
        +      "rationale": {
        +        "type": "string"
        +      }
        +    },
        +    "required": [
        +      "id",
        +      "condition_id",
        +      "proposed",
        +      "rationale",
        +      "proposed_at"
        +    ],
        +    "type": "object"
        +  },
        +  "type": "array"
        +}
      • addedOutput schema / properties / proposals
        Added value: +{
        +  "description": "Proposal batches (#365) — present only when any exist. Shapes reference them by origin.proposal_id; the canvas shows one Accept per batch",
        +  "items": {
        +    "additionalProperties": true,
        +    "properties": {
        +      "created_at": {
        +        "type": "string"
        +      },
        +      "id": {
        +        "type": "string"
        +      },
        +      "label": {
        +        "type": "string"
        +      },
        +      "rationale": {
        +        "type": "string"
        +      },
        +      "withdrawn_at": {
        +        "type": "string"
        +      }
        +    },
        +    "required": [
        +      "id",
        +      "label",
        +      "rationale",
        +      "created_at"
        +    ],
        +    "type": "object"
        +  },
        +  "type": "array"
        +}
      • addedOutput schema / properties / rfis
        Added value: +{
        +  "description": "Live RFI records; withdrawn tombstones are omitted",
        +  "items": {},
        +  "type": "array"
        +}
      • addedOutput schema / properties / sheets / items / properties / scale_confirmed
        Added value: +{
        +  "description": "False for agent-set calibration until a human confirms it",
        +  "type": "boolean"
        +}
      • addedOutput schema / properties / sheets / items / properties / scale_source
        Added value: +{
        +  "description": "How the exported calibration was established",
        +  "type": "string"
        +}
    • Changedfind_schedule1 field changed
      • changedInput schema / properties / kind / description
        Previous value: -"\"room finish\" (rooms → surface finishes) or \"finish\"/\"material\" (codes → products)"New value: +"\"room finish\" (rooms → surface finishes), \"finish\"/\"material\" (codes → products), or \"equipment\" (MEP device schedules — fans, pumps, heaters, AHUs, VAVs, diffusers/grilles/registers — keyed by mark, proven by a powered or air-device column)"
    • Addedget_sheet_vectors
    • Addedlist_rfis
    • Changedlist_shapes1 field changed
      • addedOutput schema / properties / shapes / items / properties / proposal_id
        Added value: +{
        +  "description": "The propose_takeoff batch this shape was committed under (#365) — present on shapes committed while a proposal was open; an accepted shape keeps it as history",
        +  "type": "string"
        +}
    • Removedone_click
    • Addedpropose_condition_edit
    • Addedpropose_takeoff
    • Addedresolve_rfi
    • Addedrevise_proposal
    • Addedscope_duplicates
    • Addedscope_merge
    • Changedsweep_schedule_row14 fields changed
      • removedOutput schema / properties / anchor / additionalProperties
        Removed value: -false
      • addedOutput schema / properties / anchor / anyOf
        Added value: +[
        +  {
        +    "additionalProperties": false,
        +    "properties": {
        +      "at": {
        +        "description": "The anchoring tag occurrence's center (image px)",
        +        "items": [
        +          {
        +            "type": "number"
        +          },
        +          {
        +            "type": "number"
        +          }
        +        ],
        +        "maxItems": 2,
        +        "minItems": 2,
        +        "type": "array"
        +      },
        +      "corroborated": {
        +        "description": "true = the fingerprint recurred at a second tag occurrence before being trusted; false = the tag is drawn too sparsely to cross-check (see note)",
        +        "type": "boolean"
        +      },
        +      "length_px": {
        +        "type": "number"
        +      },
        +      "occurrences": {
        +        "description": "Drawn occurrences of the tag across all plan sheets",
        +        "type": "integer"
        +      },
        +      "rect": {
        +        "description": "The fingerprint rect actually used [x0, y0, x1, y1] — the pad ladder's winning step",
        +        "items": {
        +          "type": "number"
        +        },
        +        "maxItems": 4,
        +        "minItems": 4,
        +        "type": "array"
        +      },
        +      "segments": {
        +        "description": "Vector segments in the marker fingerprint",
        +        "type": "integer"
        +      },
        +      "sheet": {
        +        "description": "The plan sheet the fingerprint was anchored on",
        +        "type": "string"
        +      }
        +    },
        +    "required": [
        +      "sheet",
        +      "at",
        +      "rect",
        +      "segments",
        +      "length_px",
        +      "corroborated",
        +      "occurrences"
        +    ],
        +    "type": "object"
        +  },
        +  {
        +    "type": "null"
        +  }
        +]
      • addedOutput schema / properties / anchor / description
        Added value: +"null when the sweep counted BY LABEL: no repeatable marker geometry sits around the drawn tag (the equipment convention — a device drawn to its own size, tagged by a leader), so nothing was fingerprinted"
      • removedOutput schema / properties / anchor / properties
        Removed value: -{
        -  "at": {
        -    "description": "The anchoring tag occurrence's center (image px)",
        -    "items": [
        -      {
        -        "type": "number"
        -      },
        -      {
        -        "type": "number"
        -      }
        -    ],
        -    "maxItems": 2,
        -    "minItems": 2,
        -    "type": "array"
        -  },
        -  "corroborated": {
        -    "description": "true = the fingerprint recurred at a second tag occurrence before being trusted; false = the tag is drawn too sparsely to cross-check (see note)",
        -    "type": "boolean"
        -  },
        -  "length_px": {
        -    "type": "number"
        -  },
        -  "occurrences": {
        -    "description": "Drawn occurrences of the tag across all plan sheets",
        -    "type": "integer"
        -  },
        -  "rect": {
        -    "description": "The fingerprint rect actually used [x0, y0, x1, y1] — the pad ladder's winning step",
        -    "items": {
        -      "type": "number"
        -    },
        -    "maxItems": 4,
        -    "minItems": 4,
        -    "type": "array"
        -  },
        -  "segments": {
        -    "description": "Vector segments in the marker fingerprint",
        -    "type": "integer"
        -  },
        -  "sheet": {
        -    "description": "The plan sheet the fingerprint was anchored on",
        -    "type": "string"
        -  }
        -}
      • removedOutput schema / properties / anchor / required
        Removed value: -[
        -  "sheet",
        -  "at",
        -  "rect",
        -  "segments",
        -  "length_px",
        -  "corroborated",
        -  "occurrences"
        -]
      • removedOutput schema / properties / anchor / type
        Removed value: -"object"
      • addedOutput schema / properties / counted_by
        Added value: +{
        +  "description": "How the count was made — \"label\" means no marker geometry was matched at all; look at each label_only placement before pricing",
        +  "enum": [
        +    "geometry",
        +    "label",
        +    "mixed"
        +  ],
        +  "type": "string"
        +}
      • changedOutput schema / properties / found / description
        Previous value: -"Matches carrying the row's own tag — the honest count, across every plan sheet"New value: +"Instances counted across every plan sheet: geometry matches carrying the row's own tag PLUS drawn tags counted by label — see counted_by"
      • addedOutput schema / properties / found_by_geometry
        Added value: +{
        +  "description": "…of which marker-fingerprint matches corroborated by the tag",
        +  "type": "integer"
        +}
      • addedOutput schema / properties / found_by_label
        Added value: +{
        +  "description": "…of which drawn tags amid linework the fingerprint did not (or could not) reach — an installed instance by the equipment convention, disclosed as label-counted",
        +  "type": "integer"
        +}
      • addedOutput schema / properties / sheets / items / properties / label_only
        Added value: +{
        +  "description": "Instances counted BY LABEL: the row's tag drawn amid linework that the marker fingerprint did not match (or none was anchored) — the equipment convention; each is counted and each deserves a look",
        +  "items": {
        +    "additionalProperties": false,
        +    "properties": {
        +      "at": {
        +        "items": [
        +          {
        +            "type": "number"
        +          },
        +          {
        +            "type": "number"
        +          }
        +        ],
        +        "maxItems": 2,
        +        "minItems": 2,
        +        "type": "array"
        +      },
        +      "tag_at": {
        +        "$ref": "#/properties/row/properties/citation/properties/bbox"
        +      }
        +    },
        +    "required": [
        +      "at",
        +      "tag_at"
        +    ],
        +    "type": "object"
        +  },
        +  "type": "array"
        +}
      • changedOutput schema / properties / sheets / items / properties / text_only / description
        Previous value: -"The tag drawn with NO matching marker geometry nearby — a note reference or a variant marker; a question, never a count"New value: +"The tag drawn with no marker match AND no linework near it — a note mentioning the mark; a question, never a count"
      • changedOutput schema / properties / sheets / items / required
        Previous value: -[
        -  "sheet",
        -  "found",
        -  "matches",
        -  "withheld",
        -  "excluded",
        -  "text_only",
        -  "candidates",
        -  "complete",
        -  "elapsed_ms"
        -]New value: +[
        +  "sheet",
        +  "found",
        +  "matches",
        +  "withheld",
        +  "excluded",
        +  "text_only",
        +  "label_only",
        +  "candidates",
        +  "complete",
        +  "elapsed_ms"
        +]
      • changedOutput schema / required
        Previous value: -[
        -  "tag",
        -  "row",
        -  "anchor",
        -  "found",
        -  "sheets",
        -  "complete",
        -  "skipped"
        -]New value: +[
        +  "tag",
        +  "row",
        +  "anchor",
        +  "found",
        +  "found_by_geometry",
        +  "found_by_label",
        +  "counted_by",
        +  "sheets",
        +  "complete",
        +  "skipped"
        +]
    • Changedsymbol_sweep8 fields changed
      • addedInput schema / properties / variant_guard
        Added value: +{
        +  "default": false,
        +  "description": "Whole-symbol mode: demote richer-variant placements (>30% extra linework inside the footprint) to withheld instead of counting them with an `extra` disclosure. Use when the seed is a COMPLETE symbol (a grille, a drain); leave off when seeding a contained sub-shape. Stands down when exclude counter-examples are passed",
        +  "type": "boolean"
        +}
      • addedOutput schema / properties / commit_refused
        Added value: +{
        +  "description": "commit mode (#376): present when the seed was too small and too common to commit on shape alone — fewer than 40 segments of seed linework and more than 50 placements cleared the bar. NOTHING was committed; the placements are still listed in matches (or per sheet in sheets[]) so you can look, and the text says what stands the guard down: variant_guard: true, exclude counter-examples, or a seed rect that captures more of the symbol",
        +  "type": "string"
        +}
      • changedOutput schema / properties / committed / description
        Previous value: -"commit mode: count shapes committed — one per match"New value: +"commit mode: count shapes committed — one per match (0 when commit_refused is present)"
      • addedOutput schema / properties / matches / items / properties / extra
        Added value: +{
        +  "description": "Richer-variant disclosure: the fraction of the seed's total length found as UNMATCHED extra linework fully inside this placement's footprint, present when past the 0.30 bar — the classic grille-counted-as-register shape; LOOK at these first. Under variant_guard such placements demote to withheld instead of matching",
        +  "type": "number"
        +}
      • addedOutput schema / properties / rejected / items / properties / extra
        Added value: +{
        +  "$ref": "#/properties/matches/items/properties/extra"
        +}
      • addedOutput schema / properties / sheets / items / properties / matches / items / properties / extra
        Added value: +{
        +  "$ref": "#/properties/matches/items/properties/extra"
        +}
      • addedOutput schema / properties / sheets / items / properties / withheld / items / properties / extra
        Added value: +{
        +  "$ref": "#/properties/matches/items/properties/extra"
        +}
      • addedOutput schema / properties / withheld / items / properties / extra
        Added value: +{
        +  "$ref": "#/properties/matches/items/properties/extra"
        +}
    • Changedtakeoff_summary5 fields changed
      • addedOutput schema / properties / proposals
        Added value: +{
        +  "description": "The proposal ledger (#365): per batch, how many shapes are still pending, how many the estimator accepted, whether it was withdrawn, and which batch new commits attach to (current). Present only when a proposal exists",
        +  "items": {
        +    "additionalProperties": false,
        +    "properties": {
        +      "accepted": {
        +        "description": "Shapes from the batch the estimator already accepted — ink, outside every agent verb",
        +        "type": "integer"
        +      },
        +      "current": {
        +        "const": true,
        +        "description": "New agent commits attach to this batch",
        +        "type": "boolean"
        +      },
        +      "label": {
        +        "type": "string"
        +      },
        +      "pending": {
        +        "description": "Shapes attached to the batch and not yet affirmed by a human",
        +        "type": "integer"
        +      },
        +      "proposal_id": {
        +        "type": "string"
        +      },
        +      "rationale": {
        +        "type": "string"
        +      },
        +      "withdrawn": {
        +        "const": true,
        +        "type": "boolean"
        +      }
        +    },
        +    "required": [
        +      "proposal_id",
        +      "label",
        +      "rationale",
        +      "pending",
        +      "accepted"
        +    ],
        +    "type": "object"
        +  },
        +  "type": "array"
        +}
      • addedOutput schema / properties / proposed_condition_edits
        Added value: +{
        +  "description": "Pending condition-edit diffs (#365) beside the current knobs. The rows above are the CURRENT values — nothing changes until the estimator accepts. Present only when any are pending",
        +  "items": {
        +    "additionalProperties": false,
        +    "properties": {
        +      "condition": {
        +        "type": "string"
        +      },
        +      "condition_id": {
        +        "type": "string"
        +      },
        +      "current": {
        +        "additionalProperties": true,
        +        "description": "The condition's knobs as they stand — what every total above is computed from",
        +        "properties": {
        +          "finish_tag": {
        +            "type": "string"
        +          },
        +          "height_ft": {
        +            "type": "number"
        +          },
        +          "multiplier": {
        +            "type": "number"
        +          },
        +          "roll_setup": {
        +            "anyOf": [
        +              {
        +                "additionalProperties": true,
        +                "properties": {},
        +                "type": "object"
        +              },
        +              {
        +                "type": "null"
        +              }
        +            ]
        +          },
        +          "waste_pct": {
        +            "type": "number"
        +          }
        +        },
        +        "type": "object"
        +      },
        +      "proposal_id": {
        +        "type": "string"
        +      },
        +      "proposed": {
        +        "additionalProperties": true,
        +        "description": "Only the fields that would change",
        +        "properties": {
        +          "finish_tag": {
        +            "$ref": "#/properties/proposed_condition_edits/items/properties/current/properties/finish_tag"
        +          },
        +          "height_ft": {
        +            "$ref": "#/properties/proposed_condition_edits/items/properties/current/properties/height_ft"
        +          },
        +          "multiplier": {
        +            "$ref": "#/properties/proposed_condition_edits/items/properties/current/properties/multiplier"
        +          },
        +          "roll_setup": {
        +            "$ref": "#/properties/proposed_condition_edits/items/properties/current/properties/roll_setup"
        +          },
        +          "waste_pct": {
        +            "$ref": "#/properties/proposed_condition_edits/items/properties/current/properties/waste_pct"
        +          }
        +        },
        +        "type": "object"
        +      },
        +      "proposed_at": {
        +        "type": "string"
        +      },
        +      "rationale": {
        +        "type": "string"
        +      }
        +    },
        +    "required": [
        +      "proposal_id",
        +      "condition",
        +      "condition_id",
        +      "current",
        +      "proposed",
        +      "rationale",
        +      "proposed_at"
        +    ],
        +    "type": "object"
        +  },
        +  "type": "array"
        +}
      • addedOutput schema / properties / shared_floor_sf
        Added value: +{
        +  "description": "Scope collision (#366): floor claimed by more than one shape across the whole takeoff, counted once per cell (Σ areas − union), in SF through each sheet's scale. Has to read 0 before a total means anything — scope_duplicates names the pairs",
        +  "type": "number"
        +}
      • addedOutput schema / properties / shared_floor_unmeasured
        Added value: +{
        +  "description": "Floor shapes the collision check could not measure (unscaled sheet, degenerate ring) — left OUT of shared_floor_sf rather than counted as zero; present only when any",
        +  "items": {
        +    "additionalProperties": false,
        +    "properties": {
        +      "reason": {
        +        "type": "string"
        +      },
        +      "shape_id": {
        +        "type": "string"
        +      },
        +      "sheet_id": {
        +        "type": "string"
        +      }
        +    },
        +    "required": [
        +      "shape_id",
        +      "sheet_id",
        +      "reason"
        +    ],
        +    "type": "object"
        +  },
        +  "type": "array"
        +}
      • changedOutput schema / required
        Previous value: -[
        -  "conditions",
        -  "totals"
        -]New value: +[
        +  "conditions",
        +  "totals",
        +  "shared_floor_sf"
        +]
    • Changedundo_last1 field changed
      • changedOutput schema / properties / steps / items / properties / op / enum
        Previous value: -[
        -  "commit",
        -  "edit",
        -  "delete",
        -  "materials",
        -  "condition",
        -  "approval",
        -  "duplicate_condition",
        -  "split_condition",
        -  "cutout",
        -  "cutout_restore",
        -  "runcut"
        -]New value: +[
        +  "commit",
        +  "scale",
        +  "edit",
        +  "annotation_text",
        +  "delete",
        +  "materials",
        +  "condition",
        +  "approval",
        +  "duplicate_condition",
        +  "split_condition",
        +  "cutout",
        +  "cutout_restore",
        +  "runcut",
        +  "rfi_create",
        +  "rfi_resolve",
        +  "rfi_delete",
        +  "proposal_open",
        +  "proposal_revise",
        +  "proposal_withdraw",
        +  "condition_proposal",
        +  "condition_proposal_withdraw",
        +  "condition_proposal_accept"
        +]
    • Addedwithdraw_condition_edit
    • Addedwithdraw_proposal
  4. 1 tool updatev0.1.20
    • Addedexport_dxf
  5. 5 tool updatesv0.1.19
    • Addedcount_marks
    • Changedcut_out11 fields changed
      • changedInput schema / properties / parent_shape_id / description
        Previous value: -"A committed floor_area shape id (list_shapes)"New value: +"A committed floor_area shape id, or an open run (surface_area / linear) to clip (list_shapes)"
      • changedInput schema / properties / verts / description
        Previous value: -"The hole's ring, image px, fully inside the parent"New value: +"The ring, image px — fully inside the parent for an area; over the stretch to remove for a run"
      • changedOutput schema / properties / deduct_shape_id / description
        Previous value: -"The reconciled deduct — carries cuts_shape_id; totals skip it (the parent nets the hole)"New value: +"Area parent: the reconciled deduct — carries cuts_shape_id; totals skip it (the parent nets the hole)"
      • changedOutput schema / properties / hole_sf / description
        Previous value: -"What this cut actually removed from the parent's net — 0 when the ring fell entirely inside an existing hole"New value: +"Area parent: what this cut actually removed from the parent's net — 0 when the ring fell entirely inside an existing hole"
      • changedOutput schema / properties / holes / description
        Previous value: -"Holes the parent now carries"New value: +"Area parent: holes the parent now carries"
      • addedOutput schema / properties / measure_role
        Added value: +{
        +  "description": "Run parent: surface_area or linear",
        +  "type": "string"
        +}
      • addedOutput schema / properties / pieces
        Added value: +{
        +  "description": "Run parent: every stretch that survived the cut — more than one when the ring fell in the middle",
        +  "items": {
        +    "additionalProperties": false,
        +    "properties": {
        +      "lf": {
        +        "description": "This piece's own length",
        +        "type": "number"
        +      },
        +      "sf": {
        +        "description": "LF × the height (wall) or the thickness (border) it was measured at",
        +        "type": "number"
        +      },
        +      "shape_id": {
        +        "type": "string"
        +      }
        +    },
        +    "required": [
        +      "shape_id",
        +      "lf",
        +      "sf"
        +    ],
        +    "type": "object"
        +  },
        +  "type": "array"
        +}
      • addedOutput schema / properties / removed_lf
        Added value: +{
        +  "description": "Run parent: length the cut took out",
        +  "type": "number"
        +}
      • addedOutput schema / properties / removed_sf
        Added value: +{
        +  "description": "Run parent: the SF that rode on that length",
        +  "type": "number"
        +}
      • addedOutput schema / properties / shape_id
        Added value: +{
        +  "description": "Run parent: the run that was clipped — it keeps its id and takes the first surviving stretch",
        +  "type": "string"
        +}
      • changedOutput schema / required
        Previous value: -[
        -  "deduct_shape_id",
        -  "parent_shape_id",
        -  "hole_sf",
        -  "parent_net",
        -  "holes",
        -  "note"
        -]New value: +[
        +  "note"
        +]
    • Changedsymbol_sweep20 fields changed
      • addedInput schema / properties / commit_seed
        Added value: +{
        +  "default": false,
        +  "description": "Sheet scope + commit only (#296): also commit the SEED instance — in sheet scope the seed is almost always installed work, and a count that excludes it bids one short. Joins the same one-undo-step batch, origin score 1. Refused in set scope, where a detail/legend seed is a reference drawing",
        +  "type": "boolean"
        +}
      • addedInput schema / properties / exclude
        Added value: +{
        +  "description": "Counter-examples: rects around instances you do NOT mean, same gesture as seed_rect — 'count the triangles, not the keynote ones'. Marquee the LOOKALIKE ITSELF (the flush-floor variant with its box, the keynote triangle with its letter) or an EMPTY position whose background line a real instance would break (a bare ceiling grid tile). You never say which kind it is: the rect's own contents decide. Every rejection comes back in rejected[] with which negative did it and what it saw",
        +  "items": {
        +    "items": [
        +      {
        +        "$ref": "#/properties/seed_rect/items/0"
        +      },
        +      {
        +        "$ref": "#/properties/seed_rect/items/0"
        +      }
        +    ],
        +    "maxItems": 2,
        +    "minItems": 2,
        +    "type": "array"
        +  },
        +  "type": "array"
        +}
      • addedInput schema / properties / luminance_tolerance
        Added value: +{
        +  "description": "Stroke-luminance gate, 0–254 (#260): a sheet segment only answers for a seed segment when their stroke luminances (Rec. 709, 0 black – 255 white) are within this. For flattened exports where a black device and its grey background twin are geometrically identical — 32–64 separates black from grey. Omit to score on geometry alone; stated, the reply's lum_gate discloses the seed's luminance band and every placement the gate pulled under the commit bar",
        +  "maximum": 254,
        +  "minimum": 0,
        +  "type": "integer"
        +}
      • addedOutput schema / properties / lum_gate
        Added value: +{
        +  "additionalProperties": false,
        +  "description": "Sheet scope only. The stated stroke-luminance gate's accounting (#260): the tolerance, the seed's own luminance band, and every placement the geometry would have committed that the pen pulled under the bar — NEVER counted in found, never silent. Set scope accounts per sheet in sheets[]",
        +  "properties": {
        +    "at": {
        +      "description": "Where each of them is, image px — view_sheet and look before trusting the gate; place_count reinstates one you disagree with",
        +      "items": {
        +        "items": [
        +          {
        +            "type": "number"
        +          },
        +          {
        +            "type": "number"
        +          }
        +        ],
        +        "maxItems": 2,
        +        "minItems": 2,
        +        "type": "array"
        +      },
        +      "type": "array"
        +    },
        +    "rejected": {
        +      "description": "Placements the geometry alone would have COMMITTED and the gate did not — one entry per physical spot",
        +      "type": "integer"
        +    },
        +    "seed_lum": {
        +      "description": "The seed's own stroke luminances, deduplicated — the band candidates were held to",
        +      "items": {
        +        "type": "number"
        +      },
        +      "type": "array"
        +    },
        +    "tol": {
        +      "description": "The luminance tolerance that was applied, 0–254",
        +      "type": "number"
        +    }
        +  },
        +  "required": [
        +    "tol",
        +    "seed_lum",
        +    "rejected",
        +    "at"
        +  ],
        +  "type": "object"
        +}
      • addedOutput schema / properties / matches / items / properties / label
        Added value: +{
        +  "description": "The drawing's own tag for this placement (#308) — a fixture token written beside it or connected by a drawn leader (e.g. \"P-7\", \"FD1\"). Disclosure, never a recount: a match with NO label in a labeled family was counted on shape alone (look before trusting), and a withheld row carrying the seed's own tag is the drawing vouching for it",
        +  "type": "string"
        +}
      • addedOutput schema / properties / matches / items / properties / label_via
        Added value: +{
        +  "description": "How the tag reached this placement: written beside it, or followed along a drawn leader line (leader-following arms only on multi-pen sheets, where the annotation pen separates from the work)",
        +  "enum": [
        +    "adjacent",
        +    "leader"
        +  ],
        +  "type": "string"
        +}
      • addedOutput schema / properties / negatives
        Added value: +{
        +  "description": "What each `exclude` rect was read as, in the order you passed them (#259)",
        +  "items": {
        +    "additionalProperties": false,
        +    "properties": {
        +      "center": {
        +        "description": "Where the seed's own geometry was located inside that rect, image px — what the negative aligned to",
        +        "items": [
        +          {
        +            "type": "number"
        +          },
        +          {
        +            "type": "number"
        +          }
        +        ],
        +        "maxItems": 2,
        +        "minItems": 2,
        +        "type": "array"
        +      },
        +      "mode": {
        +        "enum": [
        +          "shape",
        +          "crossing"
        +        ],
        +        "type": "string"
        +      },
        +      "segments": {
        +        "description": "Discriminating segments this counter-example contributes — the linework that is NOT the seed",
        +        "type": "integer"
        +      }
        +    },
        +    "required": [
        +      "mode",
        +      "segments",
        +      "center"
        +    ],
        +    "type": "object"
        +  },
        +  "type": "array"
        +}
      • addedOutput schema / properties / rejected
        Added value: +{
        +  "description": "Sheet scope only. Placements the geometry accepted and a counter-example refused (#259) — NEVER counted in found, and never silent: each says which negative did it and what it saw. Reinstate one by hand with place_count at its `at` if you disagree",
        +  "items": {
        +    "additionalProperties": false,
        +    "properties": {
        +      "at": {
        +        "$ref": "#/properties/matches/items/properties/at"
        +      },
        +      "by": {
        +        "description": "Which counter-example rejected it — 1-based index into the `exclude` rects you passed",
        +        "type": "integer"
        +      },
        +      "evidence": {
        +        "description": "Fraction of that counter-example's discriminating linework found at this placement, 0..1 (rejection bar 0.5)",
        +        "type": "number"
        +      },
        +      "label": {
        +        "$ref": "#/properties/matches/items/properties/label"
        +      },
        +      "label_via": {
        +        "$ref": "#/properties/matches/items/properties/label_via"
        +      },
        +      "mirrored": {
        +        "$ref": "#/properties/matches/items/properties/mirrored"
        +      },
        +      "mode": {
        +        "description": "What that counter-example was read as. \"shape\": it carries extra linework the seed does not, and that linework is present here too. \"crossing\": it carries no extra linework of its own — what marks it is a line running THROUGH it, and that line runs unbroken through this placement",
        +        "enum": [
        +          "shape",
        +          "crossing"
        +        ],
        +        "type": "string"
        +      },
        +      "reason": {
        +        "type": "string"
        +      },
        +      "rotation": {
        +        "$ref": "#/properties/matches/items/properties/rotation"
        +      },
        +      "score": {
        +        "$ref": "#/properties/matches/items/properties/score"
        +      }
        +    },
        +    "required": [
        +      "at",
        +      "score",
        +      "rotation",
        +      "mirrored",
        +      "by",
        +      "mode",
        +      "evidence",
        +      "reason"
        +    ],
        +    "type": "object"
        +  },
        +  "type": "array"
        +}
      • addedOutput schema / properties / rejected_total
        Added value: +{
        +  "description": "Set scope: placements counter-examples rejected across every swept sheet",
        +  "type": "integer"
        +}
      • addedOutput schema / properties / seed / properties / label
        Added value: +{
        +  "description": "The drawing's own tag for the seed instance (#308) — the family's identity, e.g. seeding a drain the sheet labels \"P-7\"",
        +  "type": "string"
        +}
      • addedOutput schema / properties / seed / properties / label_via
        Added value: +{
        +  "enum": [
        +    "adjacent",
        +    "leader"
        +  ],
        +  "type": "string"
        +}
      • addedOutput schema / properties / seed_committed
        Added value: +{
        +  "description": "Present when commit_seed: true minted the seed instance into the batch (#296) — ea_total then includes it",
        +  "type": "boolean"
        +}
      • addedOutput schema / properties / sheets / items / properties / lum_gate
        Added value: +{
        +  "$ref": "#/properties/lum_gate",
        +  "description": "This sheet's stated-luminance-gate accounting (#260) — present only when luminance_tolerance was stated"
        +}
      • addedOutput schema / properties / sheets / items / properties / matches / items / properties / label
        Added value: +{
        +  "$ref": "#/properties/matches/items/properties/label"
        +}
      • addedOutput schema / properties / sheets / items / properties / matches / items / properties / label_via
        Added value: +{
        +  "$ref": "#/properties/matches/items/properties/label_via"
        +}
      • addedOutput schema / properties / sheets / items / properties / rejected
        Added value: +{
        +  "description": "Placements a counter-example rejected on this sheet (#259) — never counted, always named",
        +  "items": {
        +    "$ref": "#/properties/rejected/items"
        +  },
        +  "type": "array"
        +}
      • addedOutput schema / properties / sheets / items / properties / withheld / items / properties / label
        Added value: +{
        +  "$ref": "#/properties/matches/items/properties/label"
        +}
      • addedOutput schema / properties / sheets / items / properties / withheld / items / properties / label_via
        Added value: +{
        +  "$ref": "#/properties/matches/items/properties/label_via"
        +}
      • addedOutput schema / properties / withheld / items / properties / label
        Added value: +{
        +  "$ref": "#/properties/matches/items/properties/label"
        +}
      • addedOutput schema / properties / withheld / items / properties / label_via
        Added value: +{
        +  "$ref": "#/properties/matches/items/properties/label_via"
        +}
    • Changedundo_last1 field changed
      • changedOutput schema / properties / steps / items / properties / op / enum
        Previous value: -[
        -  "commit",
        -  "edit",
        -  "delete",
        -  "materials",
        -  "condition",
        -  "approval",
        -  "duplicate_condition",
        -  "split_condition",
        -  "cutout",
        -  "cutout_restore"
        -]New value: +[
        +  "commit",
        +  "edit",
        +  "delete",
        +  "materials",
        +  "condition",
        +  "approval",
        +  "duplicate_condition",
        +  "split_condition",
        +  "cutout",
        +  "cutout_restore",
        +  "runcut"
        +]
    • Changedview_sheet1 field changed
      • addedInput schema / properties / marks
        Added value: +{
        +  "additionalProperties": false,
        +  "description": "Disclosure marks to burn into the render (#297): what the reply names, the picture shows. Coordinates in image px",
        +  "properties": {
        +    "question": {
        +      "description": "Open questions — withheld placements, spots to look at. Orange ?-in-circle",
        +      "items": {
        +        "items": [
        +          {
        +            "type": "number"
        +          },
        +          {
        +            "type": "number"
        +          }
        +        ],
        +        "maxItems": 2,
        +        "minItems": 2,
        +        "type": "array"
        +      },
        +      "type": "array"
        +    },
        +    "ring": {
        +      "description": "Reference points — the sweep's seed.center, an anchor. Violet double ring",
        +      "items": {
        +        "$ref": "#/properties/marks/properties/question/items"
        +      },
        +      "type": "array"
        +    },
        +    "struck": {
        +      "description": "Refusals — rejected[] placements, lum_gate.at. Magenta struck ×",
        +      "items": {
        +        "$ref": "#/properties/marks/properties/question/items"
        +      },
        +      "type": "array"
        +    }
        +  },
        +  "type": "object"
        +}
  6. 5 tool updatesv0.1.18
    • Changedfind_schedule1 field changed
      • addedOutput schema / properties / matches / items / properties / revised_rows
        Added value: +{
        +  "description": "Rows carrying a delta/REV marker — the ink changed there; resolve those tags to see which",
        +  "type": "integer"
        +}
    • Changedresolve_tag10 fields changed
      • changedOutput schema / properties / finishes / items / properties / definition / properties / source / $ref
        Previous value: -"#/properties/finishes/items/properties/source"New value: +"#/properties/room/anyOf/0/properties/revision/properties/source"
      • addedOutput schema / properties / finishes / items / properties / source / $ref
        Added value: +"#/properties/room/anyOf/0/properties/revision/properties/source"
      • removedOutput schema / properties / finishes / items / properties / source / additionalProperties
        Removed value: -false
      • removedOutput schema / properties / finishes / items / properties / source / description
        Removed value: -"An evidence pointer — the sheet, the literal text, and where it sits (image px). Every edge in the graph carries one; pass the bbox to view_sheet to LOOK at the source."
      • removedOutput schema / properties / finishes / items / properties / source / properties
        Removed value: -{
        -  "bbox": {
        -    "$ref": "#/properties/room/anyOf/0/properties/bbox"
        -  },
        -  "sheet": {
        -    "type": "string"
        -  },
        -  "text": {
        -    "type": "string"
        -  }
        -}
      • removedOutput schema / properties / finishes / items / properties / source / required
        Removed value: -[
        -  "sheet",
        -  "text",
        -  "bbox"
        -]
      • removedOutput schema / properties / finishes / items / properties / source / type
        Removed value: -"object"
      • addedOutput schema / properties / revisions
        Added value: +{
        +  "description": "resolved only — delta/REV markers on the answering schedule row or the plan bubble. The finishes above are the POST-revision answer, but the ink changed: check the marker (view_sheet its bbox) and the addendum before pricing",
        +  "items": {
        +    "$ref": "#/properties/room/anyOf/0/properties/revision"
        +  },
        +  "type": "array"
        +}
      • changedOutput schema / properties / room / anyOf
        Previous value: -[
        -  {
        -    "additionalProperties": false,
        -    "properties": {
        -      "bbox": {
        -        "additionalProperties": false,
        -        "properties": {
        -          "x0": {
        -            "type": "number"
        -          },
        -          "x1": {
        -            "type": "number"
        -          },
        -          "y0": {
        -            "type": "number"
        -          },
        -          "y1": {
        -            "type": "number"
        -          }
        -        },
        -        "required": [
        -          "x0",
        -          "y0",
        -          "x1",
        -          "y1"
        -        ],
        -        "type": "object"
        -      },
        -      "building": {
        -        "description": "The building the room belongs to, when the set names one — its plan sheet's BUILDING/BLDG context, or the tag's own qualifier ('A-134')",
        -        "type": "string"
        -      },
        -      "name": {
        -        "description": "The name span stacked over the tag ('' when none)",
        -        "type": "string"
        -      },
        -      "sheet": {
        -        "type": "string"
        -      },
        -      "tag": {
        -        "type": "string"
        -      }
        -    },
        -    "required": [
        -      "tag",
        -      "name",
        -      "sheet",
        -      "bbox"
        -    ],
        -    "type": "object"
        -  },
        -  {
        -    "type": "null"
        -  }
        -]New value: +[
        +  {
        +    "additionalProperties": false,
        +    "properties": {
        +      "bbox": {
        +        "additionalProperties": false,
        +        "properties": {
        +          "x0": {
        +            "type": "number"
        +          },
        +          "x1": {
        +            "type": "number"
        +          },
        +          "y0": {
        +            "type": "number"
        +          },
        +          "y1": {
        +            "type": "number"
        +          }
        +        },
        +        "required": [
        +          "x0",
        +          "y0",
        +          "x1",
        +          "y1"
        +        ],
        +        "type": "object"
        +      },
        +      "building": {
        +        "description": "The building the room belongs to, when the set names one — its plan sheet's BUILDING/BLDG context, or the tag's own qualifier ('A-134')",
        +        "type": "string"
        +      },
        +      "corroboration": {
        +        "description": "Why this number is believed to be a room: \"schedule\" (a room-finish row answers for it), \"name\" (a name is drawn with it and the set has no room-finish schedule), or \"name+schedule\"",
        +        "type": "string"
        +      },
        +      "name": {
        +        "description": "The name span stacked over the tag ('' when none)",
        +        "type": "string"
        +      },
        +      "revision": {
        +        "additionalProperties": false,
        +        "description": "A revision marker (delta triangle / 'REV 2' tag) attached to this item: the ink CHANGED under that revision. The value read is the post-revision answer — view_sheet the marker's bbox and check the addendum before pricing",
        +        "properties": {
        +          "drawn": {
        +            "description": "true = a DRAWN delta: a bare digit inside a triangle of linework (the common CAD convention — the text layer carries only the digit; the geometry proved the triangle). The evidence bbox spans digit and triangle",
        +            "type": "boolean"
        +          },
        +          "rev": {
        +            "type": "string"
        +          },
        +          "source": {
        +            "additionalProperties": false,
        +            "description": "An evidence pointer — the sheet, the literal text, and where it sits (image px). Every edge in the graph carries one; pass the bbox to view_sheet to LOOK at the source.",
        +            "properties": {
        +              "bbox": {
        +                "$ref": "#/properties/room/anyOf/0/properties/bbox"
        +              },
        +              "sheet": {
        +                "type": "string"
        +              },
        +              "text": {
        +                "type": "string"
        +              }
        +            },
        +            "required": [
        +              "sheet",
        +              "text",
        +              "bbox"
        +            ],
        +            "type": "object"
        +          }
        +        },
        +        "required": [
        +          "rev",
        +          "source"
        +        ],
        +        "type": "object"
        +      },
        +      "sheet": {
        +        "type": "string"
        +      },
        +      "tag": {
        +        "type": "string"
        +      }
        +    },
        +    "required": [
        +      "tag",
        +      "name",
        +      "sheet",
        +      "bbox"
        +    ],
        +    "type": "object"
        +  },
        +  {
        +    "type": "null"
        +  }
        +]
      • changedOutput schema / properties / sources / items / $ref
        Previous value: -"#/properties/finishes/items/properties/source"New value: +"#/properties/room/anyOf/0/properties/revision/properties/source"
    • Changedsheet_graph6 fields changed
      • addedOutput schema / properties / counts / properties / unmatched_tags
        Added value: +{
        +  "type": "integer"
        +}
      • addedOutput schema / properties / revisions
        Added value: +{
        +  "description": "Every delta-triangle / REV-tag marker the set carries — text markers ('Δ2', 'REV 2') and DRAWN deltas (a bare digit inside a triangle of linework, drawn: true) — where one sits, the ink changed under that revision. Markers on a schedule row or room bubble also attach there (and ride resolve_tag). A revision CLOUD is arc-chain linework these detectors do not read — absence here is not absence of revisions",
        +  "items": {
        +    "additionalProperties": false,
        +    "properties": {
        +      "bbox": {
        +        "$ref": "#/properties/sheets/items/properties/evidence/properties/bbox"
        +      },
        +      "drawn": {
        +        "type": "boolean"
        +      },
        +      "rev": {
        +        "type": "string"
        +      },
        +      "sheet": {
        +        "type": "string"
        +      }
        +    },
        +    "required": [
        +      "rev",
        +      "sheet",
        +      "bbox"
        +    ],
        +    "type": "object"
        +  },
        +  "type": "array"
        +}
      • changedOutput schema / properties / rooms / description
        Previous value: -"Room tags read off plan-role sheets — schedule sheets contribute rows, never phantom rooms"New value: +"Numbers CORROBORATED as rooms — a room-finish row answers for them, or (where the set carries no room-finish schedule) a room name is drawn with them. Each says which in `corroboration`. Schedule sheets contribute rows, never phantom rooms"
      • addedOutput schema / properties / rooms / items / properties / corroboration
        Added value: +{
        +  "description": "Why this number is believed to be a room: \"schedule\" (a room-finish row answers for it), \"name\" (a name is drawn with it and the set has no room-finish schedule), or \"name+schedule\"",
        +  "type": "string"
        +}
      • addedOutput schema / properties / rooms / items / properties / revision
        Added value: +{
        +  "additionalProperties": false,
        +  "description": "A revision marker (delta triangle / 'REV 2' tag) attached to this item: the ink CHANGED under that revision. The value read is the post-revision answer — view_sheet the marker's bbox and check the addendum before pricing",
        +  "properties": {
        +    "drawn": {
        +      "description": "true = a DRAWN delta: a bare digit inside a triangle of linework (the common CAD convention — the text layer carries only the digit; the geometry proved the triangle). The evidence bbox spans digit and triangle",
        +      "type": "boolean"
        +    },
        +    "rev": {
        +      "type": "string"
        +    },
        +    "source": {
        +      "$ref": "#/properties/sheets/items/properties/evidence"
        +    }
        +  },
        +  "required": [
        +    "rev",
        +    "source"
        +  ],
        +  "type": "object"
        +}
      • addedOutput schema / properties / unmatched_tags
        Added value: +{
        +  "description": "Numbered tags on plan sheets that are NOT counted as rooms — keynote hexagons, detail markers, dimension fragments, legend rows. Listed with a reason, never dropped. A real finish plan is covered in 2–3 digit numbers that are not rooms; counting them as rooms makes every one come back \"no schedule row\", which reads exactly like the lost-bid case and buries it",
        +  "items": {
        +    "additionalProperties": false,
        +    "properties": {
        +      "bbox": {
        +        "$ref": "#/properties/sheets/items/properties/evidence/properties/bbox"
        +      },
        +      "building": {
        +        "type": "string"
        +      },
        +      "name": {
        +        "description": "Text drawn with the number, when there is any — on a keynote legend this is the accessory description, not a room name",
        +        "type": "string"
        +      },
        +      "reason": {
        +        "description": "WHY this number is not counted as a room. Read these: one of them may be a room the schedule left out, which is a hole in the bid",
        +        "type": "string"
        +      },
        +      "sheet": {
        +        "type": "string"
        +      },
        +      "tag": {
        +        "type": "string"
        +      }
        +    },
        +    "required": [
        +      "tag",
        +      "sheet",
        +      "bbox",
        +      "reason"
        +    ],
        +    "type": "object"
        +  },
        +  "type": "array"
        +}
    • Changedsweep_schedule_row4 fields changed
      • addedOutput schema / properties / complete
        Added value: +{
        +  "description": "True when every proposed placement was scored on every swept sheet — false means at least one sheet's count is a FLOOR, not a total (#261)",
        +  "type": "boolean"
        +}
      • addedOutput schema / properties / sheets / items / properties / complete
        Added value: +{
        +  "description": "True when every proposed placement on this sheet was scored — false means this sheet's count is a FLOOR, not a total (#261)",
        +  "type": "boolean"
        +}
      • changedOutput schema / properties / sheets / items / required
        Previous value: -[
        -  "sheet",
        -  "found",
        -  "matches",
        -  "withheld",
        -  "excluded",
        -  "text_only",
        -  "candidates",
        -  "elapsed_ms"
        -]New value: +[
        +  "sheet",
        +  "found",
        +  "matches",
        +  "withheld",
        +  "excluded",
        +  "text_only",
        +  "candidates",
        +  "complete",
        +  "elapsed_ms"
        +]
      • changedOutput schema / required
        Previous value: -[
        -  "tag",
        -  "row",
        -  "anchor",
        -  "found",
        -  "sheets",
        -  "skipped"
        -]New value: +[
        +  "tag",
        +  "row",
        +  "anchor",
        +  "found",
        +  "sheets",
        +  "complete",
        +  "skipped"
        +]
    • Changedsymbol_sweep5 fields changed
      • addedOutput schema / properties / complete
        Added value: +{
        +  "description": "True when every proposed placement was scored (every swept sheet, in set scope) and the count is a total. FALSE MEANS THE COUNT IS A FLOOR — acknowledge it before trusting found (#261)",
        +  "type": "boolean"
        +}
      • changedOutput schema / properties / sheets / items / properties / candidates / description
        Previous value: -"The work cap applies PER SHEET; dropped > 0 here names exactly where the count is incomplete"New value: +"The work ceiling applies PER SHEET; dropped > 0 here names exactly where the count is incomplete"
      • addedOutput schema / properties / sheets / items / properties / complete
        Added value: +{
        +  "description": "True when every proposed placement on this sheet was scored — false means this sheet's count is a FLOOR, not a total (#261)",
        +  "type": "boolean"
        +}
      • changedOutput schema / properties / sheets / items / required
        Previous value: -[
        -  "sheet",
        -  "found",
        -  "matches",
        -  "withheld",
        -  "candidates",
        -  "elapsed_ms"
        -]New value: +[
        +  "sheet",
        +  "found",
        +  "matches",
        +  "withheld",
        +  "candidates",
        +  "complete",
        +  "elapsed_ms"
        +]
      • changedOutput schema / required
        Previous value: -[
        -  "scope",
        -  "found",
        -  "seed"
        -]New value: +[
        +  "scope",
        +  "found",
        +  "seed",
        +  "complete"
        +]
  7. 2 tool updatesv0.1.16
    • Changedset_scale2 fields changed
      • addedOutput schema / properties / confirmed
        Added value: +{
        +  "description": "Always false here: set_scale is the agent surface, and an agent-set scale stays UNCONFIRMED until a human confirms it in the canvas — quantities still flow, wearing the caveat",
        +  "type": "boolean"
        +}
      • changedOutput schema / required
        Previous value: -[
        -  "sheet",
        -  "upp",
        -  "source"
        -]New value: +[
        +  "sheet",
        +  "upp",
        +  "source",
        +  "confirmed"
        +]
    • Changedtakeoff_summary1 field changed
      • addedOutput schema / properties / scale_unconfirmed
        Added value: +{
        +  "description": "Sheets whose scale is agent-set and no human has confirmed — these totals stand on an unverified scale; verify against a stated dimension or confirm in the canvas",
        +  "items": {
        +    "type": "string"
        +  },
        +  "type": "array"
        +}
  8. 8 tool updatesv0.1.15
    • Addedapply_rules
    • Addedcut_out
    • Changeddelete_shape1 field changed
      • addedOutput schema / properties / note
        Added value: +{
        +  "description": "Cutout interplay (#206), when it applies: the parent's cut was reverted, could not be rebuilt, or reconciled deducts were orphaned by a parent delete",
        +  "type": "string"
        +}
    • Changedexport_marked_pdf1 field changed
      • addedInput schema / properties / overwrite
        Added value: +{
        +  "description": "Replace the file at path even when it is not an OpenTakeoff export. Off by default: re-exporting over a previous export of your own already overwrites without this, so you only need it to deliberately destroy an unrelated file.",
        +  "type": "boolean"
        +}
    • Changedexport_report1 field changed
      • addedInput schema / properties / overwrite
        Added value: +{
        +  "description": "Replace the file at path even when it is not an OpenTakeoff export. Off by default: re-exporting over a previous export of your own already overwrites without this, so you only need it to deliberately destroy an unrelated file.",
        +  "type": "boolean"
        +}
    • Changedexport_takeoff1 field changed
      • addedInput schema / properties / overwrite
        Added value: +{
        +  "description": "Replace the file at path even when it is not an OpenTakeoff export. Off by default: re-exporting over a previous export of your own already overwrites without this, so you only need it to deliberately destroy an unrelated file.",
        +  "type": "boolean"
        +}
    • Changedimport_takeoff2 fields changed
      • addedOutput schema / properties / rules_imported
        Added value: +{
        +  "description": "Correction rules (#88) that arrived with the file — apply_rules re-runs them",
        +  "type": "integer"
        +}
      • changedOutput schema / required
        Previous value: -[
        -  "file",
        -  "replaced",
        -  "shapes_added",
        -  "shapes_pending",
        -  "conditions_merged",
        -  "conditions_added",
        -  "scales_adopted",
        -  "unknown_files",
        -  "shapes_total",
        -  "note"
        -]New value: +[
        +  "file",
        +  "replaced",
        +  "shapes_added",
        +  "shapes_pending",
        +  "conditions_merged",
        +  "conditions_added",
        +  "scales_adopted",
        +  "unknown_files",
        +  "rules_imported",
        +  "shapes_total",
        +  "note"
        +]
    • Changedundo_last1 field changed
      • changedOutput schema / properties / steps / items / properties / op / enum
        Previous value: -[
        -  "commit",
        -  "edit",
        -  "delete",
        -  "materials",
        -  "condition",
        -  "approval",
        -  "duplicate_condition",
        -  "split_condition"
        -]New value: +[
        +  "commit",
        +  "edit",
        +  "delete",
        +  "materials",
        +  "condition",
        +  "approval",
        +  "duplicate_condition",
        +  "split_condition",
        +  "cutout",
        +  "cutout_restore"
        +]
  9. 7 tool updatesv0.1.14
    • Addedduplicate_condition
    • Changededit_materials6 fields changed
      • changedInput schema / properties / add / items / properties / basis / description
        Previous value: -"Which of the condition's totals this row divides against — default 'area' (total SF)"New value: +"Which of the condition's totals this row divides against — default 'area' (total SF). 'seam_lf' is the figured roll-layout seam length (weld rod, seam tape), 0 until the condition carries a roll_setup"
      • changedInput schema / properties / add / items / properties / basis / enum
        Previous value: -[
        -  "area",
        -  "linear",
        -  "count"
        -]New value: +[
        +  "area",
        +  "linear",
        +  "count",
        +  "seam_lf"
        +]
      • changedOutput schema / properties / materials / items / properties / basis / description
        Previous value: -"Which of the condition's totals this row's quantity is computed against"New value: +"Which of the condition's totals this row's quantity is computed against — 'seam_lf' is the FIGURED roll-layout seam length (weld rod, seam tape), 0 until the condition carries a roll_setup"
      • changedOutput schema / properties / materials / items / properties / basis / enum
        Previous value: -[
        -  "area",
        -  "linear",
        -  "count"
        -]New value: +[
        +  "area",
        +  "linear",
        +  "count",
        +  "seam_lf"
        +]
      • addedOutput schema / properties / materials / items / properties / inherited
        Added value: +{
        +  "description": "On a twin: true while the row still follows the family — a patch on it takes it local, split_condition freezes them all",
        +  "type": "boolean"
        +}
      • addedOutput schema / properties / materials / items / properties / origin_id
        Added value: +{
        +  "description": "On a twin: the parent row this one follows (the variants.ts family link)",
        +  "type": "string"
        +}
    • Changededit_shape3 fields changed
      • addedInput schema / properties / label
        Added value: +{
        +  "description": "The room (or phase/area) this shape belongs to, e.g. \"134\" or \"OFFICE 101\" — what per-room reporting groups by. Pass \"\" to clear it",
        +  "type": "string"
        +}
      • changedOutput schema / properties / changed / items / enum
        Previous value: -[
        -  "verts",
        -  "condition",
        -  "role"
        -]New value: +[
        +  "verts",
        +  "condition",
        +  "role",
        +  "label"
        +]
      • addedOutput schema / properties / label
        Added value: +{
        +  "description": "The shape's room/phase label after this call — absent when it carries none (a cleared label reports as absent, not as an empty string)",
        +  "type": "string"
        +}
    • Changedexport_report2 fields changed
      • changedOutput schema / properties / conditions / items / properties / materials / items / properties / basis / enum
        Previous value: -[
        -  "area",
        -  "linear",
        -  "count"
        -]New value: +[
        +  "area",
        +  "linear",
        +  "count",
        +  "seam_lf"
        +]
      • changedOutput schema / properties / conditions / items / properties / materials / items / properties / basis_qty / description
        Previous value: -"The condition total this row divides (SF, LF, or EA — multiplier applied, waste not)"New value: +"The condition total this row divides (SF, LF, EA, or figured seam LF — multiplier applied, waste not)"
    • Changedlist_shapes1 field changed
      • addedOutput schema / properties / shapes / items / properties / label
        Added value: +{
        +  "description": "The room (or phase/area) this shape belongs to — detect_rooms stamps the room number it traced from; edit_shape sets or clears it. Absent when unlabeled",
        +  "type": "string"
        +}
    • Addedsplit_condition
    • Changedundo_last1 field changed
      • changedOutput schema / properties / steps / items / properties / op / enum
        Previous value: -[
        -  "commit",
        -  "edit",
        -  "delete",
        -  "materials",
        -  "condition",
        -  "approval"
        -]New value: +[
        +  "commit",
        +  "edit",
        +  "delete",
        +  "materials",
        +  "condition",
        +  "approval",
        +  "duplicate_condition",
        +  "split_condition"
        +]
  10. 15 tool updatesv0.1.12
    • Addeddelete_verdict
    • Addedderive_transitions
    • Changeddetect_rooms7 fields changed
      • addedOutput schema / properties / rooms / items / properties / confidence
        Added value: +{
        +  "description": "0..1 — the trace scored from the engine's own signals (sealed openings, door wedges, min-passage rule, hatch tier, raster boundary, mask coarseness, implausible size). A review PRIORITIZER, not a verification: 1.0 means every signal came back clean, never that the trace is right. A low score is a view_sheet {overlay:true} audit prompt, not a fact to bid from",
        +  "type": "number"
        +}
      • addedOutput schema / properties / rooms / items / properties / confidence_factors
        Added value: +{
        +  "description": "The named factors behind a sub-1.0 confidence (e.g. \"sealed-opening(10% synthetic boundary)\") — each names the edge worth putting eyes on; absent when every signal ran clean",
        +  "items": {
        +    "type": "string"
        +  },
        +  "type": "array"
        +}
      • addedOutput schema / properties / rooms / items / properties / door_wedges
        Added value: +{
        +  "description": "Door-swing wedges annexed into the region under grow-but-verify — how many doorways' swings were included, the canvas's own door handling; rides origin.door_wedges",
        +  "type": "integer"
        +}
      • addedOutput schema / properties / rooms / items / properties / gap_sealed_px
        Added value: +{
        +  "description": "Present when the seal ladder closed a genuine OPENING this many mask px wide (doorway-scale — scaled by the sheet's feet, distinct from gap_bridged_px's drafting-pinhole rescue). Part of the boundary is synthetic, and confidence deducts by that share; rides origin.gap_sealed_px on the committed shape",
        +  "type": "number"
        +}
      • addedOutput schema / properties / rooms / items / properties / min_pass_delta
        Added value: +{
        +  "description": "Fraction of the verbatim flood the minimum-passage rule removed; 1 means the drawn linework bounds nothing here and the rule is the only reason there is a measurement — audit before trusting",
        +  "type": "number"
        +}
      • addedOutput schema / properties / rooms / items / properties / min_pass_px
        Added value: +{
        +  "description": "The feet-true minimum-passage rule (openings under ~0.5 ft never connect two spaces) ran at this dilation radius AND changed the answer — present only with min_pass_delta",
        +  "type": "number"
        +}
      • addedOutput schema / properties / rooms / items / properties / ring_interiors
        Added value: +{
        +  "description": "Of those wedges, how many were a CLOSED ring's interior (round column, callout bubble) rather than a door swing — annexed floor you may want as a deduct instead",
        +  "type": "integer"
        +}
    • Changedexport_marked_pdf3 fields changed
      • addedOutput schema / properties / approvals_drawn
        Added value: +{
        +  "description": "Approval-family glyphs burned in (#176) — estimator APPROVED rings + agent AGENT diamonds; the cover tallies the split when any exist",
        +  "type": "integer"
        +}
      • changedOutput schema / properties / sheets_marked / description
        Previous value: -"Sheets carrying shapes or annotations — unmarked sheets are omitted"New value: +"Sheets carrying shapes, annotations, or approval marks — unmarked sheets are omitted"
      • changedOutput schema / required
        Previous value: -[
        -  "path",
        -  "pages",
        -  "sheets_marked",
        -  "shapes_drawn",
        -  "annotations_drawn",
        -  "note"
        -]New value: +[
        +  "path",
        +  "pages",
        +  "sheets_marked",
        +  "shapes_drawn",
        +  "annotations_drawn",
        +  "approvals_drawn",
        +  "note"
        +]
    • Changedexport_takeoff1 field changed
      • addedOutput schema / properties / approvals
        Added value: +{
        +  "description": "Approval-family records (#176) — the estimator's APPROVED seals and the agent's verdict marks {id, actor, ts, sheet_id, at:[nx,ny], shape_id?, text?}. Present only when any exist (the canvas payload's own convention), so a verdict-free export stays byte-identical",
        +  "items": {},
        +  "type": "array"
        +}
    • Changedfind_schedule5 fields changed
      • addedOutput schema / properties / matches / items / properties / building
        Added value: +{
        +  "description": "The building this table answers for, when its title or sheet names one",
        +  "type": "string"
        +}
      • addedOutput schema / properties / matches / items / properties / parts
        Added value: +{
        +  "description": "Present when the table CONTINUES across sheets ('… SCHEDULE — CONT'D'): every fragment, base first, each with its own viewable region",
        +  "items": {
        +    "additionalProperties": false,
        +    "properties": {
        +      "region": {
        +        "additionalProperties": false,
        +        "properties": {
        +          "x0": {
        +            "$ref": "#/properties/matches/items/properties/region/properties/x0"
        +          },
        +          "x1": {
        +            "$ref": "#/properties/matches/items/properties/region/properties/x1"
        +          },
        +          "y0": {
        +            "$ref": "#/properties/matches/items/properties/region/properties/y0"
        +          },
        +          "y1": {
        +            "$ref": "#/properties/matches/items/properties/region/properties/y1"
        +          }
        +        },
        +        "required": [
        +          "x0",
        +          "y0",
        +          "x1",
        +          "y1"
        +        ],
        +        "type": "object"
        +      },
        +      "rows": {
        +        "type": "integer"
        +      },
        +      "sheet": {
        +        "type": "string"
        +      },
        +      "title": {
        +        "type": "string"
        +      }
        +    },
        +    "required": [
        +      "sheet",
        +      "title",
        +      "rows",
        +      "region"
        +    ],
        +    "type": "object"
        +  },
        +  "type": "array"
        +}
      • changedOutput schema / properties / matches / items / properties / region / description
        Previous value: -"Pass to view_sheet to look at the table"New value: +"Pass to view_sheet to look at the table (the BASE fragment's region when the table continues)"
      • addedOutput schema / properties / matches / items / properties / rotated_headers
        Added value: +{
        +  "description": "true when the column headers were read at a quarter-turn",
        +  "type": "boolean"
        +}
      • addedOutput schema / properties / matches / items / properties / rows / description
        Added value: +"Total data rows — a continued schedule counts every fragment's rows"
    • Changedlist_annotations3 fields changed
      • addedOutput schema / properties / verdict_count
        Added value: +{
        +  "type": "integer"
        +}
      • addedOutput schema / properties / verdicts
        Added value: +{
        +  "description": "Approval-family records (#176) under the same filters: sheet applies directly; a condition filter reaches a verdict THROUGH its target shape (a sheet-point mark carries no scope and drops out)",
        +  "items": {
        +    "additionalProperties": false,
        +    "properties": {
        +      "actor": {
        +        "description": "\"estimator\" = the human APPROVED ring (ink — import-borne here, never minted over MCP), \"agent\" = the AGENT diamond",
        +        "enum": [
        +          "estimator",
        +          "agent"
        +        ],
        +        "type": "string"
        +      },
        +      "at": {
        +        "description": "Render anchor (image px) — absent only when the record rides a sheet from a file this session hasn't loaded (#152)",
        +        "items": [
        +          {
        +            "type": "number"
        +          },
        +          {
        +            "type": "number"
        +          }
        +        ],
        +        "maxItems": 2,
        +        "minItems": 2,
        +        "type": "array"
        +      },
        +      "condition": {
        +        "description": "The targeted shape's finish tag, resolved — '' for sheet-point marks",
        +        "type": "string"
        +      },
        +      "id": {
        +        "type": "string"
        +      },
        +      "shape_id": {
        +        "description": "Present when the verdict targets a committed shape — WHAT was marked, not where it draws",
        +        "type": "string"
        +      },
        +      "sheet": {
        +        "type": "string"
        +      },
        +      "text": {
        +        "description": "The optional short note riding the record",
        +        "type": "string"
        +      },
        +      "ts": {
        +        "description": "ISO-8601 mint time",
        +        "type": "string"
        +      }
        +    },
        +    "required": [
        +      "id",
        +      "actor",
        +      "sheet",
        +      "condition"
        +    ],
        +    "type": "object"
        +  },
        +  "type": "array"
        +}
      • changedOutput schema / required
        Previous value: -[
        -  "annotations",
        -  "count",
        -  "unattached"
        -]New value: +[
        +  "annotations",
        +  "count",
        +  "unattached",
        +  "verdicts",
        +  "verdict_count"
        +]
    • Addedmark_verdict
    • Changedone_click7 fields changed
      • addedOutput schema / properties / confidence
        Added value: +{
        +  "description": "0..1 — the trace scored from the engine's own signals (sealed openings, door wedges, min-passage rule, hatch tier, raster boundary, mask coarseness, implausible size). A review PRIORITIZER, not a verification: 1.0 means every signal came back clean, never that the trace is right. A low score is a view_sheet {overlay:true} audit prompt, not a fact to bid from",
        +  "type": "number"
        +}
      • addedOutput schema / properties / confidence_factors
        Added value: +{
        +  "description": "The named factors behind a sub-1.0 confidence (e.g. \"sealed-opening(10% synthetic boundary)\") — each names the edge worth putting eyes on; absent when every signal ran clean",
        +  "items": {
        +    "type": "string"
        +  },
        +  "type": "array"
        +}
      • addedOutput schema / properties / door_wedges
        Added value: +{
        +  "description": "Door-swing wedges annexed into the region under grow-but-verify — how many doorways' swings were included, the canvas's own door handling; rides origin.door_wedges",
        +  "type": "integer"
        +}
      • addedOutput schema / properties / gap_sealed_px
        Added value: +{
        +  "description": "Present when the seal ladder closed a genuine OPENING this many mask px wide (doorway-scale — scaled by the sheet's feet, distinct from gap_bridged_px's drafting-pinhole rescue). Part of the boundary is synthetic, and confidence deducts by that share; rides origin.gap_sealed_px on the committed shape",
        +  "type": "number"
        +}
      • addedOutput schema / properties / min_pass_delta
        Added value: +{
        +  "description": "Fraction of the verbatim flood the minimum-passage rule removed; 1 means the drawn linework bounds nothing here and the rule is the only reason there is a measurement — audit before trusting",
        +  "type": "number"
        +}
      • addedOutput schema / properties / min_pass_px
        Added value: +{
        +  "description": "The feet-true minimum-passage rule (openings under ~0.5 ft never connect two spaces) ran at this dilation radius AND changed the answer — present only with min_pass_delta",
        +  "type": "number"
        +}
      • addedOutput schema / properties / ring_interiors
        Added value: +{
        +  "description": "Of those wedges, how many were a CLOSED ring's interior (round column, callout bubble) rather than a door swing — annexed floor you may want as a deduct instead",
        +  "type": "integer"
        +}
    • Changedresolve_tag6 fields changed
      • changedInput schema / properties / tag / description
        Previous value: -"The room tag as drawn, e.g. \"134\" or \"139A\""New value: +"The room tag as drawn, e.g. \"134\" or \"139A\" — or building-qualified on a multi-building set, e.g. \"A-134\" (building A, room 134)"
      • addedOutput schema / properties / building
        Added value: +{
        +  "description": "resolved only — the building whose schedule row answered, when the set names buildings",
        +  "type": "string"
        +}
      • addedOutput schema / properties / candidates
        Added value: +{
        +  "description": "unresolved only — every schedule row that COULD have answered (an ambiguous multi-building tag lists one per building; qualify the tag, e.g. \"A-134\", to pick)",
        +  "items": {
        +    "additionalProperties": false,
        +    "properties": {
        +      "building": {
        +        "type": "string"
        +      },
        +      "key": {
        +        "type": "string"
        +      },
        +      "sheet": {
        +        "type": "string"
        +      },
        +      "table": {
        +        "type": "string"
        +      }
        +    },
        +    "required": [
        +      "key",
        +      "sheet",
        +      "table"
        +    ],
        +    "type": "object"
        +  },
        +  "type": "array"
        +}
      • changedOutput schema / properties / room / anyOf
        Previous value: -[
        -  {
        -    "additionalProperties": false,
        -    "properties": {
        -      "bbox": {
        -        "additionalProperties": false,
        -        "properties": {
        -          "x0": {
        -            "type": "number"
        -          },
        -          "x1": {
        -            "type": "number"
        -          },
        -          "y0": {
        -            "type": "number"
        -          },
        -          "y1": {
        -            "type": "number"
        -          }
        -        },
        -        "required": [
        -          "x0",
        -          "y0",
        -          "x1",
        -          "y1"
        -        ],
        -        "type": "object"
        -      },
        -      "name": {
        -        "description": "The name span stacked over the tag ('' when none)",
        -        "type": "string"
        -      },
        -      "sheet": {
        -        "type": "string"
        -      },
        -      "tag": {
        -        "type": "string"
        -      }
        -    },
        -    "required": [
        -      "tag",
        -      "name",
        -      "sheet",
        -      "bbox"
        -    ],
        -    "type": "object"
        -  },
        -  {
        -    "type": "null"
        -  }
        -]New value: +[
        +  {
        +    "additionalProperties": false,
        +    "properties": {
        +      "bbox": {
        +        "additionalProperties": false,
        +        "properties": {
        +          "x0": {
        +            "type": "number"
        +          },
        +          "x1": {
        +            "type": "number"
        +          },
        +          "y0": {
        +            "type": "number"
        +          },
        +          "y1": {
        +            "type": "number"
        +          }
        +        },
        +        "required": [
        +          "x0",
        +          "y0",
        +          "x1",
        +          "y1"
        +        ],
        +        "type": "object"
        +      },
        +      "building": {
        +        "description": "The building the room belongs to, when the set names one — its plan sheet's BUILDING/BLDG context, or the tag's own qualifier ('A-134')",
        +        "type": "string"
        +      },
        +      "name": {
        +        "description": "The name span stacked over the tag ('' when none)",
        +        "type": "string"
        +      },
        +      "sheet": {
        +        "type": "string"
        +      },
        +      "tag": {
        +        "type": "string"
        +      }
        +    },
        +    "required": [
        +      "tag",
        +      "name",
        +      "sheet",
        +      "bbox"
        +    ],
        +    "type": "object"
        +  },
        +  {
        +    "type": "null"
        +  }
        +]
      • changedOutput schema / properties / room / description
        Previous value: -"The plan tag, when the room appears on a plan sheet — cited even when resolution fails"New value: +"The plan tag, when the room appears on a plan sheet — cited even when resolution fails. null on a multi-building ambiguity: citing one building's tag would be quietly wrong"
      • changedOutput schema / properties / sources / description
        Previous value: -"The chain: plan tag → schedule row"New value: +"The chain: plan tag → schedule row (the row cites the sheet that CARRIES it — under a continuation that is the CONT'D sheet)"
    • Changedsheet_context1 field changed
      • addedOutput schema / properties / text / properties / spans / items / properties / rot
        Added value: +{
        +  "description": "Run direction in degrees, clockwise, y down — present only when rotated (90/270 = a quarter-turn, e.g. rotated schedule headers)",
        +  "type": "number"
        +}
    • Changedsheet_graph7 fields changed
      • addedOutput schema / properties / buildings
        Added value: +{
        +  "description": "Every building designator the set names (sorted) — present only on multi-building-aware sets. Room numbers reused across these need qualified tags ('A-134')",
        +  "items": {
        +    "type": "string"
        +  },
        +  "type": "array"
        +}
      • addedOutput schema / properties / counts / properties / schedules / description
        Added value: +"LOGICAL tables — a schedule continued across sheets counts once"
      • addedOutput schema / properties / notes
        Added value: +{
        +  "description": "Named gaps found while indexing (e.g. a continuation whose rows could not be aligned) — the graph refuses silently dropping anything",
        +  "items": {
        +    "type": "string"
        +  },
        +  "type": "array"
        +}
      • addedOutput schema / properties / rooms / items / properties / building
        Added value: +{
        +  "description": "The building the room belongs to, when the set names one — its plan sheet's BUILDING/BLDG context, or the tag's own qualifier ('A-134')",
        +  "type": "string"
        +}
      • addedOutput schema / properties / sheets / items / properties / building
        Added value: +{
        +  "description": "The sheet's building context, when it names exactly one (BUILDING A / BLDG 2)",
        +  "type": "string"
        +}
      • addedOutput schema / properties / sheets / items / properties / schedules / items / properties / continues
        Added value: +{
        +  "description": "Present on a continuation fragment ('… SCHEDULE — CONT'D'): the sheet carrying the table's base fragment. The fragments read as ONE table — resolve_tag and find_schedule already see the union",
        +  "type": "string"
        +}
      • addedOutput schema / properties / sheets / items / properties / schedules / items / properties / rotated_headers
        Added value: +{
        +  "description": "true when the column headers were read at a quarter-turn",
        +  "type": "boolean"
        +}
    • Addedsweep_schedule_row
    • Changedsymbol_sweep13 fields changed
      • addedInput schema / properties / scope
        Added value: +{
        +  "default": "sheet",
        +  "description": "\"sheet\" = this sheet only; \"set\" = every PLAN-role sheet in the working set (needs a text layer for the sheet graph; non-plan sheets are excluded and disclosed)",
        +  "enum": [
        +    "sheet",
        +    "set"
        +  ],
        +  "type": "string"
        +}
      • addedInput schema / properties / sheet / description
        Added value: +"The sheet the seed rect sits on — in scope 'set' it may be ANY sheet (a detail/legend seed sheet is fingerprint source only, never counted)"
      • addedOutput schema / properties / candidates / description
        Added value: +"Sheet scope only — set scope accounts per sheet in sheets[]"
      • changedOutput schema / properties / found / description
        Previous value: -"Placements that cleared the commit bar — matches.length"New value: +"Placements that cleared the commit bar — across every swept sheet in set scope"
      • changedOutput schema / properties / matches / description
        Previous value: -"Deterministic reading order (y, then x). The seed's own location is never listed here"New value: +"Sheet scope only. Deterministic reading order (y, then x). The seed's own location is never listed here"
      • addedOutput schema / properties / scope
        Added value: +{
        +  "description": "\"sheet\" = the swept sheet alone (matches/withheld/candidates at top level); \"set\" = every PLAN-role sheet in the working set (per-sheet results in sheets[], exclusions in skipped[])",
        +  "enum": [
        +    "sheet",
        +    "set"
        +  ],
        +  "type": "string"
        +}
      • addedOutput schema / properties / seed / properties / role
        Added value: +{
        +  "description": "Set scope: the seed sheet's graph role — a non-plan seed sheet is the fingerprint SOURCE and is excluded from counting",
        +  "type": "string"
        +}
      • addedOutput schema / properties / seed / properties / sheet
        Added value: +{
        +  "description": "The sheet the seed rect was marqueed on",
        +  "type": "string"
        +}
      • changedOutput schema / properties / seed / required
        Previous value: -[
        -  "segments",
        -  "center",
        -  "rect",
        -  "length_px"
        -]New value: +[
        +  "sheet",
        +  "segments",
        +  "center",
        +  "rect",
        +  "length_px"
        +]
      • addedOutput schema / properties / sheets
        Added value: +{
        +  "description": "Set scope only: one entry per swept PLAN-role sheet, load order",
        +  "items": {
        +    "additionalProperties": false,
        +    "properties": {
        +      "candidates": {
        +        "additionalProperties": false,
        +        "description": "The work cap applies PER SHEET; dropped > 0 here names exactly where the count is incomplete",
        +        "properties": {
        +          "considered": {
        +            "$ref": "#/properties/candidates/properties/considered"
        +          },
        +          "dropped": {
        +            "$ref": "#/properties/candidates/properties/dropped"
        +          }
        +        },
        +        "required": [
        +          "considered",
        +          "dropped"
        +        ],
        +        "type": "object"
        +      },
        +      "elapsed_ms": {
        +        "description": "Wall-clock for this sheet's sweep",
        +        "type": "number"
        +      },
        +      "found": {
        +        "type": "integer"
        +      },
        +      "matches": {
        +        "items": {
        +          "additionalProperties": false,
        +          "properties": {
        +            "at": {
        +              "$ref": "#/properties/matches/items/properties/at"
        +            },
        +            "mirrored": {
        +              "$ref": "#/properties/matches/items/properties/mirrored"
        +            },
        +            "rotation": {
        +              "$ref": "#/properties/matches/items/properties/rotation"
        +            },
        +            "score": {
        +              "$ref": "#/properties/matches/items/properties/score"
        +            }
        +          },
        +          "required": [
        +            "at",
        +            "score",
        +            "rotation",
        +            "mirrored"
        +          ],
        +          "type": "object"
        +        },
        +        "type": "array"
        +      },
        +      "scale_assumed": {
        +        "description": "#186: present when the true ratio is UNKNOWN (a scale is missing on the seed sheet or this one) and the sweep ran at 1:1 — an unstated ratio plus a zero count is not evidence of absence",
        +        "type": "string"
        +      },
        +      "scaled": {
        +        "additionalProperties": false,
        +        "description": "#186: present only when the seed was resized for this sheet",
        +        "properties": {
        +          "footprint_px": {
        +            "description": "The symbol's size on THIS sheet after the resize",
        +            "type": "number"
        +          },
        +          "ratio": {
        +            "description": "Seed-sheet px per target-sheet px, computed from the two sheets' own committed scales (upp_seed / upp_target) — stated, never scale-searched",
        +            "type": "number"
        +          },
        +          "segments": {
        +            "description": "Fingerprint segments that survived the resize and were actually searched for",
        +            "type": "integer"
        +          },
        +          "sub_pixel_dropped": {
        +            "description": "Seed segments that fell below matchable length when scaled down — excluded from the score rather than depressing it, so a score here is a fraction of what survived, not of the whole seed",
        +            "type": "integer"
        +          },
        +          "tol_px": {
        +            "description": "The endpoint tolerance actually applied — it rides the ratio up when the seed is magnified (its drawn jitter magnifies too) and never down",
        +            "type": "number"
        +          }
        +        },
        +        "required": [
        +          "ratio",
        +          "segments",
        +          "sub_pixel_dropped",
        +          "footprint_px",
        +          "tol_px"
        +        ],
        +        "type": "object"
        +      },
        +      "sheet": {
        +        "type": "string"
        +      },
        +      "withheld": {
        +        "items": {
        +          "additionalProperties": false,
        +          "properties": {
        +            "at": {
        +              "$ref": "#/properties/matches/items/properties/at"
        +            },
        +            "mirrored": {
        +              "$ref": "#/properties/matches/items/properties/mirrored"
        +            },
        +            "reason": {
        +              "type": "string"
        +            },
        +            "rotation": {
        +              "$ref": "#/properties/matches/items/properties/rotation"
        +            },
        +            "score": {
        +              "$ref": "#/properties/matches/items/properties/score"
        +            }
        +          },
        +          "required": [
        +            "at",
        +            "score",
        +            "rotation",
        +            "mirrored",
        +            "reason"
        +          ],
        +          "type": "object"
        +        },
        +        "type": "array"
        +      }
        +    },
        +    "required": [
        +      "sheet",
        +      "found",
        +      "matches",
        +      "withheld",
        +      "candidates",
        +      "elapsed_ms"
        +    ],
        +    "type": "object"
        +  },
        +  "type": "array"
        +}
      • addedOutput schema / properties / skipped
        Added value: +{
        +  "description": "Set scope only: every sheet excluded from counting, with role and reason — including the seed's own sheet when it is not a plan",
        +  "items": {
        +    "additionalProperties": false,
        +    "properties": {
        +      "reason": {
        +        "type": "string"
        +      },
        +      "role": {
        +        "description": "The sheet's graph role (plan / schedule / legend / detail / …)",
        +        "type": "string"
        +      },
        +      "sheet": {
        +        "type": "string"
        +      }
        +    },
        +    "required": [
        +      "sheet",
        +      "role",
        +      "reason"
        +    ],
        +    "type": "object"
        +  },
        +  "type": "array"
        +}
      • changedOutput schema / properties / withheld / description
        Previous value: -"Near-matches in the [0.75, 0.92) band — reported with a reason, NEVER committed. A withheld placement is a question you can answer with view_sheet; a hidden one is a miscount"New value: +"Sheet scope only. Near-matches in the [0.75, 0.92) band — reported with a reason, NEVER committed. A withheld placement is a question you can answer with view_sheet; a hidden one is a miscount"
      • changedOutput schema / required
        Previous value: -[
        -  "found",
        -  "matches",
        -  "withheld",
        -  "seed",
        -  "candidates"
        -]New value: +[
        +  "scope",
        +  "found",
        +  "seed"
        +]
    • Changedundo_last2 fields changed
      • changedOutput schema / properties / steps / items / properties / op / enum
        Previous value: -[
        -  "commit",
        -  "edit",
        -  "delete",
        -  "materials",
        -  "condition"
        -]New value: +[
        +  "commit",
        +  "edit",
        +  "delete",
        +  "materials",
        +  "condition",
        +  "approval"
        +]
      • changedOutput schema / properties / steps / items / properties / shapes / description
        Previous value: -"Shapes affected by reversing this step — 0 for a materials step (it restores a condition's supporting-materials rows, not shapes) and for a condition step (it restores the waste/multiplier pair)"New value: +"Shapes affected by reversing this step — 0 for a materials step (it restores a condition's supporting-materials rows, not shapes), for a condition step (it restores the waste/multiplier pair), and for an approval step (it re-seats or removes a verdict mark)"
  11. 5 tool updatesv0.1.11
    • Changedannotate6 fields changed
      • changedInput schema / properties / from / description
        Previous value: -"Arrow tail (image px)"New value: +"Arrow tail / dimension start (image px)"
      • changedInput schema / properties / text / description
        Previous value: -"The note. A cloud with no text still reads as 'look here'; a bubble's text draws centered in the circle"New value: +"The note. A cloud with no text still reads as 'look here'; a bubble's text draws centered in the circle; a dimension appends it after the measured length"
      • changedInput schema / properties / to / description
        Previous value: -"Arrow head — what it points at (image px)"New value: +"Arrow head / dimension end (image px)"
      • changedInput schema / properties / type / description
        Previous value: -"cloud/highlight need rect; text/callout/bubble need at; callout also needs target; arrow needs from + to"New value: +"cloud/highlight need rect; text/callout/bubble need at; callout also needs target; arrow and dimension need from + to"
      • changedInput schema / properties / type / enum
        Previous value: -[
        -  "cloud",
        -  "text",
        -  "callout",
        -  "highlight",
        -  "arrow",
        -  "bubble"
        -]New value: +[
        +  "cloud",
        +  "text",
        +  "callout",
        +  "highlight",
        +  "arrow",
        +  "bubble",
        +  "dimension"
        +]
      • addedOutput schema / properties / length_lf
        Added value: +{
        +  "description": "Dimension only: the measured length (real feet) the annotation will label itself with",
        +  "type": "number"
        +}
    • Changeddetect_rooms1 field changed
      • addedOutput schema / properties / rooms / items / properties / raster_traced
        Added value: +{
        +  "const": true,
        +  "description": "Present when the room was bounded by rendered pixels (scanned-sheet raster fallback, #154) rather than vector linework — sheet-wide per sweep, and it rides origin.raster_traced on the committed shape",
        +  "type": "boolean"
        +}
    • Changedlist_annotations3 fields changed
      • changedOutput schema / properties / annotations / items / properties / from / description
        Previous value: -"Arrow tail (image px)"New value: +"Arrow tail / dimension start (image px)"
      • addedOutput schema / properties / annotations / items / properties / length_lf
        Added value: +{
        +  "description": "Dimension only: the measured length in real feet, snapshotted at annotate time from the sheet scale",
        +  "type": "number"
        +}
      • changedOutput schema / properties / annotations / items / properties / to / description
        Previous value: -"Arrow head (image px)"New value: +"Arrow head / dimension end (image px)"
    • Changedone_click1 field changed
      • addedOutput schema / properties / raster_traced
        Added value: +{
        +  "const": true,
        +  "description": "Present when the region was bounded by the sheet's RENDERED PIXELS (the scanned-sheet raster fallback, #154) rather than vector linework — absent means the vector path ran. Rides origin.raster_traced on the committed shape; a raster ring's corners are unsnapped (a scan has no true endpoints), so audit it with view_sheet overlay before trusting the total",
        +  "type": "boolean"
        +}
    • Addedsymbol_sweep
  12. 3 tool updatesv0.1.10
    • Changeddetect_rooms6 fields changed
      • addedInput schema / properties / assign_from_schedule
        Added value: +{
        +  "default": false,
        +  "description": "Commit each room under the FLOOR finish its OWN room-finish schedule row states (resolve_tag's chain, per room): the citation rides origin.assignment, and rooms the schedule cannot answer for — no row, no FLOOR cell, a compound cell like \"CPT-1/VCT-1\" — are returned in unresolved[] with reasons and seeds instead of committed under a guess. Needs the sheet's scale and a room-finish schedule in the working set (merge the schedule sheet in with load_plan first). Mutually exclusive with condition",
        +  "type": "boolean"
        +}
      • changedInput schema / properties / condition / description
        Previous value: -"Finish tag to commit every detected room under (minted on first use)"New value: +"Finish tag to commit every detected room under (minted on first use). Mutually exclusive with assign_from_schedule"
      • addedOutput schema / properties / rooms / items / properties / condition
        Added value: +{
        +  "description": "The finish tag this room committed under — the passed condition, or in assign mode the FLOOR finish its own schedule row states. Present exactly when shape_id is",
        +  "type": "string"
        +}
      • addedOutput schema / properties / unresolved
        Added value: +{
        +  "description": "Assign mode only, empty array included: [] is the positive claim that every detected room resolved against its own schedule row",
        +  "items": {
        +    "additionalProperties": false,
        +    "properties": {
        +      "area_sf": {
        +        "description": "The room's real traced area — withheld from committing, not from reporting",
        +        "type": "number"
        +      },
        +      "label": {
        +        "description": "The room tag as drawn",
        +        "type": "string"
        +      },
        +      "perimeter_lf": {
        +        "type": "number"
        +      },
        +      "reason": {
        +        "description": "WHY the schedule could not answer — resolveTag's own reason, \"states no FLOOR finish\", or \"ambiguous: …\" for a compound cell",
        +        "type": "string"
        +      },
        +      "seed": {
        +        "description": "The flood seed (image px) — once the estimator answers, one_click here with the stated condition commits it",
        +        "items": [
        +          {
        +            "type": "number"
        +          },
        +          {
        +            "type": "number"
        +          }
        +        ],
        +        "maxItems": 2,
        +        "minItems": 2,
        +        "type": "array"
        +      }
        +    },
        +    "required": [
        +      "label",
        +      "reason",
        +      "area_sf",
        +      "perimeter_lf",
        +      "seed"
        +    ],
        +    "type": "object"
        +  },
        +  "type": "array"
        +}
      • addedOutput schema / properties / withheld / properties / unresolved
        Added value: +{
        +  "description": "Assign mode: rooms the schedule could not answer for (no row, no FLOOR cell, or a compound cell) — withheld into unresolved[], never committed under a guess. Always present; 0 outside assign mode",
        +  "type": "integer"
        +}
      • changedOutput schema / properties / withheld / required
        Previous value: -[
        -  "total",
        -  "degenerate",
        -  "duplicate",
        -  "bubble",
        -  "implausible"
        -]New value: +[
        +  "total",
        +  "degenerate",
        +  "duplicate",
        +  "bubble",
        +  "implausible",
        +  "unresolved"
        +]
    • Changedexport_takeoff1 field changed
      • changedOutput schema / properties / shapes / items / properties / origin / description
        Previous value: -"Provenance: method (manual|one_click_v1), actor (omitted=human, 'agent'=MCP/automation), reviewed (human affirmed at an explicit gate), and correction fields (edited, edited_before_create, copied, proposed_verts_norm, edits)"New value: +"Provenance: method (manual|one_click_v1), actor (omitted=human, 'agent'=MCP/automation), reviewed (human affirmed at an explicit gate), assignment (where the finish tag came from — {source: 'schedule', room_tag, surface, schedule_sheet} when the room's own schedule row decided it, {source: 'asserted'} when the agent chose; stamped on every agent commit), and correction fields (edited, edited_before_create, copied, proposed_verts_norm, edits)"
    • Changedlist_shapes1 field changed
      • addedOutput schema / properties / shapes / items / properties / assignment
        Added value: +{
        +  "description": "Where the finish tag came from: \"schedule\" = resolved from the room's own schedule row, \"asserted\" = the agent chose it. origin.assignment in export_takeoff carries the citation. Absent on human canvas shapes",
        +  "enum": [
        +    "schedule",
        +    "asserted"
        +  ],
        +  "type": "string"
        +}
  13. 21 tool updatesv0.1.9
    • Changedannotate7 fields changed
      • changedInput schema / properties / at / description
        Previous value: -"Anchor point (image px) — text and callout"New value: +"Anchor point (image px) — text, callout, and bubble (the circle's center)"
      • addedInput schema / properties / from
        Added value: +{
        +  "$ref": "#/properties/at",
        +  "description": "Arrow tail (image px)"
        +}
      • addedInput schema / properties / r
        Added value: +{
        +  "description": "Bubble radius (image px); omitted → the canvas default (2% of sheet width)",
        +  "exclusiveMinimum": 0,
        +  "type": "number"
        +}
      • changedInput schema / properties / text / description
        Previous value: -"The note. A cloud with no text still reads as 'look here'"New value: +"The note. A cloud with no text still reads as 'look here'; a bubble's text draws centered in the circle"
      • addedInput schema / properties / to
        Added value: +{
        +  "$ref": "#/properties/at",
        +  "description": "Arrow head — what it points at (image px)"
        +}
      • changedInput schema / properties / type / description
        Previous value: -"cloud/highlight need rect; text/callout need at; callout also needs target"New value: +"cloud/highlight need rect; text/callout/bubble need at; callout also needs target; arrow needs from + to"
      • changedInput schema / properties / type / enum
        Previous value: -[
        -  "cloud",
        -  "text",
        -  "callout",
        -  "highlight"
        -]New value: +[
        +  "cloud",
        +  "text",
        +  "callout",
        +  "highlight",
        +  "arrow",
        +  "bubble"
        +]
    • Addedderive_base
    • Changeddetect_rooms2 fields changed
      • addedInput schema / properties / layers
        Added value: +{
        +  "additionalProperties": false,
        +  "description": "Override the sheet's classified layer roles for THIS call (see sheet_info.layers)",
        +  "properties": {
        +    "exclude": {
        +      "description": "Layer names or ids whose ink must not block the flood at all",
        +      "items": {
        +        "type": "string"
        +      },
        +      "type": "array"
        +    },
        +    "include": {
        +      "description": "Layer names or ids whose ink must plot as HARD boundary",
        +      "items": {
        +        "type": "string"
        +      },
        +      "type": "array"
        +    }
        +  },
        +  "type": "object"
        +}
      • addedOutput schema / properties / multiple_scales
        Added value: +{
        +  "const": true,
        +  "description": "Several DISTINCT scale notes on this sheet (#153) — rooms inside an enlarged viewport may be figured at the wrong scale",
        +  "type": "boolean"
        +}
    • Changededit_condition5 fields changed
      • addedInput schema / properties / height_ft
        Added value: +{
        +  "description": "Wall height in feet — the canvas's H knob; measure_surface quantifies traced LF × this",
        +  "exclusiveMinimum": 0,
        +  "type": "number"
        +}
      • addedInput schema / properties / roll_setup
        Added value: +{
        +  "anyOf": [
        +    {
        +      "description": "Opt the condition OUT of roll goods",
        +      "type": "null"
        +    },
        +    {
        +      "additionalProperties": false,
        +      "properties": {
        +        "direction": {
        +          "description": "Run direction; auto lets the engine pick per room",
        +          "enum": [
        +            "auto",
        +            "ns",
        +            "ew"
        +          ],
        +          "type": "string"
        +        },
        +        "doorway_overage_in": {
        +          "minimum": 0,
        +          "type": "number"
        +        },
        +        "material": {
        +          "description": "Material class — fresh opt-ins and material changes start from this class's engine defaults (carpet sells sy, others sf)",
        +          "enum": [
        +            "carpet",
        +            "sheet_vinyl",
        +            "rubber"
        +          ],
        +          "type": "string"
        +        },
        +        "price_unit": {
        +          "description": "Sell unit the order quantity is figured in",
        +          "enum": [
        +            "sy",
        +            "sf",
        +            "lf"
        +          ],
        +          "type": "string"
        +        },
        +        "roll_length_ft": {
        +          "description": "Physical roll length; 0 = unlimited",
        +          "minimum": 0,
        +          "type": "number"
        +        },
        +        "roll_width_ft": {
        +          "exclusiveMinimum": 0,
        +          "type": "number"
        +        },
        +        "seam_allowance_in": {
        +          "minimum": 0,
        +          "type": "number"
        +        },
        +        "wall_overage_in": {
        +          "minimum": 0,
        +          "type": "number"
        +        }
        +      },
        +      "type": "object"
        +    }
        +  ],
        +  "description": "Roll-goods opt-in (#147): presence of a setup is what makes the condition roll goods — seams figured, cuts packed, order footage beside the measured quantities. Same-material partial edits patch the existing setup; null opts out. The reply echoes the figured order (cuts, order_lf, rolls, order_qty) whenever floor shapes exist on scaled sheets, and export_report's roll_goods block carries the same rows"
        +}
      • addedOutput schema / properties / height_ft
        Added value: +{
        +  "description": "The condition's wall height after this write — present once set (measure_surface multiplies traced LF by it)",
        +  "type": "number"
        +}
      • addedOutput schema / properties / roll
        Added value: +{
        +  "additionalProperties": true,
        +  "description": "The figured order (same row export_report's roll_goods carries) — present when the roll-goods condition has floor shapes on scaled sheets",
        +  "properties": {
        +    "condition_id": {
        +      "type": "string"
        +    },
        +    "cuts": {
        +      "type": "integer"
        +    },
        +    "direction": {
        +      "type": "string"
        +    },
        +    "finish_tag": {
        +      "type": "string"
        +    },
        +    "material": {
        +      "type": "string"
        +    },
        +    "order_lf": {
        +      "description": "Full-width roll footage to order, ×N applied, rounded up to the inch",
        +      "type": "number"
        +    },
        +    "order_qty": {
        +      "type": "number"
        +    },
        +    "order_unit": {
        +      "type": "string"
        +    },
        +    "oversize": {
        +      "description": "true when a cut exceeds the physical roll length (roll_length_ft binds)",
        +      "type": "boolean"
        +    },
        +    "roll_length_ft": {
        +      "type": "number"
        +    },
        +    "roll_width_ft": {
        +      "type": "number"
        +    },
        +    "rolls": {
        +      "type": "number"
        +    }
        +  },
        +  "required": [
        +    "condition_id",
        +    "finish_tag",
        +    "material",
        +    "roll_width_ft",
        +    "roll_length_ft",
        +    "direction",
        +    "cuts",
        +    "order_lf",
        +    "rolls",
        +    "order_qty",
        +    "order_unit",
        +    "oversize"
        +  ],
        +  "type": "object"
        +}
      • addedOutput schema / properties / roll_setup
        Added value: +{
        +  "additionalProperties": true,
        +  "description": "The condition's roll-goods setup after this write — present while opted in",
        +  "properties": {},
        +  "type": "object"
        +}
    • Changededit_shape8 fields changed
      • changedInput schema / properties / role / description
        Previous value: -"Switch what the shape measures"New value: +"Switch what the shape measures — flipping INTO surface_area needs a height on the shape or its condition"
      • changedInput schema / properties / role / enum
        Previous value: -[
        -  "floor_area",
        -  "deduct",
        -  "linear"
        -]New value: +[
        +  "floor_area",
        +  "deduct",
        +  "linear",
        +  "surface_area",
        +  "count"
        +]
      • changedInput schema / properties / verts / description
        Previous value: -"Replacement geometry (image px): ≥3 vertices for an area shape, ≥2 points for a linear one"New value: +"Replacement geometry (image px): ≥3 vertices for an area shape, ≥2 points for a linear/surface run, ≥1 for a count marker"
      • changedOutput schema / properties / area_sf / description
        Previous value: -"0 for linear shapes"New value: +"0 for linear shapes; LF × height for surface_area; absent for count"
      • addedOutput schema / properties / count
        Added value: +{
        +  "description": "count shapes only — the marker's EA (preserved across the edit)",
        +  "type": "number"
        +}
      • changedOutput schema / properties / measure_role / enum
        Previous value: -[
        -  "floor_area",
        -  "deduct",
        -  "linear"
        -]New value: +[
        +  "floor_area",
        +  "deduct",
        +  "linear",
        +  "surface_area",
        +  "count"
        +]
      • changedOutput schema / properties / perimeter_lf / description
        Previous value: -"Length for linear shapes, perimeter for closed ones"New value: +"Length for linear/surface runs, perimeter for closed ones; absent for count"
      • changedOutput schema / required
        Previous value: -[
        -  "shape_id",
        -  "changed",
        -  "measure_role",
        -  "nverts",
        -  "area_sf",
        -  "perimeter_lf",
        -  "agent_edits"
        -]New value: +[
        +  "shape_id",
        +  "changed",
        +  "measure_role",
        +  "nverts",
        +  "agent_edits"
        +]
    • Addedexport_marked_pdf
    • Changedexport_report3 fields changed
      • addedInput schema / properties / project_name
        Added value: +{
        +  "description": "Label for the document's project_name field (a headless session has no project of its own; omitted → null)",
        +  "type": "string"
        +}
      • addedOutput schema / properties / roll_goods
        Added value: +{
        +  "description": "Roll-goods order rows (#136) — order_lf / rolls / order_qty per roll-goods condition, ×N applied; empty when no condition carries a roll_setup (always the case for a headless session today)",
        +  "items": {
        +    "additionalProperties": {},
        +    "type": "object"
        +  },
        +  "type": "array"
        +}
      • changedOutput schema / required
        Previous value: -[
        -  "schema",
        -  "project_name",
        -  "generated_with",
        -  "sheets",
        -  "conditions",
        -  "by_sheet",
        -  "totals",
        -  "materials",
        -  "markups",
        -  "rfis",
        -  "condition_columns",
        -  "shape_labels",
        -  "by_label",
        -  "units",
        -  "display_units"
        -]New value: +[
        +  "schema",
        +  "project_name",
        +  "generated_with",
        +  "sheets",
        +  "conditions",
        +  "by_sheet",
        +  "totals",
        +  "materials",
        +  "markups",
        +  "rfis",
        +  "condition_columns",
        +  "shape_labels",
        +  "by_label",
        +  "units",
        +  "display_units",
        +  "roll_goods"
        +]
    • Changedexport_takeoff4 fields changed
      • addedOutput schema / properties / shapes / items / properties / computed / description
        Added value: +"count shapes carry {count} alone; every other role carries area_sf + perimeter_lf"
      • addedOutput schema / properties / shapes / items / properties / computed / properties / count
        Added value: +{
        +  "type": "number"
        +}
      • removedOutput schema / properties / shapes / items / properties / computed / required
        Removed value: -[
        -  "area_sf",
        -  "perimeter_lf"
        -]
      • changedOutput schema / properties / shapes / items / properties / measure_role / enum
        Previous value: -[
        -  "floor_area",
        -  "deduct",
        -  "linear"
        -]New value: +[
        +  "floor_area",
        +  "deduct",
        +  "linear",
        +  "surface_area",
        +  "count"
        +]
    • Addedfind_schedule
    • Addedimport_takeoff
    • Changedlist_annotations3 fields changed
      • addedOutput schema / properties / annotations / items / properties / from
        Added value: +{
        +  "description": "Arrow tail (image px)",
        +  "items": [
        +    {
        +      "type": "number"
        +    },
        +    {
        +      "type": "number"
        +    }
        +  ],
        +  "maxItems": 2,
        +  "minItems": 2,
        +  "type": "array"
        +}
      • addedOutput schema / properties / annotations / items / properties / r
        Added value: +{
        +  "description": "Bubble radius (image px)",
        +  "type": "number"
        +}
      • addedOutput schema / properties / annotations / items / properties / to
        Added value: +{
        +  "description": "Arrow head (image px)",
        +  "items": [
        +    {
        +      "type": "number"
        +    },
        +    {
        +      "type": "number"
        +    }
        +  ],
        +  "maxItems": 2,
        +  "minItems": 2,
        +  "type": "array"
        +}
    • Addedlist_shapes
    • Changedload_plan6 fields changed
      • addedInput schema / properties / merge
        Added value: +{
        +  "description": "true = ADD this document to the working set, keeping all existing work (merge into an empty session is just a load)",
        +  "type": "boolean"
        +}
      • addedOutput schema / properties / file / description
        Added value: +"The document just loaded (basename)"
      • addedOutput schema / properties / files
        Added value: +{
        +  "description": "Every document in the working set, load order (#152 — one entry unless merge was used)",
        +  "items": {
        +    "type": "string"
        +  },
        +  "type": "array"
        +}
      • addedOutput schema / properties / page_count / description
        Added value: +"Total sheets across the working set"
      • addedOutput schema / properties / sheets / description
        Added value: +"EVERY sheet in the working set, not just the file loaded by this call"
      • changedOutput schema / required
        Previous value: -[
        -  "file",
        -  "page_count",
        -  "sheets",
        -  "note"
        -]New value: +[
        +  "file",
        +  "files",
        +  "page_count",
        +  "sheets",
        +  "note"
        +]
    • Changedmeasure_polygon1 field changed
      • addedOutput schema / properties / warning
        Added value: +{
        +  "description": "Mixed-scale warning (#153): a scale note disagreeing with the sheet's sits in the measured region — verify before trusting these numbers",
        +  "type": "string"
        +}
    • Addedmeasure_surface
    • Changedone_click2 fields changed
      • addedInput schema / properties / layers
        Added value: +{
        +  "additionalProperties": false,
        +  "description": "Override the sheet's classified layer roles for THIS call (see sheet_info.layers)",
        +  "properties": {
        +    "exclude": {
        +      "description": "Layer names or ids whose ink must not block the flood at all",
        +      "items": {
        +        "type": "string"
        +      },
        +      "type": "array"
        +    },
        +    "include": {
        +      "description": "Layer names or ids whose ink must plot as HARD boundary",
        +      "items": {
        +        "type": "string"
        +      },
        +      "type": "array"
        +    }
        +  },
        +  "type": "object"
        +}
      • changedOutput schema / properties / warning / description
        Previous value: -"Preview mode (no scale): why quantities are unavailable and what to do"New value: +"Preview mode (no scale): why quantities are unavailable — OR, in scaled mode, a mixed-scale warning (#153): a scale note disagreeing with the sheet's sits in the measured region (enlarged plan/detail viewport likely)"
    • Addedplace_count
    • Addedresolve_tag
    • Changedset_scale1 field changed
      • addedOutput schema / properties / warning
        Added value: +{
        +  "description": "Present when the sheet carries MULTIPLE distinct scale notes (#153) — enlarged plans/details likely; region measurements under a disagreeing note will warn",
        +  "type": "string"
        +}
    • Addedsheet_graph
    • Changedsheet_info3 fields changed
      • addedOutput schema / properties / layers
        Added value: +{
        +  "description": "The sheet's PDF layer table (#85) — [] when no Optional Content survived export (every engine path then runs the heuristics unchanged)",
        +  "items": {
        +    "additionalProperties": false,
        +    "properties": {
        +      "confidence": {
        +        "description": "0..1 — how sure the name classifier is",
        +        "type": "number"
        +      },
        +      "id": {
        +        "description": "Optional Content Group id — pass to one_click/detect_rooms layers.include/exclude",
        +        "type": "string"
        +      },
        +      "name": {
        +        "description": "The CAD layer name as exported (e.g. A-WALL-FULL)",
        +        "type": "string"
        +      },
        +      "role": {
        +        "description": "What this layer's linework IS to a takeoff (lib/layers.ts) — boundary/structure plot hard, pattern/annotation/demolition are excluded, unknown falls back to the hatch heuristics",
        +        "enum": [
        +          "boundary",
        +          "finish-pattern",
        +          "annotation",
        +          "structure",
        +          "demolition",
        +          "unknown"
        +        ],
        +        "type": "string"
        +      },
        +      "seg_count": {
        +        "description": "Segments this layer owns on this sheet",
        +        "type": "integer"
        +      },
        +      "visible": {
        +        "description": "Default-config visibility — a hidden layer's ink is excluded outright (or you trace demolition)",
        +        "type": "boolean"
        +      }
        +    },
        +    "required": [
        +      "id",
        +      "name",
        +      "role",
        +      "confidence",
        +      "visible",
        +      "seg_count"
        +    ],
        +    "type": "object"
        +  },
        +  "type": "array"
        +}
      • addedOutput schema / properties / multiple_scales
        Added value: +{
        +  "const": true,
        +  "description": "Several DISTINCT scale notes on this sheet (#153) — enlarged plans/details likely",
        +  "type": "boolean"
        +}
      • changedOutput schema / required
        Previous value: -[
        -  "sheet",
        -  "page",
        -  "width_pt",
        -  "height_pt",
        -  "width_px",
        -  "height_px",
        -  "seg_count",
        -  "has_vector_linework",
        -  "scale_set",
        -  "shape_count"
        -]New value: +[
        +  "sheet",
        +  "page",
        +  "width_pt",
        +  "height_pt",
        +  "width_px",
        +  "height_px",
        +  "seg_count",
        +  "has_vector_linework",
        +  "scale_set",
        +  "shape_count",
        +  "layers"
        +]
  14. 5 tool updatesv0.1.8
    • Changeddetect_rooms1 field changed
      • addedOutput schema / properties / rooms / items / properties / gap_bridged_px
        Added value: +{
        +  "description": "Present when the seal ladder bridged a drafting pinhole this many px wide to close the region",
        +  "type": "number"
        +}
    • Addededit_condition
    • Addedexport_report
    • Changedone_click1 field changed
      • addedOutput schema / properties / gap_bridged_px
        Added value: +{
        +  "description": "Present when the seal ladder bridged a drafting pinhole this many px wide to close the region — the rescue rides provenance (origin.gap_bridged_px) rather than passing as a clean fill",
        +  "type": "number"
        +}
    • Changedundo_last2 fields changed
      • changedOutput schema / properties / steps / items / properties / op / enum
        Previous value: -[
        -  "commit",
        -  "edit",
        -  "delete",
        -  "materials"
        -]New value: +[
        +  "commit",
        +  "edit",
        +  "delete",
        +  "materials",
        +  "condition"
        +]
      • changedOutput schema / properties / steps / items / properties / shapes / description
        Previous value: -"Shapes affected by reversing this step — 0 for a materials step (it restores a condition's supporting-materials rows, not shapes)"New value: +"Shapes affected by reversing this step — 0 for a materials step (it restores a condition's supporting-materials rows, not shapes) and for a condition step (it restores the waste/multiplier pair)"
  15. 10 tool updatesv0.1.6
    • Addedannotate
    • Changeddetect_rooms5 fields changed
      • changedInput schema / properties / min_area_sf / description
        Previous value: -"Plausibility floor: enclosed regions smaller than this are withheld as label bubbles/cavities, not rooms. Default 5 SF — below any real finished space (a broom closet is ~10 SF). Lower it to inspect what was skipped."New value: +"Plausibility floor: enclosed non-bubble regions smaller than this are withheld as cavities, not rooms. Default 5 SF — below any real finished space (a broom closet is ~10 SF). Lower it to inspect what was skipped."
      • addedInput schema / properties / sensitivity
        Added value: +{
        +  "description": "Fill sensitivity, the same knob the canvas has: 0 strict (hatch/light linework always blocks), 0.5 balanced (default), 1 aggressive (crosses more hatch, tolerates more growth). Raise it when a flood stops short at hatching INSIDE the room; verify the grown ring with view_sheet overlay before committing",
        +  "maximum": 1,
        +  "minimum": 0,
        +  "type": "number"
        +}
      • addedOutput schema / properties / withheld / properties / bubble
        Added value: +{
        +  "description": "Labels whose every clean flood was their own label BUBBLE (ring bbox ≈ label bbox — plans box their room numbers). Scale-free, so it guards unscaled previews too",
        +  "type": "integer"
        +}
      • changedOutput schema / properties / withheld / properties / implausible / description
        Previous value: -"Enclosed and clean, but smaller than min_area_sf — a label bubble, door swing, or wall cavity rather than a room"New value: +"Enclosed, clean, non-bubble, but smaller than min_area_sf — a door swing or wall cavity rather than a room"
      • changedOutput schema / properties / withheld / required
        Previous value: -[
        -  "total",
        -  "degenerate",
        -  "duplicate",
        -  "implausible"
        -]New value: +[
        +  "total",
        +  "degenerate",
        +  "duplicate",
        +  "bubble",
        +  "implausible"
        +]
    • Addededit_materials
    • Addededit_shape
    • Addedfind_text
    • Addedlink_annotation
    • Addedlist_annotations
    • Changedone_click1 field changed
      • addedInput schema / properties / sensitivity
        Added value: +{
        +  "description": "Fill sensitivity, the same knob the canvas has: 0 strict (hatch/light linework always blocks), 0.5 balanced (default), 1 aggressive (crosses more hatch, tolerates more growth). Raise it when a flood stops short at hatching INSIDE the room; verify the grown ring with view_sheet overlay before committing",
        +  "maximum": 1,
        +  "minimum": 0,
        +  "type": "number"
        +}
    • Addedsheet_context
    • Addedundo_last
  16. 5 tool updatesv0.1.5
    • Addeddetect_rooms
    • Addedload_plan
    • Addedsheet_info
    • Addedtakeoff_summary
    • Addedview_sheet

TDQS

A3.9/5.0

Scored across 53 tools

Disambiguation4/5

Most tools target clearly distinct resource+action verbs (measure_polygon vs measure_line vs measure_surface; create/list/resolve/delete_rfi). The main friction is a cluster of four counting tools (place_count, symbol_sweep, sweep_schedule_row, count_marks) and four exports (takeoff/report/marked_pdf/dxf), but the descriptions laboriously cross-reference each other to define boundaries. Boundaries are mostly legible, just heavy to read.

Naming Consistency5/5

Almost uniformly snake_case verb_noun (load_plan, measure_polygon, edit_shape, create_rfi). The few noun-first names (sheet_info, sheet_graph, sheet_context) are a consistent convention for read/info tools, and there is no camelCase/snake_case mixing. Very predictable.

Tool Count2/5

53 tools is well past the 25-tool threshold and into territory where the surface is hard to hold in mind at once. The domain (full takeoff workflow) is genuinely broad and justifies many tools, but several clusters (exports, counting sweeps, condition-edit proposals) could plausibly be consolidated. Heavy rather than well-scoped.

Completeness5/5

The surface covers the full lifecycle: load/read the plan set, measure and derive, edit and delete shapes, manage conditions/materials/proposals, four export formats, import round-trip, annotations, verdicts, RFI CRUD, and undo. Almost no dead ends; even deletion of annotations is handled via edit_annotation's clear-text path.

Maintenance

ActivityActive
ResponsivenessResponsive

Related MCP Connectors

Related MCP Servers

  • A
    license
    Not graded
    quality
    C
    maintenance
    A local MCP server that drives PDFium and pypdf to perform comprehensive PDF operations including inspection, assembly, page editing, watermarking, rendering, extraction, form filling, encryption, compression, attachments, bookmarks, and metadata management.
    MIT
  • F
    license
    Not graded
    quality
    C
    maintenance
    MCP server for hard decoration quoting: parses DXF floor plans, reconstructs room topology, computes material quantities, and generates deterministic itemized quotes with tiered pricing. Exposes tools parse_dxf, compute_quote, and generate_report via stdio.
    -
  • A
    license
    B
    quality
    B
    maintenance
    A local, evidence-driven MCP runtime and control plane for open-source maintainers that provides workspace-bounded tools including controlled file operations, command execution, validation primitives, durable execution records, and human review workflows via stdio and Streamable HTTP transports.
    33
    MIT
  • A
    license
    A
    quality
    A
    maintenance
    Modern CAD CLI + MCP Server: 2D/3D drawing, editing, measurement, validation, batch automation, real-time collaboration (CRDT + WebSocket).
    22
    1
    Apache 2.0