Skip to content

fix(tables): make the agent tool picker version-aware and enrich the v2 query - #7328

Merged
mzxchandra merged 21 commits into
stagingfrom
table-v2-picker-enrichment
Sep 1, 2026
Merged

fix(tables): make the agent tool picker version-aware and enrich the v2 query#7328
mzxchandra merged 21 commits into
stagingfrom
table-v2-picker-enrichment

Conversation

@mzxchandra

Copy link
Copy Markdown
Contributor

Summary

Two prerequisites for the Table v1 → v2 cutover (#follow-up PR), shipped first because they are independently safe: table_v2 is still preview: true on this branch, so nothing here is user-visible yet.

1. The Agent block's tool picker was version-blind. It gated on a hardcoded set naming 'table' with no version resolution:

const CORE_AGENT_TOOL_TYPES = new Set([..., 'table', 'file_v5'])
return !block.hideFromToolbar && (block.category === 'tools' || CORE_AGENT_TOOL_TYPES.has(block.type))

Both Table blocks are category: 'blocks', so that set is their only route into the picker. table_v2 would therefore never appear there — even after it ships and the toolbar has moved to it. The set now stores base types and matches after stripping the _vN suffix, so a cutover never has to remember this file again.

Broadening 'file_v5''file' admits nothing new: file, file_v2, file_v3, file_v4 all carry hideFromToolbar: true and are caught by the first guard. Same for unrevealed preview blocks, which getAllBlocks() projects into clones carrying that flag.

2. table_query_rows_v2 shipped with no toolEnrichment, while all seven other table tools have one. Enrichment is what injects the table's real column names into the LLM's tool schema — without it an agent using the v2 query tool has to guess field names. lib/table/llm/enrichment.ts only knew v1's MongoDB grammar, so adding the id to the existing sets would have taught the wrong syntax; v2 gets its own branch emitting predicate examples, order rather than sort, and cursor-paging guidance.

Unlike v1, a v2 query with no filter is valid and returns every row, so the new branch deliberately does not push filter into required.

The shared bulk tools (table_update_rows_by_filter, table_delete_rows_by_filter) stay on $eq. The rows route accepts either grammar via resolveBulkFilter, and enrichment is keyed on tool id alone so it cannot tell which block invoked it — one grammar has to win, and $eq works for both.

Type of Change

  • Bug fix
  • New feature
  • Breaking change
  • Documentation
  • Other: ___________

Testing

Full suite green: 2,776 tests across the affected areas (blocks/, lib/table/, tools/table/, tools/schema-enrichers, tool-input/), plus turbo run type-check, lint:check, and check-block-registry.ts on this branch alone.

New coverage:

  • tool-input.test.ts — base-type match, superseded-version exclusion, a versioned block whose base is unlisted (memory_v2), and every tools-category block regardless of version.
  • enrichment.test.ts (new) — v2 teaches predicate grammar and never $eq/offset; filter stays optional; both single-column arms of the example builder; v1 regression guard that table_query_rows still forces filter required and still teaches $eq.
  • schema-enrichers.test.ts — the toolEnrichment wiring itself. This is the gap that let v2 ship unenriched in the first place, and a typo in the tool-id literal degrades silently to the generic description, so it is now pinned.

Reviewers should focus on whether the stripVersionSuffix broadening in isAgentToolBlock can admit any block that should not be agent-callable. I enumerated every registered block whose stripped base lands in the set; net picker delta is zero on this branch.

Verified in the browser with PREVIEW_BLOCKS=table_v2 set locally: the Table tool appears in the picker under built-ins, offers v2's Cursor field rather than v1's Offset, and disappears again when the flag is unset.

Checklist

  • Code follows project style guidelines
  • Self-reviewed my changes
  • Tests added/updated and passing
  • No new warnings introduced
  • I confirm that I have read and agree to the terms outlined in the Contributor License Agreement (CLA)

Notes

ncontains was missing from the operator list every v2 surface advertises, although FILTER_OPS declares it and sql.ts compiles it to a negated ILIKE. Added. That list is now spelled out verbatim in four places and had already drifted from its source of truth on arrival — deriving it from FILTER_OPS is the real fix and is worth a follow-up.

…v2 query

The Agent block's tool picker gated on a hardcoded `CORE_AGENT_TOOL_TYPES`
set naming `'table'` with no version resolution. Both Table blocks are
`category: 'blocks'`, so that set is their only route into the picker —
meaning `table_v2` would never appear there, even once it ships and the
toolbar has moved to it. Store base types and match after stripping the
`_vN` suffix so a cutover never has to re-list a block here. The existing
`hideFromToolbar` guard already excludes superseded versions (file v1-v4)
and unrevealed preview blocks, so broadening `file_v5` to `file` admits
nothing new. `BUILT_IN_TOOL_TYPES` had the same gap and would have filed
v2 under Integrations.

`table_query_rows_v2` also shipped with no `toolEnrichment` while all seven
other table tools have one, so an agent using it never saw the table's
column names and had to guess field values. The enrichment module only knew
v1's MongoDB grammar, so adding the id to the existing sets would have
taught the wrong syntax — v2 gets its own branch emitting predicate
examples, `order` rather than `sort`, and cursor paging guidance.

Unlike v1, a v2 query with no filter is valid and returns every row, so the
new branch deliberately does not push `filter` into `required`.

The shared bulk tools stay on `$eq`: the rows route accepts either grammar
via `resolveBulkFilter`, and enrichment is keyed on tool id alone, so it
cannot tell which block invoked it.
…ichment wiring

Review findings.

The operator list the v2 surfaces advertise omitted `ncontains`, which
`FILTER_OPS` declares and `sql.ts` compiles to a negated ILIKE. An agent
reading the tool schema therefore never knew the operator existed. The list
is spelled out verbatim in several places and had already drifted from its
source of truth on arrival; deriving it from `FILTER_OPS` is the real fix
and is left as follow-up.

The `toolEnrichment` block this branch adds had no test, which is the same
gap that let v2 ship without enrichment in the first place — a refactor
could silently drop it again, and a typo in the tool-id literal degrades to
the generic description with no failure. Pin both halves: the tool declares
the wiring, and the enricher answers in predicate grammar with `filter`
left optional.

Also cover the two `v2PredicateExample` arms an all-numeric or all-text
table takes, and assert the empty-order case the previous test only
executed without checking.
@vercel

vercel Bot commented Sep 1, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
docs Ready Ready Preview Sep 1, 2026 2:27am UTC

Request Review

@greptile-apps

greptile-apps Bot commented Sep 1, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

The PR makes agent tool discovery version-aware and adds schema-aware enrichment for the v2 table query tool.

  • Matches versioned core blocks by base type while preserving preview and legacy visibility guards.
  • Adds v2 predicate, ordering, projection, limit, and cursor guidance using real table columns.
  • Carries select cardinality into enrichment and derives operator restrictions from the validator’s shared allowlists.

Confidence Score: 5/5

The PR appears safe to merge.

No blocking failure remains.

Important Files Changed

Filename Overview
apps/sim/lib/table/llm/enrichment.ts Adds v2-specific, schema-aware query guidance and completes the prior select-operator fix across tool and parameter descriptions.
apps/sim/lib/internal/table/read-schema.ts Preserves select cardinality so enrichment can distinguish single-select and multi-select operator contracts.
apps/sim/lib/table/column-types/select.ts Exports shared v2 select-operator allowlists used by both validation and LLM guidance.
apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/tool-input/utils.ts Resolves versioned core block types by base name while retaining toolbar visibility checks.
apps/sim/blocks/blocks/table_v2.ts Updates user-facing query guidance to describe select-column restrictions accurately.
apps/sim/tools/table/query_rows_v2.ts Wires table-schema enrichment into the v2 query tool contract.

Reviews (5): Last reviewed commit: "fix(tables): cover all four pattern oper..." | Re-trigger Greptile

Comment thread apps/sim/lib/table/llm/enrichment.ts Outdated

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All reported issues were addressed across 7 files

Heads up: you’re close to your included review allowance. Set a flex budget so reviews don’t pause.

Fix all with cubic | Re-trigger cubic

Comment thread apps/sim/lib/table/llm/enrichment.ts Outdated
…accepts

Greptile and cubic both flagged this independently, and they were right.

A select column accepts only a subset of the filter operators — eq/ne/in/nin
when single, contains/ncontains when multi — and `buildFilterConditions`
THROWS on anything else rather than returning no rows. The new v2 enrichment
advertised the full operator list for every column and, worse, asserted
"there are no array columns - for substring matching use ilike with *x*".
A multi-select cell holds a list, so that sentence is false and it steers
the model straight into a rejected query on exactly the columns it describes.

The enricher could not tell single from multi because `readTableSchemaAsExecutor`
dropped `multiple` when projecting the summary. Carry it through — for select
columns only, where it means something — and annotate each select column in
the description with the operators it actually takes.

The block's bestPractices carried the same false claim, so the model reached
it a second way even when enrichment was absent. Corrected there too.

Also regenerate the tool metadata bundle, which is what CI was failing on:
the ncontains commit changed the v2 tool description without regenerating
the derived artifact.
@mzxchandra

Copy link
Copy Markdown
Contributor Author

@cubic review

@cubic-dev-ai

cubic-dev-ai Bot commented Sep 1, 2026

Copy link
Copy Markdown

@cubic review

@mzxchandra I have started the AI code review. It will take a few minutes to complete.

Comment thread apps/sim/lib/table/llm/enrichment.ts Outdated
@simstudioai simstudioai deleted a comment from cubic-dev-ai Bot Sep 1, 2026

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All reported issues were addressed across 12 files

Heads up: you’re close to your included review allowance. Set a flex budget so reviews don’t pause.

Fix all with cubic | Re-trigger cubic

Comment thread apps/sim/blocks/blocks/table_v2.ts Outdated
Comment thread apps/sim/lib/table/llm/enrichment.ts Outdated
Comment thread apps/sim/lib/table/llm/enrichment.ts Outdated
… sets

Round 2 review. Both bots were right, and the second finding corrected me.

The first fix annotated the column list but left the `filter` PARAMETER
description advertising every operator for every column. That description is
what a model reads when deciding the filter's shape, so the restriction has
to appear there too — it now names the restricted columns inline.

More importantly, the annotation itself was wrong: I omitted isNull and
isNotNull. I had traced the v2 grammar through `predicateToFilter` to the
`$`-prefixed sets in `column-types/select.ts`, which genuinely lack them —
but the v2 query path never goes through that conversion. It compiles
predicates directly in `fieldPredicate`, which gates on a SEPARATE pair of
`FilterOp` sets that do allow both.

Two allowlists for one concept is what made a hand-copied list wrong, so the
v2 sets move next to their v1 siblings in the column-type registry — where
per-type knowledge belongs — and the enrichment now derives its text from
the exact sets the validator gates on. It can no longer drift.

Same correction applied to the block's bestPractices, which carried the
short list too.
Follows the gmail_v2 / github_v2 / confluence_v2 / cursor_v2 cutovers: v1
is renamed "(Legacy)", hidden from discovery, and points at its successor;
v2 drops the preview gate. Both edits must land together — the registry
check fails a sunset block whose replacedBy is still preview, verified by
splitting them locally.

v1's `triggers.enabled` deliberately stays true. Webhook execution gates
on it at runtime rather than on discovery, so flipping it would break every
deployed v1 table-trigger workflow. Both versions host the same
`table_new_row` trigger and dispatch is provider-keyed, so hiding v1
changes nothing for triggers.

No BLOCK_META_REGISTRY entry is needed — meta coverage is required only for
`category: 'tools'` blocks and both Table blocks are `category: 'blocks'`.

Docs regenerate from v2 now that v1 is skipped as a source, so the
generated page moves to the predicate grammar and cursor pagination. The
hand-written regions did not, and are updated here: the operator reference
and combining examples, the workflow guide's field walkthrough, and the
pagination advice — which previously told readers to advance an offset
while looping on nextCursor, mixing both versions.

Verified in the browser: the toolbar yields a `table_v2` block whose Query
Rows shows Cursor/Order; the Agent tool picker stores
`type: table_v2, toolId: table_query_rows_v2` and groups it under built-ins;
and a pre-existing v1 block still renders its own Offset/Sort fields behind
an amber legacy badge.
Review findings, all consequences of v2 becoming the docs source.

`BlockPreview` looks the block type up in a hand-maintained map and renders
nothing when it misses, so pointing the trigger page at `table_v2` silently
deleted its hero. Move the entry to the new type; the trigger config it
displays is identical across both versions.

The docs generator extracts `longDescription` with a single-literal regex,
so v2's `+`-concatenated string published as its first fragment alone — the
page lost every mention of the predicate grammar and cursor pagination.
Join it into one literal. v1's was a single literal, which is why this only
surfaced now.

The hand-written operator table called `contains` case-sensitive. It
compiles to ILIKE, as do `startsWith` and `endsWith`; the pre-cutover page
had this right, so the rewrite regressed it. Restore the qualifiers and
document `ncontains` alongside `contains`.

Drop `'table'` from BUILT_IN_TOOL_TYPES: it now only ever reaches blocks
that already passed `isAgentToolBlock`, which excludes hidden ones, so the
entry is dead. Matches the `file`/`file_v5` precedent the sibling test
already asserts.
Regenerate the two artifacts the `ncontains` commit left stale: the tool
metadata bundle and the generated Table integration page both still published
the operator list without it, so `bun run tool-metadata:check` failed at HEAD.

Point the remaining docs previews at `table_v2`. `BLOCK_DISPLAY_WORKFLOWS`
already moved, but the two hand-written table example workflows and the
OutputBundle on the workflows guide still named the v1 type, so every table
example in the docs described the block the toolbar no longer yields. They
render identically today only because both types share an icon-map entry.

The workflows guide told readers the log shows "the filter and sort it sent";
v2 sends `order`.

Document the `sunset` step in the three skills that describe the v1 -> v2
cutover. All three stopped at `(Legacy)` + `hideFromToolbar`, but
`check-block-registry` fails a legacy block with no `replacedBy`, and the
amber badge and its click-to-upgrade action read from that field - following
the procedure verbatim produced a build failure. Also record the ordering
constraint this cutover hit: the v1 `sunset` edit and the v2 `preview` removal
must land together, since the check rejects a `replacedBy` that is still
preview.

Left alone: the academy video previews still show v1 labels. They mirror
recorded footage, so correcting the label without a re-record would only make
the still disagree with the video it claims to depict.
The regenerated page reverted to the "there are no array columns" claim that
the select-operator fix corrected. Restore the allowed-operator table.
Greptile: convert the v1 lifecycle comment to TSDoc. The repo rule is
TSDoc-only, and none of the four reference cutover blocks comment here at
all, so the `//` form was not following a local convention either. The note
is worth keeping as declaration documentation — it records why
`triggers.enabled` must stay true.

Cubic: fix a link to `#tips`, an anchor that does not exist on the page.
The pagination guidance lives under Variations.
Review finding. The add-integration cutover steps said to add
`sunset.replacedBy` without stating the precondition, so an author following
them while v2 was still preview-gated would get a `check-block-registry`
failure — the exact constraint this cutover hit.

The sibling add-block and add-block-preview skills already spell this out;
this brings the third in line.
@mzxchandra

Copy link
Copy Markdown
Contributor Author

@greptile review

@mzxchandra

Copy link
Copy Markdown
Contributor Author

@cubic review

@cubic-dev-ai

cubic-dev-ai Bot commented Sep 1, 2026

Copy link
Copy Markdown

@cubic review

@mzxchandra I have started the AI code review. It will take a few minutes to complete.

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All reported issues were addressed across 15 files

Heads up: you’re close to your included review allowance. Set a flex budget so reviews don’t pause.

Fix all with cubic | Re-trigger cubic

Comment thread apps/sim/lib/table/llm/enrichment.ts Outdated
…richment

# Conflicts:
#	apps/sim/tools/generated/tool-metadata.ts
Review finding. Instruction 5 hardcoded `{"field":"name",...}` while every
other example in the block is derived from the table's own columns. An
unknown field is rejected outright — `Unknown filter column "name". It is
not a column on this table.` — so on a table without that column the
example invited the model to write a query that cannot run.

Derived from a real text column now, and dropped entirely when the table
has none rather than falling back to a placeholder.
@mzxchandra

Copy link
Copy Markdown
Contributor Author

@greptile review

@mzxchandra

Copy link
Copy Markdown
Contributor Author

@cubic review

@cubic-dev-ai

cubic-dev-ai Bot commented Sep 1, 2026

Copy link
Copy Markdown

@cubic review

@mzxchandra I have started the AI code review. It will take a few minutes to complete.

The amber legacy badge and its click-to-upgrade action already mark a
sunset block in the UI, so the suffix restated it — a placed v1 block
rendered a "Table (Legacy)" type tag next to a "legacy" badge saying the
same thing.

The four earlier cutovers (gmail, github, confluence, cursor) carry the
suffix, but the most recent one does not: the slack_v2 GA left v1 named
"Slack" and leaned on the badge. Following that.

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All reported issues were addressed across 15 files

Heads up: you’re close to your included review allowance. Set a flex budget so reviews don’t pause.

Fix all with cubic | Re-trigger cubic

Comment thread apps/sim/lib/table/llm/enrichment.ts Outdated
Comment thread apps/sim/lib/table/llm/enrichment.ts Outdated
…ional

Review findings, both in the instruction text.

The wildcard rule named only like/ilike, but nlike and nilike route through
the same `buildPatternClause`. That matters more than an omission: the
builder ESCAPES `%` before translating `*`, so a model reaching for `%` on a
negative pattern gets a literal-percent match and silently wrong rows rather
than an error. The rule now names all four and says never to use `%`.

Instructions 1 and 11 together pushed the model to invent a predicate for a
question that carries no condition — "the 5 most recent rows" is answered
with order and limit, and v2 accepts an omitted filter. Both now say so.
@mzxchandra

Copy link
Copy Markdown
Contributor Author

@greptile review

@mzxchandra

Copy link
Copy Markdown
Contributor Author

@cubic review

@cubic-dev-ai

cubic-dev-ai Bot commented Sep 1, 2026

Copy link
Copy Markdown

@cubic review

@mzxchandra I have started the AI code review. It will take a few minutes to complete.

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All reported issues were addressed

Heads up: you’re close to your included review allowance. Set a flex budget so reviews don’t pause.

Fix all with cubic | Re-trigger cubic

Comment thread apps/sim/lib/table/llm/enrichment.ts Outdated
Comment thread apps/sim/lib/table/llm/enrichment.ts Outdated
Co-authored-by: cubic-dev-ai[bot] <191113872+cubic-dev-ai[bot]@users.noreply.github.com>
Co-authored-by: cubic-dev-ai[bot] <191113872+cubic-dev-ai[bot]@users.noreply.github.com>
feat(tables): GA table_v2 and mark the v1 Table block legacy
@mzxchandra
mzxchandra merged commit 8430f0f into staging Sep 1, 2026
25 checks passed
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