Build a market-research agent with structured memory¶
A learn-by-doing walkthrough of Hadron's opt-in structured storage. You will stand up a market-research memory that holds competitor records as a typed collection, capture a few of them, then run the payoff query:
Series-A competitors funded in the last 90 days, sorted by funding descending.
By the end you'll have a memory that behaves like a tiny database — declared
columns, WHERE, ORDER BY, and a VALIDATE CONSTRAINT audit — without ever
leaving the graph.
For the mental model behind this, read Structured vs. unstructured memory first (optional). This tutorial cross-references the how-tos where you'd go next for depth.
What you'll build¶
- A
competitorcollection with typed fields:name(text, required),stage(enum),fundingUsd(number),lastRoundAt(datetime). - A handful of conforming competitor objects captured by an agent.
- The payoff query in one
findObjectscall —match(stage) +where(a datetime window) +sort(funding, descending). - A conformance audit pass proving the collection is clean.
Prerequisites¶
- A Hadron memory you can write to, in an organization where you're Admin or
Contributor. A
knowledge-class memory is a good fit for a research corpus. If you're starting from scratch, Getting started sets up an org and an agent. - The Hadron MCP server connected to your coding agent (Claude Code, Cursor, …) so you can create nodes through conversation — see Add Hadron to Claude Code (OAuth).
- Access to the GraphQL API or the
hadronCLI (memory set --schema) for step 1, which declares the schema — the one step the MCP tools don't cover. The capture, query, and audit steps all run over MCP. - 20–30 minutes.
Throughout, replace <memoryId> with your memory's ID and
acme.com:market with your memory's URN.
Step 1 — Declare the competitor schema¶
A schema declares the collections a memory holds and the typed fields each
carries. Define one objectType, competitor, via updateMemory:
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", "series-c"] }
fundingUsd: { type: number }
lastRoundAt: { type: datetime }
}
}
}
}
) {
id
schema
}
}
Each field's type doubles as its query cast later: fundingUsd is a
number, so you'll query and sort it with as: number; lastRoundAt is a
datetime. name is required, so every competitor must have one. From now
on, any node written with objectType: "competitor" is validated against this
shape.
For the full field-type table,
strictcollections, and the well-formedness rules, see Give a memory a structured schema.
Step 2 — Capture competitor records as objects¶
Now capture a few competitors. The friendly way is the object store: each
competitor is a flat record { name, stage, fundingUsd, lastRoundAt } in the
competitor collection — no graph-node ceremony. Ask your coding agent to
create them:
Create these competitors in
acme.com:marketascompetitorobjects:
- Acme Corp — series-a, $12,000,000, last round 2026-05-01
- Globex — series-b, $40,000,000, last round 2026-02-10
- Initech — series-a, $8,000,000, last round 2026-06-20
- Umbrella — seed, $2,000,000, last round 2026-06-28
Under the hood each becomes a hadron_create_object call:
{
"tool": "hadron_create_object",
"memoryUrn": "acme.com:market",
"type": "competitor",
"fields": {
"name": "Acme Corp",
"stage": "series-a",
"fundingUsd": 12000000,
"lastRoundAt": "2026-05-01T00:00:00Z"
},
"key": "acme"
}
It returns the flat record { "id": "…", "type": "competitor", "name": "Acme
Corp", … }. Because the memory now has a schema, each write is validated:
name must be present, stage must be in the enum, and each field must coerce
to its type. If the agent tries stage: "pre-seed" or omits name, the write
is rejected — fix it and retry. This is the guardrail that keeps the collection
clean as it grows.
An object is a node with an
objectType—hadron_create_objectis the record-shaped projection ofhadron_create_node. See Object store API.
Step 3 — Run the payoff query¶
You want series-A competitors whose last round closed in the last 90 days,
sorted by funding descending. With today at 2026-07-18, the cutoff is
2026-04-19. hadron_find_objects expresses the whole sentence in one call —
match for the equality, where for the date window, sort for the order:
{
"tool": "hadron_find_objects",
"memoryUrn": "acme.com:market",
"type": "competitor",
"match": { "stage": "series-a" },
"where": { "path": ["lastRoundAt"], "as": "datetime", "gte": "2026-04-19T00:00:00Z" },
"sort": { "fundingUsd": "desc" }
}
It returns { objects, total } — here two objects: Acme ($12M, series-A,
2026-05-01) ahead of Initech ($8M, series-A, 2026-06-20). Globex is
series-B, so match filters it out; Umbrella is seed. The datetime cast
makes the date comparison chronological rather than lexical, and a record with
a missing or unparseable lastRoundAt drops out rather than erroring.
(Recompute the cutoff against your own "today" if you're following along on a
different date.)
This is the SQL sentence "SELECT … WHERE stage = 'series-a' AND lastRoundAt >=
… ORDER BY fundingUsd DESC" — over a memory. Note that sort works right here
on MCP: unlike the node-level sortProperty (GraphQL/CLI only), the object
store's findObjects carries ordering on every surface.
matchis an equality shorthand;whereis the full predicate (in,between,exists, …). For the underlying node-level grammar see Query nodes by their properties; for the object store, Object store API.
Step 4 — Audit conformance¶
Suppose some competitor nodes existed before you declared the schema in step 1 — imported from a spreadsheet, say. Schema enforcement is schema-on-write and non-retroactive: those older rows were never checked. The conformance audit finds them without changing anything:
hadron_validate walks the memory and reports any node whose
objectType/properties violate the schema (alongside its other checks —
broken edges, sparse nodes, stale abstracts). A clean run means every
competitor object conforms. If a legacy row shows up, fix it with an
hadron_update_object that merges in the missing or mistyped fields, then
re-run the audit.
What you built¶
You now have a memory that behaves like a small database, on one substrate:
- a
competitorcollection with typed, validated fields (Memory.schema); - records created as objects (
hadron_create_object), rejected at write time when malformed; - the payoff query —
match+where+sortin onefindObjectscall — Hadron'sWHEREandORDER BY; - a conformance audit as the
VALIDATE CONSTRAINTbackstop.
The rest of the memory stays free-form: uncollected knowledge nodes coexist
with the typed competitor records, retrieved by meaning and text as always.
Where to go next¶
- Object store API — every operation's signature on GraphQL and MCP.
- Store and query records with the object store — the object store as a focused how-to.
- Structured storage and queries — the
underlying substrate: schema,
where,sortProperty, the surface matrix. - Structured vs. unstructured memory — when to reach for a schema, and when not to.