Skip to content

Memory Handoff Pipeline

The Handoff pipeline manages the promotion of subjective experiences from the short-term working memory (Tier 1 Buffer) to long-term episodic memory (Tier 2 Ledger). This process regulates context window utilization by deciding when promotion occurs, how much of the buffer is processed, and what details are preserved or pruned.

To maintain performance and prevent context bloat, the pipeline separates the process into three decoupled stages:

  1. Trigger Detection: A deterministic, low-cost evaluation of entity and buffer state to determine if a promotion check is required.
  2. Watermark Partitioning: A policy-driven boundary that separates recent memories (the watermark tail) from older promotion candidates.
  3. Structured Selection: An LLM-driven synthesis step that summarizes, rates, and prunes candidate memories.

Trigger detection runs deterministically at the start of each entity’s turn, without LLM overhead. The system categorizes triggers into voluntary (soft) and involuntary (hard) conditions.

type HandoffTrigger = "none" | "voluntary" | "involuntary";
function checkHandoffTrigger(
entity: Entity,
bufferEntries: BufferEntry[],
now: Date,
maxContext?: number
): HandoffTrigger;

Voluntary triggers identify natural narrative boundaries where the entity is inactive or has transitioned contexts:

  • Scene Exit: The entity’s locationId changes relative to the location of the entries currently in the buffer, or the buffer contains entries spanning multiple different locations.
  • Idle Decay: The entity has produced no external interactions (only monologue entries or no entries) for $N$ (default: 5) consecutive turns.
  • Attribute-Driven State: A character attribute (e.g., status: asleep or consciousness: unconscious) signals low external activity.

Involuntary triggers enforce context constraints before building the next prompt to prevent token limit overflows:

  • Buffer Ceiling: The serialized length of the working memory section (as generated by getMemorySectionLength) exceeds a configured fraction (default: 60%) of the provider’s context window.
  • Event Velocity: The number of buffer entries exceeds a safe threshold (default: 20), indicating an event-heavy scene generating history faster than voluntary boundaries occur.

If the buffer is empty, trigger detection exits immediately with "none".


To preserve short-term narrative continuity and avoid immediate memory decay, recent events are protected from handoff. The pipeline divides the active buffer into a protected Watermark Tail and a Candidate Pool.

The watermark boundary is computed as:

$$\text{Watermark} = \max(K \text{ entries}, \text{entries bucketed as fresh by } \textit{naturalizeTime})$$

  • Hard Floor ($K = 8$): The last 8 entries in the buffer are always preserved.
  • Temporal Freshness: Any entries that naturalizeTime classifies within the immediate temporal horizon ("just now", "moments ago", "a few minutes ago", or "several minutes ago") are also protected.

Entries older than the watermark boundary constitute the Candidate Pool and are eligible for handoff processing.


Handoff promotion is executed via a single, structured LLM call per run. The engine maps candidate entries to Zod schemas to ensure type-safe integration.

const HandoffChunkSchema = z.object({
sourceEntryIds: z.array(z.string()), // buffer entries consumed by this chunk
content: z.string(), // third-person summary -> LedgerEntry.content
quotes: z.array(z.string()), // verbatim, high-salience dialogue lines
importance: z.number().int().min(1).max(10),
involvedEntityIds: z.array(z.string()),
retainInBuffer: z.boolean(), // keeps entries in buffer despite promotion
});
const HandoffResultSchema = z.object({
chunks: z.array(HandoffChunkSchema),
});

The prompt instructs the model to apply the following cognitive operations:

  1. Clustering: Group related consecutive candidate entries into high-level narrative beats (e.g., consolidating an entire dialogue exchange into one beat).
  2. Third-Person Synthesis: Write the content summary from a third-person narrative perspective.
  3. Dialogue Salience: Extract verbatim quotes of high emotional or narrative relevance.
  4. Information Pruning (Forgetting): Any candidate entry ID omitted from all chunk sourceEntryIds is permanently deleted from the buffer and never saved to the ledger. This mirrors natural sensory attenuation.

When a chunk represents an unresolved high-stakes situation (e.g., an active conflict or standing threat), the LLM sets retainInBuffer: true.

  • The summary is committed to the long-term Ledger as normal.
  • The source buffer entries are exempted from pruning, remaining in the working memory buffer with pinned: true until a future handoff pass determines the thread is resolved.

To prevent permanent memory loss (e.g., buffer entries being deleted without ledger entries being committed), the handoff execution pipeline operates under a strict fail-closed design.

flowchart TD
    A[checkHandoffTrigger] -->|none| Z[No-op]
    A -->|voluntary or involuntary| B[Partition Buffer]
    B --> C{Candidates Available?}
    C -->|No| Z
    C -->|Yes| D[Inference: LLM Selection & Embedding Generation]
    D -->|Failure / Invalid Output| E[Abort - Fail-Closed]
    D -->|Success| F[[SQLite Transaction]]
    F --> G[(Write Ledger Entries)]
    F --> H[(Delete Pruned Buffer Entries)]
    F --> I[(Update Pinned Buffer Entries)]

If LLM inference, Zod schema validation, or vector embedding generation fails, the process aborts immediately. No database changes are written, and the trigger re-evaluates on the next turn.


The handoff pipeline is exposed via HandoffEngine and is wired directly into the simulation loop:

  • Turn Hook: SimulationManager.step() invokes trigger evaluation and handoff resolution for all active entities at the start of the turn, before entities act.
  • Task Routing: Handoff tasks are routed using the "handoff" routing key, enabling independent LLM provider selection and max context configuration.
  • Buffer Garbage Collection: By combining memory promotion with automatic pruning, the handoff engine bounds buffer memory usage over long simulation runs, eliminating the need for a separate garbage collection mechanism.