Agentic Workforce ME Developer PortalDocs 1.7 · Widget 0.5.0

Backend integration

Memory & the memory graph

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.

Who sees what — scopes

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.

ScopeWho writes itWho recalls it
tenantAdministrators (console “Add fact”, POST /v1/memory/graph/facts without ids, POST /v1/memory).Every agent in the workspace.
agentThe curator and memory.save in anonymous runs (no served contact); admins with agent_id.That agent, in every conversation.
end_userThe 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.

The memory graph

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.

Contradictions invalidate, nothing is deleted

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.

What the agent sees

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:

BuiltinInputWhat it answers
memory.graph_search{ query, entity?, top_k?, as_of?, include_superseded? }Hybrid seed + k-hop dated facts for a question; as_of (ISO date-time or YYYY-MM-DD) answers “what was true then”. Evidence carries run ids.
memory.graph_explain{ entity }One entity’s summary, aliases and full timeline — superseded facts included, with the runs they came from. For “how do you know” and “what changed”.

GET /v1/memory/graph/search runs the same engine and returns the exact prompt block, so you can see what the agent would see for a contact:

what would the agent see?Shell
curl -s "$HIVE_API_URL/v1/memory/graph/search?q=who%20handles%20billing%20at%20Acme&agent_id=$AGENT_ID&end_user_id=$CONTACT_ID" \
  -H "Authorization: Bearer $HIVE_API_KEY" -H "X-Tenant-Id: $HIVE_TENANT_ID"
# → { facts: [{ subject: "Acme", predicate: "billing_contact", object: "Nadia",
#               fact: "Nadia handles billing at Acme.", valid_from: "2026-09-01T…",
#               valid_to: null, status: "active", hop: 0, score: 0.91,
#               sources: ["01a0a7…"] }],
#     block: "# Memory graph (as of 2026-09-16)\n- [since 2026-09-01] Nadia handles billing at Acme.",
#     tokens: 23 }

Enabling it — the memory.graph manifest block

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

agent manifest (excerpt)JSON
{
  "schema_version": "2.10",
  "memory": {
    "long_term": { "enabled": true, "scope": "agent" },
    "graph": {
      "enabled": true,
      "extraction": "curator",
      "write_scopes": ["agent", "end_user"],
      "max_hops": 2,
      "recall_budget_tokens": 600
    }
  }
}
FieldTypeMeaning
memory.graph.enabledboolean, default falseMaster 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.extractionoff | curator, default curatoroff keeps recall and the builtins but never extracts — for human-authored graphs.
memory.graph.write_scopes(agent | end_user)[], default bothScopes 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_hopsinteger 1–3, default 2Expansion depth for the prompt block and memory.graph_search.
memory.graph.recall_budget_tokensinteger 100–4000, default 600Token budget of the injected # Memory graph (as of <date>) block.
memory.graph.seed_top_kinteger 1–20, default 8Seeds per retrieval leg (entity cosine, entity full-text, fact cosine, fact full-text).
memory.graph.max_entities_per_episode · max_edges_per_episodeinteger 1–50, default 15 · integer 1–80, default 25Per-run extraction caps.
memory.graph.contradiction_checkboolean, default trueWhen 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_promptboolean, default falseAlso 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.

Authoring and curating facts

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.

Data-subject requests and retention

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

Endpoints

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.

GET/v1/memory/graph/entitiesList entities (keyset cursor, newest first).
Auth
API key + X-Tenant-Id (any member; an API key acts as admin)
Errors
UNAUTHORIZED TENANT_REQUIRED VALIDATION

Request

FieldTypeDescription
agent_iduuid?Only this agent’s scope.
end_user_iduuid?Only this contact’s scope (the per-person graph).
crew_iduuid?Only this crew’s scope.
qstring? (1–200)Full-text match on the entity name.
typestring?Entity type (person, organization, …).
status`active` | `merged` | `archived`Default active.
pinned`true` | `false`?Pinned entities only / never.
cursor / limitstring? / number?Keyset pagination (default 50, max 200).

Response

FieldTypeDescription
items[]{ id, scope, agent_id, end_user_id, crew_id, entity_type, name, aliases[], summary, status, merged_into, pinned, mention_count, first_seen_at, last_seen_at, created_by, created_at, updated_at }Entities.
next_cursor / has_morestring | null / booleanPagination.
GET/v1/memory/graph/entities/:idOne entity with its active facts, full timeline and possible duplicates.
Auth
API key + X-Tenant-Id (any member; an API key acts as admin)
Errors
UNAUTHORIZED TENANT_REQUIRED NOT_FOUND VALIDATION

Response

FieldTypeDescription
entityGraphEntityThe entity.
facts[]GraphEdge[]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)
Errors
UNAUTHORIZED TENANT_REQUIRED NOT_FOUND VALIDATION

Request

FieldTypeDescription
entity_iduuid?Start entity. Omitted ⇒ the top-limit active entities of the filter by pinned desc, mention_count desc.
agent_iduuid?Only this agent’s scope.
end_user_iduuid?Only this contact’s scope (the per-person graph).
hopsnumber? (1–3)Expansion depth (default 1).
as_ofISO date-time?Replay the graph as it stood at this instant (validity filter). Default now.
include_superseded`true` | `false`?Also return facts whose validity window had already ended at as_of.
limitnumber? (1–200)Max nodes (default 60).

Response

FieldTypeDescription
nodes[] / edges[]GraphEntity[] / GraphEdge[]The closure: every visible edge among the returned nodes.
root_iduuid | nullentity_id when given.
as_ofISO date-timeThe instant applied.
truncatedbooleanTrue when the node cap, the fan-out (20 per node) or the 500-edge cap cut the result.
Auth
API key + X-Tenant-Id (any member; an API key acts as admin)
Errors
UNAUTHORIZED TENANT_REQUIRED VALIDATION UNAVAILABLE

Request

FieldTypeDescription
qstring (1–1000)The question.
agent_iduuid?Only this agent’s scope.
end_user_iduuid?Only this contact’s scope (the per-person graph).
entity_iduuid?Restrict seeds to this entity.
hopsnumber? (0–3)Override the agent’s max_hops.
as_ofISO date-time?Facts true at this instant.
include_superseded`true` | `false`?Also return superseded facts.
limitnumber? (1–50)Default 20.

Response

FieldTypeDescription
facts[]{ id, subject_id, object_id, subject, predicate, object, fact, valid_from, valid_to, status, confidence, pinned, mention_count, hop, score, sources[] }hop 0 = seed hit; sources = run ids of the episodes that asserted the fact (newest first, ≤ 5).
blockstringThe rendered # Memory graph (as of …) prompt block.
tokensnumberEstimated 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)
Errors
UNAUTHORIZED TENANT_REQUIRED NOT_FOUND VALIDATION

Response

FieldTypeDescription
edge{ id, scope, agent_id, end_user_id, crew_id, subject_id, object_id, subject_name, object_name, predicate, fact, valid_from, valid_to, invalidated_at, invalidated_by, status, confidence, source, pinned, mention_count, last_recalled_at, created_by, created_at, updated_at }The fact.
mentions[]{ episode_id, run_id, thread_id, kind, reference_time, digest, created_at }Episodes that asserted it, newest first (≤ 100). kind is run, human or import.
superseded_byGraphEdge | nullThe newer fact that invalidated this one, when any.
  • pending (unapproved) facts are 404 for members; admins see them.
GET/v1/memory/graph/episodes/:idOne extraction episode: counts, model, tokens and a ≤ 600-char digest — never the transcript.
Auth
API key + X-Tenant-Id (any member; an API key acts as admin)
Errors
UNAUTHORIZED TENANT_REQUIRED NOT_FOUND VALIDATION

Response

FieldTypeDescription
id, agent_id, end_user_id, run_id, thread_id, kind, reference_time, digest, entities_extracted, edges_extracted, edges_invalidated, model, tokens_in, tokens_out, created_by, created_atGraphEpisodeEpisode metadata.
POST/v1/memory/graph/facts201Assert a fact as a human. Scope derives from the ids you pass.
Auth
API key + X-Tenant-Id (tenant admin)
Errors
UNAUTHORIZED TENANT_REQUIRED FORBIDDEN NOT_FOUND VALIDATION UNAVAILABLE

Request

FieldTypeDescription
subject / objectstring (1–200)Entity names; resolved to existing entities by normalized name / alias, created otherwise.
subject_type / object_typestring? (default `unknown`)Entity types.
predicatestring (snake_case, ≤ 64)reports_to, prefers_channel, works_on, …
factstring (1–400)One plain sentence.
agent_iduuid?Agent scope. Required when `end_user_id` is set — a contact fact is always owned by the agent that serves the contact.
end_user_iduuid?Contact scope (with agent_id).
valid_fromISO date-time?When the fact became true (default now).
confidencenumber? (0–10)Default 8.

Response

FieldTypeDescription
(body)GraphEdgeThe 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.
Auth
API key + X-Tenant-Id (tenant admin)
Errors
UNAUTHORIZED TENANT_REQUIRED FORBIDDEN NOT_FOUND VALIDATION UNAVAILABLE

Request

FieldTypeDescription
factstring? (1–400)New sentence (re-embedded).
valid_toISO date-time | null?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.
pinnedboolean?Pinned facts are exempt from decay and retention purges.
confidencenumber? (0–10)

Response

FieldTypeDescription
(body)GraphEdgeThe 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.
Auth
API key + X-Tenant-Id (tenant admin)
Errors
UNAUTHORIZED TENANT_REQUIRED FORBIDDEN NOT_FOUND ENTITY_NAME_TAKEN VALIDATION

Request

FieldTypeDescription
namestring? (1–200)Recomputes the normalized name and re-embeds.
summarystring | null? (≤ 500)
entity_typestring? (≤ 64)
pinnedboolean?Exempt from decay / retention.

Response

FieldTypeDescription
(body)GraphEntityThe updated entity.
  • Audit: memory.graph.entity.update.
POST/v1/memory/graph/entities/:id/mergeMerge :id into another entity of the same scope; returns the survivor.
Auth
API key + X-Tenant-Id (tenant admin)
Errors
UNAUTHORIZED TENANT_REQUIRED FORBIDDEN NOT_FOUND SCOPE_MISMATCH VALIDATION

Request

FieldTypeDescription
intouuidThe surviving entity.

Response

FieldTypeDescription
(body)GraphEntityThe survivor.
  • 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).
Auth
API key + X-Tenant-Id (tenant admin)
Errors
UNAUTHORIZED TENANT_REQUIRED FORBIDDEN NOT_FOUND VALIDATION
  • Audit: memory.graph.fact.delete. For “this is no longer true” use PATCH with valid_to instead — history is kept.
DELETE/v1/memory/graph/entities/:id204Forget an entity (hard delete; its facts in both directions and their mentions cascade).
Auth
API key + X-Tenant-Id (tenant admin)
Errors
UNAUTHORIZED TENANT_REQUIRED FORBIDDEN NOT_FOUND VALIDATION
  • Audit: memory.graph.entity.delete.