URN composition¶
Hadron uses URNs (Uniform Resource Names) to identify every addressable entity — orgs, agents, apps, memories, nodes, users, and more. URNs are stable, globally unique, and human-readable. This page is the reference for how they're built.
The rules below are locked by spec 021 (URN composition) and apply across hadron-server, hadron-portal, hadron-client, and the MCP tool surface.
Anatomy of a URN¶
Every Hadron URN has the same shape:
hrn:— the canonical scheme prefix (Hadron Resource Name, in the spirit of AWS'sarn:). It signals at a glance that the value is a Hadron reference.<type>— the URN's resource type, drawn from the registered set below.<path>— one or more hierarchy segments describing where the entity lives.
Scheme prefix: hrn: (canonical) and urn: (legacy)¶
The legacy scheme urn:<type>: is accepted on input forever — there is no deprecation window. Both hrn:agent:acme.com::coding-agent and urn:agent:acme.com::coding-agent parse identically. The server always emits the hrn: form: a urn:-prefixed input round-trips to a hrn:-prefixed canonical form (the parser records a legacy-urn-scheme rewrite for observability). Prefer hrn: in new content; you'll still see urn: in older data and it will keep working.
The path uses two delimiters:
::— separates hierarchy levels (org / app / agent / memory-role / node-loc).:— separates slug atoms within a single level (e.g., a type marker before a slug, or an author-org before an agent slug).
That distinction is the most important thing to internalize. Everything below builds on it.
Type registry¶
The complete set of URN types Hadron recognizes:
| Group | Types |
|---|---|
| Core | agent, app, app-user, ai-config, asset, edge, license, memory, node, org, platform, reference, session, subscription, usage, user |
| Node types | abstract, partial, parent, plan, prompt, record, task, review |
| Node roles | chat, chat-message, config, conversation, event, goal, stage |
| Node parts | condition, data |
Types are matched case-sensitively. The list is locked but extensible — a future spec will define a registration mechanism for third-party orgs. For now, treat the set as fixed.
loc: is deprecated as a URN type. Stored data containing hrn:loc:... (or legacy urn:loc:...) segments is treated as a data defect; new code MUST use the current grammar.
Hierarchy levels by entity¶
The number and meaning of ::-separated levels depends on what's being addressed.
Org URN¶
One level. The org slug is conventionally a domain name (micromentor.org, hadronmemory.com).
Examples:
App URN¶
Two levels. An App is the runtime deployment of an Agent into an org.
Examples:
Agent URN — definition¶
The agent as authored by its owning org:
Examples:
Agent URN — installed (R2 form)¶
When an Agent is installed into another org's App, the install gets a canonical URN that embeds the author-org so two installs of same-slug agents from different authors stay distinguishable:
Note the single : between <author-org> and <agent-slug> — that's not a hierarchy boundary; the author-org and agent-slug together form a single path-segment that's a verbatim sub-path of the agent's definition URN.
Examples:
hrn:agent:micromentor.org::coding-app::hadronmemory.com:coding-agent
hrn:agent:micromentor.org::coding-app::competitor.com:coding-agent
These two URNs differ only at the author-org segment — they point to two distinct installs of two same-slug agents from different authors. The author-org is the disambiguator.
If the installing org is the author org (install-by-self), the duplicate org segment is dropped:
hrn:agent:hadronmemory.com::coding-app::hadronmemory.com:coding-agent
↓ collapses
hrn:agent:hadronmemory.com::coding-app::coding-agent
You can query this canonical install URN from the GraphQL API via the App.installedAgentUrn field — the server computes it for you.
Memory URN¶
A memory's URN depends on its scope. There are six shapes in total — one standalone org-owned shape and five App/Agent-scoped shapes:
# Org-owned knowledge memory (standalone, no App or Agent context)
hrn:memory:<org-slug>::<memory-slug>
# System memory of an installed agent
hrn:memory:<installing-org>::<app-slug>::<author-org>:<agent-slug>::system
# App-wide memory (no agent segment — belongs to the App globally)
hrn:memory:<installing-org>::<app-slug>::app-mem
# Per-user memory in an App
hrn:memory:<installing-org>::<app-slug>::<author-org>:<agent-slug>::app-user:<user-slug>
# Private per-user memory
hrn:memory:<installing-org>::<app-slug>::<author-org>:<agent-slug>::priv:<user-slug>
# Group-shared memory
hrn:memory:<installing-org>::<app-slug>::<author-org>:<agent-slug>::group-mem:<group-slug>
The trailing segment of the App/Agent-scoped shapes is the memory-role marker. There are exactly five:
| Role | Parameterized? | Includes agent segment? | Use |
|---|---|---|---|
system |
no | yes | The agent's system memory (read-only at runtime). |
app-mem |
no | no | App-wide memory; belongs to the App globally, not to a specific agent install. |
app-user |
yes — slug or PK | yes | A user's memory inside the App. |
priv |
yes — slug or PK | yes | A user's private memory (not visible to the App owner). |
group-mem |
yes — slug or PK | yes | Memory shared by a named group within the App. |
See Memory ownership and lifecycle for the per-role ownership and visibility rules.
Examples (canonical form throughout — author-org included where it applies):
hrn:memory:micromentor.org::shared-glossary
hrn:memory:micromentor.org::coding-app::hadronmemory.com:coding-agent::system
hrn:memory:micromentor.org::coding-app::app-mem
hrn:memory:micromentor.org::coding-app::hadronmemory.com:coding-agent::app-user:shadowbrush
hrn:memory:micromentor.org::coding-app::hadronmemory.com:coding-agent::priv:c0123abc456def789012345678
Node URN¶
Nodes live inside memories. The node's URN extends its memory's URN by one additional ::-segment — the node loc (the colon-separated intra-memory path):
For a node-role node, the URN can also use the role-type directly:
hrn:prompt:<memory-path>::<node-loc>
hrn:review:<memory-path>::<node-loc>
hrn:record:<memory-path>::<node-loc>
Both forms address the same node; the role-form is canonical at the surface (storage, audit logs), while hrn:node: is accepted as an alias for backward compatibility.
Example — a prompt-typed node at loc review:sort-imports in the system memory of an installed coding-agent:
Note the node loc review:sort-imports keeps its internal : unchanged — node locs are free-form intra-memory paths and aren't ::-separated.
Slug rules¶
Every slug atom — the :-separated pieces within a path-segment — follows the same rules:
- Charset: letters, digits, dots, underscores, and hyphens. Must start and end with a letter or digit.
- Regex:
/^[a-zA-Z0-9][a-zA-Z0-9._-]*[a-zA-Z0-9]$|^[a-zA-Z0-9]$/ - Length: 64 characters maximum.
- Case-sensitive storage; case-insensitive reserved-word check.
- Reserved words: the full URN-type registry plus the role-marker names — see below.
The charset deliberately allows dots, so domain-name slugs like a.b.c.com and dotted-namespace identifiers work uniformly.
Reserved words¶
A slug equal (case-insensitive) to any of these is rejected at entity-create time:
- All URN types:
agent,app,app-user,ai-config,asset,edge,license,loc,memory,node,org,platform,reference,session,subscription,usage,user,abstract,partial,parent,plan,prompt,record,task,review,chat,chat-message,config,conversation,event,goal,stage,condition,data - All memory-role markers:
system,app-mem,app-user,group-mem,priv
loc is in the reserved-word list even though loc: is deprecated — future-proofing the namespace.
The check is case-insensitive: Agent, AGENT, aGeNt all fail.
ID or slug at parameterized role-marker positions¶
For the three parameterized memory-role markers (app-user, priv, group-mem), Hadron's server dispatches on the shape of the parameter:
- CUID (
^c[a-z0-9]{24}$) or 32-char hex PK (^[0-9a-f]{32}$) → resolved as an internal ID. - Anything else → resolved as a slug.
The 32-char hex pattern is named UUIDV7_HEX in the server source for historical reasons but accepts any 32-character lowercase hex string; it doesn't enforce the UUIDv7 version/variant nibbles.
Both forms address the same memory; backend code can pass the PK it holds, UI code can pass the slug:
hrn:memory:micromentor.org::coding-app::hadronmemory.com:coding-agent::app-user:shadowbrush
hrn:memory:micromentor.org::coding-app::hadronmemory.com:coding-agent::app-user:c0123abc456def789012345678
The two URNs resolve to the same memory.
Aliases and canonical forms¶
Hadron accepts several alias forms at input but emits a single canonical form in storage, audit logs, and API responses. There are five alias categories.
0. Legacy urn: scheme¶
The legacy urn: scheme prefix is an alias for the canonical hrn: scheme. Accepted on input forever (no deprecation); always emitted as hrn::
| Form | Example |
|---|---|
| Alias | urn:agent:micromentor.org::coding-app::hadronmemory.com:coding-agent |
| Canonical | hrn:agent:micromentor.org::coding-app::hadronmemory.com:coding-agent |
1. Source / installation memory¶
The install-by-self URN is an alias for the definition URN:
| Form | Example |
|---|---|
| Alias | hrn:memory:hadronmemory.com::hadronmemory.com:coding-agent::system |
| Canonical | hrn:memory:hadronmemory.com::coding-agent::system |
2. Node-role polymorphism¶
A node with a known role is addressable as either hrn:node: or hrn:<role>::
| Form | Example |
|---|---|
| Alias | hrn:node:micromentor.org::coding-app::hadronmemory.com:coding-agent::system::review |
| Canonical | hrn:review:micromentor.org::coding-app::hadronmemory.com:coding-agent::system::review |
3. Path/slug shortening¶
When the slug is unique in scope, the shortened form omits the author-org:
| Form | Example |
|---|---|
| Alias | hrn:agent:micromentor.org::coding-app::coding-agent |
| Canonical | hrn:agent:micromentor.org::coding-app::hadronmemory.com:coding-agent |
The canonical form always includes the author-org so an audit entry doesn't silently shift meaning if a future install adds a same-slug agent from a different author.
4. Type-marker optionality¶
Type markers (app:, agent:, memory:) inside path-segments are optional:
| Form | Example |
|---|---|
| Alias | hrn:agent:micromentor.org::app:coding-app::agent:hadronmemory.com:coding-agent |
| Canonical | hrn:agent:micromentor.org::coding-app::hadronmemory.com:coding-agent |
The canonical form is the bare shape, without the markers.
Case sensitivity¶
URN slug storage is case-sensitive. hrn:agent:org::Coding-Agent and hrn:agent:org::coding-agent are two different URNs — they identify two different agents.
The reserved-word check is the only case-insensitive part of slug validation; it prevents users from creating slugs like Agent or aGENT that would collide with the reserved type names.
Deprecated: the loc: prefix¶
The pre-2026-05 loc: sugar — a relative-path shorthand for node locs — is removed entirely. The parser rejects any URN containing a loc: segment.
Stored data with loc: segments is treated as a data defect; no transparent migration is provided.
Where to use the canonical form¶
| Surface | Form |
|---|---|
| GraphQL responses | Canonical |
| MCP tool inputs | Any alias accepted; canonical persisted |
| Audit logs | Canonical |
Portal <Urn> rendering |
Canonical |
| Database storage | Canonical |
| User-pasted inputs | Any alias accepted |
Two callers submitting two different alias forms for the same logical URN produce byte-identical persisted records and audit entries.
API input contract — URN inputs must be fully qualified¶
Per spec 022, every API surface that accepts a URN — MCP tool, GraphQL resolver, hadron-client SDK function — requires URN inputs to be fully qualified. (ID-shape inputs and shorthand bare forms are accepted per the exceptions below.)
| Entity type | Minimum shape | Examples |
|---|---|---|
org |
<org> |
acme.com or hrn:org:acme.com |
memory |
<org>:<memory> |
acme.com:mmdata or hrn:memory:acme.com::mmdata |
agent / app |
<org>:<slug> |
acme.com:coding-agent or hrn:agent:acme.com::coding-agent |
node |
<org>:<memory>:<loc> |
acme.com:mmdata:review:sort-imports or hrn:node:acme.com::mmdata::review:sort-imports |
The prefixed examples use the canonical hrn: scheme; the legacy urn: scheme (e.g. urn:node:acme.com::mmdata::review:sort-imports) is equally accepted on input.
Relative-form URNs (review:sort-imports without the <org>:<memory> prefix) are rejected at the API boundary with:
URN "review:sort-imports" is not fully qualified.
Expected a node URN with at least 3 hierarchy segments
(org:memory:loc, e.g., "acme.com:mmdata:review:sort-imports").
GraphQL surfaces this as a typed error: { extensions: { code: "URN_NOT_QUALIFIED" } }. MCP tool error results carry the same code. The error message embeds the offending value AND the canonical-form fix template.
Why fully-qualified at the API boundary?¶
A user prompt like "add a review node 'sort-imports'" lets the agent construct one of two things:
- Relative:
review:sort-imports— depends on a hidden "active memory" the user can't see. - Fully qualified:
acme.com:mmdata:review:sort-imports— picks one memory unambiguously.
With one writable memory in an org, the two were equivalent. With two writable memories (e.g., mmdata and mm-app, both containing a review section), the relative form silently routes to whichever memory the server's fallback heuristics pick — and the user has no visibility into the choice.
Spec 022 closes that path: the agent layer is responsible for picking the memory (it asks the user when ambiguous), and the API rejects anything less than a fully-qualified URN. The fallback heuristics are removed.
Exception: ID-shape inputs¶
Spec 007's ID-or-URN dispatch is preserved: inputs matching the ID regexes (^c[a-z0-9]{24}$ CUID or ^[0-9a-f]{32}$ 32-char hex) are treated as internal IDs and bypass the qualification rule. Callers that already hold an entity's ID continue to work without changes.
Exception: content text¶
The qualification rule applies at the API boundary, not inside content prose. A YAML import or a node's markdown body may legitimately contain relative URNs, loc:-style historical references, or other non-qualified strings — those are parsed by import / render code that intentionally accepts looser forms. Only inputs flowing through GraphQL inputs, MCP tool arguments, or SDK call signatures are subject to the qualification rule.
See spec 007 (Entity Reference Convention) FR-019 for the canonical statement of the rule, and spec 022 (Fully-Qualified URN Inputs) for the design discussion and per-tool enforcement.