Reading a workspace back
memory:inspect lists what a workspace actually holds - the stored chunks, the
graph nodes and edges, and the execution-log steps - without requiring a search query.
It is a diagnostic and audit surface, not a retrieval path. Use it to check
what a writer produced, to audit stored history, or to hand someone a complete
account of what you hold about them. Don't use it to feed an agent - that's what
memory:query is for.
It is addressed by scope, so it reads a subject scope exactly as it
reads an agent workspace: POST /v1/scopes/{scopeId}/memory:inspect, with
/v1/agents/{id}/memory:inspect serving the same operation for agent-shaped
callers. That matters for the last of those uses above, because what one subject's
memory amounts to is a question about a subject scope. The console reads it too, on
either kind's detail page, with the same paging and the same pinning to a single
instant described below; it is read-only there, and commits and supersessions stay
in the CLI and the SDK.
Requesting sections
Each section is requested by including it; leave one out and it isn't read. All present sections are evaluated at one snapshot, so they agree with each other.
{
"vectors": { "limit": 50 },
"graph": { "nodeLimit": 100, "edgeLimit": 100 },
"log": { "limit": 50 }
}
A request that names no section returns INVALID_ARGUMENT.
Chunks come back with their content and metadata but never their embedding vector - it is large and unreadable, and no caller has wanted it.
Inspect is subject to your plan's per-second query rate, but it does not count against the daily query allowance.
Paging to the end
Every listing is bounded, and the bounds are lower than people expect:
| Section | Default | Maximum per page |
|---|---|---|
vectors |
50 | 200 |
graph nodes |
100 | 500 |
graph edges |
100 | 500 |
log |
50 | 200 |
A busy workspace passes those within its first year, so a single response is a page, not an inventory. Each section returns its own continuation token; feed it back on the next request until the token comes back empty:
The graph section pages its two listings separately, with nodePageToken and
edgePageToken. The log section takes pageToken, plus an optional since
timestamp that lists only steps from that instant on:
{
"graph": {
"nodePageToken": "<nextNodeToken>",
"edgePageToken": "<nextEdgeToken>"
},
"log": { "since": "2026-10-01T00:00:00Z", "pageToken": "..." }
}
The response carries one token per listing:
{
"vectors": { "chunks": [ ... ] },
"nextChunkToken": "CjE3NTQw...",
"nextNodeToken": "",
"nextEdgeToken": "",
"nextLogToken": ""
}
Pagination completion
An empty next*Token in the response indicates that the listing has been returned in full. The pagination loop terminates when that token comes back empty, not upon receiving a short page.
Sections exhaust independently. A workspace with thousands of chunks and a few hundred log steps will report the log done while chunks keep going - keep paging each listing on its own token.
Consistency while you page
By default each page is read at its own instant, so a commit landing mid-walk can show up. That's usually fine for a debug listing and wrong for an audit.
For a stable walk, set asOf to the read timestamp of your first page and
send the same value on every subsequent request:
Every page then observes the workspace as it was at that instant. The first
response's readTimestamp is the value to pin.
asOf cannot reach back indefinitely
Historical reads are bounded to about an hour. Once the pinned asOf is older
than that, requests fail with INTERNAL ("Failed to inspect memory."), so for
a very large workspace either page faster (raise limit to the maximum) or
accept the unpinned walk.
Tokens are opaque
Tokens are server-generated position cursors:
- Do not construct or modify tokens. Invalid tokens return INVALID_ARGUMENT.
- Tokens are scoped to specific workspaces. Tokens cannot be used across different scopes.
- Tokens are transient. Do not store tokens as persistent bookmarks.
Worked example
Reading every chunk in a workspace:
token, chunks = "", []
while True:
body = {"vectors": {"limit": 200}}
if token:
body["vectors"]["pageToken"] = token
r = post(f"/v1/agents/{agent_id}/memory:inspect", body)
chunks += r["vectors"]["chunks"]
token = r.get("nextChunkToken", "")
if not token:
break # exhausted - not merely a short page
From the command line
jnh agents memory inspect (and jnh scopes memory inspect) lists the same
sections. With no section flag it lists all three; narrow with --chunks,
--graph, and --log. --limit sets the rows per section (default 50).
# One page per section, with its continuation tokens
jnh agents memory inspect my.agent --chunks
# Every chunk, paged to the end at one pinned instant
jnh agents memory inspect my.agent --chunks --all --limit 200
# Follow new log steps as they land
jnh agents memory inspect my.agent --log --tail --interval 5s
--all handles pagination: it walks each requested listing to its
end, pins every page to the first page's read timestamp, and prints one listing
with no tokens. Like a manual pinned walk, it fails rather than mixing snapshots
if it outlives the hour. --tail polls every --interval (default 2s) and
prints only rows it has not printed before, until interrupted; it cannot be
combined with --all.
To audit embedding truncation across stored chunks, see Chunking.
Listings include superseded records, since inspect reports what a workspace
holds rather than what is current. Each chunk and edge carries its validity
window; superseded records include an invalidAt timestamp (see
Temporal memory).