Skip to content

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.

Create a new shape with field definitions:

{
"operation": "add",
"kind": "shape",
"name": "Location",
"data": { "fields": { "x": "number", "y": "number", "label": "string" } }
}

Create a new thing under an existing shape:

{
"operation": "add",
"kind": "thing",
"name": "Location/cave",
"data": { "x": 3, "y": 7, "label": "Dark Cave" }
}

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 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.

Update a shape’s field definitions:

{
"operation": "revise",
"kind": "shape",
"name": "Location",
"data": { "fields": { "x": "number", "y": "number", "z": "number" } }
}

Update a thing’s data:

{
"operation": "revise",
"kind": "thing",
"name": "Location/cave",
"data": { "x": 5, "y": 3, "label": "Bright Cave" }
}

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 }
}

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.

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.

To mark a collection inactive, use a RETRACT operation (see RETRACT Operations):

{
"operation": "retract",
"name": "Pair/Location/Av1+Bv1"
}

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.

  • data is 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
  • about is required on ADD assertion, immutable on REVISE assertion

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.

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.

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.

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.

Within a single chunk, the following sequences on the same target are rejected by preflight:

SequenceAllowed?
ADD + ADDNo — duplicate add
REVISE + ADDNo — can’t add something that already exists
ADD + REVISEYes — create then immediately update
REVISE + REVISEYes — 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.

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):

Terminal window
# 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/repo

The 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 }
}
]

Use wh shape template to scaffold operations from shape definitions (template generation lives in the shape domain):

Terminal window
# Single shape -> thing scaffold
wh shape template Hypothesis --repo org/repo
# Multiple shapes at once
wh shape template Hypothesis Evidence Decision --repo org/repo
# Assertion scaffold for a shaped claim about a target
wh shape template Hypothesis --kind assertion \
--about ResearchTopic/example --repo org/repo
# Write to file, then edit and commit
wh shape template Hypothesis Evidence -o experiment.json --repo org/repo
$EDITOR experiment.json
wh commit submit --file experiment.json -m "add experiment" --repo org/repo

The 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.

For large datasets, use JSONL format (one operation per line) with streaming:

Terminal window
wh commit submit --file dataset.jsonl --progress -m "bulk ingest" --repo org/repo

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.