No description
  • JavaScript 58.3%
  • TypeScript 41.7%
Find a file
2026-03-06 23:57:01 +00:00
.pi Monika (+Global Install) (#1) 2026-02-21 01:31:40 +00:00
agent Backport from monika-mono 2026-03-06 23:52:00 +00:00
docs Backport from monika-mono 2026-03-06 23:52:00 +00:00
scripts Monika (+Global Install) (#1) 2026-02-21 01:31:40 +00:00
stateful-memory Backport from monika-mono 2026-03-06 23:52:00 +00:00
tests Backport from monika-mono 2026-03-06 23:52:00 +00:00
.gitignore Monika (+Global Install) (#1) 2026-02-21 01:31:40 +00:00
package-lock.json Backport from monika-mono 2026-03-06 23:52:00 +00:00
package.json Backport from monika-mono 2026-03-06 23:52:00 +00:00
README.md Readme update 2026-03-06 23:57:01 +00:00

Pi Stateful Memory Extension

This package adds long-term statefulness to Pi via an extension. It maintains a persistent persona (stateful-memory/SOUL.md), per-session memory logs, pinned facts (FACTS.md), orientation context (WAKE.md), and topic-based knowledge addenda (stateful-memory/persona_topics/).

Documentation

The layout here mirrors the global install layout: agent/ maps to ~/.pi/agent, and stateful-memory/ maps to ~/.pi/stateful-memory. In the monorepo, everything lives under /persist/pi/.

What's Included

  • agent/extensions/stateful-memory/: extension implementation (memory store, retrieval, topic routing).
  • stateful-memory/: persona files, topic addenda, and the topic routing index.
    • stateful-memory/SOUL.md, STYLE.md, REGISTER.md, SLEEP.md: persona source files (tracked).
    • stateful-memory/persona_topics/, stateful-memory/PERSONALITY_MATRIX.md: topic addenda + routing table (tracked).
    • FACTS.md, WAKE.md, dreams/: not here — these are live runtime state in ~/.pi/stateful-memory/, written by the sleep cycle, not tracked in the repo.
  • agent/extensions/delegate/: fractal delegation — spawns focused sub-sessions for long-running tasks and returns summaries as tool results. Two files:
    • index.ts — registers the delegate tool, enforces depth limit
    • fork-runner.ts — full fork lifecycle: session creation, depth registry, summary extraction, manual session_shutdown for memory durability See DELEGATE.md for full details.
  • agent/extensions/: other Pi extensions (SSH, force-tools, web-search, etc.).
  • scripts/: smoke tests for memory and topic routing.
  • tests/: unit tests.

Architecture Diagram (High-Level)

User Prompt
   │
   ▼
Trunk Session (depth 0)
   │
   ├─ before_agent_start
   │    ├─ System Prompt = Base + Memory Context + Topic Addenda
   │    │    Memory Context order:
   │    │      Persona (SOUL + STYLE + REGISTER + SLEEP)
   │    │      Current Context (WAKE.md)
   │    │      Pinned Facts (FACTS.md)
   │    │      Recent Themes (last 5 memory entries)
   │    │      Recollections (4 summaries + 4 explicit entries)
   │    └─ Topic Addenda: hybrid-scored, persisted across turns
   │
   ├─ Tools
   │    ├─ remember / remember_session / recall
   │    ├─ list_topics / load_topic
   │    │
   │    └─ delegate (task, context?)
   │         │  Spawns a fork session — same identity + memory, clean context.
   │         │  Depth tracked in globalThis.__delegateForkDepths (survives
   │         │  jiti module reloads). Hard limit: 3 levels from trunk.
   │         │
   │         ▼
   │         Fork Session (depth 1)
   │           ├─ Full persona + memory system (same agentDir)
   │           ├─ Seeded with task + explicit context only
   │           ├─ Can call delegate again (depth 2, then 3, then blocked)
   │           ├─ session_shutdown fired manually → summary written to
   │           │    memory/sessions/*.md (fork work is recallable)
   │           └─ Returns TASK_COMPLETE: summary to calling session
   │
   ├─ Session lifecycle hooks
   │    ├─ session_shutdown / session_before_switch / session_before_fork
   │    └─ Memory summary → memory/sessions/*.md
   │         (fires for trunk + forks — all work is recallable)
   │
   └─ /sleep command
        ├─ Phase 0: pre-sleep summary (main thread, synchronous)
        ├─ Phase 1: WAKE.md fork — reads all session summaries; writes WAKE.md
        │    (uses its own fork runner in memory-sleep.js, not the delegate tool)
        ├─ Phase 2: FACTS.md fork — reads summaries + FACTS.md; rewrites FACTS.md
        ├─ Phase 3: Dreams fork — reads recent summaries + topics; writes dream log
        └─ ctx.newSession() → fresh session with updated WAKE.md + FACTS.md in context

Running With Pi

The extension is loaded globally via ~/.pi/agent/extensions/stateful-memory, which is a bind mount from /persist/pi/agent/extensions/stateful-memory (configured in the NixOS monika module).

To load explicitly from the monorepo during tests:

cd /persist/pi
pi -e agent/extensions/stateful-memory/extension.js

Configuration

The live config is at /persist/pi/agent/stateful-memory.json. It uses absolute paths so it works regardless of which directory Pi is launched from. See docs/PROJECT_STATE.md for all defaults and environment overrides.

Global Install Layout (NixOS)

On the monika host, bind mounts wire the monorepo into the live Pi install:

  • /persist/pi/agent/extensions/stateful-memory~/.pi/agent/extensions/stateful-memory
  • /persist/pi/stateful-memory/~/.pi/stateful-memory/
  • FACTS.md and WAKE.md are not symlinked — they are real files in ~/.pi/stateful-memory/ written by the sleep cycle. Do not commit them to the repo.

Tools and Commands

Tools:

  • remember: store facts to session memory (target: "memory") or directly to FACTS.md (target: "profile").
  • remember_session: force a session summary now.
  • recall: two-phase memory retrieval — LLM planner selects files, second LLM call synthesises a response.
  • list_topics: list available topic addenda and their trigger keywords.
  • load_topic: load a full topic addendum by id.
  • delegate: spawn a focused sub-session for a task. Takes task (required) and context (optional — excerpt relevant facts from the current conversation). The fork runs with full identity and memory access but a clean context window. Returns the fork's TASK_COMPLETE: summary. Max depth 3 from trunk. Use for work that would dominate the current context: reading many files, extended research, multi-step implementation. Short actions are better done directly.

Slash commands:

  • /sleep: run the sleep cycle — pre-sleep summary, then three fork phases (WAKE.md, FACTS.md, dreams), then open a fresh session. See docs/PROJECT_STATE.md for full details.

Delegate Extension

The delegate tool and the sleep cycle's fork runner share the same core pattern but are separate implementations:

delegate tool Sleep fork runner
File delegate/fork-runner.ts stateful-memory/memory-sleep.js
Called by Agent (tool call) /sleep command handler
Task prompt Structured focused-task message Phase-specific (WAKE / FACTS / dreams)
Depth tracking globalThis.__delegateForkDepths Not tracked (sleep forks don't nest)
Timeout 10 min 15 min
Session output dir sessions/forks/ sessions/forks/

Why separate implementations? The sleep fork runner was written to be self-contained — importing only from @mariozechner/pi-coding-agent — to avoid any risk of cross-file import breakage under jiti's real-path resolution. Now that both live in the same repo, sharing is more feasible, but it hasn't been done yet.

The globalThis depth registry: jiti loads extensions with moduleCache: false, so each fork session gets a fresh module instance. A module-level Map would reset on every load, breaking depth enforcement. Storing the registry on globalThis instead means it survives across all module loads in the process, regardless of how they were loaded.

The session_shutdown trick: dispose() on a Pi session only disconnects listeners — it does not fire lifecycle events. To get stateful-memory to write a session summary for a fork (making its work recallable), both fork runners explicitly call session_shutdown on the fork's _extensionRunner before disposing. This is accessing a TypeScript private field compiled to a regular JS property; if a Pi SDK upgrade breaks it, fork sessions degrade gracefully to explicit-remember-only durability.

force-tools dependency: The delegate tool must be present in agent/extensions/force-tools.ts's active tool list. force-tools.ts calls setActiveTools() on every session start and controls which tools are available. If delegate is missing from that list, it will be silently dropped.

Tests

cd /persist/pi
npm test
npm run smoke:memory
npm run smoke:topics

See docs/PROJECT_STATE.md for validation steps and smoke test expectations.