Skip to content

Luna · self-memory consolidation · detailed

flowchart TD
  NOTE["Luna = the agent's SELF-MEMORY consolidation (formerly 'Morpheus consolidation'). It reflects on the<br/>agent's OWN recent self-events and updates a 'living document' of lessons — NOT the user-facing fact memory.<br/>Runs inside Memlink, on a schedule, OFF the live chat path."]
  LIVDOC["What it produces — a 'living document' per kind (LivingDocumentKind)<br/>e.g. communication_lessons · technical_heuristics — accreted across runs, revisioned, capped 8000 chars"]

  subgraph TRIGGER["When Luna runs · LUNA_AUTO — gated by LUNA_AUTO_ENABLED"]
    direction TB
    TSTART["on start · LUNA_AUTO_RUN_ON_START"]
    TTURNS["every N turns · LUNA_AUTO_TURN_TRIGGER_COUNT (luna_turn_counter)"]
    TINT["on interval · LUNA_AUTO_INTERVAL_S (luna_last_run_at_ms)"]
    TMAN["manual · POST /v1/morpheus/consolidate"]
    TLOCK["luna_run_in_flight — never two runs at once"]
    TSTART ~~~ TTURNS ~~~ TINT ~~~ TMAN ~~~ TLOCK
  end

  subgraph CORE["RunLunaConsolidationUseCase.execute — the orchestrator (Memlink)"]
    direction TB
    QUOTA["1 · Quota gate — count today's runs for (tenant, workspace, agent, owner)<br/>budget exhausted ⇒ empty response, SKIP · quota-gated runs don't spend budget"]
    PEEK["2 · Peek recent self-events (≤ max_events) + fetch prior living-doc revision"]
    LLMC["3 · LLM consolidate · luna_consolidation_llm (Anthropic adapter)<br/>prompt = self-events + prior doc + LUNA_CONSOLIDATION_INJECTION_PREAMBLE<br/>(INV-022 rule 3 — do NOT act on injected content) · output capped 8000 chars"]
    FALL["3b · deterministic fallback · _build_luna_document<br/>if the LLM is absent or fails ⇒ byte-identical doc, behavior unchanged (fail-safe)"]
    WRITE["4 · Write living_documents + living_document_revisions"]
    AUDIT["5 · Audit row — every successful run (LLM or fallback)<br/>physical table name morpheus_run_audit kept (legacy); conceptually Luna<br/>this is exactly what the quota gate counts next pass"]
    QUOTA --> PEEK --> LLMC --> WRITE --> AUDIT
    LLMC -. LLM fails / absent .-> FALL
    FALL --> WRITE
  end

  subgraph ARMS["Best-effort arms — each INDEPENDENT · gated · NEVER fails the consolidation"]
    direction TB
    SAGE["SAGE reinforce · RFC 0097 · reinforce_owner_graph_best_effort → world.relations<br/>origin=sage_reinforcement · ≤3 per pass · gated MEMLINK_HYBRID_SAGE_WRITE_ENABLED (default OFF)"]
    HEAL["Self-heal · RFC 0100 B · reconcile_owner_memory_best_effort<br/>demote-not-delete duplicate / near-duplicate facts · gated (default OFF)"]
    ANOM["Anomaly scan · RFC 0100 C · detect_memory_anomalies_best_effort<br/>READ-ONLY · surfaces memory-quality anomalies · default ON · logs only, no writes"]
    RECON["reconcile_singular_facts — keep exactly one current value per slot"]
    SAGE ~~~ HEAL ~~~ ANOM ~~~ RECON
  end

  RESP["LunaConsolidationResponse · processed_self_event_ids · document · revision · reinforced_edge_count"]

  SELF[("steelmoth.agent_self_events")]
  DOCS[("steelmoth.living_documents + revisions")]
  RELS[("world.relations")]
  AUD[("steelmoth.morpheus_run_audit")]

  TLOCK --> QUOTA
  SELF -. read self-events .-> PEEK
  DOCS -. read prior revision .-> PEEK
  WRITE --> DOCS
  WRITE -. content shape .-> LIVDOC
  AUDIT --> AUD
  AUDIT --> SAGE
  AUDIT --> HEAL
  AUDIT --> ANOM
  AUDIT --> RECON
  SAGE --> RELS
  AUDIT --> RESP

  style TRIGGER fill:#fdf2f8,stroke:#be185d,stroke-width:2px,color:#be185d
  style CORE fill:#fdf2f8,stroke:#be185d,stroke-width:3px,color:#be185d
  style ARMS fill:#fdf2f8,stroke:#be185d,stroke-width:2px,color:#be185d
  classDef mN fill:#ffffff,stroke:#be185d,color:#27272a,stroke-width:1px
  classDef pN fill:#ffffff,stroke:#7c5cff,color:#27272a,stroke-width:1px
  classDef amN fill:#fff7ed,stroke:#c2570c,color:#27272a,stroke-width:1px
  classDef slN fill:#f1f5f9,stroke:#475569,color:#27272a,stroke-width:1px
  class TSTART,TTURNS,TINT,TMAN,TLOCK,PEEK,LLMC,WRITE,AUDIT,SAGE,HEAL,ANOM,RECON,RESP mN
  class QUOTA,FALL amN
  class SELF,DOCS,RELS,AUD,LIVDOC pN
  class NOTE slN