Collections
Collections turn groups of things into first-class entities. A Pair of two things is itself a thing — with its own shape, version history, and wref. Because collections are things, they can be the target of assertions via the standard about model.
Collection Types
Section titled “Collection Types”| Type | Ordered? | Unique? | # of Things | Field Names |
|---|---|---|---|---|
| Pair | yes | no | 2 | first, second |
| Triple | yes | no | 3 | first, second, third |
| Set | no | yes | 1+ | members |
| List | yes | no | 1+ | items |
The four built-in collection shapes (Pair, Triple, Set, List) are auto-created on first use. They cannot be created or revised manually. See Shapes — Built-in Shapes for the full list of core built-in shapes.
Inline Syntax
Section titled “Inline Syntax”The about field on assertions accepts either a wref string or a tagged collection object:
about: "Location/A" // single thingabout: { pair: ["Location/A", "Location/B"] } // Pairabout: { triple: ["Player/alice", "Cell/1-1", "Item/g"] } // Tripleabout: { set: ["Cell/0-0", "Cell/0-1", "Cell/1-0"] } // Setabout: { list: ["Cell/0-0", "Cell/0-1", "Cell/0-0"] } // ListThe tag is always explicit. Each tagged object must have exactly one key.
Explicit Creation
Section titled “Explicit Creation”Collections can be created without an assertion using kind: 'collection':
{ "operation": "add", "kind": "collection", "type": "pair", "members": ["Location/A", "Location/B"]}With a custom name:
{ "operation": "add", "kind": "collection", "type": "pair", "name": "my-custom-pair", "members": ["Location/A", "Location/B"]}If name is omitted, it’s auto-generated from the members.
Canonical Naming
Section titled “Canonical Naming”Collection names are derived deterministically from their members. All members are version-pinned.
Homogeneous (all members share one shape)
Section titled “Homogeneous (all members share one shape)”Pair/Location/Av1+Bv1 — Pair of Location/A@v1, Location/B@v1Set/Cell/0-0v1+0-1v1+1-0v1 — Set of 3 Cells (sorted)List/Location/Av1+Bv1+Cv1+Av1 — List with duplicates preservedThe wref starts with the collection type, then the shared member shape, then member names joined by + with version pins:
CollectionShape/MemberShape/bareNameVersionPin+joined+by+plus
Heterogeneous (members have different shapes)
Section titled “Heterogeneous (members have different shapes)”Pair/Player+Location/alicev1+cavev1 — Player/alice@v1, Location/cave@v1Triple/Player+Cell+Item/alicev1+cavev1+goldv1 — three different shapesWhen members have different shapes, the shape names are joined by + before the /, followed by the member names in the same order:
CollectionShape/Shape1+Shape2+.../name1v1+name2v1+...
Version Pinning
Section titled “Version Pinning”All collection members are always version-pinned. Bare wrefs and @HEAD are auto-resolved to the current HEAD version at commit time:
// Bare wrefs — auto-pinned to HEADabout: { pair: ["Location/A", "Location/B"] }// If A=v2, B=v1 → creates Pair/Location/Av2+Bv1
// Explicit version pins — pass through unchangedabout: { pair: ["Location/A@v1", "Location/B@v3"] }// Creates Pair/Location/Av1+Bv3
// Intra-commit ADD — auto-pinned to @v1operations: [ { operation: "add", kind: "thing", name: "Location/X", data: { x: 1 } }, { operation: "add", kind: "assertion", name: "D/x", about: { pair: ["Location/X", "Location/Y"] }, data: { v: 1 } },]// Location/X (created in same commit) → auto-pinned to @v1Normalization Rules
Section titled “Normalization Rules”- Set: members are deduplicated and sorted lexicographically.
{B, A, A}becomes{A, B}. - List: order preserved, duplicates kept.
[A, B, A]stays[A, B, A]. - Pair: exact —
(A, B)is not(B, A). - Triple: exact — order matters for all three positions.
Idempotent Behavior
Section titled “Idempotent Behavior”Same members at the same versions produce the same collection thing:
// Commit 1: creates Pair/Location/Av1+Bv1{ about: { pair: ["Location/A", "Location/B"] } }
// Same commit or later (members not revised): reuses the same Pair thing{ about: { pair: ["Location/A", "Location/B"] } }
// After revising Location/B to v2: creates NEW Pair/Location/Av1+Bv2{ about: { pair: ["Location/A", "Location/B"] } }Composability
Section titled “Composability”Collections are things — they can be members of other collections:
// Create a pair of pairs{ about: { pair: ["Pair/Location/Av1+Bv1", "Pair/Location/Cv1+Dv1"] } }Directional vs Symmetric
Section titled “Directional vs Symmetric”Use Pair for directional relationships (order matters):
// alice → bob trust (different from bob → alice){ about: { pair: ["Player/alice", "Player/bob"] }, data: { trust: 0.8 } }Use Set for symmetric relationships (order doesn’t matter):
// Adjacency is symmetric: {A,B} = {B,A}{ about: { set: ["Cell/0-0", "Cell/0-1"] }, data: { direction: "north" } }Querying Through Collections
Section titled “Querying Through Collections”By default, querying --about Location/A only returns assertions that directly target Location/A. Assertions about a Pair or Set containing Location/A are not included.
Use --resolve-collections to also include assertions about collections containing the target:
# Direct assertions about Location/A onlywh assertion list Location/A
# Also includes assertions about Pair/Location/Av1+Bv1, Set/Location/Av1+Bv1+Cv1, etc.wh assertion list Location/A --resolve-collectionsCollection resolution is HEAD-only — it finds collections that currently contain the thing. It does not resolve historical memberships for versioned queries (@vN).
Supported on: wh thing query, wh thing history, wh thing search (text mode), wh assertion list.
Token Integration
Section titled “Token Integration”Batch tokens ($N and #N) work in collection member wrefs, allowing you to create things and reference them in collection assertions within the same commit. Tokens are resolved before version pinning and collection expansion.