Skip to content

Primitives

A provider exposes its capabilities through four primitives. Choosing the right one for a given capability is the most important modelling decision you will make, so this page explains what each is for and how they differ.

An action is a callable capability with a typed input and a typed output. It is the primary primitive: agents call actions, the sandbox calls actions, and the HTTP API exposes actions.

import { defineAction, z } from '@grundlag/core';
const sendMessage = defineAction({
id: 'sendMessage',
name: 'Send message',
description: 'Sends a message to a recipient.',
input: z.object({ recipientId: z.string(), body: z.string().min(1) }),
output: z.object({ messageId: z.string() }),
execute: async ({ input }) => ({ messageId: await deliver(input) }),
});

Reach for an action when the caller wants to cause an effect or compute a result. If the verb in the sentence is “send”, “create”, “summarise”, “set”, or “fetch a specific thing”, it is an action. See Define actions.

An entity type describes a searchable kind of resource the provider owns: contacts, files, calendar events, notes. It pairs a data schema (which must include a string id) with two operations — search (paginated, cursor-based) and get (by id).

import { defineEntityType, z } from '@grundlag/core';
const contact = defineEntityType({
id: 'contact',
name: 'Contact',
description: 'A person in the address book.',
data: z.object({ id: z.string(), displayName: z.string() }),
searchInput: z.object({ query: z.string().min(1) }),
search: async ({ input, limit, cursor }) => findContacts(input.query, limit, cursor),
get: async ({ id }) => getContact(id),
});

Reach for an entity when the caller needs to discover, select, or retrieve a record it can later reference by id. Entities are nouns; actions are verbs. A calendar provider exposes events as an entity (so an agent can find “my meeting with Sam”) and “create event” as an action. See Expose entities.

An event is a signal a provider emits that automations subscribe to: a message arrived, a device changed state. An event carries a typed payload, and a filter / check pair so a subscriber can express which occurrences it cares about.

import { defineEvent, z } from '@grundlag/core';
const messageReceived = defineEvent({
id: 'messageReceived',
name: 'Message received',
description: 'Emitted when a new message arrives.',
payload: z.object({ messageId: z.string(), senderId: z.string() }),
filter: z.object({ senderId: z.string().optional() }),
check: ({ payload, input }) => input.senderId === undefined || input.senderId === payload.senderId,
});

Reach for an event when the provider needs to push a notification that something changed, rather than waiting to be called. Payloads should carry enough ids for the subscriber to decide what to do next — prefer ids over large embedded objects. See Publish events.

Items — put a typed artifact in the transcript

Section titled “Items — put a typed artifact in the transcript”

An item is a typed artifact that lives in a conversation transcript: a rendered document, a chart, a UI widget, a structured result. An item type controls whether and how its data is shown to the model.

import { defineItemType, z } from '@grundlag/core';
const chart = defineItemType({
id: 'chart',
name: 'Chart',
data: z.object({ title: z.string(), series: z.array(z.number()) }),
// Omit `agent` entirely to keep an item presentation-only (UI sees it,
// the model does not). Provide `agent.render` to control what the model sees.
agent: { render: (data) => `Chart "${data.title}" with ${data.series.length} points` },
});

Reach for an item when a step produces output a human should see in the transcript, that is not itself a tool result the model must reason over. Items are how a provider or agent contributes rich, typed content to the conversation. See Emit items.

You want to… Use
Cause an effect or return a computed result Action
Let a caller search for and fetch a resource by id Entity
Notify subscribers that something happened Event
Add a typed artifact to the conversation transcript Item

Actions, entities, and events are registered on a ProviderInstance and are the provider’s public surface. Storage is not a primitive — neither databases (see Persist state with a database) nor buckets (see Store blobs in a bucket). Both are private implementation detail, and you expose behaviour over stored state through the primitives above — never directly.