Operations
Every write request contains one or more operations. There are three operation types — add, revise, and retract — across shape, thing, assertion, and collection records. Collections provide inline syntax for creating grouped things.
A write request can include multiple operations. CLI (wh commit submit), SDK
(client.commit.apply), and MCP (warmhub_commit_submit) writes apply
operations one at a time — if a later operation fails, earlier ones are still
in place, and you can retry just the failed ones. See Writes.
ADD Operations
Section titled “ADD Operations”ADD Shape
Section titled “ADD Shape”Create a new shape with field definitions:
{ "operation": "add", "kind": "shape", "name": "Location", "data": { "fields": { "x": "number", "y": "number", "label": "string" } }}ADD Thing
Section titled “ADD Thing”Create a new thing under an existing shape:
{ "operation": "add", "kind": "thing", "name": "Location/cave", "data": { "x": 3, "y": 7, "label": "Dark Cave" }}ADD Assertion
Section titled “ADD Assertion”Create an assertion about another thing. The about field is required:
{ "operation": "add", "kind": "assertion", "name": "Belief/cave-safe", "about": "Location/cave", "data": { "safe": true, "confidence": 0.8 }}about accepts local wrefs, canonical wrefs, or inline collection syntax.
REVISE Operations
Section titled “REVISE Operations”Revise only changes data. It does not accept an active field — to mark
an entity inactive, use a retract operation instead.
TypeScript SDK users see this as a compile error because ReviseOperation
declares active?: never.
REVISE Shape
Section titled “REVISE Shape”Update a shape’s field definitions:
{ "operation": "revise", "kind": "shape", "name": "Location", "data": { "fields": { "x": "number", "y": "number", "z": "number" } }}REVISE Thing
Section titled “REVISE Thing”Update a thing’s data:
{ "operation": "revise", "kind": "thing", "name": "Location/cave", "data": { "x": 5, "y": 3, "label": "Bright Cave" }}REVISE Assertion
Section titled “REVISE Assertion”Update an assertion’s data. The about target cannot be changed:
{ "operation": "revise", "kind": "assertion", "name": "Belief/cave-safe", "data": { "safe": false, "confidence": 0.2 }}Collection Operations
Section titled “Collection Operations”Collections are a convenience for creating grouped things (pairs, triples, sets, lists). Under the hood, the commit pipeline expands collection operations into standard thing operations — collections are things with built-in shapes.
ADD Collection
Section titled “ADD Collection”Create a collection thing explicitly:
{ "operation": "add", "kind": "collection", "type": "pair", "members": ["Location/A", "Location/B"]}The name field is optional — if omitted, a canonical name is derived from the members (e.g., Pair/Location/Av1+Bv1).
You can also create collections inline within an assertion’s about field. See Collections for the full syntax.
Retracting a Collection
Section titled “Retracting a Collection”To mark a collection inactive, use a RETRACT operation (see RETRACT Operations):
{ "operation": "retract", "name": "Pair/Location/Av1+Bv1"}RETRACT Operations
Section titled “RETRACT Operations”Mark an entity inactive. The entity’s data and version history are preserved; it is hidden from default HEAD queries.
{ "operation": "retract", "name": "Location/cave", "reason": "replaced by Location/dungeon"}The reason field is optional (max 500 chars). The kind field is an optional safety hint — the operation will error if the resolved entity’s kind doesn’t match.
Retract is the only path to setting an entity inactive. Once retracted, you can add a new entity at the same name to create a fresh identity.
Operation Rules
Section titled “Operation Rules”Data Requirements
Section titled “Data Requirements”datais required on revise for shape, thing, and assertion- Revise is a full replacement — you must include all fields in
data, not just the ones that changed - Omitted fields will be absent in the new version, which will fail shape validation if they are required
aboutis required on ADD assertion, immutable on REVISE assertion
Conditional Operations
Section titled “Conditional Operations”When you retry a write or have more than one writer touching the same data, you often want an operation to apply only under a condition: skip it if the target already exists, reject it if someone changed the target first, or do nothing when the data is unchanged. WarmHub supports three conditional patterns. The first two — skipExisting and expectedVersion — are opt-in fields you set on an operation. The third happens automatically.
Add if missing
Section titled “Add if missing”By default, adding a name that already exists fails with a CONFLICT error (Thing "Location/cave" already exists). Set skipExisting on an add to return operation: "noop" instead — the existing entity is left untouched and the write does not fail.
{ "operation": "add", "kind": "thing", "name": "Location/cave", "data": { "x": 3, "y": 7, "label": "Dark Cave" }, "skipExisting": true}This makes an add idempotent, which is what you want when re-running a seed script or retrying a write safely. skipExisting is available on add for shapes, things, assertions, and collections. On the CLI it is the --skip-existing flag on wh commit submit; in the SDK it is a field on each add operation or a top-level option on client.commit.apply.
Revise if version matches
Section titled “Revise if version matches”By default, a revise overwrites the current version no matter who wrote it last. To guard against a lost update — you read v3, another writer commits v4, and your revise would silently clobber it — pass expectedVersion. The revise applies only if the target is still at that version; otherwise it is rejected with a CONFLICT error whose details.reason is "expected_version_mismatch", carrying the expected and current version numbers so you can refetch and retry.
{ "operation": "revise", "kind": "thing", "name": "Location/cave", "data": { "x": 5, "y": 3, "label": "Bright Cave" }, "expectedVersion": 3}expectedVersion is available on revise for shapes, things, and assertions. On the CLI it is the --expected-version flag on wh thing revise (and wh commit submit --revise); in the SDK it is a field on the revise operation. When you need exclusive access across a read-modify-write rather than an optimistic check, use a read lease instead.
Idempotent revise
Section titled “Idempotent revise”If a revise produces the same data as the current version, it returns operation: "noop" — no new version is created. (WarmHub compares server-computed data hashes; clients never generate them.) Re-submitting an unchanged revise is therefore safe and leaves version history untouched.
For the full SDK signatures see Write Methods; for the CLI flags see Commands; for the MCP tool schema see MCP Tools Reference.
Illegal Operation Sequences
Section titled “Illegal Operation Sequences”Within a single chunk, the following sequences on the same target are rejected by preflight:
| Sequence | Allowed? |
|---|---|
| ADD + ADD | No — duplicate add |
| REVISE + ADD | No — can’t add something that already exists |
| ADD + REVISE | Yes — create then immediately update |
| REVISE + REVISE | Yes — multiple updates in sequence |
TypeScript callers binding an inline operation literal to a variable should
either annotate it : Operation[] or use satisfies Operation[] to keep
the operation discriminant narrowed — see SDK Write Methods — Typing
Operation arrays.
Batch Files
Section titled “Batch Files”The --file flag on wh commit submit accepts a JSON file containing an array of operations. For large datasets, prefer JSONL streaming (one operation per line):
# JSON array (small/medium batches)wh commit submit --file operations.json --message "batch update" --repo org/repo
# JSONL stream (large datasets — chunked automatically)wh commit submit --file dataset.jsonl --progress -m "bulk import" --repo org/repoThe JSON file must contain a JSON array of operation objects:
[ { "operation": "add", "kind": "thing", "name": "Location/cave", "data": { "x": 3, "y": 7, "label": "Dark Cave" } }, { "operation": "add", "kind": "assertion", "name": "Belief/cave-safe", "about": "Location/cave", "data": { "safe": true, "confidence": 0.8 } }]Generating templates
Section titled “Generating templates”Use wh shape template to scaffold operations from shape definitions (template generation lives in the shape domain):
# Single shape -> thing scaffoldwh shape template Hypothesis --repo org/repo
# Multiple shapes at oncewh shape template Hypothesis Evidence Decision --repo org/repo
# Assertion scaffold for a shaped claim about a targetwh shape template Hypothesis --kind assertion \ --about ResearchTopic/example --repo org/repo
# Write to file, then edit and commitwh shape template Hypothesis Evidence -o experiment.json --repo org/repo$EDITOR experiment.jsonwh commit submit --file experiment.json -m "add experiment" --repo org/repoThe template fills fields with placeholder values ("", 0, false) and FILL_IN: hints for fields with descriptions. Replace placeholders with real data before writing.
Shapes define the payload schema, not whether an operation is a thing or an assertion. To scaffold an assertion, pass --kind assertion; add --about <TargetShape/name> when you want a concrete target instead of the default placeholder.
Streaming large files
Section titled “Streaming large files”For large datasets, use JSONL format (one operation per line) with streaming:
wh commit submit --file dataset.jsonl --progress -m "bulk ingest" --repo org/repoToken System
Section titled “Token System”The $N (allocate) and #N (reference) tokens let you create entities and reference them within the same stream — for example, adding a thing and an assertion about it in the same chunk, or in a later chunk of the same stream. Token state is scoped to the streamId and is echoed back on each stream.append response so the next chunk can resolve #N references against earlier allocations.
#N cannot reference future tokens — order operations so adds precede the references that depend on them.
See Wrefs: Batch Tokens for the full syntax, rules, and examples.
Hit a problem or have a question? Get in touch.