Skip to content

Tool-UI Schemas

How the Analyst Workbench keeps backend Python and frontend TypeScript in sync for every tool call surfaced in chat. Read this before adding a tool, changing a Result class, or editing an arg schema.

What this is

  • Backend Pydantic models (per-cluster Result classes) are the single source of truth for every tool's output shape.
  • A build-time generator emits Zod schemas under ui/lib/tool-schemas/<cluster>/<tool>.ts from those Pydantic models. The generated files are checked into git; the UI imports them directly.
  • Each cluster consumer (ui/components/tool-ui/tools/<cluster>.tsx) calls Schema.safeParse(result) at the top of every render branch. On failure the consumer renders <ToolErrorCard> with the Zod issue summary instead of crashing or dumping JSON.
  • This is enforced by ADR-012: no raw JSON in chat, every visible tool needs a registered component.

When to regenerate

Regenerate and commit the Zod schemas whenever you:

  1. Add a new tool to any cluster.
  2. Change a field on a Result class (added, removed, renamed, retyped).
  3. Change a tool's arg schema (Pydantic input model).
  4. Edit a sidecar override (interrupt_args, needs_data_proxy).

The CI gate (below) will fail your PR if you forget. Always commit the regenerated Zod files in the same commit as the backend change so the two halves of the contract land together.

Commands

# Regenerate Zod schemas from the Pydantic source-of-truth
npm run gen:tool-schemas

# Verify render-success + error tests still pass
npm test

# Full strict lint (matches CI)
npm run lint:strict

gen:tool-schemas runs a Python sidecar (ui/scripts/dump-tool-schemas.py) in-process to read the authoritative Pydantic schemas, then a TypeScript walker (ui/scripts/gen-tool-schemas.ts) inlines $defs references and runs json-schema-to-zod over each tool.

CI gate

Every UI PR runs:

npm run gen:tool-schemas
git diff --exit-code ui/lib/tool-schemas/

Failure means drift between backend Pydantic and committed Zod. Fix it:

  1. Run npm run gen:tool-schemas locally.
  2. git add ui/lib/tool-schemas/ and commit alongside the backend edit.
  3. Push.

There is no third option. A merged PR with stale Zod schemas would surface as runtime safeParse failures — degraded UX (every affected tool call renders <ToolErrorCard>) but never raw JSON or a crash.

Cluster status

All six clusters shipped Zod schemas during the 2026-04 rollout:

Cluster Tools Substrate Special mechanisms
decision 7 FastMCP (in-process) flat Pydantic Results
visualization 5 Workbench LangGraph @workbench_tool decorator
reasoning 8 Workbench LangGraph is_interrupt=True on HITL tools
case 16 Workbench LangGraph base-shape inheritance, list-wrap
workspace 5 Workbench LangGraph is_interrupt=True, interrupt_args
kg 10 Workbench LangGraph is_interrupt=True, extra="allow", needs_data_proxy

Mechanisms cheatsheet

Four mechanisms cover every tool surface; pick whichever the tool needs.

  • is_interrupt=True — marks a tool as Human-In-The-Loop. The agent pauses execution and waits for the analyst to resume with a payload. Use for any tool that requires explicit human approval before continuing (ask_analyst, present_section, workspace edits, the KG search wizard).
  • interrupt_args — sidecar override that replaces the args schema for an interrupt tool when the runtime payload differs from the static Pydantic input model. Lives next to the dumped schema and is applied by gen-tool-schemas.ts before Zod conversion.
  • needs_data_proxy — sidecar flag declaring that the tool calls a data connector at runtime. The UI wires the proxy plumbing accordingly. Used by KG tools whose payloads route through the data proxy.
  • extra="allow" — Pydantic ConfigDict setting that allows extra fields in the parsed result. Used by KG standard tools because Graphiti-fork responses evolve externally; combined with the CI drift gate this is safe (drift is caught by safeParse at render time).

Pointers

  • ADR-012 (decision + rollout status): docs/decisions/ADR-012-assistant-ui-tool-ui.md
  • Internal pipeline diagram + per-cluster failure-mode table: docs/how-it-works/workbench-tool-ui.md
  • Generator scripts:
  • ui/scripts/gen-tool-schemas.ts
  • ui/scripts/dump-tool-schemas.py
  • Cluster consumers (where safeParse lives): ui/components/tool-ui/tools/<cluster>.tsx
  • Render-success and error tests: ui/components/tool-ui/tools/__tests__/*.render.test.tsx
  • Generated Zod schemas (never hand-edit): ui/lib/tool-schemas/<cluster>/<tool>.ts