Memory scopes and subjects
A memory scope is the isolation boundary for agent memory. All vector chunks, graph elements, and execution logs belong to a specific scope, and all read and write operations target one or more scopes.
Jennah supports two scope kinds:
| Kind | Description | Resource Paths |
|---|---|---|
agent |
An individual agent workspace for autonomous tasks or sessions. | /v1/agents/{scopeId} and /v1/scopes/{scopeId} |
subject |
A durable entity, such as an end user or customer profile, whose memory persists across multiple agents. | /v1/scopes/{scopeId} |
Both scope kinds share the same identifier space within an enterprise and use the same underlying storage layout: interleaved vector chunks, knowledge graphs, and execution logs with transactional cascades on deletion.
Why subjects exist
When user-related facts are stored directly inside individual agent workspaces (such as a support bot, a billing assistant, or a scheduler), memory becomes fragmented across agents. Each agent must re-learn user preferences, and updating or auditing customer data requires inspecting multiple workspaces.
A subject scope provides a centralized, persistent memory container for a single principal. Multiple agents can read from and write to the same subject scope, making user data shared across workflows and straightforward to delete in a single operation for data privacy compliance.
Creating a scope
Scope creation requires setting kind explicitly. Scope kinds are immutable once created.
POST /v1/scopes
{
"scopeId": "acme.user.u-4471",
"scopeName": "Dana Whitfield",
"kind": "SCOPE_KIND_SUBJECT"
}
Using the CLI:
The --kind flag is required. Scope IDs are unique across all scope kinds within an enterprise; attempting to create a subject using an existing agent ID (or vice versa) returns ALREADY_EXISTS.
From the console, open Subjects and use the create form at the foot of the page. The kind is implied by the surface rather than chosen in the form: Subjects creates subject scopes and Agents creates agent workspaces, and neither page offers a control that switches between them. Creating a subject also requires a subject selector that already covers the ID you choose, so a caller with no subject reach is refused a create the platform would reject anyway.
Scope kinds cannot be modified after creation because access control rules and selector namespaces depend on the scope kind.
Structure IDs for hierarchical selector matching
Selectors match on .-delimited segments. Using hierarchical IDs like acme.user.u-4471 allows granting access to acme.user.* with a single selector pattern. Flat IDs require individual selector grants or full * wildcards.
Compatibility with /v1/agents routes
Existing /v1/agents endpoints remain backward-compatible and operate exclusively on agent scopes. They create, resolve, list, and delete only agent scopes. If a subject scope ID is passed to /v1/agents/{id}, the API returns NOT_FOUND.
Existing agent integrations and agent listings are unaffected by the presence of subject scopes.
The CLI provides two command groups:
jnh agents: Scoped exclusively to agent workspaces.jnh scopes: Manages bothagentandsubjectscopes.
jnh scopes list # lists both kinds, displaying a KIND column
jnh scopes list --kind subject # filters to subject scopes only
The console splits the same way, one surface per kind: Agents lists agent workspaces and Subjects lists subject scopes. The subject listing is paged and says when it has stopped at a page boundary, and because the API offers no filter on ID or name it also accepts an ID directly ("Open a subject by ID"), which resolves through the point read rather than by walking pages. An ID you cannot reach and one that does not exist give the same answer, on every surface.
Memory commands are available under both command trees. jnh scopes memory <verb> <scope-id> operates on either scope kind, while jnh agents memory <verb> <agent-instance-id> remains available for agent-specific workflows.
Access control: the subject selector namespace
Subject scopes use a dedicated subject selector namespace, separate from agent and dataset namespaces:
| Permission | Class | Description |
|---|---|---|
agent.subjects:read |
data | Read subject metadata and list accessible subject scopes |
agent.subjects:manage |
management | Manage subject selector configurations on roles and API keys |
Separating namespaces ensures that agent workspace administrators do not automatically inherit access to end-user memory. Selectors configured under agentSelectors match only agent scopes, while subjectSelectors match only subject scopes.
POST /v1/roles
{
"name": "Support tier 1",
"permissions": ["agent.memory:read", "agent.memory:write"],
"agentSelectors": ["support.*"],
"subjectSelectors": ["acme.user.*"]
}
Roles and API keys do not grant access to subject scopes by default unless explicitly configured in subjectSelectors. Only the built-in root and admin roles possess wildcard (*) reach across all namespaces by default.
Data residency and regions
Each subject scope is assigned to a specific region at creation time and cannot be relocated. All associated vector, graph, and log data is stored exclusively in that region's database.
This guarantees data residency: queries against an EU-based subject scope are always evaluated in that scope's home region, regardless of the caller or agent location.
POST /v1/scopes
{
"scopeId": "acme.user.u-4471",
"kind": "SCOPE_KIND_SUBJECT",
"region": "eu-west-1"
}
Single-scope queries automatically route to the target scope's home region. Regional placement only restricts multi-scope queries, which require all queried scopes to reside in the same region.
Querying across multiple scopes
Multi-scope recall is an API and CLI capability. The console has no anchored query surface at all: it inspects a scope's stored memory and does not rank it, so there is nothing there that spans scopes.
The memory:query API accepts an optional list of additionalScopes to evaluate alongside the primary scope in the URL path:
POST /v1/scopes/support.tier1.bot-09/memory:query
{
"additionalScopes": ["acme.user.u-4471"],
"semantic": {
"embedding": [...],
"limit": 10
}
}
In the CLI, the --scope flag specifies additional scopes and can be repeated:
CLI tabular output includes a SCOPE column whenever multiple scopes are queried. Structured output formats (-o json, -o ndjson) include scopeId on every item regardless of scope count.
Multi-scope query consistency and ranking
Multi-scope queries provide the following guarantees:
- Consistent read timestamp: All query sections across all requested scopes are evaluated at a single database snapshot timestamp.
- Global semantic ranking: Semantic vector matches are ranked globally across the union of all queried scopes rather than partitioned per scope.
- Explicit scope attribution: Every returned chunk, graph element, or log entry includes its origin
scopeId. Because chunk IDs are unique only within their own scope,scopeIdprovides unambiguous attribution.
{
"semantic": {
"matches": [
{
"scopeId": "acme.user.u-4471",
"chunkId": "pref-travel",
"distance": 0.11
},
{
"scopeId": "support.tier1.bot-09",
"chunkId": "ticket-8821",
"distance": 0.19
}
]
}
}
Error conditions
Jennah does not return partial results when querying multiple scopes. If any target scope fails validation, the entire query is rejected:
| Situation | Error |
|---|---|
| Inaccessible scope (missing permissions or selectors) | PERMISSION_DENIED |
| Scope does not exist | NOT_FOUND |
| Scopes reside in different regions | FAILED_PRECONDITION |
| Exceeded maximum allowed scope count | INVALID_ARGUMENT |
Limitations
Multi-scope queries currently have the following constraints:
- Metadata filtering: Metadata filters are supported only for single-scope queries, in every section. Item ids are unique within a scope but not across them, so a filter spanning several scopes could let one scope's tag admit another scope's identically-named item.
- Server-side query embedding: The
queryTextfield is supported only on single-scope queries. For multi-scope semantic search, supply precomputed vectors in theembeddingfield via the SDK. - Graph traversal boundaries: Graph queries traverse nodes and edges within each requested scope separately. Graph edges cannot link nodes across different scopes, so traversal returns isolated subgraphs per scope.
Committing memory to a scope
The memory:commit endpoint writes to exactly one scope per transaction. To write to a scope other than the path parameter, specify targetScope in the request body:
POST /v1/scopes/support.tier1.bot-09/memory:commit
{
"targetScope": "acme.user.u-4471",
"vectors": [
{
"chunkId": "pref-travel",
"rawContent": "prefers rail over air"
}
]
}
Using the CLI:
jnh scopes memory commit support.tier1.bot-09 \
--target-scope acme.user.u-4471 \
--from-file chunks.json
The commit receipt confirms the written scopeId. In the CLI, target scopes must be specified using the --target-scope flag; specifying targetScope inside payload files is rejected to keep the destination explicit.
Each commit is strictly atomic to a single scope. Target scopes are authorized against their respective namespace: committing to a subject scope requires subject selector reach, regardless of the URL path used.
Deleting a subject scope
Deleting a subject scope removes all vector chunks, graph nodes and edges, and execution logs stored in that scope in a single transaction:
Response:
Using the CLI:
The CLI displays the scope metadata (kind, name, region) and prompts for confirmation before executing the deletion.
From the console, open the subject from Subjects and use Delete subject at the foot of its page. What identifies the scope there is the read-back above the control - the kind, region and creation instant the platform holds for it - rather than the ID typed into the confirmation box: the mistake worth guarding against is a wrong ID that exists, and retyping it repeats the mistake. The console states the isolation rule below at the point of deleting, not only here, and on success it shows the instant the erasure committed. It reports no count of what was removed, because the platform makes no itemised erasure claim; the commit instant is the whole receipt on every surface.
Deletion cascades across all underlying tables for the scope, matching the behavior of agent workspace deletion.
Scope isolation during deletion
Deleting a subject scope purges only the memory stored within that specific scope. It does not remove data that agents might have recorded separately in their own agent workspaces.
To support complete data erasure workflows, write user-specific facts into the user's subject scope rather than agent workspaces.