Skip to content

Object store API

The object store is the legible, record-oriented surface over structured storage. An object is a node — it's a projection, not a separate store — presented as a flat record { id, type, ...fields }. It's the primary path for working with typed records; drop to the node API only when you need the graph (edges, non-record nodes, loc hierarchy).

This page is the API contract on GraphQL and MCP. For a task walkthrough, see Store and query records with the object store; for the CLI, Work with objects from the CLI.

Spec: cor:api:170:01 identity/projection, :02 write semantics, :03 query. Data model: cor:dmo:020:05.

The flat projection

An object is { id, type, ...fields }:

  • id — the object's stable handle (the node id). Use it to get, update, and delete.
  • type — the collection (the node's objectType), e.g. competitor.
  • ...fields — the node's typed properties, lifted to top-level keys.

id and type are the reserved envelope — they can't be used as field names. The node's loc and name are auto-derived and hidden: loc is <type>:<key ?? generated-id>. An optional natural key (a single loc segment, no :) gives a human-meaningful id; omit it for a server-generated one. name overrides the derived node name.

On a memory with a declared schema for the collection, writes are validated against it. Reads and queries work on encrypted memories (properties is plaintext); writes are session-gated like every write.

Operations

Operation GraphQL MCP
Create createObject hadron_create_object
Read one object hadron_get_object
Update (merge) updateObject hadron_update_object
Delete deleteObject hadron_delete_object
Query a collection findObjects hadron_find_objects

Create

createObject(
  memoryRef: String!    # memory ID or fully-qualified URN (org::memory)
  type: String!         # the collection, e.g. "competitor"
  fields: JSON!         # the flat, typed fields
  key: String           # optional natural id (single segment, no ":")
  name: String          # optional node-name override
): JSON!                # the flat object { id, type, ...fields }

fields is validated against the collection schema when one is declared — a missing required field, an out-of-enum value, or a wrong type is rejected before anything is written. id/type can't be field names.

mutation {
  createObject(
    memoryRef: "acme.com::market"
    type: "competitor"
    fields: { name: "Letta", stage: "series-a", fundingUsd: 12000000 }
    key: "letta"
  )
}
{
  "tool": "hadron_create_object",
  "memoryUrn": "acme.com::market",
  "type": "competitor",
  "fields": { "name": "Letta", "stage": "series-a", "fundingUsd": 12000000 },
  "key": "letta"
}

Both return the flat record:

{ "id": "019f7d…", "type": "competitor", "name": "Letta", "stage": "series-a", "fundingUsd": 12000000 }

Read one

object(ref: ID!): JSON      # { id, type, ...fields }, or null if not found

ref is the object id or a fully-qualified node URN. GraphQL returns null when the ref names nothing readable, or a node that isn't an object; hadron_get_object returns Object not found. in the same cases.

Update — a shallow merge

updateObject(
  ref: ID!              # object id or node URN
  fields: JSON!         # fields to merge in
  reason: String        # optional; recorded in revision history
): JSON!                # the updated flat object

updateObject performs an atomic, server-side shallow merge: the patch wins on a key collision, unmentioned fields are preserved, and concurrent updates to distinct fields don't clobber each other. The merged result is re-validated against the schema, so a merge can't leave a record non-conforming.

Merge, not replace

This is the one semantic that differs from the node layer. updateObject / hadron_update_object merge; updateNode / hadron_update_node replace the whole properties bag. Choose the object op for patch semantics, the node op for wholesale replacement.

Delete

deleteObject(ref: ID!, hard: Boolean): Boolean!

Soft by default — the object disappears from reads and queries but the row is retained. hard: true removes the row permanently. Deletion is non-recursive: an object is a single record.

Query a collection

findObjects(
  memoryRef: String!    # memory ID or fully-qualified URN
  type: String!         # the collection to query
  match: JSON           # equality shorthand { field: value, ... }
  where: JSON           # full structured predicate (ANDed with match)
  sort: JSON            # single-field { "<field>": "asc" | "desc" }
  limit: Int
  offset: Int
): ObjectList!          # { objects: [JSON!]!, total: Int }

Three filters, all optional:

  • match — an equality shorthand. { field: value, … } desugars to an AND of eq predicates, with each field's cast inferred from the schema.
  • where — the full structured predicate, the same grammar as NodeWhereInput. AND-combined with match.
  • sort — a single-field shorthand, { "<field>": "asc" | "desc" }, desugared to a property-path sort. Missing/unparseable values sort last.

Returns an ObjectList{ objects, total } — where total is the full match count, for paging with limit/offset.

query {
  findObjects(
    memoryRef: "acme.com::market"
    type: "competitor"
    match: { stage: "series-a" }
    where: { path: ["fundingUsd"], as: number, gt: 10000000 }
    sort: { fundingUsd: "desc" }
  ) {
    objects
    total
  }
}
{
  "tool": "hadron_find_objects",
  "memoryUrn": "acme.com::market",
  "type": "competitor",
  "match": { "stage": "series-a" },
  "where": { "path": ["fundingUsd"], "as": "number", "gt": 10000000 },
  "sort": { "fundingUsd": "desc" }
}

Object vs. node — which surface?

Reach for the object store when… Drop to the node API when…
You want flat typed records — CRUD by field, query by value. You need the graph: edges, loc hierarchy, non-record nodes.
You want merge-on-update. You want replace-on-update (updateNode / --properties).
The record is self-contained. The node carries content/markdown, edges, or children.

Both write to the same substrate — an object is a node with an objectType, so a record created with createObject is visible to findNodes / hadron_find_nodes with objectType: competitor, and vice versa.