Skip to content

Ralph loop stops after compaction due to lost in-memory state #11

@elecnix

Description

@elecnix

Problem

When pi performs compaction (auto-compaction or /compact), the Ralph Wiggum loop silently stops working. The agent continues, but ralph_done returns "No active Ralph loop" and the iteration counter freezes.

Root Cause

The extension maintains loop state in two places:

  1. On-disk (persistent): .ralph/<name>.state.json tracks status, iteration count, etc.
  2. In-memory (ephemeral): A closure variable let currentLoop: string | null = null tracks which loop is active.

When compaction happens, the session reloads and the extension module is re-initialized, resetting currentLoop to null. The on-disk state remains intact, but the extension has no reference to it.

The session_start handler finds active loops on disk and notifies the user, but never restores currentLoop:

pi.on("session_start", async (_event, ctx) => {
    const active = listLoops(ctx).filter((l) => l.status === "active");
    if (active.length > 0 && ctx.hasUI) {
        const lines = active.map(/* ... */);
        ctx.ui.notify(`Active Ralph loops:\n${lines.join("\n")}\n\nUse /ralph resume <name> to continue`, "info");
    }
    updateUI(ctx);  // currentLoop is still null here
});

After this, all currentLoop-dependent code silently fails:

  • ralph_done: returns "No active Ralph loop"
  • agent_end: early-returns without checking completion markers
  • before_agent_start: early-returns without injecting loop instructions

Proposed Fix

Restore currentLoop from disk state in the session_start handler:

pi.on("session_start", async (_event, ctx) => {
    const active = listLoops(ctx).filter((l) => l.status === "active");
    if (active.length > 0) {
        // Restore currentLoop from disk state (critical for surviving compaction)
        if (!currentLoop) {
            currentLoop = active[0].name;
        }
        if (ctx.hasUI) {
            const lines = active.map(/* ... */);
            ctx.ui.notify(`Active Ralph loops:\n${lines.join("\n")}\n\nUse /ralph resume <name> to continue`, "info");
        }
    }
    updateUI(ctx);
});

This ensures that after compaction (or any session reload), the first active loop found on disk is restored as currentLoop, allowing ralph_done and agent_end to function correctly.

Alternative Considerations

  • If multiple loops are active, restoring active[0] may not be the one the user was working on. A more robust solution might track a "last active" loop or use a dedicated session_compact handler.
  • Consider using pi.appendEntry() to persist loop state in the session itself, making it survive compaction without relying on the session_start rehydration path.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions