Skip to content

How model context composes

assistant-ui keeps one registry of what the model should know and can call: the model context. Components add to it while they are mounted and leave when they unmount. Several providers compose: their system texts concatenate and their tool sets merge. super-assist keeps no registry of its own. Everything it lends goes through this one.

Piece Lends How
AssistedProvider The universal instructions useAssistantInstructions
AssistedProvider offer_choices, and go_to_page and show_links with destinations One registration per tool
The open AssistedPage Its note useAssistantContext, read at send time
The open AssistedPage get_page_content, when it has content useAssistedTool under the page
AssistedAction One tool per name on its page useAssistedTool’s path, grouped by name
AssistedInput, AssistedField One tool per name on its page, and the note’s fields line useAssistedTool’s path, grouped by name
useAssistedTool One tool aui.modelContext.register

At send time the model context is read once. The backend receives the concatenated system text and the merged tools.

Pages can mount inside each other, like a dialog over a list. The page the user has open is the last one mounted, ordered by when each first rendered. Only that page lends its note and get_page_content. A dialog that mounts becomes the open page. When it closes, the page under it is open again.

A page under the open one keeps its tools and actions lent, but no note names them. The model can still call them. The note tells it which ones belong to what the user sees.

useAssistedTool looks for the nearest AssistedPage. Under one, the tool joins that page’s set, and the note lists it. Outside any page, the tool is global: lent on every page and listed in no note. Moving a hook in the tree is how a host scopes a tool.

Actions work the same way, with one more rule. Actions with the same name on one page become one tool. Each row’s id joins an enum, and the description pairs each id with its label. A table of fifty rows lends one ship_order, not fifty tools.

The model’s view lags the page. It may call a tool from the last request after the page that lent it has gone. A call that finds nothing would stall the turn. So when a tool leaves, the provider keeps a disabled stand-in under its name. assistant-ui leaves disabled tools out of what the backend sees, but still runs them for a call that arrives. The stand-in answers “The page no longer offers …” with the note of the page open now.

Answers that may follow a change of page carry the note of the page open now: a press, go_to_page, and the stand-in for a tool that left. The universal instructions tell the model that such an answer is newer than the note sent with the message.

A component re-renders often. Lending a tool again on each render would churn the registry. useAssistedTool lends once, and again only when the description or the parameters’ JSON Schema changes. execute reads a ref to the latest version, so it always sees current state without a new registration.