Skip to Content
文档📐 ADR 决策记录ADR-0009: Relationship Authoring and the Wiki-Link Convention's Layer

ADR-0009: Relationship Authoring and the Wiki-Link Convention’s Layer

  • Status: Accepted (2026-06-22)
  • Date: 2026-06-22

Decision (2026-06-22): The synthesis described in the options below has been implemented:

  • Structural typed relations → option 2 (name-resolved ref). ref fields stay as ref in the schema (clean edge declarations). The CLI/facade write path (node create/update --field "关系=名称") accepts a target name (natural key), resolves it to a node ID, and calls store.set_links() to create the typed edge. Comma-separated for multi-value. No [[ ]] needed; no reactive .scm action; no kernel. See src/runex/cli.py:_coerce_cli_value and node_create/node_update.
  • Associative free links → body [[ ]] → generic mentions (the ZK layer). [[Atom标题]] embedded in prose (e.g. 正文 / 描述) stays a kernel (extract-wiki-link-pairs) + reactive .scm (sync_mentions in wiki-links.scm). This is a Zettelkasten convention, not a graph primitive — kept out of L2 core so it’s swappable.
  • Scheduling relations (属于冲刺 etc.) remain on the option 1 hybrid as a working baseline — they are text fields holding [[Target]] wired by wiki-links.scm. They can be migrated to option 2 when the write path is proven stable; the mechanism is identical.

Context

Building the scheduling layer (Task ↔ Sprint ↔ Goal ↔ Thread) surfaced a recurring question: how should a typed relationship be authored, and where does the wiki-link ([[ ]]) convention belong in the architecture?

Three facts frame the question:

  1. A ref field is already an edge, not a scalar. L2’s scalar field types are text/longtext/number/date/bool/blobref is not among them. A ref field declaration is the schema-level declaration of a typed edge; the relationship lives in the links table, never the fields table (store._migrate_ref_fields_to_links enforces this). So “relationships are edges” is already true; the open part is how edges get authored.

  2. Interactive ref authoring is unsolved. runex node update --field "属于冲刺=<id>" fails with unknown value_type: ref, and ADR-0005 forbids an imperative node link verb. So there is currently no clean high-level way for an agent to attach one existing node to another interactively.

  3. The wiki-link mechanism is already layered (config/ontology/wiki_links.scm): parse = a built-in kernel (extract-wiki-link-pairs, L3 Python); wire = reactive .scm actions (find-by-namecreate-link-with-props, with deferred back-fill); store = the L2 links table. It mirrors Logseq/Roam (materialized refs recomputed on write, resolved by name) rather than Obsidian (computed on read). It works but has barely been used in anger.

The first scheduling implementation took a hybrid shortcut: relationship fields were made text holding [[Target]], then a per-field reactive action (packs/wiki-links.scm) projected them to typed edges. This stores the relation twice — as field text and as a derived edge — which is conceptually impure.

A competing value is at stake: conceptual simplicity of the node object (scalar fields + typed edges + body) versus the ergonomics of typed, interactively-authored relationships.

What seems settled (lean accept)

The wiki-link / Zettelkasten convention should stay a kernel + .scm policy, never sink into L2 core.

  • The typed links table is foundational (L2) — edges are core.
  • [[ ]] parsing stays a kernel; the wiring stays optional .scm.
  • Rationale: [[ ]] is a convention, not a graph primitive. Keeping it out of core (a) preserves node-object simplicity — L2 knows only “nodes have typed edges” — and (b) lets a user who dislikes Zettelkasten, or who wants a different link syntax, swap or drop it. The engine functions fully without wiki-links (ingest and actions create edges regardless).
  • Counter-pull we accept: Zettelkasten workflows need inline link-style titles ([[a claim as a title]]) in prose — once we serve ZK users, the “impure” inline syntax is natural and expected in the body/knowledge layer. That is a reason to support [[ ]] well, not a reason to make it a core primitive.

Open options — how to author a typed relationship interactively

  1. Hybrid (current): relationship field is text holding [[Target]]; a per-field reactive action builds the typed edge (relation = field name), multi-value, deferred back-fill. Works today; impure (link syntax in a scalar field; relation stored twice).

  2. Name-resolved ref (leaning): keep relationship fields as ref (clean edge declarations); make the write path accept a name--field "属于冲刺=W26冲刺" — and reactively resolve it to a typed edge. No [[ ]] ceremony, no scalar duplication, no link verb. Requires a small write-path/convention change so a ref field set-by-name becomes an edge.

  3. Body-only [[ ]] (Logseq-pure): all links live in 正文; give up typed structural relations (one generic mentions/wiki_link type). Maximally simple node object, but loses typed scheduling relations.

A likely synthesis: structural typed relations → option 2 (ref, name-resolved); associative free links → body [[ ]] → generic mentions (the ZK layer). This maps each mechanism to the need and keeps the body-syntax impurity confined to the knowledge layer where it’s natural. → This synthesis is accepted and partially implemented (2026-06-22).

What is decided

  • Structural typed relations → option 2 (name-resolved ref). ref fields are now writable from the CLI: --field "主题=知识工程" → facade resolves name to typed edge. Already in use for PKM relations (Atom/HowTo/MOC.主题, MOC.收录). The mechanism generalizes to all ref fields — CRM, media, etc. can adopt the same pattern without schema changes.
  • [[ ]] stays kernel + .scm, never L2 core. Settled.
  • Body [[ ]] for associational/ZK links is correct — that’s where the inline syntax belongs (embedded in prose).

What is still undecided

  • Whether to migrate the scheduling hybrid (text + [[ ]] + per-field wire_* actions) to option 2. The mechanism is ready; the question is when.
  • Whether deferred name-resolution (auto-resolve when the target later appears) should be a general write-path feature or stay per-rule .scm. Currently: ref fields require the target to exist (unresolved → error); wiki-links.scm handles deferred resolution for body [[ ]] and the scheduling text fields.

Consequences

  • Structural ref fields are now writable from the CLI. The facade write path (node create/update) resolves target names to typed edges. Agent skills can write --field "关系=名称" without [[ ]] ceremony.
  • The scheduling layer ships on the hybrid (option 1) as a working baseline. Not yet migrated to option 2; mechanism is ready.
  • The kernel/data layering for [[ ]] is settled. New link behavior defaults to .scm + kernel for the ZK/prose layer, facade name-resolution for structural ref fields.
  • PKM relations (主题, 收录) are the first adopters of option 2. CRM and media ref fields (Deal.客户, 自媒体作品.作者/平台) can follow the same pattern — no schema changes needed, just CLI usage.
Last updated on