Build Your Own Pack
A pack is the schema for a space: the node types, their attribute schemas, the edge types, and the rules for which node types an edge may connect. The engine is mechanism-only — it enforces whatever the pack declares, in the database — so a pack is how you turn Engraphy into your memory application (a coding assistant's memory, a CRM, a reading log, a research notebook).
A pack is a YAML (or JSON) file validated against packs/schema.json. You
engram-admin pack validate it, then engram-admin pack apply it into a space.
Anatomy of a pack
| Key | Required | What it declares |
|---|---|---|
pack |
✓ | pack name, ^[a-z][a-z0-9-]{1,40}$. |
version |
✓ | the pack's own content revision (an integer ≥ 1). |
node_types |
✓ | the memory types and their attribute schemas. |
edge_types |
✓ | the relationship types (and whether each is bidirectional). |
edge_rules |
which node types each edge may connect. | |
ambient_scopes |
scopes unioned into every read/dedup candidate set (see note). | |
briefing |
the session-start sections briefing returns. |
|
tool_aliases |
pack-specific tool names that bind to core tools with presets. | |
tool_descriptions |
per-tool description overrides shown in tools/list. |
|
pack_format |
the pack file format version (distinct from version); absent = 1. |
Node types
Each node type has a description and an optional attribute schema:
node_types:
book:
description: "A book someone has read or is reading."
attrs:
required:
author: {type: string}
optional:
finished_on: {type: date}
rating: {enum: ["1", "2", "3", "4", "5"]}
closed: true # reject any attr not declared here (the default)
- An attribute is declared with either
type(string|int|number|bool|date) orenum(a fixed list of allowed string values). closed: true(the default) rejects any attribute not inrequired/optional. Setclosed: falsefor a free-form bag.- Attribute validation is enforced by a database trigger, so a write with a bad attr is rejected at the row regardless of which path wrote it.
The searchable attribute flag
Content-bearing attributes enter the searchable surface (the embedding + the
lexical tsvector), so a fact stored only in an attribute is found by search, not
merely retrievable by get. The default is by construct:
stringanddateattributes are searchable by default (they carry content — an author's name, a place, a date).enum,bool,int,numberare not searchable by default (they discriminate rather than describe).
Override per attribute with searchable: true/false:
attrs:
optional:
occupation: {type: string} # searchable (string default)
department: {type: string, searchable: false} # excluded on purpose
role: {enum: [lead, ic], searchable: true} # include a low-cardinality enum
Applying a pack that uses
searchablewarns unless it declarespack_format: 2. After changing searchability on a live space, runengram-admin surface rebuild --space <space>to re-index existing rows — the flag only affects new writes until a rebuild.
Edge types and rules
edge_types:
about: {description: "This note is about this book.", bidirectional: false}
by: {description: "This book was written by this author.", bidirectional: false}
relates_to: {description: "Weak generic association.", bidirectional: true}
supersedes: {description: "Replacement; old node becomes superseded.", bidirectional: false}
same_topic: {description: "Same topic, distinct content; attached automatically on merge.", bidirectional: true}
edge_rules:
- {type: about, src: "*", dst: book}
- {type: by, src: book, dst: author}
- {type: relates_to, src: "*", dst: "*"}
- {type: same_topic, src: "*", dst: "*"}
- {type: supersedes, src: "*", dst: "*"}
edge_rulesconstrain(type, src_type → dst_type);"*"is a wildcard. Alinkor auto-attached edge that violates the rules is rejected in the database.- Always declare
same_topicandsupersedes(both"*" → "*"is fine): the engine's merge path attachessame_topicautomatically, and thesupersedecore tool insertssupersedes— the pack must permit the edges the engine itself writes.pack validatewarns ifsame_topicis missing.
Ambient scopes
ambient_scopes: [personal] unions the named scope into every read and every dedup
candidate set. Useful for a "personal" scope that should always be in play — but
know that it makes those scopes' facts merge candidates across the space, so use it
deliberately.
Briefing sections
briefing declares the session-start read. Sections run in pack order:
briefing:
sections:
- {name: currently_reading, type: book,
where_attr: {key: finished_on, after: "+0d"}, order_by_attr: finished_on}
- {name: relevant, semantic: true, types: [book, note], top_k: 5}
- {name: recent_notes, type: note, recent: 7d}
footer: {inbox_pending_count: true}
- A
semantic: truesection runs hybrid search over the caller'shint(with a relevance floor); non-semantic sections filter/sort by type,where_attr,recent: Nd, etc.top_kcaps at 10.
Tool aliases & descriptions
tool_aliases lets a pack expose its own tool names bound to core tools with preset
arguments (e.g. a remember alias that presets write's type). tool_descriptions
overrides the one-line description a client sees for a tool. Both are optional.
Dedup thresholds are per-space config, not pack keys
The three dedup/resonance thresholds are not in the pack — they are per-space
config rows, so you can tune them for a space without editing its ontology:
| Config key | Default | Effect |
|---|---|---|
dedup.t_high |
0.95 |
at/above → merge band (auto-merge or merge-link) |
dedup.t_low |
0.80 |
[t_low, t_high) → pending (caller resolves) |
resonance.floor |
0.75 |
related-memory report threshold on writes |
Set them with the operator CLI (value is parsed as JSON):
engram-admin config set --space demo --key dedup.t_high --value 0.97
Raise t_high toward 1.0 to make auto-merge stricter (more things stay distinct);
raise t_low to shrink the pending band. Bands must satisfy
0 < t_low ≤ t_high ≤ 1.
A complete worked example
Save as reading-pack.yaml:
pack: reading
version: 1
pack_format: 2 # uses the `searchable` attr flag
node_types:
book:
description: "A book someone has read or is reading."
attrs:
required:
author: {type: string} # searchable (string)
optional:
finished_on: {type: date} # searchable (date)
rating: {enum: ["1","2","3","4","5"]} # not searchable (enum)
closed: true
author:
description: "A book's author: who they are and where they're from."
attrs:
required:
country: {type: string}
closed: true
note:
description: "A durable thought, quote, or takeaway worth keeping."
attrs: {closed: false} # free-form
edge_types:
about: {description: "This note is about this book.", bidirectional: false}
by: {description: "This book was written by this author.", bidirectional: false}
relates_to: {description: "Weak generic association.", bidirectional: true}
supersedes: {description: "Replacement; old node becomes superseded.", bidirectional: false}
same_topic: {description: "Same topic, distinct content; attached automatically on merge.", bidirectional: true}
edge_rules:
- {type: about, src: "*", dst: book}
- {type: by, src: book, dst: author}
- {type: relates_to, src: "*", dst: "*"}
- {type: same_topic, src: "*", dst: "*"}
- {type: supersedes, src: "*", dst: "*"}
briefing:
sections:
- {name: recent_notes, type: note, recent: 14d}
- {name: relevant, semantic: true, types: [book, note], top_k: 5}
footer: {inbox_pending_count: true}
tool_descriptions:
write: "Save a book, an author, or a reading note. The server deduplicates — re-telling is safe."
Validate and apply it:
engram-admin pack validate reading-pack.yaml
# -> pack 'reading-pack.yaml' is valid
engram-admin pack apply reading-pack.yaml --space demo
# -> applied pack 'reading-pack.yaml' to space 'demo'
pack validate checks the file against the schema plus cross-checks (edge rules
reference declared types, briefing sections reference declared types, a
same_topic-declared warning). pack apply validates then writes the node/edge
registries, rules, and pack config into the space. It runs once per space — to
change an applied pack use engram-admin pack upgrade, which diffs the new pack
against the space and applies changes by class (additive immediately; tightening
only after a conformance scan or --allow-nonconforming; destructive refused while
active rows of that type exist).
Keeping a space healthy
engram-admin doctor --space <space> is a read-only hygiene report: stale pending
verdicts, attribute-nonconforming counts, registry-vs-pack drift, orphaned merges,
canonical chains longer than 3, nodes with more than 20 addenda. Run it after an
upgrade or when something looks off.
Next: the Tool reference for calling into your pack, or the Tutorial to see one end-to-end.