Research-Stack/2-Search-Space/tardygrada/docs/plans/2026-04-04-frames-crdt-design.md

7.6 KiB

Frames + CRDT + Datalog: Internal Consistency World Model

For Claude: REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.

Goal: Replace probabilistic grounding with algebraic consistency checking. Frames define structure, CRDTs guarantee merge correctness, Datalog derives consequences. All deterministic, no external data.

Architecture: Three layers that compose:

  1. Frames (schema): what relationships exist and their constraints
  2. CRDT (algebra): merge function derived from frame constraints
  3. Datalog (inference): derive new facts from existing ones (already built)

A new fact arrives -> frame match -> dry-run CRDT merge -> Datalog derivation -> CONSISTENT / CONFLICT / DERIVED.

Tech Stack: C11, extends existing Datalog engine, ~300 lines new code.


Data Model

// A frame slot: one relationship in a frame
typedef struct {
    char name[64];       // slot name: "city", "country", "creator"
    char type[64];       // type constraint: "City", "Country", "Person"
    int  functional;     // 1 = one value per key, 0 = many values ok
    int  required;       // 1 = must be filled, 0 = optional
} tardy_frame_slot_t;

// A frame: structured expectation about a type of relationship
typedef struct {
    char name[64];                  // "Capital", "Creation", "Location"
    char predicate[64];             // which predicate this frame covers
    tardy_frame_slot_t slots[8];    // subject slot at [0], object slot at [1]
    int  slot_count;
    int  transitive;                // 1 = locatedIn(X,Y) + locatedIn(Y,Z) -> locatedIn(X,Z)
    int  symmetric;                 // 1 = relatedTo(X,Y) -> relatedTo(Y,X)
    int  inverse;                   // 1 = has an inverse relationship
    char inverse_pred[64];          // inverse predicate name
} tardy_frame_t;

// Frame registry
#define TARDY_MAX_FRAMES 64

typedef struct {
    tardy_frame_t frames[TARDY_MAX_FRAMES];
    int           count;
} tardy_frame_registry_t;

CRDT Merge Semantics

typedef enum {
    TARDY_MERGE_OK,        // merges cleanly, no conflict
    TARDY_MERGE_DUPLICATE, // already exists, no change
    TARDY_MERGE_CONFLICT,  // violates functional dependency
    TARDY_MERGE_DERIVED,   // already derivable from existing facts
} tardy_merge_result_t;

// Dry-run merge: would this fact conflict with existing state?
// Does NOT modify state. Pure query.
tardy_merge_result_t tardy_crdt_dry_merge(
    const tardy_frame_registry_t *frames,
    const tardy_dl_program_t *datalog,
    const tardy_dl_atom_t *new_fact);

// Actual merge: add fact if consistent, reject if conflict
tardy_merge_result_t tardy_crdt_merge(
    const tardy_frame_registry_t *frames,
    tardy_dl_program_t *datalog,
    const tardy_dl_atom_t *new_fact);

Synthetic Backbone Frames

Pre-loaded at startup:

Frame: Capital
  predicate: capitalOf
  slots: [(city, City, functional=1), (country, Country, functional=1)]
  inverse: capitalCity
  -- One capital per country. One country per capital.

Frame: Location
  predicate: locatedIn
  slots: [(entity, Thing, functional=0), (place, Place, functional=0)]
  transitive: yes
  inverse: contains
  -- Many things in one place. Containment is transitive.

Frame: Creation
  predicate: creator
  slots: [(creation, Thing, functional=0), (agent, Agent, functional=0)]
  inverse: createdBy
  -- Things can have multiple creators.

Frame: Founding
  predicate: founder
  slots: [(organization, Organization, functional=0), (agent, Agent, functional=0)]
  inverse: foundedBy

Frame: Temporal
  predicate: dateCreated
  slots: [(thing, Thing, functional=1), (date, Date, functional=1)]
  -- Each thing has one creation date.

Frame: Type
  predicate: type
  slots: [(instance, Thing, functional=0), (class, Class, functional=0)]
  transitive: no
  -- Multiple types per thing.

Frame: Description
  predicate: description
  slots: [(thing, Thing, functional=0), (text, Text, functional=0)]

Frame: KnownFor
  predicate: knownFor
  slots: [(agent, Agent, functional=0), (achievement, Thing, functional=0)]

Type Learning

Types are NOT pre-defined. They emerge from usage.

When the system sees creator(Python, GuidoVanRossum):

  • Python is assigned type ?a
  • GuidoVanRossum is assigned type ?b
  • creator frame says slot[0] type is "Thing", slot[1] type is "Agent"
  • Unify: Python : Thing, GuidoVanRossum : Agent

After 10 creator facts:

  • All slot[0] values are typed as Thing
  • All slot[1] values are typed as Agent
  • New claim creator(Linux, LinusTorvalds): both slots type-check immediately

Types are stored as Datalog facts:

type(Python, Thing).
type(GuidoVanRossum, Agent).
type(Paris, City).        -- from capitalOf frame
type(France, Country).    -- from capitalOf frame

Verification Flow

Claim: "Berlin is the capital of Germany"
Decomposed: capitalOf(Berlin, Germany)

1. FRAME MATCH: capitalOf -> Capital frame
   slots: (city=Berlin, country=Germany)

2. TYPE CHECK:
   - Germany: seen before? Yes, type=Country. Slot expects Country. OK.
   - Berlin: seen before? No. Slot expects City. UNRESOLVED.
   - Result: PARTIAL TYPE MATCH (1/2 slots confirmed)

3. DRY-RUN MERGE:
   - Capital frame is functional on country: one capital per country.
   - Does Germany already have a capital in the Datalog? No.
   - No conflict. Merge would succeed.
   - Result: MERGE_OK

4. DATALOG DERIVE:
   - capitalOf(Berlin, Germany) would trigger rule:
     locatedIn(Berlin, Germany) [from backbone rule]
   - No contradictions with existing facts.
   - Result: DERIVATION_CLEAN

5. FINAL VERDICT:
   - Frame: matched
   - Types: 1/2 confirmed (Germany=Country), 1/2 unresolved (Berlin=?City)
   - Merge: no conflict
   - Derivation: clean
   -> CONSISTENT (structurally valid, no conflicts, one unresolved type)

Compare with a FALSE claim:

Claim: "Tokyo is the capital of Germany"
Decomposed: capitalOf(Tokyo, Germany)

1. FRAME MATCH: capitalOf -> Capital frame. OK.

2. TYPE CHECK:
   - Germany: type=Country. OK.
   - Tokyo: type=City (from capitalOf(Tokyo, Japan)). OK.

3. DRY-RUN MERGE:
   - If capitalOf(Berlin, Germany) was previously verified:
     Capital frame is functional on country. Germany already has Berlin.
     CONFLICT.
   - If NO capital for Germany exists:
     No conflict. But Tokyo already is capitalOf Japan.
     Capital frame is functional on city too (one country per capital).
     CONFLICT: Tokyo is already Japan's capital.

4. FINAL VERDICT: CONFLICT (functional dependency violation)

Integration with Existing Code

The frame registry and CRDT merge replace the grounding confidence scoring:

Old: ground triples -> count evidence -> compute confidence -> threshold check
New: ground triples -> frame match -> CRDT merge check -> CONSISTENT/CONFLICT/DERIVED

No probabilities. No thresholds. No confidence scores. Three deterministic outcomes.

The pipeline layers still run but the grounding layer (Layer 2) changes from "count matching triples" to "check CRDT merge result."

Implementation Tasks

Task 1: Frame data structures + registry + backbone frames

  • Create: src/ontology/frames.h, src/ontology/frames.c
  • Load 8 backbone frames at startup

Task 2: CRDT merge function

  • Implement tardy_crdt_dry_merge and tardy_crdt_merge
  • Check functional dependencies, type compatibility

Task 3: Type learning from verified facts

  • When a fact is added, extract types from frame slot constraints
  • Store types as Datalog facts

Task 4: Wire into verification pipeline

  • Replace confidence-based grounding with frame + CRDT check
  • Pipeline outputs: CONSISTENT / CONFLICT / DERIVED / UNRESOLVABLE

Task 5: Integration test

  • Test chain reasoning, functional dep conflicts, type learning