Primitives
ADK threads eight primitives through every turn, and nothing else gets a free pass. If the loop needs to know about a message, memory, tool call, retrieved document, binary asset, or reasoning trace, it enters as one of these shapes or it does not enter.
This is opinionated on purpose. A vague Record<string, unknown> does not survive contact with three providers, four storage layers, and a year of feature pressure. A Message with a typed role, a Tokenizable body, and a required Identity does. The primitives are small because the library is small about what counts.
Four rules every primitive obeys
- Validation runs at construction. Pass bad input, get
E_INVALID_INITIAL_*_VALUEbefore the instance exists. There is no partially validMemory, no half-builtToolCallwaiting to be filled in. - Instances are immutable. You don't edit fields on the object you already have; you call the matching
ctx.mutate*persistence callback (mutateMessage,TurnContext.mutateMemory, and the rest), which delegates to the consumer-implemented store. Whether subsequent reads see the new instance is up to that store — the ADK does not transparently swap the instance behind the same id. The instance you were already holding stays valid for the read you were doing; constructing a new instance with the updated fields and persisting it is the canonical pattern. - Text fields store
Tokenizable, notstring. Strings work at construction; the constructor wraps them. After that, you have aTokenizablethat can.estimateTokens(encoding)on demand. This is the difference between guessing your context budget and knowing it. - Date fields accept many input types and store Luxon
DateTime. The full list of accepted inputs is in the API reference. The API reference is the property catalogue. This page is the map of why each primitive exists and what mistake it prevents.
Required fields are required
There is no "we'll fill it in later." Every primitive's constructor checks the full set of required fields up front and throws E_INVALID_INITIAL_*_VALUE if anything is missing or wrong. You do not get a half-built Memory waiting on a score, a Retrievable waiting on a tier, or a Message waiting on an identity. The instance either exists with every contract satisfied, or it doesn't exist at all.
How this page is ordered
Foundation first, then the dialogue surface, then the contents of a turn, then what happens during the dispatch. Tokenizable is the wrapper every other primitive's text field stores, so it comes first. Identity is the speaker label a Message carries, so it comes next. Message is the visible dialogue surface, and Media is the binary peer that rides on Message.attachments (and later, on ToolCall.results) — introduced here so every later reference to attachments points backwards instead of forwards. Memory is what an agent recalls from previous turns; Retrievable is what it pulls in fresh for this one. Thought and ToolCall are what happens during the dispatch — reasoning and action — and ToolCall.results is the second surface that can carry a Media.
Tokenizable
The string wrapper every text-bearing field on every other primitive stores. estimateTokens(encoding) is exact for the encodings whose tokenizers are publicly available and a conservative heuristic for everything else, so the budget logic upstream can treat token cost as a first-class property of content.
→ Continue reading: Tokenizable
Identity
The two-view bridge between your application's notion of who a participant is (Identity.identifier) and the model's notion of who is speaking (Identity.representation, as Tokenizable). Two views of one entity, kept on the same record so they cannot drift.
→ Continue reading: Identity
Message
One unit of dialogue, attributed to a speaker, shaped for the model's next read. Two roles only — 'user' and 'assistant'. System content, tool results, reasoning, retrieved docs, and durable memories each live in their own primitive.
→ Continue reading: Message
Media
The typed handle for a binary asset — image, audio, video, document — that rides on Message.attachments and ToolCall.results. Dual-peer to Tokenizable (silo) and SpooledArtifact (handle). Two-axis trust model (Media.trustTier + Media.modalityHazard), both required.
→ Continue reading: Media
Memory
Long-term memory: what was learned in previous conversations that should still inform this one. Carries Memory.confidence and Memory.importance scores in [0,1] — required, no default, and the retrieval middleware (not the storage layer) is what decides them.
→ Continue reading: Memory
Retrievable
Content the agent pulled in fresh for this turn — RAG chunks, web results, KB snippets. The required field is Retrievable.trustTier, the single most opinionated thing in the entire primitives set: no 'unknown', no auto-classification, no safe default.
→ Continue reading: Retrievable
Thought
Reasoning, kept deliberately separate from dialogue. Text plus an optional vendor-shaped Thought.payload — when the payload is set, a Thought.replayCompatibility tag is required so a future executor can recognise the wire shape.
→ Continue reading: Thought
ToolCall
One resolved tool invocation: tool name, validated args, ToolCall.results (a SpooledArtifact, a Tokenizable for ArtifactTool calls, or a Media), and a stable ToolCall.checksum the rest of the loop uses to correlate. The ToolCall.inline flag is the rendering hint that travels with the call.
→ Continue reading: ToolCall
What is not a primitive
The system prompt and standing instructions are Tokenizable, live on TurnContext, and are not primitive classes. An executor that opts into developer-policy framing renders them through its own pattern (see Trust tiers → Envelopes), but the ADK treats them as read-only inputs to prompt assembly — never stored, never mutated, never re-emitted as records. Wrapping them in a class would imply a lifecycle they do not have.