The two-file surface
A host’s entire server wiring is two files, both scaffolded byvendo init:
vendo/registry.tsx: the component registry. One object keyed by component name; each entry holds the real component reference, a description the model reads, and an optional zod props schema. The same object serves both sides:createVendoreads it ascatalog(data fields only) and<VendoRoot components={registry}>reads the component references. There is no second map to keep in sync.- The composition: one
createVendo({...})call. On Next.js it lives in the catch-all routeapp/api/vendo/[...vendo]/route.ts; on Express invendo/server.ts(server.mjswhen the host has no TypeScript), mounted withapp.use("/api/vendo", mountVendo()).
Tools come from extraction, not from you
vendo sync (run by init, and hooked into predev/prebuild) statically
extracts the host API (OpenAPI operations, tRPC procedures, GraphQL
operations, Next.js server actions, and a route scan) into
.vendo/tools.json. Only tools in that file exist.
The rules:
- Never hand-edit
.vendo/tools.json. It is regenerated. Durable corrections go in.vendo/overrides.json, which wins field by field and is never touched by sync. - Never invent a tool. If an endpoint was not extracted, fix the source it
is extracted from (or its OpenAPI spec) and re-run
npx vendo sync; do not add entries by hand. - Unclassifiable routes are extracted
disabledwith a note. Enable them deliberately inoverrides.jsonafter review, never blanket-enable.
What makes a good tool
- A task-oriented description, written for the model: what the tool does for the user, when to reach for it, not a restatement of the route path.
- An honest risk label.
readauto-runs,writeanddestructiveare policy-gated (default policy: destructive asks first). Risk can be raised in overrides, never lowered. critical: trueon irreversible actions (delete, send, cancel) so policy always asks before running them.
npx vendo init --agent prints a read-only JSON plan
whose aiPolish object carries instructions, the draft schema, and the apply
command. Read the codebase against the instructions, write the draft, then:
overrides.json and a hand-written brief.md
always win. Guard refusals print per entry while the rest applies.
The catalog contract (anti-prop-invention)
The registry is a closed contract: only components registered invendo/registry.tsx exist, and only with the props their schemas declare.
Generated views validate against those schemas; an invented prop name fails
instead of rendering. Inventing prop names is the single most observed agent
failure: models reach for generic names like data, rows, items,
labelKey, or onPress that the real component never had.
When you register a host component:
- Copy prop names from the component’s source, never from convention. If
SpendingDonuttakesslices, the schema saysslices, notdata. - Give it a zod props schema. A schema-less entry is legal (the model infers props), but a schema is what makes prop invention impossible; prefer it whenever the component has typed props.
- Schema only what the model should control. Data-shaped props belong in the schema; internal callbacks and render props do not.
- Write the description for selection: what the component shows and when to use it, e.g. “Spending by category. Use for where-did-my-money-go requests.”
tools.json. If something you need is
missing, register or extract it; do not name it into existence.
Full extraction reference: Tools from your API.
Registry details: Quickstart.