Skip to content

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:

{
  "vectors": {
    "limit": 200,
    "pageToken": "<nextChunkToken from the previous response>"
  }
}

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:

{
  "vectors": { "limit": 200, "pageToken": "..." },
  "asOf": "2026-07-31T04:31:16.999646Z"
}

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).