SANCTOT: A Complete L5 Example
From "dad always has snacks" to a working ontology compiler.
One Sentence Is Not A Specification
Everyone says "L5 means the agent reasons against an ontology." Cool. What does that actually look like?
Here is a working L5 system, end to end. It is small enough to read in one sitting. The source is open. The pipeline is exactly seven typed stages. The output is a knowledge graph fragment, not a paragraph.
An L5 system's output is not prose. It is typed structure that composes with other typed structure. The structure IS the constraint that prevents hallucination.
The Origin: Dad Always Has Snacks
My dad always has snacks. If you go on a walk with him, a granola bar appears. If you visit, there's cheese. If you're upset, there's something with chocolate in it.
I asked: why does my dad always have snacks?
The shallow answer is "he's a thoughtful guy." The deep answer is that "dad has snacks" is a sanctuary meme — a primitive survival operation wearing a costume of ordinary domesticity. A surplus-bearing protector anticipates dependent bodily dysregulation before distress collapses exploration. Translation: dad carries snacks so I never get hungry enough to stop playing. Without that, children stop exploring.
The primitive: carried_surplus_buffer. The function: provisions(agent, dependents, buffer_resource). The promise: "surplus is for you." The ritual: maintaining the buffer.
If I can resolve dad has snacks into that structure, I can resolve any ordinary human thing into structure. That's SANCTOT.
The Seven-Step Chain
SANCTOT is a compiler. Input: any ordinary human thing. Output: a typed 7-stage sanctuary algebra. Here's the actual system prompt, lifted directly from sanctot_mcp/utils.py:
You are the Sanctuary Compiler. You take any ordinary human thing
and resolve it into sanctuary algebra.
Every human behavior is a sanctuary operation — maintaining, building,
repairing, or transferring protected space. Your job is to reveal
what was always there.
For any input, produce a JSON object with EXACTLY these fields:
{
"sanc_meme_name": "short_snake_case_slug — funny, memorable, meme-grade",
"ordinary_human_thing": "restate the input clearly in one sentence",
"primitive_survival_substrate": "what survival/coherence function is
actually happening? Be RAZOR SPECIFIC in biological/social
primitive terms.",
"sanctuary_maintenance_function": "what protected field is being
maintained, repaired, or violated? Who is protected? What is
the buffer?",
"rite_role_transition": {
"from_role": "what the being currently is",
"to_role": "what the being must become",
"transition_logic": "how the transition works",
"failure_mode": "what breaks if the transition fails — name the
CASCADE specifically"
},
"algebra": "the symbolic chain: A → B → C → D",
"when_misconfigured": "what this BECOMES when badly configured",
"when_reconfigured": "what this BECOMES when properly configured",
"promise_structure": "what must be kept across time",
"ritual_comedy_form": "how humans encode this without saying the
terrifying part",
"agent_action": "one concrete thing to build, record, teach, protect,
or change",
"universal_pattern": "deepest universalizable pattern name",
"relates_to": ["list of other sanctuary memes this connects to"]
}
The fields aren't decoration. Each field corresponds to a typed concept in the knowledge graph. The output is a SancTot dataclass:
@dataclass
class SancTot:
sanc_meme_name: str
ordinary_human_thing: str
primitive_survival_substrate: str
sanctuary_maintenance_function: str
rite_role_transition: RiteRoleTransition
algebra: str
when_misconfigured: str
when_reconfigured: str
promise_structure: str
ritual_comedy_form: str
agent_action: str
universal_pattern: str
relates_to: list[str] = field(default_factory=list)
From Dataclass To Graph
This is where it stops being JSON and starts being L5. The SancTotAlgebraicForm.from_sanctot() classmethod converts the resolved dataclass into a chain of typed concepts with typed relationships:
chain = [
{"step": "primitive_substrate",
"concept": f"Sanctuary_Primitive_{s.sanc_meme_name}",
"is_a": "Sanctuary_Primitive",
"content": s.primitive_survival_substrate},
{"step": "sanctuary_function",
"concept": f"Sanctuary_Function_{s.sanc_meme_name}",
"is_a": "Sanctuary_Function",
"content": s.sanctuary_maintenance_function,
"depends_on": f"Sanctuary_Primitive_{s.sanc_meme_name}"},
{"step": "role_transition",
"concept": f"Sanctuary_Transition_{s.sanc_meme_name}",
"is_a": "Sanctuary_Transition",
"depends_on": f"Sanctuary_Function_{s.sanc_meme_name}"},
{"step": "promise_structure",
"concept": f"Sanctuary_Promise_{s.sanc_meme_name}",
"is_a": "Sanctuary_Promise",
"depends_on": f"Sanctuary_Transition_{s.sanc_meme_name}"},
{"step": "ritual_form",
"concept": f"Sanctuary_Ritual_{s.sanc_meme_name}",
"is_a": "Sanctuary_Ritual",
"depends_on": f"Sanctuary_Promise_{s.sanc_meme_name}"},
{"step": "agent_action",
"concept": f"Sanctuary_Action_{s.sanc_meme_name}",
"is_a": "Sanctuary_Action",
"depends_on": f"Sanctuary_Ritual_{s.sanc_meme_name}"},
{"step": "universal_pattern",
"concept": f"Sanctuary_Pattern_{s.universal_pattern}",
"is_a": "Sanctuary_Universal_Pattern"},
]
Every resolution produces seven concepts. Each concept has a type (is_a) and a dependency (depends_on). The chain composes. The chain commits to the knowledge graph. The next resolution can reference the concepts produced by this one through the relates_to field.
This is L5. The output is not text. The output is structured nodes with typed edges that must validly compose to be admitted.
SANCocrates: Multi-Meme Extraction
The L5 primitive is resolve() — one meme in, one chain out. But humans don't speak in single memes. A rant about your job contains a dozen sanctuary operations layered on top of each other.
SANCocrates is the agent persona that wraps the SANCTOT primitive. You give it a paragraph of ordinary human grumbling. It identifies N sanctuary memes inside the paragraph. Then it calls sanctuary_resolve() once per meme.
Same compiler. Different orchestration layer. The compiler stays small and typed. The orchestration handles the messiness.
This is the L4/L5 boundary made concrete. SANCTOT itself (the compiler) is L5 — it produces typed structure with deduction chains. SANCocrates (the persona around it) is L4 — it's a harness that picks when to call the L5 primitive.
Why This Is The L5 Example
Three things have to be true for something to count as L5:
- The output is typed structure, not prose. SANCTOT produces seven typed concepts with typed relationships. The output is a graph fragment.
- The structure composes. Each concept depends_on the previous one. The chain forms a closure or it doesn't. If it doesn't, the resolution failed — the agent literally cannot produce a half-resolved chain that looks complete.
- The graph extends across runs. Each
relates_tolink connects to memes produced by prior resolutions. The knowledge graph compounds. The 100th resolution is not the same as the first — it has access to 99 prior resolutions to reference.
Where The Code Lives
SANCTOT is open source. The compiler is about 200 lines of Python. The complete source — resolve(), SancTot, SancTotAlgebraicForm, the CartON queue writer — is at sanctot_mcp/utils.py. You can read it in ten minutes. You can fork it. You can replace the LLM call with your own model. You can swap the ontology for your domain.
This is what L5 looks like at small scale. It's not exotic. It's not even hard, once you've seen one. The hard part was deciding the output had to be typed structure instead of prose — and that decision is the same decision at any scale.
Build that, and you have L5. Everything past it is more of the same idea, with more constraints.