Skip to Content
文档📐 ADR 决策记录ADR-0011: Use Obsidian as the Human-Sovereign Mirror of Ontology State

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 → CanonicalItemMarkdownFileSink → 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:

  1. Ingest parses Markdown and Frontmatter into a typed CanonicalItem.
  2. Every projected node records ordinary internal sync fields such as:
    • _sync_file_hash
    • _sync_file_mtime
    • _sync_store_cursor
    • _sync_projection_version
  3. Before writeback, Runex compares the current file hash/version with the last ingested baseline.
  4. If unchanged, it patches the projected Frontmatter/body, writes the file, and records the new checksum/version.
  5. FileWatcher computes the new checksum. If it matches the last Runex write, it suppresses the echo.
  6. If the file changed since the baseline, writeback fails closed and creates or updates a Conflict node 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 Conflict node
  • 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:

  1. obsidian-markdown Connector
    • 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
  2. ontology-frontmatter Recipe
    • resolves type/supertag through the current manifest
    • maps declared fields and refs into CanonicalItem fields/links
    • validates enum/type/natural-key rules
    • performs no file I/O
  3. a saved IngestJob
    • binds vault path, connector, recipe, watch/sync mode, deletion policy, projection policy, and cursor
    • is stored as an ordinary IngestJob node, 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 continuation

CLI 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:

  1. a file-hash kernel — 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.
  2. a guarded projection action in .scm — compare the node’s _sync_file_hash baseline to the current (file-hash …). Equal or absent → (writeback …) then restamp the baseline. Diverged → upsert a Conflict node and do not overwrite. The conflict policy is now a declaration.
  3. StoreWatcher is 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.

Last updated on