Skip to content

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:

competitor: {
  strict: true
  fields: { name: { type: text, required: true } }
}

2. Tag nodes with an objectType

A node joins a collection by setting objectType to the collection name. This is orthogonal to nodeTypenodeType 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:

{ "tool": "hadron_validate", "memoryUrn": "acme.com:market" }

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