name: sage-ontology description: > Typed knowledge graph stored in sage-memory. Use when creating or querying structured entities (Person, Project, Task, Event, Document), linking related objects, checking dependencies, planning multi-step actions as graph transformations, or when skills need to share structured state. Trigger on "remember that X is Y", "what do I know about", "link X to Y", "show dependencies", "what blocks X", entity CRUD, cross-skill data access, or any request involving structured relationships between things. version: "1.2.0" type: process
Ontology
Typed knowledge graph. Entities stored as memories, relationships stored as edges — searchable, traversable, persistent.
Part of the unified knowledge system. Ontology entries surface alongside regular knowledge and self-learning entries during recall.
Capabilities by Backend
| Capability | MCP | Files |
|---|---|---|
| Create entities | ✅ sage_memory_store |
✅ .sage-memory/ont-*.md files |
| Search entities | ✅ BM25 + tag filter | ⚠️ filename scan + read |
| Update entities | ✅ sage_memory_update |
✅ edit file |
| Delete entities | ✅ CASCADE removes edges | ✅ delete file + clean relations |
| Create relations | ✅ sage_memory_link |
✅ relations: in entity file |
| Multi-hop traversal | ✅ sage_memory_graph |
❌ not available |
| Cycle detection | ✅ built into graph tool | ⚠️ manual trace |
| Browse by type | ✅ sage_memory_list |
✅ scan ont- files |
How to detect backend: At session start, call sage_memory_set_project
with the project root path. If it responds, use MCP. If not, use
.sage-memory/ files.
Agent-driven extraction (0.9+). Ontology entities are populated
primarily by passing entities and relations to sage_memory_store
from the calling agent:
sage_memory_store(
content: "...",
entities: [{name: "PaymentOrchestrator", type: "CONCEPT"}],
relations: [{from: "PaymentOrchestrator", to: "Stripe", rel: "depends_on"}]
)
The background worker is a fallback that runs only when an LLM API key is configured AND the agent didn't pass an extraction payload. Prefer the agent-driven path: your model has the full conversational context, the worker only sees the stored content.
Core Model
Entities hold properties. Relations connect two entities with a typed, directed edge.
Entity: [Task:task_a1b2] "Fix payment timeout"
Relation: task_a1b2 —blocks→ task_f3a4
Entity Operations
Create Entity
With MCP:
sage_memory_store(
title: "[Task:task_a1b2] Fix payment timeout in checkout flow",
content: '{"id":"task_a1b2","type":"Task","properties":{"title":"Fix payment timeout","status":"open","priority":"high"}}',
tags: ["ontology", "entity", "task", "billing", "checkout"],
scope: "project"
)
With files:
File: .sage-memory/ont-task-a1b2-fix-payment-timeout.md
---
tags: [ontology, entity, task, billing, checkout]
type: ontology
scope: project
entity_id: task_a1b2
entity_type: Task
created: 2026-03-20
---
{"id":"task_a1b2","type":"Task","properties":{"title":"Fix payment timeout","status":"open","priority":"high"}}
Filename convention: ont-{type}-{id}-{short-description}.md
ID format: {type_prefix}_{8_hex} — e.g., task_a1b2c3d4,
pers_e5f6a7b8, proj_c9d0e1f2
Search Entities
With MCP:
sage_memory_search(query: "task_a1b2", filter_tags: ["ontology", "entity"])
sage_memory_search(query: "open tasks billing", filter_tags: ["ontology", "entity", "task"])
FTS5 safety: Search by bare entity ID, never by bracket title.
With files: Scan .sage-memory/ for ont- prefixed files. Filter
by type: ont-task-* for tasks, ont-pers-* for persons. Read matching
files to check properties.
Browse All Entities
With MCP: sage_memory_list(tags: ["ontology", "entity", "task"])
With files: List all ont-task-*.md files in .sage-memory/.
Delete Entity
With MCP: sage_memory_delete(id: "<entity_memory_id>") — CASCADE
automatically removes all edges.
With files: Delete the entity file. Then scan other ont-*.md files
for relations: entries referencing this entity's ID and remove them.
Relation Operations
Create Relation
With MCP:
sage_memory_link(
source_id: "<source_memory_id>",
target_id: "<target_memory_id>",
relation: "blocks",
properties: {"reason": "payment flow must complete first"}
)
One call. Validation before creating:
- Check type compatibility (see schema reference)
- Check cardinality (many_to_one → replace existing if found)
- For
blocks/depends_on→ cycle check viasage_memory_graph
With files: Add a relations: section to the source entity's file:
File: .sage-memory/ont-task-a1b2-fix-payment-timeout.md
---
tags: [ontology, entity, task, billing, checkout]
type: ontology
scope: project
entity_id: task_a1b2
entity_type: Task
created: 2026-03-20
relations:
- rel: blocks
target: task_f3a4
target_type: Task
- rel: assigned_to
target: pers_e5f6
target_type: Person
---
{"id":"task_a1b2","type":"Task","properties":{"title":"Fix payment timeout","status":"open","priority":"high"}}
Relations live inside the source entity's frontmatter. This is
denormalized but avoids separate relation files and works without a
database. To find what blocks task_f3a4, scan all ont-task-*.md files
for relations: entries with target: task_f3a4.
Delete Relation
With MCP:
sage_memory_link(
source_id: "<source_id>", target_id: "<target_id>",
relation: "blocks", delete: true
)
With files: Edit the source entity file — remove the relation entry
from the relations: list.
Cycle Check
With MCP:
sage_memory_graph(
id: "<target_memory_id>",
relation: "blocks",
direction: "outbound",
depth: 5
)
If source_id appears in results → cycle → reject.
With files: Manually trace the chain. Read target's file → check its
relations: for outbound blocks → follow to next entity → repeat.
If you return to the source, it's a cycle. Practical for graphs under
20 edges of that type.
Graph Traversal (MCP only)
What tasks does project X have?
sage_memory_graph(id: "<project_id>", relation: "has_task", direction: "outbound", depth: 1)
What blocks task X, and what blocks those?
sage_memory_graph(id: "<task_id>", relation: "blocks", direction: "inbound", depth: 2)
Full neighborhood
sage_memory_graph(id: "<entity_id>", direction: "both", depth: 1)
With files: Multi-hop traversal is not available. Use single-hop
lookups by scanning relations: in entity files. For tasks in a project,
scan all ont-task-*.md files for relations: entries like
{rel: part_of, target: <project_id>}.
Planning as Graph Transformation
Plan: "Set up feature project with tasks"
1. CREATE Project → sage_memory_store (or file)
2. CREATE Task1 → sage_memory_store (or file)
3. CREATE Task2 → sage_memory_store (or file)
4. RELATE has_task: Project → Task1 → sage_memory_link (or add to file)
5. RELATE has_task: Project → Task2 → sage_memory_link (or add to file)
6. RELATE blocks: Task1 → Task2 → sage_memory_link (after cycle check)
7. VALIDATE: no cycles ✓, cardinality ✓
Validating a planned mutation with graph_check.py
scripts/graph_check.py is a zero-dependency Python validator. Pipe
it a JSON document describing the entities and proposed relations to
get a structural OK / FAIL plus a list of violations. Use it before
the agent calls sage_memory_link for a non-trivial plan.
python3 scripts/graph_check.py <<'EOF'
{
"entities": [
{"id": "proj_c9d0", "type": "Project"},
{"id": "task_a1b2", "type": "Task"},
{"id": "task_f3a4", "type": "Task"}
],
"relations": [
{"from": "proj_c9d0", "to": "task_a1b2", "rel": "has_task"},
{"from": "proj_c9d0", "to": "task_f3a4", "rel": "has_task"},
{"from": "task_a1b2", "to": "task_f3a4", "rel": "blocks"}
]
}
EOF
Exit code 0 = valid; 1 = violations (printed as JSON to stdout).
Checks type compatibility, cardinality, and acyclicity for blocks
and depends_on chains. See the script's docstring for the full
input schema.
Validation Rules
Required Properties
| Type | Required |
|---|---|
| Task | title, status |
| Person | name |
| Project | name |
| Event | title, start |
| Document | title |
Relation Types
The full controlled vocabulary lives in
references/schema.md. All 11 built-in
relations:
| Relation | From → To | Cardinality | Acyclic |
|---|---|---|---|
| has_owner | Project, Task → Person | many_to_one | no |
| assigned_to | Task → Person | many_to_one | no |
| has_task | Project → Task | one_to_many | no |
| has_goal | Project → Goal | one_to_many | no |
| part_of | Task, Document, Event → Project | many_to_one | no |
| blocks | Task → Task | many_to_many | yes |
| depends_on | Task, Project → Task, Project, Event | many_to_many | yes |
| member_of | Person → Organization | many_to_many | no |
| mentions | Document, Message, Note → Person, Project, Task, Event | many_to_many | no |
| follows_up | Task, Event → Event, Message | many_to_one | no |
| attendee_of | Person → Event | many_to_many | no |
Custom relation strings are accepted by sage_memory_link but only
the 11 above are validated by graph_check.py. Stick to the
controlled vocabulary unless the project clearly needs an extension.
Credential safety: Never store password, secret, token,
api_key as properties.
Extending Types
Store a schema extension for custom types (works with both backends):
With MCP:
sage_memory_store(
title: "[Schema:Sprint] Custom entity type",
content: '{"type":"Sprint","required":["name","start","end"]}',
tags: ["ontology", "schema"]
)
With files:
File: .sage-memory/ont-schema-sprint.md
---
tags: [ontology, schema]
type: ontology
---
{"type":"Sprint","required":["name","start","end"],"enums":{"status":["planning","active","closed"]}}
Codebase Scan (0.11+)
For projects where code symbols (functions, classes, modules) are a recurring discovery target, sage-memory can pre-index the source tree via tree-sitter.
If sage-memory scan-codebase --help exits 0: the [codebase]
extra is installed. Run sage-memory scan-codebase (default: project
root) at session start or after notable code changes. Then use:
sage_memory_search(query: "...", filter_tags: ["codebase"])— surface files matching the query.sage_memory_scan_codebase(...)MCP tool — same as the CLI.
If the extra is NOT installed: the CLI / MCP tool errors with an
install hint. Fall back to extracting code entities yourself via
your existing AST tools and the 0.9.0 entities / relations
payload on sage_memory_store.
The pre-indexed codebase saves 10-50× tokens on cross-file queries ("where is X called?", "what imports Y?", "what functions exist in this module?"). For line-level edits, you still need to read source.
10 languages supported: Python, TypeScript (incl. TSX), JavaScript
(incl. JSX), Go, Rust, Java, Ruby, PHP, C, C++. Symbol kinds
captured: FUNCTION, CLASS, METHOD, INTERFACE, STRUCT,
ENUM. Relations captured: imports, calls — with
confidence: resolved | unresolved so you can filter for
high-signal cross-file links.
Safety guards (T10b): scans are project-scoped (different
projects can scan in parallel), refuse to run against $HOME,
respect --limit (default 5000) BEFORE writes happen, and hold a
600s-recovery advisory lock that prevents two scans from racing on
the same project.
References
references/encoding.md— Full format, examples, search patternsreferences/schema.md— All types, relations, constraintsscripts/graph_check.py— Structural validator