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.
Name-Based Forms
Section titled “Name-Based Forms”Local Wrefs
Section titled “Local Wrefs”Within the current repo, use the short form:
Location # shapeLocation/cave # thingObservation/cave-safe # assertionGameState/round-1/state # thing with hierarchical nameThe 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.
Canonical Wrefs
Section titled “Canonical Wrefs”For cross-repo references, use the fully qualified form:
wh:org/repo/Location # shape in another repowh:org/repo/Location/cave # thing in another repoCanonical wrefs always start with wh: followed by org/repo/ and then the local wref.
What Wrefs Can Target
Section titled “What Wrefs Can Target”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.
Visibility gate on resolution
Section titled “Visibility gate on resolution”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.
Version Modifiers
Section titled “Version Modifiers”Append @ followed by a version modifier to target a specific version:
| Modifier | Meaning | Example |
|---|---|---|
@HEAD | Current (latest) version only | Location/cave@HEAD |
@vN | Pinned to version N | Location/cave@v3 |
@ALL | All versions | Location/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(), CLIwh thing about/wh assertion list --about, MCPwarmhub_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.
Durable Ids
Section titled “Durable Ids”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.
Path Segment Rules
Section titled “Path Segment Rules”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
Retired Batch Tokens
Section titled “Retired Batch Tokens”$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.
Resolving Wrefs
Section titled “Resolving Wrefs”To resolve a wref to its canonical identity:
wh thing resolve Location/caveVia 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.
Optional Wref Fields
Section titled “Optional Wref Fields”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_FOUNDand 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_absenttarget tonullinstead of failing the operation. The operation succeeds,nullis stored in place of the reference, and acoalescedWrefswarning 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[]) —nullcannot 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.)
Examples
Section titled “Examples”# Shape-only wrefLocation
# Thing wrefLocation/cave
# Pinned to version 3Location/cave@v3
# Canonical (cross-repo)wh:warmhub-data/us.congress/Legislator/p000197
# Hierarchical thing nameGameState/round-1/turn-5/state
# Named collectionArc/cave-route
# Deterministic generated nameLocation/loc-2026-07-08-001