ADR-0011: Use Obsidian as the Human-Sovereign Mirror of Ontology State
- Status: Proposed
- Date: 2026-06-24
Context
Runex is intended to be used by a human without building a separate GUI. Structured state, machines, and relationships live in the ontology, while the human currently works in Obsidian Markdown files.
The repository already contains the two one-way technical paths:
- file change →
FileWatcher→ source adapter →CanonicalItem→ Store - Store node →
CanonicalItem→MarkdownFileSink→ file
They are not yet a safe bidirectional system. Composing them directly creates an echo loop:
Store write → Markdown writeback → FileWatcher → ingest → Store write → …Checksum suppression breaks that echo but does not by itself prevent a machine write from overwriting a human edit made concurrently.
This capability is not optional product polish. Approval, natural-language rules, Agent-generated thoughts, and human correction all depend on Obsidian being a reliable input and output surface.
Decision
For v0.2, Runex adopts a human-sovereign mirror:
- the ontology manifest is authoritative for schema and executable rules
- the ontology graph is authoritative for structured current state, identities, relationships, and lifecycle
- Obsidian is the authoritative human editing surface for projected text and human-owned fields
- projection policy and field ownership decide which direction wins for each field; there is no vague whole-file “single source of truth”
The synchronization protocol is:
- Ingest parses Markdown and Frontmatter into a typed
CanonicalItem. - Every projected node records ordinary internal sync fields such as:
_sync_file_hash_sync_file_mtime_sync_store_cursor_sync_projection_version
- Before writeback, Runex compares the current file hash/version with the last ingested baseline.
- If unchanged, it patches the projected Frontmatter/body, writes the file, and records the new checksum/version.
FileWatchercomputes the new checksum. If it matches the last Runex write, it suppresses the echo.- If the file changed since the baseline, writeback fails closed and creates
or updates a
Conflictnode rather than overwriting the human edit.
Checksum is for echo suppression. Version/baseline comparison and field ownership are for concurrent-edit safety.
Ontology Representation
No sync table is added.
- sync metadata is stored as ordinary internal fields on the projected node
- projection policy is ontology data (Supertag policy or policy nodes)
- a synchronization conflict is an ordinary
Conflictnode - source/projection relationships are ordinary links
- machine-generated sync fields are excluded from Markdown Frontmatter
This keeps synchronization visible to DSL rules, queries, events, and audit history without changing the L1 object model.
Ingest Boundary
The Obsidian relationship must not be implemented by expanding the
user-specific obsidian-thought adapter into a larger monolith.
The v0.2 vertical slice uses ADR-0008’s split:
obsidian-markdownConnector- watches/enumerates vault Markdown
- parses Frontmatter and Obsidian-flavored syntax
- emits path, body, links/embeds, attachments, hash, and mtime as RawRecord
- knows no ontology business type
ontology-frontmatterRecipe- resolves
type/supertagthrough the current manifest - maps declared fields and refs into CanonicalItem fields/links
- validates enum/type/natural-key rules
- performs no file I/O
- resolves
- a saved IngestJob
- binds vault path, connector, recipe, watch/sync mode, deletion policy, projection policy, and cursor
- is stored as an ordinary
IngestJobnode, not a configuration table
The existing obsidian-thought DataSourceSpec is retained as a compatibility
Preset, not treated as the primary architecture.
This same boundary permits a future feishu-webhook Connector to feed a
Recipe without changing Store ingest or ontology semantics. Webhook support is
explicitly deferred beyond Phase 1.
Phase 1 implements only the Obsidian connector. Other generic interface entries remain catalog proposals until backed by a real consumer.
Projection Policy
Every projected Supertag declares:
- whether it has an Obsidian projection
- path/template policy
- body field
- human-owned fields
- ontology-owned fields
- merge/escalation policy for shared fields
Default policy:
- human-authored prose and explicit human decisions are human-owned
- machine lifecycle state and derived fields are ontology-owned
- shared fields require baseline/version comparison and conflict escalation
Approval Implication
The primary v0.2 approval path is an Obsidian projection:
Proposal/Approval node
→ projected approval note
→ human changes Frontmatter decision
→ ingest
→ approval state transition
→ reactive continuationCLI approval remains an operator/debug fallback, not the product interaction contract.
Alternatives Considered
Obsidian is the complete source of truth
Rejected. It cannot authoritatively represent graph-only relationships, derived lifecycle state, runtime-loaded behavior, or native nodes without turning every graph mutation into file choreography.
Store always overwrites files
Rejected. It violates human sovereignty and can silently destroy concurrent edits.
Files always overwrite Store state
Rejected as a universal rule. Machine-owned state would be reset by stale projections and reactive behavior could not safely advance.
Checksum alone
Rejected as incomplete. It suppresses self-generated echoes but does not resolve or detect concurrent edits against an older baseline.
Consequences
Positive
- Runex is usable through Obsidian without a new GUI
- approvals and rule authoring become real human workflows
- Agent-generated Markdown re-enters the same ingest path
- sync loops are suppressed and concurrent edits fail visibly
- all sync state remains ontology-visible
Negative / Tradeoffs
- requires explicit projection and field-ownership policies
- generic Markdown round-trip must preserve unrelated Frontmatter and body
- conflict handling becomes a first-class product behavior
- full-file overwrite is no longer acceptable; safe patching is required
- Connector/Recipe/Job runtime contracts must be completed; the current core datasource catalog is metadata-only
Supplement (2026-06-25): Where the Writeback Guard Lives
The conflict-guarded writeback is business-mutable policy (when to fail
closed, what a Conflict records), so it must be ontology-governed — typed,
trace-able, event-emitting, and subject to the migration gate — not imperative
Python glue the runtime cannot see. It is therefore decomposed as:
- a
file-hashkernel — the one new I/O primitive: read the projected file and return its content digest. It is fast and non-blocking. Reading the file inside a synchronous action is consistent with(writeback)already writing the file synchronously; it is not slow/durable work and does not belong outside the bus. - a guarded projection action in
.scm— compare the node’s_sync_file_hashbaseline to the current(file-hash …). Equal or absent →(writeback …)then restamp the baseline. Diverged → upsert aConflictnode and do not overwrite. The conflict policy is now a declaration. StoreWatcheris a thin cursor dispatcher (a peripheral). It observes tx changes and fires the guarded action by node id; it holds no policy.
There is a single guarded entry: a projected supertag is never written by
calling (writeback …) directly. (writeback …) is demoted to a pure
projection primitive still used by non-mirror sinks (e.g. the NocoDB sink).
This keeps “all writeback goes through the guard” and “business logic lives in
scm + kernel” true at the same time.
Related Documents
../architecture.md../roadmap.md../../tasks/prd-state-machine-proactive-agent-framework.mdadr-0004-canonicalitem-bidirectional-boundary.mdadr-0010-durable-agent-work-outside-reactive-bus.md