XMD schema format
XMD (xmemory Model Description) is the YAML format that defines what an xmemory instance can store and how natural-language writes are interpreted. It is both a storage schema and an extraction specification: object, field, and relation descriptions are supplied to the extraction pipeline, not treated as passive comments.
XMD is an explicit control and quality surface that agents can author directly. Direct authoring is useful when the desired objects, fields, extraction rules, and relations are already known. It is also optional: to make creation easier, an agent can pass a detailed plain-language memory description to the instance-creation workflow and let xmemory generate and validate the same XMD contract. Both paths are supported.
An XMD schema defines:
- the object types xmemory can extract and store
- the typed fields available on each object
- optional identity matching and deduplication through
primary_key - closed categorical values through
enum - relationships between objects and their cardinality
- the domain meaning extraction should apply through descriptions
The current format version is v1.
A complete example
Section titled “A complete example”View the complete support memory schema
xmd_version: v1title: Support memorydescription: Customers, their support issues, and who reported each issue.
objects: Customer: description: >- A person or organisation receiving support. Create one Customer for each explicitly identified customer; do not create one for support staff. fields: name: type: str required: true description: The customer's full name or organisation name as stated in the text. email: type: str required: false description: The customer's email address, normalised to lowercase. primary_key: [name]
Issue: description: >- One support problem or request. A message can contain more than one Issue when it describes distinct problems. fields: reference: type: str required: true description: >- Stable issue reference stated in the source, in the canonical format CASE- followed by digits, for example CASE-1042. category: type: str required: true enum: [billing, technical, account, other] description: >- Classify the main problem. Charges, invoices, refunds, or duplicate payments are billing; failures and defects are technical; login, permissions, and profile access are account; use other only when none fit. severity: type: int required: false enum: [1, 2, 3] default: 3 description: >- Operational severity: 1 for service unusable or data-loss risk, 2 for major degradation with a workaround, and 3 for minor or routine requests. occurred_on: type: str required: false description: Date the problem occurred, normalised as YYYY-MM-DD. summary: type: str required: false description: Concise account of the problem, preserving concrete symptoms and impact. primary_key: [reference]
relations: reported_issue: description: >- Connect a Customer to an Issue only when the text identifies that customer as the person or organisation reporting or experiencing that issue. objects: reporter: type: Customer on_delete: nullify description: The customer who reported or experienced the issue. issue: type: Issue on_delete: cascade description: The reported support issue. keys: one_reporter_per_issue: [issue]This schema can classify “I was charged twice” as category: billing even when the word “billing” is absent. The text supplies the evidence; the field description supplies the domain rule used to interpret it.
Top-level structure
Section titled “Top-level structure”| Key | Type | Required | Meaning |
|---|---|---|---|
xmd_version |
v1 |
No | Format version. Omitted legacy schemas are migrated to v1, but new schemas should state it. |
title |
string or null |
No | Human-readable schema title. |
description |
string or null |
No | Human-readable purpose and domain of the whole instance. |
objects |
map | Yes | Object name to object definition. |
relations |
map | Yes | Relation name to relation definition. Use {} when there are none. |
Only the documented top-level keys are accepted. Some nested structures preserve unknown keys for rolling-version compatibility, but those keys have no public XMD semantics and should not be used as extensions.
Objects
Section titled “Objects”Each entry in objects defines one kind of record:
objects: Task: description: One actionable unit of work. fields: title: type: str required: true description: Short task title. primary_key: [title]An object supports:
| Key | Type | Default | Meaning |
|---|---|---|---|
description |
string or null |
null |
What counts as this object and how to distinguish it from other objects. |
fields |
map | — | Field name to field definition. An object must contain at least one field. |
primary_key |
list of field names | [] |
Fields used together to identify and match the same logical record. |
Object descriptions influence object detection. Use them to state inclusion and exclusion boundaries, granularity, and when one passage should create multiple records.
Fields
Section titled “Fields”fields: status: type: str required: true enum: [open, blocked, done] default: open description: >- Current task state. Waiting on an external dependency is blocked; completed or shipped work is done; otherwise use open.| Key | Type | Default | Meaning |
|---|---|---|---|
type |
field type | — | Scalar value type. |
required |
boolean | false |
Extraction-time expectation that the field should be filled when the record is usable. |
description |
string or null |
null |
Semantic and normalization instructions supplied to extraction. |
enum |
list or null |
null |
Closed set of allowed extraction values. |
default |
scalar or null |
null |
Fallback value when no value is supplied. |
Supported field types
Section titled “Supported field types”| Canonical type | Accepted aliases | Values |
|---|---|---|
str |
string |
Text, dates, UUID-shaped strings, codes, and other string values. |
int |
integer |
Whole numbers. Booleans are not accepted as integers. |
float |
— | Floating-point numbers. Use int when only whole numbers are valid. |
bool |
boolean |
true or false. |
XMD v1 fields are scalar. Arrays, nested objects, date/UUID native types, and relation attributes are not field types. Model a nested entity as another object plus a relation. Store dates, times, decimal-like values, and UUIDs as strings with an explicit format in the description when needed.
enum is a closed list of allowed values and is supported for all four field types:
priority: type: str enum: [low, medium, high] description: >- Classify urgency from the described impact. A system-wide blocker is high, limited degradation is medium, and cosmetic or non-urgent work is low.Every enum member must have exactly the field’s type. Mixed enums such as [1, "2"] are invalid. If default is set, it must also be a member of the enum.
Enums do more than validate a literal string. Extraction can categorise source text into the closest enum member when the text gives semantic evidence but does not say the label itself. Put the decision boundary in the description; do not rely on unexplained category names.
Enum constraints are part of XMD extraction metadata, not a database-native enum or SQL CHECK constraint. Normal natural-language extraction is constrained by the enum, but XMD should not be treated as a substitute for validating arbitrary out-of-band database writes.
Required fields and defaults
Section titled “Required fields and defaults”required: true is XMD metadata, not a database NOT NULL constraint. Physical columns remain
nullable. Extraction may fill a required field when its description defines a grounded derivation
or a schema-supporting generation rule, even when the exact value is not written literally in the
input. Otherwise it can still return null; required alone is not permission to invent an
unsupported real-world fact.
default is also XMD-level behavior, not a database server default. It must match the field type and, when an enum exists, belong to that enum. Defaults are best for genuine domain defaults such as an initial workflow state. They are not a reliable mechanism for timestamps, counters, or unique identifiers.
Descriptions control extraction
Section titled “Descriptions control extraction”Descriptions are the most important part of an agent-readable XMD schema. Names and types say where a value can go; descriptions influence what is detected, how evidence is interpreted, and which schema-valid knowledge may be derived. They are executable extraction guidance, not merely documentation or a glossary of workflow- and vertical-specific terminology.
Descriptions operate at three levels:
- Vocabulary: map domain terms, abbreviations, synonyms, and vertical language onto XMD concepts.
- Extraction policy: define object boundaries, field selection, exclusions, formats, enum decisions, and relation semantics.
- Grounded inference: permit values that are not literal spans when they are logically supported or deterministically derivable from the input and the stated rule.
Descriptions are used at several stages:
- an object description helps decide whether a passage contains that object and how many objects it contains
- a field description disambiguates fields of the same type and defines categorisation, units, formats, and normalization
- enum values plus the field description enable semantic classification when the literal label is absent
- a relation description tells the relation classifier what connection must be present between candidate objects
- a description can define grounded derivations, computations, or schema-supporting generated values for required fields
- descriptions are also available to schema-aware reads and agent-facing instance guidance
Good descriptions are operational. State the rule that should change an extraction decision:
# Weak: repeats the nameamount: type: float description: The amount.
# Strong: identifies which amount, its unit, and an exclusionamount: type: float description: >- Final amount charged to the customer in the transaction currency, after discounts and before any later refund; do not use the cart subtotal.Useful description content includes:
- what qualifies and what must be excluded
- how to distinguish this field from similar fields
- whether a value is copied, classified, or normalized
- which logically entailed or computed facts may be derived, including the required inputs and rule
- whether a schema-supporting value such as an application UUID should be generated when absent
- expected units and canonical string formats
- enum decision rules and representative examples
- for relations, the exact semantic link that must connect the participants
Grounded inference is broader than literal extraction
Section titled “Grounded inference is broader than literal extraction”Grounded does not mean “copied verbatim.” Descriptions can tell xmemory to infer structured facts that the input supports without stating the final value directly. For example, they can:
- map “charged twice” to
category: billingwithout the word “billing” - infer a reporting relation from “Alice escalated the issue for Acme”
- normalize “5 March 2026” to
2026-03-05 - compute a ratio, duration, or boolean when the source contains the required operands
- combine or split stated values according to a documented canonical form
- generate a unique UUID for a required application field when the input omits it and the description explicitly defines that rule
The grounding boundary is evidence plus rule: the source provides the premises, and the description provides the interpretation or derivation. A description may fine-tune extraction and authorize that transformation, but it is not evidence by itself. It must not create unsupported claims such as a payment, diagnosis, preference, or relationship that the source neither states nor entails.
Every extracted object also receives an xmemory-managed internal xuid; do not add an xuid field
just to reproduce internal identity. Add an application UUID only when consumers need it as domain
data. A description can require xmemory to generate a unique UUID when that field is missing; also
state whether an existing UUID must be preserved on later writes and which uniqueness scope applies.
Do not add or generate an application UUID merely so the object can participate in a relation;
xmemory uses its own internal identity to link the physical records.
Primary keys and identity
Section titled “Primary keys and identity”primary_key is optional. It determines whether a new mention updates an existing record or creates
a new one; it is not required for storage or for participation in a relation. It is a list because
composite keys are supported:
objects: LineItem: fields: order_number: { type: str, required: true } line_number: { type: int, required: true } quantity: { type: int, required: false } primary_key: [order_number, line_number]Primary-key field names must exactly match fields on the same object. Dot notation is not supported. Set every primary-key field to required: true; schema generation follows this rule even though v1 validation does not currently reject a primary-key field marked optional.
Choose the smallest key that is both:
- stable for the lifetime of the record
- reliably stated whenever the record is mentioned in natural language
Statuses, counters, prices, and other mutable state make poor keys. Globally unique values such as emails or account IDs can also be poor keys when ordinary text frequently omits them.
Missing key values are meaningful: matching uses primary-key values found in the text, and an omitted key component is treated as empty. Repeated writes that omit the same declared key can therefore collapse onto the same record and overwrite it. If no safe, referenceable domain key exists, use primary_key: []; xmemory will still assign internal identifiers and treat mentions as separate records. Such objects can participate normally in relations: extraction links the concrete records through xmemory’s internal physical identity.
Do not invent a key, mark an otherwise unnecessary field as required, or generate a UUID solely to make relations possible. Add a primary key only when the workflow genuinely needs repeated mentions to resolve to the same logical object and the key can be extracted reliably.
Relations
Section titled “Relations”Relations connect existing XMD objects. A v1 relation contains participant roles, deletion behavior, optional descriptions, and optional uniqueness keys:
relations: assignment: description: Connect a Task to the Person currently responsible for completing it. objects: assignee: type: Person on_delete: nullify description: The responsible person. task: type: Task on_delete: cascade description: The assigned task. keys: one_assignee_per_task: [task]Each participant entry supports:
| Key | Type | Meaning |
|---|---|---|
type |
object name | Exact key of an object in the top-level objects map. It is not one, many, or another cardinality keyword. |
on_delete |
nullify or cascade |
Whether deleting the object clears this link endpoint or deletes the junction row. |
description |
string or null |
Meaning of this participant role. Especially useful when one object type appears more than once. |
The relation’s top-level description tells extraction what connection counts. Merely mentioning two objects in the same text is not enough; relation extraction looks for source evidence matching that definition.
Relation participation is independent of object primary keys. Any participant type may declare
primary_key: []; xmemory links the specific extracted database records internally. The relation’s
own keys setting is also optional and does not identify its participant objects.
Relation keys define cardinality
Section titled “Relation keys define cardinality”relations.<name>.keys is a map of named uniqueness constraints. In v1, each key value can contain participant role names from the same relation. These keys constrain the cardinality of already-linked relation rows; they are not object primary keys. Relation attributes are not supported.
For a two-participant relation with roles a and b:
| Desired shape | keys |
Effect |
|---|---|---|
| Many-to-many, without duplicate pairs | {unique_pair: [a, b]} |
Each pair is unique; either side can appear in many rows. |
Each a has at most one b |
{unique_a: [a]} |
A given a can appear in only one junction row. |
Each b has at most one a |
{unique_b: [b]} |
A given b can appear in only one junction row. |
| One-to-one | {unique_a: [a], unique_b: [b]} |
Each side can appear in at most one row. |
| Duplicate links intentionally allowed | omit keys, set it to null, or use {} |
The junction is a bag of links; identical combinations may repeat. |
For example, both participant types and the relation itself can be unkeyed:
objects: Observation: fields: text: { type: str, required: true } primary_key: [] Topic: fields: label: { type: str, required: true } primary_key: []relations: about_topic: description: Connect an observation to a topic it discusses. objects: observation: { type: Observation, on_delete: cascade } topic: { type: Topic, on_delete: cascade } keys: {}The extractor can create and link the concrete Observation and Topic rows without inferring an
application-level identifier for either one.
For self-relations, give the two participants distinct roles:
relations: management: description: Connect an employee to their direct manager. objects: employee: { type: Person, on_delete: cascade } manager: { type: Person, on_delete: nullify } keys: one_manager_per_employee: [employee]If a relationship needs its own data—such as started_on, quantity, or role—model it as an object and connect that object to the participants with relations. XMD v1 relations themselves do not define scalar fields.
Naming and validation rules
Section titled “Naming and validation rules”Every object name, field name, relation name, participant role, and relation-key name must:
- match
^[A-Za-z][A-Za-z0-9_]*$ - begin with an ASCII letter
- contain only ASCII letters, digits, and underscores
- be at most 63 characters
Names beginning with xmemory_ are reserved. instance_config is also reserved as an object or relation name. References are case-sensitive and must match their declarations exactly.
For predictable generated class and table names, prefer:
PascalCasefor objects, such asSupportIssuesnake_casefor fields, relations, roles, and key names
Avoid names that differ only in case or underscore placement, because they can normalize to the same generated identifier. Put spaces, punctuation, display labels, and nuanced wording in description, not in schema names.
Validation checks include:
- YAML parses to a mapping with the XMD v1 structure
- field types, defaults, and enums are compatible
- objects contain fields
- primary keys reference local fields
- relation participants reference declared objects
- each declared relation-key constraint contains at least one participant role and references only declared roles; the
keysmap itself may be empty - identifiers have valid, non-reserved names
Generate, validate, and test
Section titled “Generate, validate, and test”If the xmemory-instance-creation skill is available, you can give it a detailed workflow
description. It handles the interview, generation, validation, approval, and creation flow.
An agent may instead write the XMD itself using this reference. In that path, skip generation but
still validate the file and test representative inputs with extract before instance creation.
Without the skill, generate a starting schema from the description, then review it as code:
xmemcli xmd generate \ "Track customers and support issues. Classify issues as billing, technical, account, or other. Each issue has at most one reporting customer." \ -o schema.yml
xmemcli xmd validate schema.ymlCreate the instance only after validation:
xmemcli instance create \ --name support-memory \ --description "Customer support history" \ --schema-file schema.ymlUse extract before persisting representative inputs. Test ambiguous, incomplete, and negative examples—not only a happy path. Check especially:
- whether object descriptions create the right number of records
- whether enum descriptions produce the intended categories
- whether similar fields receive the correct values
- whether missing primary-key values could collapse unrelated records
- whether a primary key is genuinely needed rather than being added only for relation participation
- whether relation descriptions and keys express the intended link and cardinality
When improving extraction quality, retrieve and enhance the live XMD instead of generating a
replacement from scratch. This lets renames and data-preserving changes become explicit migration
operations. Base adjustments on observed failures: clarify object boundaries and descriptions,
tighten field meaning, add enum decision rules, correct identity keys, and refine relation roles or
cardinality. Validate, dry-run, and test with extract before applying any migration:
xmemcli schema get "$XMEM_INSTANCE_ID" -o schema.ymlxmemcli xmd enhance schema.yml "Add Issue.resolution and a resolved status." -o schema-v2.ymlxmemcli xmd validate schema-v2.ymlxmemcli schema dry-run "$XMEM_INSTANCE_ID" --schema-file schema-v2.yml