Skip to content

Things

A thing is a named, versioned entity within a repository. Things are the core data objects in WarmHub — they represent the entities in your domain.

Things are the foundation. Use them for:

  • Entities in your domain — the objects your system reasons about (locations, players, companies, documents)
  • Reference data — stable records that serve as targets for assertions
  • Anything with a single canonical state — if there’s one truth about this entity, it’s a thing

You don’t need assertions just to track changes over time — things are already versioned, so revise gives you full history on its own.

Things are created through writes. The same add thing operation works on every surface — the CLI offers a shorthand, while the SDK and MCP take the operation directly. See Write operations for the full contract.

Terminal window
# CLI
wh commit submit --add acme --shape Company --data '{"industry": "fintech", "stage": "series-b"}' -m "Add company"
// SDK
await client.commit.apply("myorg", "world", "Add company", [
{ operation: "add", kind: "thing", name: "Company/acme", data: { industry: "fintech", stage: "series-b" } },
])
// MCP — warmhub_commit_submit
{
"name": "warmhub_commit_submit",
"arguments": {
"orgName": "myorg",
"repoName": "world",
"operations": [
{ "operation": "add", "kind": "thing", "name": "Company/acme", "data": { "industry": "fintech", "stage": "series-b" } }
]
}
}

The thing’s wref is Company/acme — the shape name followed by the thing name.

Thing names can contain / for hierarchical organization. See Naming as Navigation for a deep dive on designing effective names — how hierarchy affects agent navigation, scoped queries, and subscription routing.

Location/dungeon/room-1
Location/dungeon/room-2
GameState/round-1/state

The first segment is always the shape name. Everything after the first / is the thing name. Names cannot start or end with /, and cannot contain //.

Every mutation that changes a thing creates a new version. Versions are numbered sequentially starting at v1:

Terminal window
# CLI — v1 initial creation, then v2 revised data
wh commit submit --add cave --shape Location --data '{"x": 3, "y": 7}'
wh thing revise Location/cave --data '{"x": 5, "y": 3}' -m "Move cave"
// SDK — revise produces v2
await client.commit.apply("myorg", "world", "Move cave", [
{ operation: "revise", kind: "thing", name: "Location/cave", data: { x: 5, y: 3 } },
])
// MCP — warmhub_commit_submit
{
"name": "warmhub_commit_submit",
"arguments": {
"orgName": "myorg",
"repoName": "world",
"operations": [
{ "operation": "revise", "kind": "thing", "name": "Location/cave", "data": { "x": 5, "y": 3 } }
]
}
}

Each version is immutable — old versions are preserved and queryable:

Terminal window
# View specific version
wh thing view Location/cave --version 1
# View current (HEAD) version
wh thing view Location/cave

Revising a thing with data identical to its current HEAD is a no-op — no new version is created. See Operations for details.

Things have an active flag. By default, things are active. Retracting a thing hides it from HEAD queries and other default queries, but preserves its name, data, and full version history. A retracted thing retains its name and can still be renamed. For exactly what retracting or renaming a thing does to the assertions, collections, and wrefs that point at it, see Retract, Rename & Schema Changes.

Terminal window
# CLI — retract marks inactive, records optional reason
wh thing retract Location/cave --reason "superseded by Location/cave-v2" -m "retract"
// SDK
await client.commit.apply("myorg", "world", "Retract cave", [
{ operation: "retract", name: "Location/cave", reason: "superseded by Location/cave-v2" },
])
// MCP — warmhub_commit_submit
{
"name": "warmhub_commit_submit",
"arguments": {
"orgName": "myorg",
"repoName": "world",
"operations": [
{ "operation": "retract", "name": "Location/cave", "reason": "superseded by Location/cave-v2" }
]
}
}
Terminal window
# Snapshot of active things and assertions.
# System-managed component infrastructure records are hidden by default.
wh thing list
# Filter by shape — shows all things in that shape, including component-seeded ones
wh thing list --shape Location
# Hide all component-owned records (stricter than default)
wh thing list --exclude-components
# View a specific thing
wh thing view Location/cave
# Version history
wh thing history Location/cave --limit 10
# Filtered query
wh thing query --shape Location --limit 50
# Query only component-owned records
wh thing query --component com.acme.research --limit 50

ThingDetail.data is typed as unknown on the SDK so the compiler refuses property access until the caller narrows the payload to its shape. There is no Thing<MyShape> generic — narrow with a cast or a runtime parse before reaching for fields:

import type { ThingDetail } from "@warmhub/sdk-ts";
type LocationData = { x: number; y: number; label?: string };
const detail = await client.thing.get("acme", "world", "Location/cave");
if (!detail) throw new Error("not found");
// Option 1 — trust the shape and cast (cheapest)
const data = detail.data as LocationData;
console.log(data.x, data.y);
// Option 2 — validate at the boundary (e.g. with zod)
// const data = LocationSchema.parse(detail.data);
// History items carry the same `data?: unknown` field on each version row.
const history = await client.thing.history("acme", "world", "Location/cave");
const v1 = history.versions[0];
const previous = v1?.data as LocationData | undefined;

If the shape is known statically, prefer the cast for ergonomics. If the payload is untrusted or shape evolution is in play, parse it through a schema (zod, valibot, ajv) so the runtime check stays close to the read.

Every entity in a repo is one of four kinds, set by the kind field on a write:

KindDescription
shapeSchema definition — fields and types
thingNamed entity with data
assertionClaim about another thing (has about reference)
collectionBuilt-in grouping of things — a Pair, Triple, Set, or List

They share one lifecycle: each has a wref, is versioned, and is created or modified through writes.