Ontoref: Ontology and Reflection
The talk deck — ontology and reflection that live with the code
What are we solving?
The problems
The code does X, the team believes it does Y.
Silent. Cumulative.
Human + AI agent write to the same file,
with no coordination.
Why was this decision made? Who authorized it?
Staging works, prod doesn't. Which field changed, and when?
Written once, forgotten, never updated.
AI agents start with no context,
violate established invariants.
Example: real desynchronization
# Documentation (2024-01)
config.max_retries = 3
# Actual code (after an unregistered change)
config.max_retries = 7
# Team thinks it is using 3 ✗
# System silently uses 7 ✓
# Nobody knows when it changed, or whyThe cost of ignoring it
Technical debt → fixable
Epistemic debt → systemic
Trust debt → lethalScenario: 6 months of ignoring it
Month 1: "Just refactor this"
Month 2: "The docs are out of date"
Month 3: "Why does prod fail and staging doesn't?"
Month 4: "Nobody knows who changed what"
Month 5: Panic. Risky rollback.
Month 6: Team rejects new changes.
"We don't trust what we don't understand"They have an architectural one.
Truth and operation — two forces that must coexist
Yin — the formal layer
What must be true
- Nickel schemas → structural correctness
at definition time - ADR constraints → "this can never be violated"
- Config seals → sealed states, verifiable with sha256
- Ontology invariants → what cannot change
without a new ADR - Mathematical hashes → proof of what was sealed
and when
Yang — the operational layer
How things move and change
- Nu commands → structured data transformation
- Actors (human/agent/CI) → same protocol,
different capabilities - Register flow → captures changes,
routes them to the right artifact - Mode definitions → sequences of operations
with verification - Pre-commit hooks → synchronization enforced
at commit time
Yang without Yin = fluid but chaotic. Anything can change. Nothing is verifiable.
Yin without Yang = correct but useless. Perfect schemas, no operations = dead documentation.The ingredients
.ontology/ · adrs/ · reflection/schemas/ · reflection/configs/adr · register · config · backlog · forms · prereqsstratum.sh · actor detection · locking · NICKEL_IMPORT_PATHNickel — typed configuration, no runtime
What Nickel is
- A configuration language with strong types (by Tweag)
- Purely declarative: no side effects, no loops, no runtime
- Superset of JSON: valid JSON is valid Nickel
- Imports:
let s = import "schema.ncl" in - Contracts:
field | TypeName— checked at merge - Typed enums:
[| 'Proposed, 'Accepted, 'Superseded |] - Open/closed records:
{ _: String }vs{ field | String } nickel export file.ncl→ stable JSON for Nu pipelines
Why not the alternatives
YAML → no types, whitespace-sensitive,
implicit coercions
TOML → no types beyond primitives, no imports
JSON → no comments, no imports, no types
HCL → side effects, not pure config
CUE → similar but worse import story
KCL → types + validation, but vendor
opaque language- ADR constraints need typecheck, not manual validation
- Config profiles must fail at definition, not at runtime
nickel exportproduces stable JSON for Nu to consume
# YAML: silently accepts garbage
actor: developr # typo, accepted
# Nickel: rejected at typecheck
{ actor | [| 'developer, 'agent, 'ci |] = "developr" }
# ^^^^^^^^^
# error: tag not in enum typeNushell — structured data, not text streams
Traditional shell: opaque text
# Bash: parse text, error-prone
grep 'severity' adrs/adr-001.json |\
python3 -c "import sys,json; ..."nickel export adrs/adr-001.ncl
| from json
| get constraints
| where severity == "Hard"
| select id claim╭───┬────────────┬──────────────────────────────╮
│ # │ id │ claim │
├───┼────────────┼──────────────────────────────┤
│ 0 │ adr-001-c1 │ nickel export must succeed… │
│ 1 │ adr-001-c2 │ schema is sole source of… │
╰───┴────────────┴──────────────────────────────╯Nickel → Nu → Bash — three layers, three roles
# Hard constraints across the whole system
glob "adrs/adr-*.ncl"
| each { |f| nickel export $f | complete | get stdout }
| each { from json }
| where status == "Accepted"
| get constraints | flatten
| where severity == "Hard"DAGs and FSMs — acyclic graphs and state machines
FSM: the lifecycle of an ADR
┌──────────┐ adr accept ┌──────────┐
│ Proposed │ ───────────▶ │ Accepted │
└──────────┘ └──────────┘
│ new ADR
│ supersedes
▼
┌────────────┐
│ Superseded │
└────────────┘status | [| 'Proposed, 'Accepted, 'Superseded |]
superseded_by | String | default = ""— the type removes them from the space of possibilities.
An ADR cannot go back to Proposed.
Superseded cannot hold active constraints.
DAG: the ontology as a dependency graph
invariant: backend-agnostic-core
└─ tension: nickel-as-canonical-schema
└─ gate: nickel-primacy-gate [active]
└─ protects: nickel-integration-depth
invariant: zero-external-runtime-core
└─ gate: core-dependency-gate [active]
└─ protects: backend-agnostic-core# Active invariants
nickel export .ontology/core.ncl
| from json | get nodes
| where invariant == true
| select id descriptionThe Operational Ontology — the operational graph
core.ncl — what cannot change
# .ontology/core.ncl
{
nodes = [
{
id = "backend-agnostic-core",
invariant = true,
description = "business logic with no UI deps",
tensions = ["nickel-as-canonical-schema"],
},
],
practices = [...],
}invariant = true requires a new ADR. No exception. state.ncl and gate.ncl — where we are
nickel export .ontology/state.ncl
| from json | get dimensions
| select id current_state desired_statebackend-maturity multi-stable → all-production
nickel-integration-depth schema-input → bidirectionalnickel export .ontology/gate.ncl
| from json | get membranes
| where active == true
| select id permeability protectscore-dependency-gate Low [backend-agnostic-core, ...]
nickel-primacy-gate Low [nickel-as-canonical-schema]It is a queryable graph that the system and the agents read.
Reflection — modes, forms, modules, profiles
Mode: an executable specification
# reflection/modes/new_service.ncl
{
id = "new_service",
trigger = "When adding a service",
steps = [
{
id = "create-adr",
actor = "developer",
action = "Draft ADR for service boundary",
cmd = "stratum form new_adr",
on_error = { strategy = "abort" },
},
{
id = "seal-config",
actor = "developer",
action = "Apply initial config seal",
cmd = "stratum config apply development",
depends_on = [{ step = "create-adr" }],
on_error = { strategy = "abort" },
},
],
}Form: typed structured input
# reflection/forms/register_change.ncl
{
elements = [
{ type = "section_header", label = "Change" },
{
type = "text",
id = "title",
label = "Title",
required = true,
},
{
type = "select",
id = "change_type",
options = ["feature","fix","refactor","config"],
},
]
}sha256(nickel export profile.ncl) — instant drift detectionstratum config verify development → compares current hash against sealed hash Living ADRs
— from dead document to constraint machine
The Nickel structure of an ADR
# adrs/adr-001-nickel-as-canonical.ncl
let s = import "adrs/schema.ncl" in
{
id = "adr-001",
title = "Nickel as First-Class Form Definition",
status = "Accepted",
date = "2025-12-01",
constraints = [
{
id = "adr-001-c1",
severity = "Hard",
claim =
"nickel export must succeed before "
++ "any user interaction",
},
],
superseded_by = "",
} | s.ADRLifecycle and query
Proposed ──────▶ Accepted ──────▶ Superseded
│ ▲
│ new ADR │
└──────── superseded_by ────────────┘glob "adrs/adr-*.ncl"
| each { |f|
nickel export $f | complete
| if $in.exit_code == 0 {
$in.stdout | from json
} else { null }
}
| compact
| where status == "Accepted"
| get constraints | flatten
| where severity == "Hard"
| select id claimImplementation — the parts and how they fit
The stack of operational files
stratum.sh ← entry point, locking, env
└─ reflection/bin/stratum.nu ← dispatcher
├─ modules/adr.nu ← ADR lifecycle
├─ modules/register.nu ← CHANGELOG + ontology
├─ modules/config.nu ← sealed profiles
├─ modules/backlog.nu ← backlog items
└─ modules/forms.nu ← TypeDialog integrated
.ontology/ ← queryable truth
├─ core.ncl ← invariants
├─ state.ncl ← dimensions
└─ gate.ncl ← active guards
adrs/ ← typed decisions
reflection/configs/ ← sealed config historyThe protected-write flow
manifest (config apply/rollback) · changelog (register) · backlog (done/cancel)cfg-20260309T045206-developer — collision impossible, no TOCTOU# Advisory lock: mkdir is POSIX-atomic
mkdir .stratumiops/locks/manifest.lock 2>/dev/null
echo "$$:developer:$(date -u +%Y%m%dT%H%M%SZ)"
> .stratumiops/locks/manifest.lock/ownerMechanics and flow — real use cases
Flow: registering a change
stratum register → interactive TypeDialog formnickel typecheck must passstratum config applystratum config apply production --adr adr-006 --pr 42
# Months later:
stratum config verify production
# → ✓ verified a3f7c12d8b4e9f01...
# → ✗ DRIFT DETECTED stored vs current hashVerified rollback
stratum config history production
stratum config rollback production \
cfg-20260301T120000-developer \
--adr adr-007 \
--note "revert breaking change"snapshot_hash verifies integrity before restoringcfg-20260301T120000-developer (original)
└─ cfg-20260309T045206-agent (change)
└─ cfg-20260309T120000-developer (rollback)Project integration — onboarding to the ecosystem
What a project needs
project/
.ontology/
core.ncl ← project invariants
state.ncl ← maturity dimensions
gate.ncl ← active guards
adrs/
schema.ncl
reflection.ncl
adr-001-*.ncl ← foundational ADR
reflection/
forms/ ← TypeDialog forms
modes/ ← operational modes
configs/ ← sealed profiles
modules/ ← Nu modules
bin/stratum.nu
stratum.shInitialization
/onboard-project → generates .ontology/
+ foundational ADRnickel typecheck on every commit— the declarative layer is never left broken
./stratum.sh check
# ✓ nushell 0.110.0
# ✓ nickel 1.9.0
# ✓ .ontology/core.ncl exportable
# ✓ adrs/adr-001 Accepted
# ✗ no sealed config profiles yetClaude Code integration & concurrent use
SessionStart: automatic context
stratum-session-start.sh runs at the start of every session: === STRATUM CONTEXT: typedialog ===
ADRS
adr-001 [Accepted] Nickel as First-Class Form...
adr-002 [Accepted] NCL element order from position
HARD CONSTRAINTS
adr-001-c1 nickel export must succeed before...
adr-002-c1 web/tui must render headers interleaved
STATE
backend-maturity: multi-stable → all-production
ACTIVE GATES
nickel-primacy-gate [Low] Protects: nickel-as-...
=== END STRATUM CONTEXT ===Concurrent use: human + AI agent
cfg-20260309T045207-agent
Locking serializes writes to the same tree.
They are complementary, not alternatives.