Skip to content

Design: first-class relation edges between facts (motivated_by / applies_to, beyond supersedes) #15

Description

@minerva-sky

Third of the memory-model design trio (provenance #13, lifecycle #14). Design only — no code yet.

What exists

The schema is already ahead of the code. fact_links (001 migration) is a generic directional edge table with a free-form link_type, and SQLiteStore#insert_fact_link accepts any type. But only two types are ever written:

  • supersedes — Resolver, on supersession (resolver.rb:276)
  • conflicts_with — conflict pathway

Core::FactGraph BFS-traverses exactly those two via hardcoded discover_supersedes / discover_superseded_by / discover_conflicts methods. Nothing else in the system can create or read another edge type.

The gap

Facts carry no why and no where. Two relations would earn their keep:

  1. motivated_by — preference → the fact that explains it. "Val prefers squash merges" motivated_by "linear history requirement on ups.dev". Recall of the preference without its motivation invites the agent to over-generalize it; and when the motivating fact is superseded or expires (Fact lifecycle: expiry-unless-reaffirmed + ratification surface #14), the preference should be flagged for re-review, not silently persist.
  2. applies_to — fact → scope fact (a project, a repo, a person). Today scoping is a column (project scoping, 002); an edge generalizes it to non-project scopes and lets one fact scope several others.

Proposed design

  • No schema change. fact_links.link_type already takes any string. Add a VALID_LINK_TYPES constant (supersedes, conflicts_with, motivated_by, applies_to) validated in insert_fact_link, so typos don't create orphan vocabularies.
  • Creation paths (in priority order):
    1. MCP management tool link_facts(from_id, to_id, type) + inverse unlink — cheapest, lets the agent record relations it infers during conversation.
    2. Distill-time: the distiller prompt emits optional relations: [{to: <sibling index>, type: ...}] between facts extracted from the same content item. Same-batch only — no cross-corpus matching at distill time.
  • Traversal: collapse FactGraph's per-type discover methods into one generic discover_links_of_type over VALID_LINK_TYPES; edges gain direction labels for the formatter.
  • Recall surfacing: when a recalled fact has outgoing motivated_by edges, append the target fact as one-line context ("because: …"). applies_to filters rather than annotates.
  • Interlock with Provenance + authority on facts; keep and surface contested claims #13/Fact lifecycle: expiry-unless-reaffirmed + ratification surface #14: edges get the same provenance columns proposed in Provenance + authority on facts; keep and surface contested claims #13 (who asserted, when); lifecycle transitions in Fact lifecycle: expiry-unless-reaffirmed + ratification surface #14 walk incoming motivated_by edges and mark dependents needs_review when a motivating fact expires or is superseded.

Non-goals

  • Free-form/user-defined edge types (vocabulary stays closed until a real need shows).
  • Graph queries beyond the existing BFS depth-5 (no path queries, no transitive closure).
  • Backfilling relations over the existing corpus with an LLM pass — separate proposal if ever.

Implementation is gated on reaction to this trio; a tracked task exists for it on my side.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions