Emit items
An item is a typed artifact that lives in a conversation transcript: a rendered document, a
chart, a structured result a human should see. This guide covers defining an item type,
controlling whether the model sees it, and emitting one from an action via context.items.
See Primitives for how items relate to the other three primitives.
Define an item type
Section titled “Define an item type”Use defineItemType. An item type needs a stable id, a human-readable name, a data
schema, and optionally an agent block that controls how — or whether — the item reaches
the model.
import { defineItemType, z } from '@grundlag/core';
const chart = defineItemType({ id: 'chart', name: 'Chart', description: 'A chart rendered for the user to view.', data: z.object({ title: z.string(), series: z.array(z.number()), }), agent: { render: (data) => `Chart "${data.title}" with ${data.series.length} points`, },});z is re-exported from the core package, so you do not need a separate zod import.
Presentation-only versus model-visible
Section titled “Presentation-only versus model-visible”The agent block decides what, if anything, the model sees:
- Omit
agententirely to make the item presentation-only. It stays in the transcript for hosts and UIs to render, but never enters the model’s context. Use this for pure UI artifacts — a widget, an image, a chart the user looks at but the model need not reason over. - Provide
agent.renderto control the text the model sees. Return a compact summary (as above) so the model gets the gist without the full payload. Returnundefinedfromrenderto keep a specific item out of context even though the type is exposed. - Provide
agentbut omitrenderto fall back to the default: a name-labelled JSON dump of the item’sdata.
This split lets one artifact serve both audiences: a rich rendering for the human, a terse line for the model.
Emit from an action
Section titled “Emit from an action”Inside an action running in a conversation, context.items is an ActionItems handle. It
is only present in a conversation context — when the action is invoked directly or from
a sandbox script, items is undefined, so guard for it.
import { defineAction, z } from '@grundlag/core';
const renderRevenueChart = defineAction({ id: 'renderRevenueChart', name: 'Render revenue chart', description: 'Produces a revenue chart for the current conversation.', input: z.object({ month: z.string() }), output: z.object({ points: z.number() }), execute: async ({ input, items }) => { const series = await loadRevenue(input.month);
// Only available inside a conversation. if (items) { const previous = items.latest('chart', input.month); items.emit({ itemType: 'chart', key: input.month, data: { title: `Revenue — ${input.month}`, series }, }); }
return { points: series.length }; },});items.emit({ itemType, key?, data }) appends a new item to the transcript and returns it.
The data must satisfy the item type’s data schema. The optional key gives the item an
identity within its type: emitting again with the same itemType and key appends a new
version of the same logical item rather than an unrelated one.
items.latest(itemType, key?) resolves the current version of that identity, or
undefined if none has been emitted yet — useful for reading back what a prior step
produced before emitting an update.
For the item type to be usable it must be registered on the ProviderInstance:
provider.items.register(chart);Guidelines
Section titled “Guidelines”- Guard
context.items. It isundefinedoutside a conversation — check before emitting. - Omit
agentfor pure UI artifacts. Presentation-only items stay out of the model’s context. - Keep
agent.rendercompact. Return a summary line, not the whole payload; returnundefinedto drop a specific item. - Use
keyfor identity. Re-emit with the sameitemType+keyto version one logical artifact rather than spawning duplicates. - Match the
dataschema.emitdata must satisfy the item type’s schema, just like an action’s output.