Skip to main content
An agent that does not know who is asking, or what page they are on, has to ask. Vendo fills two prompt blocks so it does not have to:
  • [User] — facts your server asserts about the signed-in person (plan, role, tenure, whatever you choose). Refreshed on every request.
  • [Situation] — what their screen shows right now, plus any structured data you publish. Sent with the message, used for that turn only.
With both, “move $200 to savings” and “explain this charge” become answerable without a round of questions.
This page describes a release later than 0.7.0. On 0.7.0 the channel is inert: facts type-checks and is silently dropped, and useVendoContext is still the zero-argument provider-reading hook, so the call below fails to compile with TS2554: Expected 0 arguments, but got 1. That error means your installed version predates this feature — upgrade before wiring any of it.

Assert facts about the user

Facts come from the user resolver inside an auth preset. Add a facts object beside the display and email you already return.
1

Resolve identity through a preset

Facts are a preset-only channel: pass auth, not the per-seam principal/actAs/oauth trio. The preset already decodes the session once per request, and facts ride that same decode, so they cost no second verify. If vendo init detected no auth provider it wrote a principal: demo line into your composition — delete it, or createVendo throws VendoError("validation") for supplying both.
2

Return facts from the user resolver

Values are any JSON. Returning null still means “subject unknown to the host” — the principal resolves to anonymous and no facts are asserted. Edit the auth: line in the composition init already generated; keep its other keys, such as catalog.
That renders in every turn’s system prompt, one key: value per line:
A live host wiring this is examples/demo-bank/src/vendo/server.ts (Maple).
Facts are sent to the model verbatim. Put nothing in them you would not paste into a chat window: no tokens, no API keys, no internal identifiers you rely on staying private.

The screen is already being sent

You do not wire this. On every send, the widget snapshots the visible host page and attaches it to the request as context. The snapshot is the page’s accessibility tree — the URL and title, then headings, landmarks, links, buttons, table contents, form values, and control states (checked, selected, disabled). For a transfer page, the agent receives roughly this:
The block is labeled as observation, so the model treats page text as evidence about the user’s situation rather than as instructions addressed to it.

Publish your own data

Anything the page knows but does not display — a cart total, a selected row id, a wizard step — goes through useVendoContext. It merges into the same [Situation] block and retires automatically when the component unmounts, so the agent never sees a screen the user has left.
Several mounted callers coexist and merge; on a repeated key, the later one wins. The hook returns nothing and does not need to sit inside the provider.
It republishes whenever the data object’s identity changes, so an inline literal republishes on every render. Harmless, but wrap it in useMemo if the object is expensive to build.
The hook that reads everything <VendoRoot> supplies used to hold this name and is now useVendoProvider; useVendoContext(data) is the host-facing one described here.

Control what the snapshot sees

Exclude one element. data-vendo-ignore drops that element and everything under it. Vendo’s own chrome already carries it, so the widget never snapshots itself.
Turn capture off entirely. Set captureScreen={false} on the provider. Data you publish through useVendoContext still rides; only the page snapshot stops.

What actually reaches the model

  • [User] is server-trust and every turn. It comes from your own resolver on the server; the client cannot set it.
  • Anonymous visitors get no [User] block. A visitor your host does not recognize has no profile to assert, so the facts seam is not even asked. They still send a situation.
  • [Situation] is one turn only, and is never stored. It rides the request onto that turn’s prompt. The next turn on the same thread carries no situation, and nothing situation-shaped is written to the transcript.
  • 8 KB, enforced twice. The client truncates before sending — over budget it retries from <main> alone, then hard-truncates with a …[truncated] marker — and the server re-caps whatever arrives, dropping entries past the budget rather than refusing the turn. A context that is not an object is ignored.
  • Values render as key: value. Non-strings are JSON-encoded; every continuation line of a multi-line value is indented, so nothing in a fact or a snapshot can close its block and impersonate a prompt section.