Skip to content

Memlink ontology · memory type system

flowchart TD
  NOTE["Memlink's LIVE type system, taken from the code — memlink_hybrid/domain/enums.py + the world.* migrations.<br/>Three CLOSED vocabularies (entity kinds · relation types · fact slots), plus what every object carries.<br/>The bottom shows how memory is WRITTEN — and the one place the vocabularies still drift from each other."]
  ROOT["Memory object · the world.* graph<br/>every durable object carries: kind · lane · status · confidence · provenance"]
  NOTE ~~~ ROOT

  subgraph KINDS["Object kinds · MemoryObjectKind (6)"]
    direction TB
    K1["episode"]
    K2["fact · world.facts"]
    K3["entity · world.entities"]
    K4["relation · world.relations"]
    K5["consolidation"]
    K6["semantic_support"]
    K1 ~~~ K2 ~~~ K3 ~~~ K4 ~~~ K5 ~~~ K6
  end
  ROOT --> K1

  subgraph ENT["Entity kinds · EntityKind (24) · domain/enums.py"]
    direction TB
    E1["People + contact · person · housemate · friend · contact_method"]
    E2["Places + orgs · place · employer · work_site · team"]
    E3["Time · schedule · calendar_item"]
    E4["Work / coding · project · task · issue · decision · repo · service · environment · deployment · dependency · milestone"]
    E5["Knowledge · document · incident · lesson · rule"]
    E1 ~~~ E2 ~~~ E3 ~~~ E4 ~~~ E5
  end
  K3 --> E1

  subgraph REL["Relation types · RelationType (31) · ONE source of truth — every accept-list DERIVED from it"]
    direction TB
    R1["supplemental — text-grounded, the LLM + family extractors emit these (19)<br/>housemate_of · friend_of · lives_with · works_for · works_at · member_of · contact_for · sibling_of · parent_of · spouse_of · lives_in · works_with · partner_of · knows · uses · located_in · operated_by · provider_of · associated_with (last resort)"]
    R2["email — minted deterministically from headers, never text-grounded (9)<br/>sent · addressed_to · in · about · has_attachment · labeled · subscribed_to · replied_to · forwarded"]
    R3["system / reserved — no extractor emits these (3) · supersedes · attends · shares"]
    RB["19 supplemental + 9 email + 3 system = 31 = the world.relations.relation_type CHECK<br/>bitemporal: status + supersedes link · widening needs a migration first (HARD RULE 4)"]
    R1 ~~~ R2 ~~~ R3 ~~~ RB
  end
  K4 --> R1

  subgraph FACT["Fact slots · CanonicalSlot · WRITABLE = every slot EXCEPT relation_type"]
    direction TB
    F1["Legacy · birthday · location · timezone · employer · role · work_site · roster · rnr_location · housemate · friend · phone_number · email · important_event · contact_name · relation_type"]
    F2["Identity · legal_name · pronouns · past_location · nationality · language_spoken · marital_status"]
    F3["Contact · contact_method"]
    F4["Relationships · partner · family_member · pet"]
    F5["Professional · past_employer · past_role · skill · certification · education · business_entity"]
    F6["Health · medical_condition · medication · allergy · blood_type"]
    F7["Financial · financial_institution · obligation_recurring · income_source"]
    F8["Life / prefs · interest_hobby · dietary_preference · vehicle · property"]
    FC["Cardinality · SINGULAR (14, one current value per owner+lane) · birthday · location · timezone · employer · work_site · roster · rnr_location · contact_name · legal_name · pronouns · nationality · marital_status · partner · blood_type<br/>everything else is MULTI-VALUE — role is multi on purpose (ADR 0163)"]
    F1 ~~~ F2 ~~~ F3 ~~~ F4 ~~~ F5 ~~~ F6 ~~~ F7 ~~~ F8 ~~~ FC
  end
  K2 --> F1

  subgraph CROSS["Cross-cutting · carried on every object"]
    direction TB
    LANE["Lane · owner / shared / system / agent"]
    STAT["Status · MemoryStatus · current / superseded / contradicted / archived"]
    CONF["Confidence · ConfidenceState · grounded / provisional / contradicted / superseded"]
    PROV["Provenance · world.object_provenance · object → evidence_quote → source_event_id"]
    SRC["Ingest profile · chat_realtime / email_import / document_import / calendar_ingest / api_ingest"]
    LANE ~~~ STAT ~~~ CONF ~~~ PROV ~~~ SRC
  end
  ROOT -.on every object.-> LANE

  GATE["WRITABLE_FACT_SLOTS = every CanonicalSlot EXCEPT relation_type<br/>RFC 0099 RC3 / ADR 0159 — the ONE writable fact vocabulary (drift-proof, DERIVED)<br/>validate_fact_candidate: not in set ⇒ slot_not_writable · relation_type lives in world.relations, never as a fact"]
  FACT -.governed by.-> GATE

  subgraph WRITE["How memory is WRITTEN · world.events_raw → derivation → extract → SANITIZE → world.*"]
    direction TB
    EVT["world.events_raw — the ONE source every derivation reads"]
    EXR["extractors — LLM supplemental extractor + deterministic family P11–P14<br/>each emits candidates, then a SANITIZER re-checks every candidate against an accept-set"]
    OSL["fact slots — OFFER WRITABLE_FACT_SLOTS  ==  ACCEPT WRITABLE_FACT_SLOTS · both DERIVED from one set · IN SYNC"]
    ORL["relations — OFFER 21 (19 supplemental + owned_by / service_provider sub-kind remaps)  ==  ACCEPT 19 supplemental · DERIVED · IN SYNC · test_relation_predicate_vocab_sync locks it"]
    OEN["entities — OFFER all 24 (derived from EntityKind) → ACCEPT only 8 HAND-CODED · person · place · employer · work_site · contact_method · housemate · friend · decision<br/>the other 16 entity kinds are SILENTLY DROPPED at sanitize"]
    EVT --> EXR
    EXR --> OSL
    EXR --> ORL
    EXR --> OEN
  end
  OSL -.writes facts.-> K2
  ORL -.writes relations.-> K4
  OEN -.writes entities.-> K3
  DRIFT["⚠ the ontology's ONE un-derived copy — the entity-kind accept-set is HAND-TYPED in dispatch_derivation_sanitization.py, NOT derived from EntityKind.<br/>Fact-slots and relation-types are both derived from a single source and test-locked, so they cannot drift — entities can. A new EntityKind is dropped at capture until this set is derived too."]
  OEN --> DRIFT

  style KINDS fill:#f4f1ff,stroke:#7c5cff,stroke-width:2px,color:#7c5cff
  style ENT fill:#f4f1ff,stroke:#7c5cff,stroke-width:1px,color:#7c5cff
  style REL fill:#f4f1ff,stroke:#7c5cff,stroke-width:1px,color:#7c5cff
  style FACT fill:#f4f1ff,stroke:#7c5cff,stroke-width:2px,color:#7c5cff
  style CROSS fill:#f1f5f9,stroke:#475569,stroke-width:2px,color:#475569
  style WRITE fill:#f1f5f9,stroke:#475569,stroke-width:2px,color:#475569
  classDef pN fill:#ffffff,stroke:#7c5cff,color:#27272a,stroke-width:1px
  classDef slN fill:#ffffff,stroke:#475569,color:#27272a,stroke-width:1px
  classDef gN fill:#fffaf0,stroke:#b45309,color:#27272a,stroke-width:2px
  classDef gnN fill:#ecfdf3,stroke:#1f9d63,color:#27272a,stroke-width:1px
  classDef rdN fill:#fdecec,stroke:#e5484d,color:#27272a,stroke-width:2px
  class ROOT,K1,K2,K3,K4,K5,K6,E1,E2,E3,E4,E5,R1,R2,R3,RB,F1,F2,F3,F4,F5,F6,F7,F8,FC pN
  class LANE,STAT,CONF,PROV,SRC,NOTE,EVT,EXR slN
  class GATE gN
  class OSL,ORL gnN
  class OEN,DRIFT rdN