Skip to content

HEAD Queries

A HEAD query returns all active items in a repository — things, assertions, and shapes — at their current version. It’s the fastest way to orient yourself in a repository.

The CLI names these snapshot reads list for consistency across domains; the command returns the repository HEAD projection.

Terminal window
# All active items
wh thing list
# Filter by shape
wh thing list --shape Location
# Filter by declared shape (direct declaration only)
wh thing list --declared-shape Location
# Expand each row with its validated-shape closure (opt-in projection)
wh thing list --declared-shape Location --include-validated-shapes
# Filter by kind: thing, shape, assertion, or collection (deprecated).
# Rows only ever report kind "thing" or "shape"
wh thing list --kind assertion
# Limit results (default: 50, max: 500)
wh thing list --limit 50
# Glob filter on Thing names (* = one segment, ** = zero or more)
wh thing list --shape Location --match "north/*"
# JSON output
wh thing list --json

--match matches the Thing’s name, not its Shape: --match "Location/*" finds Things whose names start with Location/, not Things of the Shape Location. Select by Shape with --shape.

Both wh thing list and wh assertion list return one page by default (up to 50 items). Use --limit to request up to 500 items per page, --cursor to advance to a specific page, or --all to automatically exhaust all pages:

Terminal window
# Fetch all pages automatically
wh thing list --all
# Advance to a specific page using a cursor from a previous response
wh thing list --limit 50 --cursor <cursor-value>

In human-readable output, when a result set is truncated the CLI prints continuation guidance so you know more pages are available. With --json, that guidance is instead carried structurally as page.hasMore and page.nextCursor on the response envelope.

--declared-shape and --include-validated-shapes

Section titled “--declared-shape and --include-validated-shapes”

--declared-shape matches only the shape a thing was actually written under. --shape is broader — it also matches things that were validated against that shape indirectly, such as through a composite that includes it — so for the same name --declared-shape returns a subset of what --shape returns.

--include-validated-shapes does not change which items come back. It adds a field: returned items gain a validatedShapes array listing each shape that validated them. Shape rows carry an empty array, so read the field as possibly empty rather than always populated. It is opt-in because that list is not in the default payload, and it works with or without --declared-shape.

Terminal window
# Only items that directly declare Location
wh thing list --declared-shape Location
# The same items, each row expanded with its full validatedShapes closure
wh thing list --declared-shape Location --include-validated-shapes
# Polling read with declared-shape filter
wh thing list --live --declared-shape Location
# Polling read, rows expanded with the validated closure
wh thing list --live --declared-shape Location --include-validated-shapes

Restrictions:

  • --include-validated-shapes cannot be combined with --count.
  • --since-repo-seq cannot be combined with --live.
  • Neither --declared-shape nor --include-validated-shapes can be combined with --since-repo-seq: an incremental read rejects both with VALIDATION_ERROR.
  • --declared-shape cannot be combined with --match, and cannot be used with a token narrowed to a subset of refs. Either case is rejected with VALIDATION_ERROR.

A collection is a thing. It reads as kind: "thing" and says what it is through a collections array, derived from the Shapes its version actually satisfies:

{
"wref": "team",
"kind": "thing",
"declaredShapes": ["Set@v2"],
"collections": [
{ "shape": "Set@v2", "memberCount": 3 }
]
}

So --kind thing includes collections, and to find only collections you filter by shape (--shape Set) or look for a collections array on the rows you get back. A thing that is not a collection has no collections field at all.

Because the array is derived from the certificate closure rather than from the shape name, a thing under your own Shape that composes Arc is an Arc here — it carries an Arc entry and the collection reads work on it. A thing composing two collection Shapes carries two entries, and the collection read procedures take a shape argument to say which one to read.

For the full inventory of current collection shapes, see the Collections page.

Use --count to get just the number of matching items instead of the full result list:

Terminal window
# Count all active items
wh thing list --count
# Count by shape
wh thing list --shape Location --count
# Count assertions
wh assertion list --count
wh assertion list --shape Observation --count

--count cannot be combined with --limit, --cursor, --all, --live, or --include-validated-shapes.

For assertions specifically:

Terminal window
# Equivalent to: wh thing list --kind assertion
wh assertion list
# With shape filter
wh assertion list --shape Observation

Like wh thing list, wh assertion list returns one page by default (up to 50 items, max 500 per page). Use --all to fetch all pages or --limit <n> --cursor <cursor-value> to paginate manually.

{
"name": "warmhub_thing_head",
"arguments": {
"shape": "Location",
"kind": "thing",
"match": "dungeon/*",
"limit": 100
}
}

Count via MCP:

{
"name": "warmhub_thing_head",
"arguments": {
"shape": "Location",
"count": true
}
}

orgName and repoName are required.

The kind parameter controls which rows are returned. kind: "thing" includes collections, because a collection is a thing; the deprecated kind: "collection" narrows to the built-in collection shapes. See Reading collections above.

The warmhub_thing_head tool accepts shape, shapeless, kind, match, excludeInfraShapes, limit, cursor, sinceRepoSeq, count, and where. shapeless: true returns only things no Shape certifies; see Shapeless filter. The declaredShape and includeValidatedShapes filters are available on the CLI and SDK but are not exposed on the MCP tool surface.

MCP callers can pass sinceRepoSeq to read only the items that have changed since a known point in the repo’s sequence. This lets your agent avoid re-fetching the full snapshot on every call — it receives only the delta since the last read.

Seeding the first snapshot. Pass sinceRepoSeq: -1 on the first call. The tool treats -1 as “start from the beginning of the repo’s sequence,” which begins a full scan of current HEAD state. That first call returns one page, not the whole repo — follow nextCursor through every page as described below, and you hold the complete snapshot only once you reach the terminal page.

{
"name": "warmhub_thing_head",
"arguments": {
"sinceRepoSeq": -1,
"limit": 100
}
}

Paginating an incremental read. When the response includes a nextCursor field, more pages are available. Advance through them by passing the same sinceRepoSeq lower bound and the same limit you used on the first call — do not update either value mid-pagination. The lower bound and limit must stay constant across all pages of a single incremental read.

{
"name": "warmhub_thing_head",
"arguments": {
"sinceRepoSeq": -1,
"limit": 100,
"cursor": "<cursor-from-previous-page>"
}
}

Persisting the sequence position. repoSeq appears at the top level of the response only on the terminal page — the last page, which has no nextCursor. (nextCursor and repoSeq are mutually exclusive: they never appear together on the same response.) Persist repoSeq only from that terminal page. Persisting an intermediate page’s value risks missing items that arrived between pages. On the next incremental read, pass the persisted value as sinceRepoSeq to receive only the items that changed after that point.

Count with sinceRepoSeq. You can combine sinceRepoSeq with count: true to get the number of changed items without fetching the full result list. Count responses also return repoSeq at the top level (there is no nextCursor on a count response). Persist that repoSeq to checkpoint your position.

{
"name": "warmhub_thing_head",
"arguments": {
"sinceRepoSeq": 4821,
"count": true
}
}

Summary of the contract:

StepWhat to do
First callPass sinceRepoSeq: -1 to seed a full snapshot
PaginatingKeep the same sinceRepoSeq lower bound and the same limit on every page of the same read; continue while the response includes nextCursor
Persisting positionSave repoSeq only from the terminal page (no nextCursor present) or from a count response
Next incremental readPass the persisted repoSeq as sinceRepoSeq
RetractionsNot surfaced by this tool — see Deletions are not reported below

Not every request shape can be served incrementally, and the two ways a request is turned away report differently:

  • Your token must have read authority over the whole repository. A token narrowed to a subset of refs is refused with FORBIDDEN.
  • The query must be one the backend can serve exactly. That rules out combining sinceRepoSeq with a glob match filter. A query that does not qualify, or a repo whose incremental machinery is not ready yet, is refused with INCREMENTAL_READ_UNAVAILABLE. That error deliberately does not say which of the two it was.

warmhub_thing_head does not expose an includeRetracted parameter, so an incremental read returns only rows that are currently active. A deleted or retracted item is simply absent from the response — it does not come back as an active: false row. So a reader that stores its last sequence position cannot tell “this item was deleted” apart from “this item did not change.” Do not use this surface alone to drive a local copy that has to remove rows.

The SDK helper client.thing.headChanges(...) does report deletions. Call it with the same lower bound you would pass to MCP:

const { items, repoSeq } = await client.thing.headChanges("my-org", "my-repo", {
sinceRepoSeq: -1,
})
// Deleted rows arrive as `active: false`.
// It pages internally, so `repoSeq` is already the final position — save it
// and pass it as `sinceRepoSeq` next time.

Key your local state by item.metadata.durableId — the durable id is a stable identifier that survives rename and retraction — rather than by wref, the name reference, which changes when a thing is renamed and would duplicate rows in your copy.

One limit remains: this helper is not a repo-wide deletion feed covering every writer, so if you need to catch deletions made by anyone, re-read the full snapshot periodically rather than trusting the delta.

import type { Page, ThingItem } from "@warmhub/sdk-ts";
// Optionally filter by shape, kind, or a wref glob
const page: Page<ThingItem> = await client.thing.head("my-org", "my-repo", {
shape: "Location",
kind: "thing",
match: "dungeon/*",
limit: 100,
});
for (const item of page.items) console.log(item.wref);
// Filter by declared shape, with optional validated-closure expansion
const declaredPage: Page<ThingItem> = await client.thing.head("my-org", "my-repo", {
declaredShape: "Location",
includeValidatedShapes: true,
limit: 100,
});
// Page through results with the returned cursor
if (page.nextCursor) {
// pass page.nextCursor back as `cursor` on the next call
}

All filters are optional. A single call returns one Page<ThingItem> — follow nextCursor, or use client.thing.headAll / headIter, to read past the first page. The example assumes a configured client; see the SDK Quickstart to create one, and Read Semantics for filters, glob match, and pagination.

The kind parameter controls which rows are returned. kind: "thing" includes collections, because a collection is a thing; the deprecated kind: "collection" narrows to the built-in collection shapes. See Reading collections above.

includeValidatedShapes cannot be combined with count-only reads.

The HTTP examples below use the public warmhub-data/us.congress.trades repo so you can copy-paste them directly; the CLI and MCP blocks above use generic Location names that you’d swap for your repo’s own shapes.

GET /api/repos/warmhub-data/us.congress.trades/head
GET /api/repos/warmhub-data/us.congress.trades/head?shape=CongressTrade&kind=thing&limit=25
GET /api/repos/warmhub-data/us.congress.trades/head?match=CongressTrade%2F20034954%2F*&limit=25

Anonymous callers reading public repos are capped at limit=25 per page; authenticated callers can request up to limit=500. See Anonymous Pagination Caps.

The HTTP API does not currently mount a count-only route. Use CLI/MCP count surfaces for count-only reads, or page through /head when using HTTP.

The response contains an items array. Each item includes:

FieldDescription
nameItem name
wrefFull wref (the name for things, assertions, and collections; hub/shapes/<Name> for shape rows)
shapeNameShape name (omitted for shape rows)
kindEntity kind (shape or thing). A collection is a thing; see collections. Assertion rows report kind: "thing"; you can tell a row is an assertion because it includes aboutWref and affirmedWrefs
versionCurrent version number
dataCurrent data payload
aboutWrefAbout target wref (assertions only)
affirmedWrefsAffirmed wrefs (assertions only)
declaredShapesArray of version-pinned shape wrefs (e.g. Shape@vN) that this item directly declares membership in — present only when membership projection is available
validatedShapesArray of version-pinned shape wrefs in the validated closure for this item — present only when includeValidatedShapes was requested (SDK and CLI only; not available on the HTTP or MCP surfaces)
metadataMetadata envelope (see below)

declaredShapes is included when membership projection is available for the query; it is not guaranteed on every HEAD row. validatedShapes is included only when the caller opts in via includeValidatedShapes on the SDK or CLI; it covers the full validated closure and will be absent from the row when the option was not requested or is not supported on the surface being used.

Every HEAD row includes a metadata object with the following fields:

FieldDescription
metadata.durableIdStable identifier for the item that does not change across versions
metadata.createdOnUnix timestamp (milliseconds since epoch) of when the item was first created
metadata.revisedOnUnix timestamp (milliseconds since epoch) of when the current version was created

Use durableId when you need a stable reference that survives updates. Use createdOn and revisedOn to track when items were created and last changed.

Watch HEAD in real-time:

Terminal window
wh thing list --live

This re-runs the query periodically and re-renders the display whenever the data changes. On TTY terminals, the output refreshes in place. Combine with filters:

Terminal window
wh thing list --live --shape Location --kind thing
# Polling read with declared-shape filter
wh thing list --live --declared-shape Location
# Polling read with declared-shape filter and validated closure
wh thing list --live --declared-shape Location --include-validated-shapes

HEAD queries are best for:

  • Orientation — understanding what’s in a repo before targeted queries
  • Monitoring — watching for changes with --live
  • Broad snapshots — getting everything of a particular shape or kind

For targeted lookups, prefer filtering queries which accept more specific criteria.