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 toget,update, anddelete.type— the collection (the node'sobjectType), e.g.competitor....fields— the node's typedproperties, 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.
Both return the flat record:
{ "id": "019f7d…", "type": "competitor", "name": "Letta", "stage": "series-a", "fundingUsd": 12000000 }
Read one¶
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¶
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 ofeqpredicates, with each field's cast inferred from the schema.where— the full structured predicate, the same grammar asNodeWhereInput. AND-combined withmatch.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.
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.
Related¶
- Structured storage and queries — the underlying
substrate:
Memory.schema,objectType, thewheregrammar,sortProperty. - Store and query records with the object store — the GraphQL/MCP walkthrough.
- Work with objects from the CLI — the same
operations as
hadron objectcommands. - MCP tools · GraphQL API — the generated op reference.