Give a memory a structured schema¶
By default a memory is unstructured — nodes are free-form and you retrieve them by meaning and text. When a memory holds many records of the same shape (competitors, invoices, incidents), you can declare a schema: named collections with typed fields. Writes into a collection are then validated against it, so a malformed record is rejected at write time.
This guide declares a schema, tags nodes with an objectType, and watches a
bad write bounce. For the conceptual model see
Structured vs. unstructured memory;
for the exact contract see
Structured storage and queries.
Before you start¶
- You need write access to the memory (Admin or Contributor on its organization).
- Schema definition is GraphQL / portal — the MCP tools and the CLI can't
declare a schema (they can write
objectType, see below).
1. Declare the schema¶
A schema is a JSON object with one key, objectTypes, mapping each collection
name to its field definitions. Set it with updateMemory(schema:):
mutation DefineCompetitorSchema {
updateMemory(
id: "<memoryId>"
schema: {
objectTypes: {
competitor: {
description: "A company we track in the market."
fields: {
name: { type: text, required: true }
stage: { type: enum, values: ["seed", "series-a", "series-b"] }
fundingUsd: { type: number }
lastRoundAt: { type: datetime }
isPublic: { type: boolean }
}
}
}
}
) {
id
schema
}
}
Field types are text, number, datetime, boolean, and enum. An
enum field must carry a non-empty values array; other types must not.
Mark a field required: true to force it on every write. Collection and field
names must match [A-Za-z0-9_-]{1,64}.
A malformed schema (unknown key, an enum with no values, a bad name) is
rejected as BAD_USER_INPUT — nothing is saved until the schema is
well-formed.
The declared type is the query cast
A field's type doubles as its cast for querying
and sorting. Declare fundingUsd as number
and you query and sort it with as: number — there's no second mapping to
maintain.
Strict collections¶
By default a collection member may carry extra, undeclared properties. Set
strict: true to reject any property key that isn't declared:
2. Tag nodes with an objectType¶
A node joins a collection by setting objectType to the collection name. This
is orthogonal to nodeType — nodeType is the node's retrieval role
(info, record, …); objectType is which collection it's a row in. A node
with no objectType is an ordinary, uncollected node.
You can write objectType from GraphQL, the portal, or the MCP create/update
tools. From MCP:
{
"tool": "hadron_create_node",
"loc": "competitors:acme",
"name": "Acme Corp",
"objectType": "competitor",
"properties": {
"name": "Acme Corp",
"stage": "series-a",
"fundingUsd": 12000000,
"lastRoundAt": "2026-05-01T00:00:00Z",
"isPublic": false
}
}
On a schema-governed memory the write is validated: objectType must name a
declared collection, every required field must be present, and each present
field's value must coerce to its declared type. Note the properties bag
holds the typed fields — that's the JSONB the schema governs and that where
queries.
3. Watch a bad write get rejected¶
Try to write a record that violates the schema — an unknown collection, a
missing required field, or a wrong type. For example, a stage outside the
enum:
{
"tool": "hadron_create_node",
"loc": "competitors:globex",
"name": "Globex",
"objectType": "competitor",
"properties": { "name": "Globex", "stage": "pre-seed" }
}
The write is rejected — on MCP as a Schema violation: tool error, on
GraphQL as BAD_USER_INPUT:
Schema violation: objectType "competitor": field "stage" must be enum (one of: seed, series-a, series-b)
A missing required field (name omitted) fails the same way, as does a value
that can't coerce to its type (fundingUsd: "a lot").
Schema-on-write is not retroactive¶
Adding or tightening a schema never invalidates nodes that already exist. Enforcement applies only to writes from that point on. This makes "turn on a schema" safe on a memory that already holds data — nothing breaks.
To find rows that pre-date the schema (or were written through a path outside the write seam), run the conformance audit, which reports violators without changing anything:
Non-conforming nodes appear in the report; fix them with an hadron_update_node
that supplies the missing/typed fields. See
the audit section.
Next steps¶
- Query nodes by their properties — filter the
collection with the
wherepredicate. - Sort results by a property value — order by a
field like
fundingUsd. - Build a market-research agent with structured memory — this schema in a full worked example.