Skip to content

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.

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.

The agent block decides what, if anything, the model sees:

  • Omit agent entirely 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.render to control the text the model sees. Return a compact summary (as above) so the model gets the gist without the full payload. Return undefined from render to keep a specific item out of context even though the type is exposed.
  • Provide agent but omit render to fall back to the default: a name-labelled JSON dump of the item’s data.

This split lets one artifact serve both audiences: a rich rendering for the human, a terse line for the model.

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);
  • Guard context.items. It is undefined outside a conversation — check before emitting.
  • Omit agent for pure UI artifacts. Presentation-only items stay out of the model’s context.
  • Keep agent.render compact. Return a summary line, not the whole payload; return undefined to drop a specific item.
  • Use key for identity. Re-emit with the same itemType + key to version one logical artifact rather than spawning duplicates.
  • Match the data schema. emit data must satisfy the item type’s schema, just like an action’s output.