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