How agents remember across runs: scope-explicit long-term memory, the temporal knowledge graph (dated facts, contradictions, provenance), the memory.graph manifest block, the graph builtins, the /v1/memory/graph/* API and DSAR coverage.
Agents on Agentic Workforce ME remember across runs in two stores, both governed by the published manifest. Long-term memory is a flat list of facts, preferences and insights the curator distils after each run (and the agent can write with memory.save). The memory graph is a temporal knowledge graph beside it: typed entities linked by dated facts, with provenance to the run that asserted each one. This page is for backends that integrate identified users (so memory attaches to the right person), author or audit facts, and honour data-subject requests.
Every memory row — flat or graph — carries a scope, and recall is scope-explicit. A run sees exactly three sets: tenant rows (human authored, workspace-wide), the agent’s own agent rows, and the end_user rows of the one contact the run serves. Another contact’s rows are never visible, whatever agent is asking.
Scope
Who writes it
Who recalls it
tenant
Administrators (console “Add fact”, POST /v1/memory/graph/facts without ids, POST /v1/memory).
Every agent in the workspace.
agent
The curator and memory.save in anonymous runs (no served contact); admins with agent_id.
That agent, in every conversation.
end_user
The curator and memory.save whenever the run serves an identified contact (the default in that case); admins with agent_id + end_user_id.
That agent, only while serving that contact.
memory.long_term.scope (agent or tenant) in the manifest governs anonymous runs only. The flag memory.long_term.user_modeling is accepted for compatibility and currently has no effect — recall is already scoped to the contact.
When memory.graph resolves on for an agent, the curator runs one extra forced-tool model call after each completed run — asynchronously, in the worker, never on the reply path — and writes:
Entities: typed nodes (person, organization, place, topic, …) resolved without a model — normalized name or alias first, then an embedding match, else a new entity. Near-duplicates the resolver did not merge surface as possible_duplicates for a human to merge.
Facts: edges subject —predicate→ object with one plain sentence and a bi-temporal window — valid_from / valid_to say when the fact was true in the world, created_at / invalidated_at when the system learned and superseded it.
Episodes: one per extraction, pointing at the run and thread, with counts, model and tokens and a ≤ 600-character digest. Transcripts are never stored; a deterministic PII floor redacts e-mail addresses and long identifiers before anything is persisted.
A new fact that contradicts an active one — same subject and predicate with another object, or a close same-subject fact the contradiction check judges incompatible — invalidates the old edge: status: invalidated, valid_to set to the new fact’s valid_from, invalidated_by pointing at the successor. The same triple restated with a changed detail or date (“…in November” → “…in December”) is a temporal update and supersedes the earlier version the same way; an equivalent restatement only bumps mention_count. The runtime never deletes a fact — DELETE is the administrator’s compliance “forget”. When the check cannot decide, facts coexist: the platform never invalidates on a guess.
At run start the runtime injects a token-budgeted # Memory graph (as of <date>) block of dated facts after the flat # Relevant memory block — hybrid seeds (embedding and full-text over entity names and facts), a k-hop expansion bounded by max_hops, a validity filter, then a blended rerank. The agent also gets two read-only, never-gated builtins:
Additive and default-off (schema 2.10): every earlier manifest parses unchanged. The block rides on long-term memory and resolves tighten-only down the hierarchy — graph.enabled is true only when long_term.enabled is true, the agent asks for it, and no tenant or org-node scope sets memory: { graph: false }. write_approval is inherited from long_term: with it on, curator facts land pending, invisible to recall and to members until an administrator approves them (PATCH … { status: "active" }).
Master switch. Resolves to long_term.enabled ∧ graph.enabled ∧ no ancestor lock — a tenant or org-node memory: { graph: false } (or long_term: false) turns it off for every agent below. Off ⇒ no prompt block, no builtins, no extraction.
memory.graph.extraction
off | curator, default curator
off keeps recall and the builtins but never extracts — for human-authored graphs.
memory.graph.write_scopes
(agent | end_user)[], default both
Scopes the curator may write. end_user is used when the run serves a known contact (and is allowed), else agent. tenant facts are human-authored only.
memory.graph.max_hops
integer 1–3, default 2
Expansion depth for the prompt block and memory.graph_search.
memory.graph.recall_budget_tokens
integer 100–4000, default 600
Token budget of the injected # Memory graph (as of <date>) block.
memory.graph.seed_top_k
integer 1–20, default 8
Seeds per retrieval leg (entity cosine, entity full-text, fact cosine, fact full-text).
When a new fact has candidates (same subject + predicate with another object, or a close same-subject fact) one batched model call decides supersedes / coexists; failure ⇒ coexists — never invalidate on a guess.
memory.graph.include_superseded_in_prompt
boolean, default false
Also render up to 5 [superseded <date>] lines in the block; memory.graph_explain always includes them.
Publish a new version after changing the block; GET /v1/agents/:id/effective-config shows the resolved result. Decay, when long_term.decay is on, archives non-pinned low-confidence stale facts and the entities left without active facts — pin what must survive.
Administrators (and API keys, which act as admin) can assert, correct, approve, pin, merge and forget. Every mutation writes one audit_log row whose meta carries scope and ids only — never the fact text. Human assertions get their own human episode, so provenance stays honest.
assert a fact that supersedes an older oneShell
curl-s -X POST "$HIVE_API_URL/v1/memory/graph/facts"\-H "Authorization: Bearer $HIVE_API_KEY"-H "X-Tenant-Id: $HIVE_TENANT_ID"\-H "Content-Type: application/json"\-d '{"subject":"Acme","subject_type":"organization","predicate":"billing_contact","object":"Omar","object_type":"person","fact":"Omar handles billing at Acme from October.","agent_id":"'$AGENT_ID'","valid_from":"2026-10-01T00:00:00Z"}'# → 201 GraphEdge (status active). The earlier "Nadia handles billing at Acme."# fact is now status: invalidated, valid_to: 2026-10-01T00:00:00Z — kept, not deleted.
“No longer true” is PATCH /v1/memory/graph/edges/:id with valid_to (or status: invalidated) — history is kept and memory.graph_explain still shows it.
Duplicates are merged with POST /v1/memory/graph/entities/:id/merge; both entities must share a scope (409 SCOPE_MISMATCH otherwise), and a rename that collides with another active entity is refused with 409 ENTITY_NAME_TAKEN — merge instead.
Search and authoring embed text, so they need the deployment’s embedding gateway — 503 UNAVAILABLE when none is configured; the plain reads keep working.
The same actions live in the console under Memory → Graph (filter by agent or contact, replay “as of” a date, show superseded facts), with links from each agent and each contact.
Export. The owner export GET /v1/tenants/current/export carries memory_graph: { entities, edges, episodes } next to the flat memory rows (embeddings excluded).
Erase.POST /v1/end-users/:id/erase removes every graph row referencing the contact: its end_user-scoped entities, facts and episodes, plus any fact whose subject or object is one of its entities. Flat rows of the contact go with them.
Retention. The graph falls under the agent_memory data class: the retention purge drops non-pinned facts and episodes older than the TTL and the entities left without facts.
Reads need any member; mutations need the admin role. All paths take the usual Authorization: Bearer hive_… + X-Tenant-Id (see API keys & authentication). The memory graph has no @hive/sdk wrapper yet — call it with fetch.
Active facts in either direction (pinned first, then confidence).
timeline[]
GraphEdge[]
Every fact ever asserted about the entity, valid_from ascending — superseded ones included with their valid_to / invalidated_by. pending facts appear for admins only.
possible_duplicates[]
GraphEntity[]
Same-scope active entities whose embedding is close (cosine ≥ 0.85, ≤ 5) — candidates for merge.
GET/v1/memory/graph/neighborhoodNodes + edges for a canvas: BFS from one entity, or the top entities of a scope.
Auth
API key + X-Tenant-Id (any member; an API key acts as admin)
hop 0 = seed hit; sources = run ids of the episodes that asserted the fact (newest first, ≤ 5).
block
string
The rendered # Memory graph (as of …) prompt block.
tokens
number
Estimated tokens of block.
With agent_id the hops / seeds / budget come from that agent’s resolved memory.graph, so the block matches the run-time one. Visibility is scope-explicit: tenant facts ∪ the agent’s agent facts ∪ the given contact’s end_user facts — never another contact’s.
Search does not mark facts as recalled (last_recalled_at is untouched).
GET/v1/memory/graph/edges/:idOne fact with its provenance (which runs asserted it) and what superseded it.
Auth
API key + X-Tenant-Id (any member; an API key acts as admin)
Entity names; resolved to existing entities by normalized name / alias, created otherwise.
subject_type / object_type
string? (default `unknown`)
Entity types.
predicate
string (snake_case, ≤ 64)
reports_to, prefers_channel, works_on, …
fact
string (1–400)
One plain sentence.
agent_id
uuid?
Agent scope. Required when `end_user_id` is set — a contact fact is always owned by the agent that serves the contact.
end_user_id
uuid?
Contact scope (with agent_id).
valid_from
ISO date-time?
When the fact became true (default now).
confidence
number? (0–10)
Default 8.
Response
Field
Type
Description
(body)
GraphEdge
The new fact.
Scope: end_user when end_user_id is present, else agent when agent_id is present, else tenant. Unknown agent / contact → 404.
Writes a human episode + mention. A same-subject same-predicate active fact is superseded (invalidated with valid_to = the new valid_from), never deleted. Subject and object must resolve to two different entities (400 VALIDATION).
Audit: memory.graph.fact.create.
PATCH/v1/memory/graph/edges/:idEdit, invalidate, restore, approve, pin or archive a fact.
Non-null ⇒ invalidate at that instant (an explicit valid_to always re-dates the window, even on an already-invalidated fact); null clears the end.
status
`active` | `invalidated` | `archived`?
invalidated ends the fact now; active on a pending fact approves it (and supersedes the active same-subject same-predicate facts), otherwise restores a superseded fact; archived hides it from recall.
pinned
boolean?
Pinned facts are exempt from decay and retention purges.
confidence
number? (0–10)
Response
Field
Type
Description
(body)
GraphEdge
The updated fact.
Audit: memory.graph.fact.invalidate when invalidating, else memory.graph.fact.update.
PATCH/v1/memory/graph/entities/:idRename, summarize, retype or pin an entity.
Edges are re-pointed, aliases and mention counts unioned, :id becomes status: merged with merged_into. Facts that linked the two entities (would-be self-loops) are invalidated, not deleted.
Audit: memory.graph.entity.merge.
DELETE/v1/memory/graph/edges/:id204Forget one fact (compliance hard delete; its mentions cascade).