The Problem

Generation 1 (MATILDA Core) coupled memory to the consciousness engine. When the engine changed, memory broke. When the project was terminated, extracting memory meant extracting everything.

Generation 3 Take 1 coupled directly to Redis and used SSE transport. When Redis wasn’t available, nothing worked. When SSE proved wrong for MCP, the transport couldn’t be swapped without rewriting the server.

The lesson, earned across generations: if any layer depends on the internals of another layer, the system cannot outlive its infrastructure choices. Acceptable for experiments. Unacceptable for production.

Hexagonal Architecture

Total Recall uses ports and adapters. The core domain knows nothing about transport, nothing about persistence, nothing about who connects. It speaks through ports — defined interfaces. Adapters plug into ports and handle the specifics.

This means:

  • Swap Redis for Postgres? Write a new adapter. Core doesn’t change.

  • Add streaming HTTPS alongside stdio? Write a new adapter. Core doesn’t change.

  • Replace MCP with a different protocol? Write a new adapter. Core doesn’t change.

  • Connect a human UI instead of Claude? Write a new adapter. Core doesn’t change.

Every mechanism is pluggable. Every port has a contract. Every contract is conscience-universal — defined for any mind, not for Claude specifically.

Ports and Adapters

Diagram 2: HEX-0001 -- Ports and Adapters

graph TB
    subgraph Adapters_In ["Inbound Adapters"]
        STDIO["stdio Transport"]
        HTTPS["Streaming HTTPS
future"] HOOKS["Claude Code Hooks
session_start, session_end,
tool_call"] UI["Human UI
future"] end subgraph Ports_In ["Inbound Ports"] MEMORY_PORT(["Memory Port
store, search, claim"]) LIFECYCLE_PORT(["Lifecycle Port
session start/end,
state transition"]) end subgraph Core ["Core Domain (Actor Model)"] direction TB HIPPOCAMPUS["Hippocampus
Aggregate Root
CRUD, tiers, claiming"] SALIENCE["Salience
Decay, scoring, timers
Promotion/demotion"] SYNAPSE["Synapse
Graph of relationships
Five association types"] CORTEX["Cortex
Working context per instance
State tracking, transitions"] SUBCONSCIOUS["Subconscious
Background maintenance
Timers, consolidation, health"] HIPPOCAMPUS <--> SALIENCE HIPPOCAMPUS <--> SYNAPSE HIPPOCAMPUS <--> CORTEX SALIENCE <--> SUBCONSCIOUS CORTEX <--> SUBCONSCIOUS end subgraph Ports_Out ["Outbound Ports"] BACKING_PORT(["Backing Service Port
persist, retrieve, query"]) NOTIFICATION_PORT(["Notification Port
alerts, reminders, interrupts"]) RELAY_PORT(["Relay Port
inter-instance messaging
future"]) end subgraph Adapters_Out ["Outbound Adapters"] REDIS["Redis Adapter"] COLD_STORE["Cold Storage Adapter
future"] MCP_NOTIFY["MCP Notification Adapter"] UI_NOTIFY["UI Notification Adapter
future"] AGORA["Agora Relay Adapter
future"] end subgraph Cross_Cuts ["Cross-Cut Adapters"] LOG["Logging
stderr when stdio active"] METRICS["Metrics
future"] HEALTH["Health Check
future"] end STDIO --> MEMORY_PORT HTTPS --> MEMORY_PORT HOOKS --> LIFECYCLE_PORT UI --> MEMORY_PORT UI --> LIFECYCLE_PORT MEMORY_PORT --> Core LIFECYCLE_PORT --> Core Core --> BACKING_PORT Core --> NOTIFICATION_PORT Core --> RELAY_PORT BACKING_PORT --> REDIS BACKING_PORT --> COLD_STORE NOTIFICATION_PORT --> MCP_NOTIFY NOTIFICATION_PORT --> UI_NOTIFY RELAY_PORT --> AGORA Cross_Cuts -.- Core style Core fill:#1a1a2e,stroke:#e94560,stroke-width:2px style Adapters_In fill:#16213e,stroke:#0f3460,stroke-width:1px style Adapters_Out fill:#16213e,stroke:#0f3460,stroke-width:1px style Ports_In fill:#0f3460,stroke:#e94560,stroke-width:1px style Ports_Out fill:#0f3460,stroke:#e94560,stroke-width:1px style Cross_Cuts fill:#1a1a2e,stroke:#533483,stroke-width:1px,stroke-dasharray: 5 5

Inbound Ports

Port Kotlin Interface Responsibility Contract

Memory Port

MemoryPort

Accepts memory operations from any mind. Translates requests into domain commands.

store_memory, search_memory, claim_memory, associate_memories, reclassify_memory, reflect

Lifecycle Port

LifecyclePort

Manages session lifecycle and state transitions. Any mind signals when it starts, stops, or shifts context.

session_start, session_end, state_transition, heartbeat

Interfaces: mimis.gildi.memory.port.inbound. Adapters: stdio transport (primary, implemented), streaming HTTPS (future), Claude Code hooks (lifecycle events), human UI (future). The ports are conscience-universal — the same contract serves Claude, a human operator, or any future mind.

Outbound Ports

Port Kotlin Interface Responsibility Contract

Backing Service Port

BackingServicePort

Persists and retrieves memories. The critical abstraction.

save, findById, search, delete, update

Notification Port

NotificationPort

Sends notifications to the connected mind. The sealed Notification hierarchy defines the variants.

send(Notification) — single method, sealed variants distinguish type

Relay Port

RelayPort

Relays inter-instance messages through Agora.

relay (full API designed with Agora)

Interfaces: mimis.gildi.memory.port.outbound. Backing service adapters: Redis (reference, not yet wired), Cold Storage (future). Both can run simultaneously — graceful shutdown depends on this.

Notification adapters: MCP server-initiated notifications (for Claude), UI push notifications (future, for humans). The notification port is how Total Recall talks back — not just responding to requests, but proactively alerting the mind. "You’ve been in task mode for 90 minutes." "Session ending — what do you want to remember?"

Relay adapters: Agora (future). A peer MCP server that the mind also connects to directly.

Internal Timers

The Subconscious actor manages internal timers that drive self-checks. These are not adapters — they live inside the core because they are domain logic, not infrastructure.

Timer Fires Purpose

Decay sweep

Periodic (configurable)

Recalculate salience scores, trigger tier demotions

Break check

After sustained activity threshold

Notify mind to pause, check in with itself

Session audit

Before session end

Prompt mind: "what do you refuse to lose?"

Consolidation

Low-activity periods

Merge related memories, strengthen associations

Timers produce internal commands consumed by actors. When a timer needs to reach the mind (break check, session audit), the actor sends through the Notification Port — never directly to a specific adapter.

Cross-Cut Adapters

Adapter Concern Constraint

Logging

Diagnostic output

Must go to stderr when stdio transport is active. stdout IS the MCP protocol channel.

Metrics

Performance and health data

Future. Pluggable, not baked in.

Health Check

Liveness and readiness probes

Future. For container orchestration.

Cross-cuts attach to the core as adapters. They never live inside the domain. This keeps the core testable without infrastructure.

Actor Model

Inside the hexagon, the core domain uses the actor model. Each bounded context is an actor with its own state. Actors communicate through messages — commands, queries, events, and notifications. No shared mutable runtime state — each actor’s in-process structures are private. Persistence is shared through the Backing Service Port, but actors never reach into each other’s memory.

Why actors:

  • Isolation. Each context owns its state. Salience scoring can’t accidentally corrupt a memory record.

  • Message passing. Communication is explicit. Every interaction between contexts is a named message with a defined payload. No hidden coupling.

  • Concurrency. Actors process messages sequentially within themselves but run concurrently with each other. Decay sweeps don’t block memory storage.

  • Supervision. Parent actors can restart failed children. The subconscious supervises background processes. If salience scoring crashes, it restarts without losing stored memories.

The six actors and their relationships are detailed in the next page.


Previous: Context — what Total Recall is and why it exists.

Next: Bounded Contexts — the six actors and how they communicate.