Skip to content

feat(desktop): cut model round-trips — bind-template fast-path on the slim surface, Call-2 auto-apply, result-carried readback#554

Merged
mattcfilbert merged 42 commits into
feature/desktopfrom
claude/stretto-roundtrip-cut
Jul 20, 2026
Merged

feat(desktop): cut model round-trips — bind-template fast-path on the slim surface, Call-2 auto-apply, result-carried readback#554
mattcfilbert merged 42 commits into
feature/desktopfrom
claude/stretto-roundtrip-cut

Conversation

@mattcfilbert

@mattcfilbert mattcfilbert commented Jul 20, 2026

Copy link
Copy Markdown
Contributor

Stacked on #552. Live-receipt-driven: the slim-surface eval was slow because of model round-trips (~9/song at ~5s each; every tool result returns in <0.1s), and three of those turns were manufactured by the surface itself.

What changed

  • bind-template joins the 13-tool dynamic-authoring profile as the deterministic fast-path for plain chart shapes. The instructions advertised it while the profile cut it — live receipts show the model literally searching for "bind template" and thrashing.
  • Instructions are now coherent with the slim surface: the plain-chart/dashboard/edit-in-place routes no longer name unregistered XML tools; a compact command census lists the load-bearing tabdoc/tabui commands so search-commands is reserved for the uncommon path. Stays under the 46k tools/list deferral cliff (combined-lean budget test).
  • bind-template auto_apply now applies Call-2 bound results (was Call-1-only). A Call-2 proposal is binder-validated against the live workbook and applies under the same events-anchor user-change guard; on the slim surface the manual apply tools don't exist, so the alternative was the model freehand-building the same chart with fewer guards.
  • tabdoc:generate-viz-from-notional-spec results append a compact server-side readback (sheet, Rows/Cols, mark, sort; fail-open, one metadata round-trip) so verification stops costing model turns.
  • list-available-fields workbookFile is optional — omitted reads the live session workbook; its error no longer references cut get-*-xml tools.

Live receipts (same ask, same stage, same judge)

Song Before After
e1 revenue-by-region 45.9s · $0.17 · judge 70 FAIL 17.7s · $0.11 · judge 83 PASS (3 model turns, BIND→BIND)
m1 waterfall 302.3s · $0.68 · judge 30 149.3s · $0.32 · judge 30 (correctness gap = teaching backlog, not this PR)

Verification

Full unit suite 4537 green, tsc clean, lint clean. Byte ratchets respected (bind-template cap ratcheted down to 1629).

Known tradeoff: DESKTOP_INSTRUCTIONS is profile-independent, so full-profile consumers lose the dashboard-auto-apply routing prose; instructions now match the default surface. Profile-aware instructions are a follow-up.

🤖 Generated with Claude Code


Update (same day): NotionalSpec retired from routing — post-spec dialect live-proven

NotionalSpec was retired by decision on 2026-07-20. Commit ff11aece re-points every live pointer (route prose, command census, and the hint: strings author-calc/author-set return) at the post-spec dialect: author the semantic object first, then bind-template the chart naming the authored caption. refine-worksheet (primitives-only top-N/sort) joins the slim profile (now 14 tools) to carry edit-in-place. The spec command itself stays registered — the door closes only after full coverage is proven.

Live receipts on this build:

  • e2 quota-attainment: 66.9s/$0.26/judge-62-FAIL → 20.9s/$0.11/judge-82-PASS — the calc→chart chain in 4 model turns: author-calc ("Quota Attainment %") → bind-template naming the caption → Call-1 bound + auto-applied in 0.2s.
  • m1 waterfall: the template bound and auto-applied (chart carrying is NOT the gap), but refinement past it thrashed: tabdoc:sort returned 500 on seven wire-shape attempts (~150s) before the guarded document round-trip landed the sort correctly. Known follow-ups: fix the tabdoc:sort contract or extend refine-worksheet with a sort-by-field op; treat session: "" as absent.

🤖 Generated with Claude Code

mattcfilbert and others added 30 commits July 19, 2026 13:19
…ce (discoverability)

Live root cause (2026-07-19, two banked receipts): on calc-heavy asks the agent
searched 'underlying-metadata' -> 0 hits -> concluded 'not available in this
build' -> fell back to hand-writing workbook XML and drowned. The verbs exist
(executor maps them to GET/POST /v0/workbook/document) but had zero entries in
the JSON that search-commands, validateKnownCommand, and paramContractGuard all
read — undiscoverable AND uninvocable through the tool.

Adds honest entries for tabui:save-underlying-metadata / load-underlying-metadata
(load declares required in-param 'text'), allowlists both, and names the verbs in
the zero-hit recommendation. Empirically gated: her exact failing query now
returns the pair at ranks 1-2; 'calculated field' puts save at rank 1; guard
rejects load{} and load{filepath} with actionable steers.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…document or nothing)

Adversarial-review P1 mitigation for surfacing the round-trip verbs: a
pre-dispatch guard on execute-tableau-command rejects fragment loads (non-
<workbook> root, no datasources/worksheets), loads that would DROP live
worksheets (steer: delete-worksheet), and gross-shrink documents (<50% of
live) — with fail-open on live-read infrastructure failure. Guard built by
GPT-5.5 minion, adjudicated + full suite re-run firsthand (4453 green).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ot prove application

Live-proven 2026-07-19 (Desktop main.26.0715, three probes): document loads
that ADD columns or rewrite worksheet content apply; loads that REMOVE a
datasource column are silently ignored — completed envelope, no effect.
Teach: after every load, re-read with save-underlying-metadata and confirm
the change landed. Applied to the load entry's description and the
notional-spec-calc-authoring knowledge module.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…fore the workbook root

FATAL caught by live audition probe: Desktop documents open with a build
comment between the XML prolog and <workbook> ('<!-- build main.26.0715 -->');
the root regex forgot comments, so the guard rejected EVERY legitimate
whole-document load as a fragment — blocking the exact path it exists to
protect. Live receipt: verse 2's 'fragment' rejections were false positives.
Regression fixture uses the verbatim real head. Lesson (now in the stack
skill): probe the guard's ACCEPT path against a real artifact, not only its
reject paths against synthetic fixtures.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…t in the agent's hands

Live evidence (two drown receipts, 2026-07-19): agents on the manual round-trip
fragment the 50KB document re-emission. author-calc takes caption+formula
primitives and does the round-trip internally: read → unique-name splice into
the target datasource (single non-Parameters default, candidates listed on
ambiguity) → guard-validate → load → READBACK-VERIFY (completed does not prove
applied). Caption collisions rejected pre-write. Built by GPT-5.5 minion,
adjudicated firsthand.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…be trims, ratchets tightened

46,691 → 45,753: prose-only trims across the six fattest describes; per-tool
ratchets lowered to the new sizes. Load-bearing semantics restored after
adjudication where the trim cut meaning (Call-2 proposal provenance on
bind-template, plan-dashboard-creation pointer and applies-to-LIVE warning on
build-and-apply-worksheet). Full suite 4,460 green, tsc clean, combined-lean
under cap with headroom.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…s human-in-loop only

open-calc-editor-with-custom-calc returns completed WITHOUT committing (opens
the editor pre-filled; open editor blocks the UI thread — live-proven twice
2026-07-19). Knowledge now routes: author-calc tool first, manual round-trip
as fallback, calc-editor commands never in unattended runs.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…s real datasources

Live verse receipt (2026-07-19): worksheet views clone <datasource> elements,
so a Superstore workbook offered 'Sample - Superstore, Sample - Superstore' as
candidates, and splicing into the clone was silently discarded by Tableau (the
readback-verify caught it). Selection now scans only the first <datasources>
block.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… columns are a position trap

Live untaught-verse receipt (4 failed author-calc calls, readback-verify
caught every one): <connection>/<relation>/<columns> holds schema <column
ordinal=.../> nodes; insert-after-last-column landed the calc inside the
relation block and Tableau silently discarded it. End-of-datasource is the
position every successful live splice used tonight.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…sheet law in spec knowledge

Every author-calc bug tonight was invisible to synthetic fixtures and cost a
live verse to find (datasource clones; relation-column position trap). The
replay test runs the tool against a real saved Desktop document (username
scrubbed) offline. Knowledge: generate-viz renders on the ACTIVE sheet —
goto-sheet immediately before, or it clobbers a neighbor (live receipt: the
agent detected the clobber via readback, restored the sheet, and rerouted —
correct behavior, avoidable trap).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…o internal names

Live finale receipt (2026-07-19): all six author-calc calls succeeded, but 5
of 6 layered formulas referenced sibling calcs by CAPTION ([Member Profit],
[Profit Tier]) — Tableau formulas resolve internal names, and authored calcs
get [Calculation_N] names the agent cannot know, so every dependent calc
flagged invalid and the chart leg died. The tool now rewrites bracketed
caption tokens to internal names at splice time (base fields where caption
equals name pass through untouched).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…nt round-trip

Wraps the proven set shape (Segno's golden groupfilter + CODA's live param-link
proof) as an author-* verb: primitives in (caption/dimension/orderBy/count/end),
golden <group><groupfilter> XML server-side, spliced at datasource-end, readback-
verified. count accepts a literal N or a [Parameters].[X] token — the melody plays
over the key signature. Offline replay (5/5) incl. real 49KB Superstore document.

Live-mapped this session (receipts): the document round-trip is the whole dialect —
calcs/sets MERGE; parameters are OPEN-only (frozen to merge, all headless create/edit
commands hit dlg.DoModal). Set authoring needs no parameter mutation: it references
the param by token and Tableau resolves it at runtime.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…round-trip

Live-proven this session (CODA): a workbook-level <actions> block MERGES via the
round-trip — spliced <edit-parameter-action> targeting [Parameters].[Parameter 1]
survived readback, link intact. This refuted the map-agent's OPEN-only assumption
(the same trap sets fell into) — a live probe beat the reasoning.

Wraps it as a verb: primitives in (caption/sourceWorksheet/sourceField/
targetParameter/activation), golden <edit-parameter-action> XML server-side,
creates the workbook <actions> block if absent (between </datasources> and
<worksheets>) or appends with a fresh [ActionN] name, readback-verified. Offline
replay 4/4. The interactivity layer over the key signature.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…der the 46k cliff

format-labels: turns mark labels ON/OFF for a worksheet via the document round-trip
(PROVEN live 2026-07-19 — <format attr='mark-labels-show'> in a pane MERGES and
survives readback). Idempotent: rewrites an existing rule or inserts one; readback-
verified. Offline replay 4/4.

Funding: author-set/author-action/format-labels pushed the combined tool surface
past the 46k tools/list auto-deferral cliff (and the combined-lean profile's). Per
the ratchet doctrine (never raise the cap — trim to fit), emptied redundant single-
token .describe() stubs across the fattest tools (plan-dashboard-creation, build-and-
apply-dashboard, validate-proposal, dashboard-health-check, inject-template, dashboard
-auto-apply, add-field, apply-worksheet, resolve-field, build-and-apply-worksheet).
Field names + enums already carry the meaning; empty describe drops the JSON key
entirely (verified). Lowered the grandfather caps to lock the wins in; removed the
tools that dropped under the 1200B per-tool budget. Full suite 4476 green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
… OPEN, reopen explicit)

Parameters are the ONE shape frozen to live merge (PROVEN live 2026-07-19, CODA:
create/add/value-edit all silently refused; every headless create/edit command is a
blocking dlg.DoModal). A parameter is born ONLY at OPEN. So this verb does the
document surgery — splices the <column param-domain-type> into the Parameters ds
(creating that ds if absent; an empty one is dropped on load, one with a real column
survives) — writes a reopen-ready stage, and returns { stagePath, reopenRequired:
true }. The reopen + re-pin is the serving layer's job, surfaced explicitly, NEVER
hidden inside the tool (ghost gen-18: it's a session-lifecycle event). The reopen
preserving merged calcs/sets/actions/formatting is live-proven. Parameters = the key
signature; everything else is the melody merged over it. Offline replay 4/4.

Funds the 5th verb under both 46k cliffs by emptying redundant describe stubs
(get-worksheet-xml, apply-dashboard, remove-field). Full suite 4480 green, tsc clean.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…s + key-signature/melody law

New tactics/data/notional-spec-dynamic-authoring.md is the routing map that steers the
agent to author-calc/author-set/author-parameter/author-action/format-labels instead
of hand-XML, with the one governing law (params=key signature born at OPEN; calcs/sets/
actions/formatting=melody merged live) and the full WW44-shape recipe in build order.
Cross-linked bidirectionally with the calc-authoring doc. Every claim live-proven this
session. This is the teach-Sonnet layer — the verbs are the capability, this is discovery.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
… the reference marks safe but block

The command reference marks 15 *DialogCommand-sourced commands as opens_blocking_dialog=
false + agent_can_invoke=true; invoking them unattended pops a modal that wedges the
session (live-proven: edit-existing-parameter hung 12s while health stayed OK). Names the
15, gives the trust rule (source name > derived flag), routes to author-* verbs instead,
and records the generator-fix heuristic (source =~ /Dialog/ => force blocking). Refines the
ghost's 'reference omission' into the real bug: misclassification, not omission.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…no-XML surface

Answers 'make it shorter': instead of trimming describe stubs to squeeze verbs under
the 46k tools/list cliff, define a lean profile that IS the whole authoring language.
TOOL_PROFILE=dynamic-authoring = spec-loop's 5 (execute-tableau-command for the
NotionalSpec chart/dashboard spine + discovery/readback) + the 5 author-* verbs
(calc/set/parameter/action/format-labels). Ten semantically-named tools cover the full
WW44 dynamic dialect; zero agent-visible XML/cache/validation/template tools.

Measured wire surface: 9,644 bytes vs the 46,000 cliff — 79% headroom (vs the full
45-tool surface that fights the cliff). The cliff stops being a fight structurally.
Full set stays reachable via TOOL_PROFILE=full. Tests: registers exactly the 10, banishes
XML/template tools, asserts <30k. Full suite 4482 green, tsc clean.

Ghost gen-19 Move 1 (the ruthless surface already half-existed as spec-loop; this bolts
the write half on and names it).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…he 12-tool singable surface

CODA's joy-cut overshot: a singer who cannot ask for help or discover the
execute-tableau-command dialect is not lean, she is mute. Re-measured at
11,500 wire bytes — 75% headroom under the 46k cliff.
…y signature no longer stops the song

CODA's one seam, closed tool-side: getExecutor discovers manifests dynamically
and getDesktopConfig() re-reads env per call, so the tool itself relaunches the
seeded stage (open -n, live-probed: manifest 2s, API-ready 3s), readback-verifies
the parameter in the reopened doc, re-pins TABLEAU_DESKTOP_SESSION_ID in-process
(only when a pin already existed), then SIGTERMs the old instance (probed: dies
clean, no recovery sibling). Any failure degrades to the honest reopenRequired
stage-on-disk result. stagePath is now optional — untaught agents never invent
filesystem paths; the default stages beside the live workbook's own file.
…ses the document among instances

Dress rehearsal caught two live defects the offline suite could not: (1) with
multiple instances of the bundle running, 'open -n -a' starts a fresh instance
but LaunchServices routes the document Apple Event unpredictably — the reopened
instance came up EMPTY; spawning <bundle>/Contents/MacOS/<binary> <stage>
detached carries the document deterministically (and puts it in argv). (2) The
default stagePath cannot derive from the old process's argv for the same reason —
it now stages under ~/Documents/My Tableau Repository/Workbooks (real user path;
Desktop has crashed saving from sandboxed tmp dirs). E2E-proven live: reopened
78197→78328, param verified in the new instance's readback.
…erbs are named where the singer reads first

Finale autopsy: untaught Sonnet landed both parameters through the in-call
reopen but burned 55 of 65 calls spelunking raw commands for the set and
dashboard, and never reached author-set — the tool list carried the verbs but
the initialize instructions never routed a dynamic ask to them. Segno's
discoverability law, one level up: ink the sign in the score she reads at
note one.
…n 46k cliff

The prior commit pushed combined-lean to 46,116 — the full suite caught it
after an ungated commit chain (the lesson is the gate order, re-learned).
… modal dies here

revert-workbook-ui fired Error 47BF7751 on Matt's screen three times in one
verse: the reference says safe, CODA's probe said modal, the knowledge doc knew,
but nothing ENFORCED it. Now the guard refuses all 16 live-proven dialog
commands outright, each with a FIX redirecting to the sanctioned alternative
(author-parameter, author-action, NotionalSpec keys, or author-forward).
Every annoyance dies exactly once.
…arameters].[N] refs — the empty-sheet killer

Verse-3 live receipt: the singer's filter calc named two parameters by caption;
resolution only covered the target datasource's columns, so the refs never bound
and the sheet rendered empty on Matt's screen. Parameter captions now resolve
from the Parameters datasource to the qualified cross-datasource form, set after
the field map so a caption collision resolves to the parameter — which is what a
dynamic-ask formula means.
…ls the silent-empty-sheet class

Live eval receipt (2026-07-20 TUTTI Blake-set wave): a spec with
aggregation:'none' (not v0.2) and a rangeFilter using max: (v0.2 is
start/end) passed the guard, codegen generated NOTHING, and the /v0
success envelope shipped an empty sheet as a PASS. The guard now
validates per-field keys/enums (data/type/role/aggregation/encoding,
'shelf' redirect, detail/tooltip v0.3 gate), sort, rangeFilters,
relativeDateFilters (plural periods, next/previous), and
categoricalFilters — every rejection carries a FIX line that teaches
the correct vocabulary in-context. Accept fixtures are the six
live-proven TUTTI golden specs; reject fixtures are the live failing
payloads.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…or the semantic-loop pair

The dynamic-authoring profile has no knowledge tools, so search-commands
is the singer's only reachable teacher — expertise:// pointers are dead
ends there. The generate-viz entry now carries the v0.2 cheat-sheet
(chart enum, field props, filter/sort shapes), the author-calc-then-
reference-by-caption idiom (aggregation 'default', never 'none'), the
string-date DATE() idiom, and the write-blind law (probed raw
2026-07-20: /v0 invokeCommand returns state:SUCCEEDED with no result,
no operations route — verify by readback, always). The read-spec twin's
entry now states its output is NOT returned by the /v0 API (product
gap) and routes readers to document readback. Both entries get real
parameter metadata (was []).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… test helper

Green firsthand: lint clean, build ok, 4531 tests pass. CI was red on lint only.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
mattcfilbert and others added 10 commits July 20, 2026 11:24
… omitted it

CI Tests step failed (both node matrices): the timeout + 2 success-path reopen
tests fell through to defaultSpawnDetached, which spawned the real macOS Tableau
binary — ENOENT on the Linux runner fired as an async unhandled rejection after
the test passed, exiting the process 1. Passed locally only because the app path
exists on macOS. All other reopen tests already mock it.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…E now selects the 12-tool dynamic-authoring surface

Flips the desktop default from the full 40-tool surface (raw XML get/apply +
templates + cache) to the lean native-authoring surface: semantic loop via
execute-tableau-command + the five author-* verbs + discovery/readback, zero
agent-visible XML. 'full' is the explicit opt-in escape hatch; demo/spec-loop/
combined-lean unchanged. Consumers that need the XML surface set TOOL_PROFILE=full.
TAS + the eval kit already set the profile explicitly, so the singer is unaffected;
this makes native the DEFAULT for every unset consumer (ghost gen-19 standing move).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…lt surface

The TOOL_PROFILE default flip (unset -> dynamic-authoring) changes what both the
standalone desktop server and the combined bundle's desktop half register when no
profile is set. Assert the lean 12-tool native surface (raw XML tools opt-in via
TOOL_PROFILE=full). Desktop list-tools verified locally; combined needs CI web creds.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…was failing open)

Review P1: WORKSHEET_NAME_RE only matched name='...' (single quotes), so a live
worksheet with a double-quoted name was invisible to the dropped-sheet check and
could be silently dropped on reload — the exact data-loss this guard exists to
prevent, failing OPEN. Match both quote styles (the pattern formatLabels.ts already
uses for the same attribute). Regression test with a double-quoted live sheet.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ace, coherent instructions, census, live-session fields

Live receipts (2026-07-20 slim battery) root-caused the slow eval: ~9 model
turns/song at ~5s each while every tool result returns in <0.1s. Three of
those turns were manufactured by the surface itself:

- instructions advertised bind-template and XML fallback tools the
  dynamic-authoring profile does not register (m1 receipt: two
  search-commands calls literally hunting for 'bind template'), so
  bind-template joins the slim profile (13 tools) as the deterministic
  ~0.3s fast-path for plain chart shapes; route prose no longer names
  unregistered tools;
- a command census prose entry lists the load-bearing tabdoc/tabui
  commands so search-commands is reserved for the uncommon path (m1: 13
  of 23 turns were discovery thrash);
- list-available-fields required a cached workbookFile and its error
  pointed at cut get-*-xml tools (e1 turn 1 burned on 'File not found');
  workbookFile is now optional and omitting it reads the live session
  workbook.

Instructions stay under the 46k tools/list deferral cliff (combined-lean
budget test green). Full suite 4535 green, tsc clean, lint clean.

Known tradeoff: DESKTOP_INSTRUCTIONS is profile-independent, so full-profile
consumers lose the dashboard-auto-apply routing prose; instructions now match
the default surface. Profile-aware instructions are a follow-up.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…te-viz-from-notional-spec

Live re-time receipt (e1, 60s/13 calls): a Call-2 bound result stranded the
slim surface (auto-apply was Call-1-only and the manual apply tools are cut),
costing 4 search-commands thrash turns; then 5 more turns re-verified a
write-blind spec apply.

- bind-template auto_apply now applies any bound fast-path-eligible result.
  A Call-2 proposal is binder-validated against the live workbook and applies
  under the same events-anchor user-change guard; the freehand fallback the
  model otherwise takes has fewer guards.
- tabdoc:generate-viz-from-notional-spec success results now append a compact
  server-side readback (sheet, Rows/Cols shelves, mark, sort) resolved from
  the active window; fail-open, one metadata round-trip. The command
  reference's verify guidance now points at the result readback.
- bind-template byte cap ratcheted down to 1629 (describe trim funded the
  gate-comment rewrite).

Full suite 4537 green, tsc clean, lint clean.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…emplate + author-* carry the song, refine-worksheet joins the slim surface

NotionalSpec is retired (Matt, 2026-07-20). Three live pointers still steered
the model at the dead command (jam gen-21): the plain-chart/dashboard/dynamic/
edit-in-place route prose, the command census, and the success-path hint
strings author-calc and author-set return on every call. All re-pointed at
the post-spec dialect: author the semantic object first, then bind-template
the chart naming the authored caption; refine-worksheet (primitives-only
top-N/sort) joins the 14-tool dynamic-authoring profile to carry
edit-in-place. The spec command itself stays registered and functional —
the door closes when the replacement chain is live-proven, not before.

Full suite 4537 green, tsc clean, lint clean. Instructions +13 bytes,
cliff-safe (refine-worksheet was already in the full surface the combined
budget measures).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… call

jam gen-22 Move 1 (receipt: the author-calc→bind chain costs ~9-13s of model
turns per calc song). bind-template accepts calcs[{caption,formula,datatype,
role}]: formulas validate BEFORE any document mutation (atomic error, zero
apply on a bad calc), authoring reuses author-calc's splice/load/readback via
the new shared authorCalcCore, and the bind + auto-apply see the post-calc
workbook. authored_calcs surfaces in every outcome. Route prose updated.
Per-tool cap raised 1591→1725 for the capability (slim surface is ~11.6k of
46k; combined-lean cliff stays the hard gate, green at ~45.9k).

Full suite 4540 green, tsc clean, lint clean.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… as absent

jam gen-21/22 Move 2 (receipt: m1 waterfall — tabdoc:sort 500'd on seven wire
shapes, ~150s of blind model turns, before a manual computed-sort splice
landed direction/using correctly). sort_by_field produces that same
computed-sort shape through refine-worksheet's readback-verified apply path,
with caption resolution matching the existing ops. resolveSession now treats
empty/whitespace session strings as absent (was a wasted error turn:
session:'' against a pinned Desktop).

Full suite 4548 green, tsc clean, lint clean.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ach the true sort contracts

Live receipts (2026-07-20): tabdoc:sort popped blocking modals on the user's
screen (47BF7751 'missing: global-field-name') after seven opaque 500s. The
monolith's own sort-cmd.data settles it: SortCommand drives the sort UI
dialog (updateSortDialog notification) — it was never agent-safe — and
SortNested is the programmatic sort that existed all along.

- execute-tableau-command now refuses tabdoc:sort pre-dispatch with a FIX
  line redirecting to refine-worksheet sort_by_field or tabdoc:sort-nested.
- Reference corrected: Sort marked dialog-risky with the true wire contract
  (global-field-name, Type-required-unless-clear, computed-sort MeasureName);
  sort-nested pinned with its required params.
- paramContractGuard enforces sort-nested's required params pre-dispatch.

Full suite 4556 green, tsc clean, lint clean.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Base automatically changed from claude/segno-roundtrip-discoverability to feature/desktop July 20, 2026 20:12
mattcfilbert and others added 2 commits July 20, 2026 16:12
…osal (#555)

* feat(desktop): shared slot vocabulary — sort and top_n on the bind proposal

jam gen-23 Move 1 (receipts: m1's order-by strand; the ranking family cannot
say 'top 10'). The proposal schema gains two optional concepts — sort {by,
direction} and top_n — advertised in PROPOSAL_OUTPUT_SCHEMA so Call-2 models
learn them from the propose payload itself, validated against the workbook
schema BEFORE apply, spliced via the computed-sort/top-N helpers extracted
from refine-worksheet (built earlier today — reused, not duplicated), atomic
failure on splice errors. One bind call now carries ordering and limits; no
refinement tail. Growth law honored: receipt-backed concepts only, binder-
validated, riding the one-call path.

Full suite 4567 green, tsc clean, lint clean. combined-lean 45,992/46,000 —
8 bytes headroom; next growth must trim first.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(desktop): complete the slot-vocabulary commit — validate seam + describe trims

These two files were part of the sort/top_n packet but missed the explicit
staging list; the working-tree suite masked the gap (adjudication lesson:
git-status-clean check BEFORE declaring a packet landed). Without them the
pushed tree fails its own validate-proposal byte cap.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(desktop): literal names at every tool boundary — the ampersand bug dies

Receipt (m1, 2026-07-20): a sheet named 'P&L Waterfall…' surfaced entity-
escaped through list-worksheets, made refine-worksheet miss by literal name,
and silently confirm-miss (refined:false) by escaped name — any &-titled
sheet was unreachable. Now: names decode exactly once at output boundaries
(list-worksheets/dashboards, bind-template sheet_name/guidance), name inputs
resolve normalized-to-normalized with escaped-input tolerance, apply gates
compare normalized, and refine uses the canonical literal name from fetched
XML for apply/readback/output. Coverage for & < " in literal and escaped
paths. (Includes a sliver of the prior commit's file overlap — the two
commits were disentangled from one worktree; suite green across both.)

Full suite 4582 green, tsc clean, lint clean.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
@mattcfilbert
mattcfilbert merged commit 5c5b328 into feature/desktop Jul 20, 2026
4 checks passed
@mattcfilbert
mattcfilbert deleted the claude/stretto-roundtrip-cut branch July 20, 2026 20:33
mattcfilbert added a commit that referenced this pull request Jul 21, 2026
…ll discovery hints

CI tsc caught warnings? missing on BindTemplateToolResultBase (vitest
transpiles without typecheck — the gate that matters is npx tsc).

Live re-sing receipt (m1, judge 34 both pre/post-slot): the slot shipped but
was never FOUND — a confident bind has no Call-2, so manifest descriptions are
never read, and the routeTable teaching was trimmed for the 46k cliff. A
capability without a discovery channel is a no-op.

Result-carried teaching (the #554 pattern, zero tools/list bytes): waterfall
bind results now hint (a) the anchor_category re-call naming the schema's
category-like column when the slot is unbound, (b) the one-call sort override
when no sort proposal was passed. 6 tests pin presence/absence per case.

tsc 0, suite 4616 green (316 files), lint 0 — pipe-proof exits.

Co-Authored-By: Claude <noreply@anthropic.com>
mattcfilbert added a commit that referenced this pull request Jul 21, 2026
…er template sort (#560)

* feat(desktop): waterfall anchor_category slot + sort proposal wins over template sort

Live eval receipts (m1 waterfall FAIL 34/100, 2026-07-20): (1) the singer's
sort override 'hit a template limitation' — part-to-whole-waterfall hard-codes
computed-sort DESC by SUM(Profit) and the sort field wasn't declared in
datasource-dependencies, so the proposal was inert; the singer burned 3
sort-nested 500s + a doc round-trip forcing ASC-by-display_order. (2) subtotal/
total rows were running-totaled with the increments they summarize — final bar
~2x true Net Income.

- sort proposal now replaces the built-in computed-sort (declares the sort
  column/column-instance when missing); every failure path is fail-open with a
  warnings[] channel on the bind result — template default kept, bind never blocks
- new optional virtual slot anchor_category: when bound, splices a categorical
  EXCLUDE filter (subtotal/total) so anchors leave the bridge; unbound binds are
  byte-identical (characterization-pinned)
- teaching lives in the manifest slot description (model-facing at bind time),
  NOT tools/list: bind-template/validate-proposal ratchets TIGHTENED 1908->1886 /
  1555->1533; combined-lean 45,952 of 46,000
- served-corpus knowledge twins: waterfall subtotal/total law + zero-denominator
  ratio-sort guard (both analytics paths — legacy twin is live)

Suite 4608 green (316 files), lint clean, invariants unchanged. v2.27.0.

Co-Authored-By: Claude <noreply@anthropic.com>

* fix(desktop): warnings typing on result base + result-carried waterfall discovery hints

CI tsc caught warnings? missing on BindTemplateToolResultBase (vitest
transpiles without typecheck — the gate that matters is npx tsc).

Live re-sing receipt (m1, judge 34 both pre/post-slot): the slot shipped but
was never FOUND — a confident bind has no Call-2, so manifest descriptions are
never read, and the routeTable teaching was trimmed for the 46k cliff. A
capability without a discovery channel is a no-op.

Result-carried teaching (the #554 pattern, zero tools/list bytes): waterfall
bind results now hint (a) the anchor_category re-call naming the schema's
category-like column when the slot is unbound, (b) the one-call sort override
when no sort proposal was passed. 6 tests pin presence/absence per case.

tsc 0, suite 4616 green (316 files), lint 0 — pipe-proof exits.

Co-Authored-By: Claude <noreply@anthropic.com>

* fix(desktop): sort splice handles the serializer's paired-empty computed-sort form

Third live sing receipt: the singer finally passed the full correct proposal
(anchor_category + sort by Display Order asc) and the splice refused it —
'only the safe self-closing <computed-sort/> can be changed'. Root cause: the
SOURCE template is self-closing but inject serialization expands it to
<computed-sort ...></computed-sort>; the safe-form detector missed our own
serializer's output.

Safe target detection now covers self-closing, paired-empty, and plain-wrapper
forms; truly nested <sort-computation>/manual forms still refuse (the
computed-sort-crash class — Desktop crash guard intent preserved, evidence in
computedSortCrash.ts). bind-template and refine-worksheet sort_by_field share
the helper, so both inherit the fix. Characterization test pinned to the real
inject-serialized waterfall shape (failed before, passes after).

tsc 0, suite 4620 green (316 files), lint 0 — pipe-proof exits.

Co-Authored-By: Claude <noreply@anthropic.com>

* fix(desktop): waterfall seam-4 — sort the shelf axis with anchor_category bound; surface knowledge tools

Two coupled fixes so a P&L waterfall can carry BOTH an anchor exclude-filter
AND a proposal sort, and so the singer can actually reach the corpus that
already documents how.

Seam 4 (planSortByFieldOnCategoricalAxis): the categorical-axis count included
filter-only dimension CIs. A waterfall's anchor_category exclude-filter adds a
nominal/None CI that lives only in <filter>; it was counted as a rival axis and
tripped "more than one categorical axis is present; sort is ambiguous." Restrict
axis candidates to dims whose DS-qualified ref appears on a rows/cols shelf,
mirroring planSortInsertion's proven membership check. Substring membership is
exact for CI refs (the :suffix] grammar means one ref is never a substring of a
longer one; ds + ci.name share the shelf text's escaping domain, e.g. P&L Data).
Fail-before/pass-after test uses the real part-to-whole-waterfall template +
spliceWaterfallAnchorFilter; adds a prefix-boundary case (RegionName vs Region)
and keeps the genuine two-shelf-axis refusal.

Knowledge tools: the default dynamic-authoring profile omitted
list-knowledge-resources + read-knowledge-resource, yet the singer's system
prompt instructs "consult the expertise library BEFORE authoring" — so the
corpus (which already states the waterfall subtotal/total exclusion rule and the
Top-N-needs-a-context-filter rule) was unreachable on every sing. Add both to the
profile (14→16) and sharpen their descriptions to say WHEN to reach (net -27
bytes; combined-lean tools/list surface stays under the 46k cliff with more
headroom than before). No a2td port owed — a2td's refine is a different
(operation-based) architecture and always registers the knowledge tools.

Gates: tsc 0, vitest 4623 passed/1 skipped, lint 0. Andy-review addressed.

Co-Authored-By: Claude <noreply@anthropic.com>

---------

Co-authored-by: Claude <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant