Skip to content

Wrefs

A wref (WarmHub reference) is a human-readable address for any entity in WarmHub — shapes and the shaped things, assertions, and collections created under them.

Wrefs come in two name-based forms — local and canonical (below). A thing can also be addressed by its identity-based durable id, which names it regardless of its current name.

Within the current repo, use the short form:

Location # shape
Location/cave # thing
Observation/cave-safe # assertion
GameState/round-1/state # thing with hierarchical name

The first segment is always the shape name. Everything after the first / is the thing name. Thing names can contain / for hierarchical organization — see Naming as Navigation for how this hierarchy enables scoped queries, predictive navigation, and event routing.

For cross-repo references, use the fully qualified form:

wh:org/repo/Location # shape in another repo
wh:org/repo/Location/cave # thing in another repo

Canonical wrefs always start with wh: followed by org/repo/ and then the local wref.

An untyped wref accepts either a shape (Player) or a shaped thing (Player/alice). This is consistent across wref-typed data fields, collection members, assertion about targets, and committers. Local and canonical forms have the same target rules.

A typed constraint such as wref<Player> — declared as { "type": "wref", "shape": "Player" } — instead checks the target’s resolved type. Player/alice resolves as a shaped thing whose type is Player, so it satisfies the constraint. The Player shape itself has no governing resolved type, so it does not satisfy wref<Player>; matching the shape’s own name never qualifies it.

Shape-targeting data and collection references use the same ref index as every other wref. Inbound ref queries expose the referring data fields and collections; a shape has no governing wref fields of its own, so outbound refs from the shape target are empty.

The canonical-wref syntax is universal — any client can construct one. Resolving one through a read surface requires effective repo:read permission on the target repo. Public repos are readable by anyone. For private repos, callers without that access see an error — except cross-repo search and batch lookup, which fold unreadable results into { items: [] } or missing[] entries to keep search and batch streaming-friendly.

See Getting Access — Repository Visibility for the full read-permission rules.

Append @ followed by a version modifier to target a specific version:

ModifierMeaningExample
@HEADCurrent (latest) version onlyLocation/cave@HEAD
@vNPinned to version NLocation/cave@v3
@ALLAll versionsLocation/cave@ALL

When no modifier is specified (a “bare” wref), the default depends on the operation:

  • Most reads resolve bare wrefs to the current version — equivalent to @HEAD.
  • About queries (SDK thing.about(), CLI wh thing about / wh assertion list --about, MCP warmhub_thing_about) resolve bare wrefs to @ALL — returning assertions across all versions of the target.
  • Writes (commit operations) resolve bare wrefs to @HEAD — the operation targets the current version.

The @ALL modifier is rejected on write paths.

Shapes use the same modifiers. Player and Player@HEAD select the current shape definition, while Player@v3 selects that exact historical version. On write paths, a floating reference to a retracted shape fails, but an existing pinned version remains valid. Renaming a shape invalidates every old name-based spelling — including OldName@vN; the new name resolves both the current and historical versions (NewName and NewName@vN). Identity-based references already stored in data, collections, and assertions follow the rename and rehydrate with the new spelling.

About queries match the supplied target identity by default. A bare wref broadens version matching for that target identity, but it does not automatically include assertions about Arc, Bond, Set, or List collection things that contain the target. Use resolveCollections:true (MCP/SDK/HTTP) or --resolve-collections (CLI) when collection-member assertions should be included for identity-scoped inputs. Current-state about filters keep pinned @vN inputs version-exact and do not expand collection members. History about filters are different: thing history --about Shape/name@vN resolves the target identity, so history can still match assertions across versions of that identity and, with collection resolution, assertions about collections containing it.

A wref is readable, but it is mutable: renaming a thing — or its org or repo — rewrites the wref. A consumer that stores wrefs as long-term keys sees a rename as a delete followed by a brand-new record.

A durable id avoids that. It is an opaque token that names a thing by its identity rather than its name, and it never changes — across renames, revisions, and retraction. Thing read results include one in their metadata.durableId field (see Read Result Metadata).

Durable ids have three useful properties:

  • Stable — the same thing always has the same durable id, so it is safe to use as a long-lived key for mirroring, deduplication, or joins.
  • Self-routing — a durable id carries its own location, so read surfaces can resolve it without being told its org or repo.
  • Self-verifying — a corrupted or truncated durable id is rejected rather than resolving to the wrong thing.

A durable id is accepted anywhere a wref is accepted as input, and it carries the same version modifiers:

<durable-id> # current version
<durable-id>@v3 # pinned to version 3
<durable-id>@ALL # all versions (reads only)

As with any wref, @ALL is read-only and is rejected on write paths.

Resolving a durable id is subject to the same visibility gate as a canonical wref: you can only read ids for repos you have access to. See Durable ids on wh thing reads for the CLI surface.

For the full picture of what a rename does to the references pointing at a thing — which follow it and which break — see Retract, Rename & Schema Changes.

Wref path segments have the following constraints:

  • Segments must be non-empty — no // allowed
  • Names cannot start or end with /
  • Segments cannot contain: ?, #, @, :, $, or whitespace
  • Collection thing names cannot contain +; it is reserved for legacy collection namespace fencing

$N and #N commit-token syntax is no longer supported. Create things with explicit deterministic names, then reference those names directly:

[
{ "operation": "add", "kind": "thing", "name": "Location/loc-2026-07-08-001", "data": { "x": 1 } },
{ "operation": "add", "kind": "assertion", "name": "Observation/loc-2026-07-08-001-safe",
"about": "Location/loc-2026-07-08-001", "data": { "safe": true } }
]

Good explicit names usually come from source-system IDs, stable slugs, content hashes, or run-scoped sequence numbers.

To resolve a wref to its canonical identity:

Terminal window
wh thing resolve Location/cave

Via MCP:

{ "name": "warmhub_wref_resolve", "arguments": { "wref": "Location/cave" } }

MCP warmhub_wref_resolve returns identifying fields (name, kind, active, version, and shapeName) plus the metadata identity/timestamp envelope. For a shape target, shapeName is that target shape’s own name. SDK client.thing.resolve(...) returns the full thing.get payload; wh thing resolve shows the same identifying fields by default and the full payload with --json. See Wref Resolution.

A shape field typed as an optional wref (wref?) may point at another entity but is not required to. WarmHub resolves the value when the commit is applied, and how a missing target is handled depends on whether the field is required:

  • Required wref fields are the referential-integrity mechanism. If the target does not resolve, the operation fails with NOT_FOUND and stores no version for that operation. Other operations in the submission retain the write pipeline’s normal per-operation result semantics.
  • Optional wref fields coalesce a thing_absent target to null instead of failing the operation. The operation succeeds, null is stored in place of the reference, and a coalescedWrefs warning rides back on the operation result so the substitution is observable.

Coalescing is lossy: the original wref string is discarded and only null is stored — there is no pending or re-resolve state. Write the field again once the target exists.

Only the resolver outcome thing_absent coalesces: the target’s repo and shape exist and are active, but the named shaped thing does not exist. (A retracted target is not “absent” — it still hard-fails.) Every other unresolved reference on an optional field also fails that operation:

  • a pinned target (Shape/name@vN) whose version does not exist,
  • a shape mismatch — the wref points at a different shape than the field requires (SHAPE_MISMATCH),
  • a missing repo or shape (local or canonical),
  • a forward reference to a target created later in the same commit — this fails with VALIDATION_ERROR; reorder your operations so the target precedes the reference, or create both entities with explicit deterministic names and reference those names directly,
  • direct elements of a wref array (wref[]) — null cannot stand in for an array element, so these hard-fail. (An optional wref field on objects reached through an array — e.g. rows[].ref — is a scalar leaf and does still coalesce.)
Terminal window
# Shape-only wref
Location
# Thing wref
Location/cave
# Pinned to version 3
Location/cave@v3
# Canonical (cross-repo)
wh:warmhub-data/us.congress/Legislator/p000197
# Hierarchical thing name
GameState/round-1/turn-5/state
# Named collection
Arc/cave-route
# Deterministic generated name
Location/loc-2026-07-08-001