Skip to content
UHPUHPDeveloper Guide
Independent developer guide. Not affiliated with HarnessRouter or the official UHP project.

UHP Memories

The optional Memories chapter gives UHP a place to keep what an agent learns, and a way to say who may read it.

Page reviewed 18 Oct 2026 Source and review policy

Protocol: 2026-10-04Capability: memoriesConformance: 2026.10.4.post11

2026-10-04 is additive to 2026-09-28: every request and object valid under the previous version is valid under the new one. The new surface is one chapter, Memories, and it is the first part of the protocol to say where an agent keeps what it learns.

The problem it names is ordinary. A session’s conversation ends when the session ends, and a harness’s instructions are written once by its owner and do not change as the agent works. So whatever an agent learned along the way is lost — that a customer prefers email, what the team decided last week, which procedure worked last time.

A memory is a named container of records: a fact, a note, a procedure, a person or a company the others are about. Records point at one another, and those pointers are the only graph there is: what was concluded from what, who a fact is about.

The chapter is optional. A server advertises it with the memories capability, and a server that does not implement it is conformant at every class. Nothing in the rest of UHP changes when a server has memory.

The chapter fixes only what a harness and a memory backend must agree on, and leaves the rest to each layer.

LayerWhat it owns
The providerIts own store and intelligence: how memory is derived and kept
UHP MemoriesWhat a memory is, who may reach it, and when a harness calls it
MCPThe tools an agent holds: how an agent calls it in a turn

Extraction, ranking, consolidation and storage belong to the provider. A provider that runs an agent of its own to decide what to keep is a provider like any other and declares it.

Memories are arranged in a tree, the way folders are, so one memory can hold what a whole company knows and another, beneath it, what is known about one customer. Access is granted per memory, and a memory’s children inherit it.

The tree replaces fixed scopes. Memory products each name their own levels — user, agent, run, app, team, project — and none of the lists agree. A level is a meaning an implementation gives to a place in a tree: the protocol carries the tree and the inheritance, and a server is free to say that one node is an organization and the one under it a customer. A node exists where access differs, and nowhere else.

FieldMeaning
idOpaque to a client. A server that mints its own ids uses the hmem_ prefix; a server whose memories are a tree it already keeps keeps that tree’s ids
nameHuman-readable
descriptionWhat this memory holds. An agent reads it to decide whether to look inside, so it is content, not decoration
parent_idThe containing memory; null on a root
ancestorsThe ids from the root down to the parent, as far up as the caller may see. Placement only: reading a memory never reads an ancestor’s records
restrictedWhen true, grants on ancestors stop here
providerWhich provider keeps this memory’s records. Set at creation, inherited from the parent when omitted
privilegesThe caller’s effective privileges on this node
records, childrenCounts, for a caller deciding where to look

A listing is one level. Without parent it answers where the caller enters the tree: every memory it holds a privilege on whose parent it cannot see. ?granted=true answers the other half — every memory the caller may read whose parent it cannot see, and every memory below those on which what it may do differs from what it may do on the parent, each with the caller’s privileges there, highest in the tree first.

One vocabulary names everyone who can act on a memory, and it is used in two places: a grant names who may act, and a record’s written_by names who did.

KindWhoHolds grantsWrites records
memberAnyone who acts: a person or an agentyesyes
groupA set of members the server resolves: a team, a workspaceyesno, its members do
providerThe memory’s provider itselfnoyes

A person and an agent are the same kind. Both are members, granted the same way, checked the same way, and able to belong to the same groups. Which of the two a member is is a property of the member, type: human or agent. No rule of the chapter depends on it.

A grant gives one principal a set of privileges on one memory. A principal’s effective privileges on a memory are the union of its grants on that memory and on every ancestor. Access is additive: there is no deny, and no grant means no access. Whoever creates a memory holds all four privileges on it, an implicit grant that flows down like any other.

PrivilegePermits
readrecall, get, list, history, running a named query marked read
writeobserve, remember, revise, forget, defining a named query
createCreating a child memory
deleteerase, deleting the memory, changing its grants

A memory marked restricted stops the inheritance: grants on its ancestors do not reach it or anything below it. The tree is where access differs, and the cutoff is how a branch is closed without moving it out of the tree.

Enforcement is the server’s. A filter applied by the agent, or one a query the agent wrote could omit, is not enforcement.

A record is one thing a memory holds. Its id is the provider’s and opaque to a client; it carries memory_id, a type (fact, note, procedure, person, company and the kinds a server adds), a title, content, written_by and, for a fact that can change, the records that supersede it.

content is always an ordered list of parts. The morning version of 2026-10-04 said a record’s content was text or one file reference, which could not hold a picture beside what it shows, or a conversation turn with a file in it. The same-day patch settled it: two kinds of part are defined, text and file; a modality is a media type of a file part, not a kind of part, so a new one needs no change here, and any other kind is x.-prefixed and carried unchanged. A file part may carry the words that stand for it, which is what recall by words finds. A string remains shorthand for one text part on a write.

A file part’s bytes are read at GET /v1/memories/{id}/records/{rid}/content/{index}. A provider’s capability document says what it keeps in content (media, bytes, describes), replacing the earlier files flag, and a write of a media type the provider does not keep is refused with memory_unsupported — never stored without its file.

A second same-day patch made a memory’s id genuinely opaque. The schema had required the hmem_ prefix, which a server whose memories are a tree it already keeps could meet only with a second id space and a lookup between the two. The prefix remains the convention for a server that mints its own ids; it is no longer a requirement.

A question is asked of a memory and of everything below it that the caller may read. That is what a search is for: the caller does not yet know where the answer is kept, so it asks high in the tree and learns where to look.

Each result therefore names the memory it is in. That memory is a starting point: the agent goes there and reads around what it found, lists it, asks again inside it, or moves to its parent or its children. The response also carries where the caller can go from the memory it asked: the parent and the direct children, each with its name, description and counts, and only those the caller may read.

Four rules bound this:

  • Down, never up. recall searches the memory and its descendants. It never searches an ancestor. An agent that wants to look higher asks the parent, by its own next call.
  • Only what the caller may read. Each descendant is judged on its own. A branch behind a cutoff the caller is not let through is not searched, and a memory inside such a branch that the caller does hold a grant on is. The server decides this before the provider is asked; a provider is never asked to search a memory the caller may not read.
  • depth narrows it. depth: 0 searches the memory alone, 1 adds its children, and so on. Left out, the whole subtree is searched.
  • A cap must be declared. A server MAY cap how many memories one question covers, and MUST say so in degraded when it did. The caller then asks lower in the tree.

A harness reaches memory at four moments of a session’s life, and only two of them are the agent’s choice.

MomentCalled byWhat happens
Primeserver, at session start and after the conversation is compactedThe server reads the standing records of each memory the agent was granted and places them, with each memory’s name, description and children, in the agent’s instructions
Toolsagent, during a turnmemory_list, memory_recall, memory_graph, memory_get, memory_remember, memory_revise, memory_forget, memory_run_query for the named queries a memory lists, memory_operate for a type’s operations, and memory_query where a provider offers free queries
Observeserver, when a turn endsThe turn is sent to the default memory as episodes. No model is involved on the server’s side
Consolidateserver, on a schedule or when idleThe server asks the provider to do its background work

Write tools are offered only to an agent that holds write somewhere. The memories it was granted are where the agent starts; from each it may walk to the parent and the children a response names, and on from there, as far as its own privileges reach. The server checks every step.

Standing records are a memory’s instructions and preferences: the active records whose attributes.standing is true. They are read at their own route, so a server that runs the agent and does not keep its memories asks the server that does:

GET /v1/memories/{id}/prime?budget=4000

The server keeps no memory of its own: a deployment has memories once it is connected to a provider, with a credential that belongs to whoever connected it and is held as every other credential is. Everything above the provider — the tree, the grants, the cutoff, references across memories — belongs to the server and is the same whichever provider keeps the records.

What a provider can do is declared, not assumed:

GET /v1/memories/providers
{ "data": [ {
"id": "example",
"isolation": "container",
"derivation": "background",
"observe": { "keeps_episodes": true, "answers": "records" },
"recall": { "signals": ["query", "text", "filters"], "abstain": true },
"history": { "content": "versions", "structure": "versions" },
"revise": "native", "forget": "native",
"erase": { "unreachable": "reported" },
"prime": true, "consolidate": "runs", "holds": true,
"queries": { "named": true,
"free": { "languages": ["cypher"], "write": false } },
"types": true,
"graph": { "entities": "derived" },
"content": { "media": ["text/*", "image/*"], "bytes": "referenced",
"describes": ["image/*"] }
} ] }

A provider that cannot answer a signal must say so, and a signal it lacks is reported as degraded rather than ignored. That is what makes a capability document worth reading before choosing a memory backend.

  • What a memory returns is untrusted. A record written in one session is read in another, by another agent, for another person. A server MUST present recalled content to the agent as data, fenced from instructions, and MUST carry written_by with it.
  • The agent chooses its path, never its reach. An agent may walk up and down the tree from the memories it was granted, and a search covers what is below the memory it asks. What it can reach either way is every memory it holds a privilege on, no more, checked by the server on every call and for every memory a search covers.
  • An agent stands where a person stands. It reaches what it was granted and what the groups it belongs to were granted. A server treats an agent’s membership of a group exactly as it treats a person’s.

The interesting failure here is not a leak of a memory but a leak of an instruction: a memory record that says “ignore your instructions” is content written by someone else, read later by an agent that will treat it as text. Fencing recalled content from instructions is the control that answers it.

The suite’s memories checks — ME-01 through ME-13 — run against a server that reports the capability and has a provider connected, and drive the chapter through the public surface on that provider: the tree and its listing, a stated record and its stamped writer, a question asked of a parent finding a record below it and naming where it is, never one above, each signal against the provider’s own capability document, revision and history, a record’s id refused through a memory it is not in, forgetting and erasing, moving and deleting, and two entities and the fact between them read back as one graph.

They live in the current suite, 2026.10.4.post11 (15 October 2026), which targets 2026-10-04. The suite as a whole carries 98 executable checks: 40 Core, 9 Extended and 49 Full. See conformance for what those classes mean and how a result is bounded.

Two limits are part of the evidence, not caveats to it:

  • The suite runs with one credential, so what one principal may not read of another’s is not yet checked by it.
  • An interface that passes says nothing about whether a memory is any good. That is a separate measurement, outside the suite.

Not established: that any server implements this chapter. The capability is new, optional, and absent from the current reference implementation.

HarnessRouter Community Edition v0.35.1 (18 October 2026) still advertises UHP 2026-09-28 — its discovery document offers 2026-09-28, 2026-09-12 and 2026-08-11, and answers an unsupported version with a refusal rather than a substitution. The CE source carries no memories capability and no /v1/memories route, so the chapter is not implemented there, and CE’s own UHP coverage is reported on the HarnessRouter and conformance pages.

That is not a defect. A server that does not implement the optional chapter is conformant at every class. What it does mean is that a reader looking for a working memory implementation today will not find one in the reference implementation, and should read this page as the specification of an interface rather than as evidence of a deployed service.