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.
When to Use Things
Section titled “When to Use Things”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.
Creating Things
Section titled “Creating Things”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.
wh commit submit --add acme --shape Company --data '{"industry": "fintech", "stage": "series-b"}' -m "Add company"await client.commit.apply("myorg", "world", "Add company", [ { operation: "add", name: "acme", shapes: ["Company"], data: { industry: "fintech", stage: "series-b" } },])from warmhub import Add, WarmHubClient
with WarmHubClient.from_env() as client: world = client.repository("myorg/world") world.apply("Add company", [ Add(name="acme", shapes=["Company"], data={"industry": "fintech", "stage": "series-b"}), ]){ "name": "warmhub_thing_create", "arguments": { "orgName": "myorg", "repoName": "world", "submissionId": "5f1c2a7e-8b3d-4c6a-9e2f-1a7b3c5d9e01", "items": [ { "name": "acme", "shapes": ["Company"], "data": { "industry": "fintech", "stage": "series-b" } } ] }}The thing’s wref is its name, acme. Its shapes are stated in shapes, not in the name.
Naming
Section titled “Naming”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.
dungeon/room-1dungeon/room-2round-1/stateThe whole string is the thing name, / included. Names cannot start or end with /, and cannot contain //.
Thing names are unique within a repository across all Shapes. Two things cannot share the same name even if they belong to different Shapes — a thing foo under Experiment and a thing foo under Evaluation cannot coexist in the same repo. A write that would reuse an active name under another Shape is refused with NAME_TAKEN. The refusal is terminal: choose another name. It identifies the holder by durable ID only, and only when you can read it; see error responses for the field contract. Only active names are claimed, so retracting a thing releases its name for a new thing to take.
Versioning
Section titled “Versioning”Every mutation that changes a thing creates a new version. Versions are numbered sequentially starting at v1:
# v1 initial creation, then v2 revised datawh commit submit --add cave --shape Location --data '{"x": 3, "y": 7}'wh thing revise cave --shape Location --data '{"x": 5, "y": 3}' -m "Move cave"// revise produces v2await client.commit.apply("myorg", "world", "Move cave", [ { operation: "revise", name: "cave", shapes: ["Location"], data: { x: 5, y: 3 } },])# revise produces v2from warmhub import Revise
world.apply("Move cave", [ Revise(name="cave", shapes=["Location"], data={"x": 5, "y": 3}),]){ "name": "warmhub_thing_revise", "arguments": { "orgName": "myorg", "repoName": "world", "submissionId": "6a2d3b8f-9c4e-4d7b-8f3a-2b8c4d6e0f12", "items": [ { "name": "cave", "shapes": ["Location"], "data": { "x": 5, "y": 3 } } ] }}Each version is immutable — old versions are preserved and queryable:
# View specific versionwh thing view cave --version 1
# View current (HEAD) versionwh thing view caveRevising a thing with data identical to its current HEAD is a no-op — no new version is created. See Operations for details.
Active/Inactive Lifecycle
Section titled “Active/Inactive Lifecycle”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. The retained name no longer counts as claimed, so a new thing may be created under it — see Naming. A retracted thing cannot 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.
# retract marks inactive, records optional reasonwh thing retract cave --reason "superseded by cave-v2" -m "retract"await client.commit.apply("myorg", "world", "Retract cave", [ { operation: "retract", name: "cave", reason: "superseded by cave-v2" },])from warmhub import Retract
world.apply("Retract cave", [ Retract("cave", reason="superseded by cave-v2"),]){ "name": "warmhub_thing_retract", "arguments": { "orgName": "myorg", "repoName": "world", "submissionId": "7b3e4c9a-ad5f-4e8c-9a4b-3c9d5e7f1a23", "items": [ { "name": "cave", "reason": "superseded by cave-v2" } ] }}Querying Things
Section titled “Querying Things”# 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 oneswh thing list --shape Location
# Hide all component-owned records (stricter than default)wh thing list --exclude-components
# View a specific thingwh thing view cave
# Version historywh thing history cave --limit 10
# Filtered querywh thing query --shape Location --limit 50
# Query only component-owned records (the component's Org/Name ref)wh thing query --component acme/research --limit 50Reading Thing Data
Section titled “Reading Thing Data”Reads return the thing’s data as a raw payload — the SDK cannot know your schema at read time. Each SDK lets you narrow or validate that payload on the client side:
- TypeScript —
datais typed asunknown; narrow with a cast or runtime parse (e.g. zod) before accessing fields. - Python —
datais aJsonValue(mapping, scalar, or array); passdecode_as=a dataclass to decode and validate field names at the boundary.
See Read Semantics for full decode patterns, history traversal, and batch reads (currently TypeScript-focused; Python patterns follow the same structure with snake_case names).
Every entity in a repo is one of four kinds. A write does not state the kind: a hub/shapes/<Name> name is a shape, a thing one of whose declared shapes composes Assertion is an assertion, and a thing under Arc, Bond, Set, or List is a collection:
| Kind | Description |
|---|---|
shape | Schema definition — fields and types |
thing | Named entity with data |
assertion | Claim about a shape or shaped thing (has a data.about reference) |
collection | Built-in grouping of things — an Arc, Bond, Set, or List |
They share one lifecycle: each has a wref, is versioned, and is created or modified through writes.