Self-improving reference. This document is a living map of the codebase. It includes a regeneration protocol (section: Self-Update Protocol) so any agent or human can detect drift and refresh it as the codebase grows or shrinks.
| Field | Value |
|---|---|
| Name | pi-multiloop |
| Version | 0.3.2 |
| Description | Autoloop/autoresearch extension for Pi with multi-lane isolation |
| License | MIT |
| Author | drvova |
| Repo | git+https://github.com/drvova/pi-multiloop.git |
| Runtime | TypeScript source loaded directly by Pi at runtime (no build step; noEmit tsconfig) |
| Test runner | Vitest 4.x (single root config; seed-randomized runs) |
| Package manager | npm workspaces (packages/*) |
- Multi-loop isolation on the same worktree (lane-per-loop, not worktree-per-loop)
- Use existing benchmark scripts (wrap, don't generate)
- Segmented durable history (append-only JSONL per lane+run-tag)
- Flexible goal modes (optimize, punchlist, research, dev)
- Steerability (steer mid-loop, skip items, adjust thresholds)
- Minimal state files (results.jsonl + state.json per loop + shared registry)
- Own the benchmark script (user provides the verify command)
- Require separate worktrees (single shared worktree)
- Build in context compression (pi-boomerang composes)
- Build in goal supervision (pi-supervisor composes)
- Become a test runner or shell scheduler (records agent-reported results)
pi-multiloop/
├── package.json # published manifest (root = release unit)
├── tsconfig.base.json # shared compilerOptions (noEmit; typecheck-only)
├── tsconfig.json # include: packages/**/*.ts
├── vitest.config.ts # single test runner
├── AGENTS.md # agent instructions
├── README.md # user-facing docs
├── CHANGELOG.md
├── LICENSE
├── assets/architecture.svg # rendered diagram
├── docs/
│ ├── PLAN.md # north stars, gap analysis, checklist
│ ├── STATE.md # state layers, lifecycle, compaction policy
│ ├── MONOREPO.md # package layout + architectural decisions
│ ├── TODO.md
│ ├── FEEDBACK.md
│ ├── PUBLISH.md
│ └── diagrams/architecture.mmd # Mermaid source for the SVG
├── scripts/
│ ├── e2e-optimize.sh # real-headless end-to-end (spawns pi sessions)
│ ├── e2e-tmux.sh
│ └── e2e-mkloop.mjs
├── skills/
│ └── multiloop/ # setup wizard skill
│ ├── SKILL.md
│ └── references/LOOP_GUIDE.md
└── packages/
├── multiloop/ # core extension + tests
│ ├── extensions/pi-multiloop/ # 21 source modules (~6,100 LOC)
│ ├── tests/ # 37 test files (~6,500 LOC) + support/
│ └── package.json
├── multiloop-run/ # detached headless driver
│ ├── bin/multiloop-run.mjs # ~333 LOC
│ └── package.json
├── multiloop-agent/ # fleet agent extension
│ ├── extensions/multiloop-agent/
│ │ └── index.ts # ~807 LOC
│ └── package.json
└── child-agent/ # isolated child-session library
├── src/index.ts # ~139 LOC
└── package.json
Why root = published package: npm install, pi install git:..., and
omp plugin install all read the root package.json, which declares both
omp and pi manifest keys. Sub-packages are private: true for workspace
organization; nothing is published from packages/ directly. Skills sit at
skills/ because both runtimes discover them by scanning <root>/skills
for <name>/SKILL.md, not by following a manifest path.
21 modules, ~6,100 LOC total. The organism metaphor: each module is named after a biological process, and the system behaves like a living colony of experiment loops.
| File | LOC | Category | Responsibility |
|---|---|---|---|
index.ts |
2555 | Entry point | Extension factory, all tools, commands, event handlers, prompt builders, activeStates map, auto-continuation, compaction resume, dashboard widget, compareRuns |
loop.ts |
546 | Engine | Keep/revert decision, escalation ladder (refine@3, pivot@5, stop@2pivots), stop conditions, baseline establishment, decision application, iteration-context builder |
state.ts |
475 | Persistence | LoopState model, atomic JSON save/load (tmp+rename+fsync), JSONL append, state reconstruction, stall detection, action counters |
anchors.ts |
429 | Integrity | Protected-file hashing, config pinning, audit verifier (extension-run), revert verifier, workspace boundary fingerprint, drift refusal |
lanes.ts |
251 | Identity | Lane/run-tag validation, path resolution, registry CRUD, target resolution (exact/lane/ambiguous/unknown) |
modes.ts |
203 | Mode defs | 4 mode configs (optimize/punchlist/research/dev), keyword detection, punchlist parser |
verifiers.ts |
193 | Acceptance | Check normalization, required-check enforcement, acceptance assessment, min-measurement gate |
ui.ts |
188 | Dashboard | TUI table widget (lane, mode, iteration, status, metric, best, delta), live re-render |
mode.ts |
152 | Session mode | Input classification (suspend/arm/neutral), session-start arming, continuation gate, disarm logic |
proposals.ts |
134 | Speciation | Lane proposals with approval gate (max 5 pending, 1 per lane name) |
autophagy.ts |
125 | Self-recycling | Compress completed/archived results.jsonl to durable signal (keeps + champion) |
sentinel.ts |
119 | Immune system | Re-run champion verify commands, detect regressions, post immune signals to knowledge board |
mesh.ts |
107 | Messaging | File-based stigmergic inter-lane messaging (append-only mailbox per lane) |
peers.ts |
105 | Peer learning | Read-only cross-lane result access (Optuna JournalStorage pattern) |
pruning.ts |
99 | Memory consolidation | Deduplicate knowledge board, preserve reinforced entries, cap at 50 |
metrics.ts |
96 | Statistics | Median, MAD, confidence scoring, improvement significance test |
adoption.ts |
95 | Gene transfer | Find better sibling champions, extract winning approach for adoption |
apoptosis.ts |
62 | Programmed death | Distill postmortem on stop condition, publish to knowledge board |
vitality.ts |
59 | Metabolism | Per-lane fitness tag (hot/warm/cold/dead), aggregate swarm stress |
rhythm.ts |
54 | Circadian | Search temperature oscillation (exploit/balanced/explore from recent outcomes) |
knowledge.ts |
52 | Shared memory | Cross-lane knowledge board append/read (durable, undirected) |
index.ts (imports all modules)
├── loop.ts ← state, metrics, lanes, mesh, knowledge, adoption, rhythm, apoptosis, verifiers
├── state.ts ← lanes
├── lanes.ts ← (stdlib only)
├── metrics.ts ← (no internal deps)
├── anchors.ts ← state (types), lanes (types)
├── verifiers.ts ← state (types)
├── mode.ts ← (no internal deps — pure functions)
├── modes.ts ← node:fs only
├── mesh.ts ← lanes
├── knowledge.ts ← lanes
├── peers.ts ← lanes, state
├── proposals.ts ← lanes
├── sentinel.ts ← lanes, state, metrics, anchors, knowledge
├── pruning.ts ← (stdlib only)
├── autophagy.ts ← lanes, state (types)
├── rhythm.ts ← state (types)
├── vitality.ts ← state (types)
├── apoptosis.ts ← lanes, state (types), knowledge
├── adoption.ts ← lanes, state (types)
└── ui.ts ← state (types), metrics, pi-tui
Tools registered:
| Tool | Phase | Purpose |
|---|---|---|
multiloop_start |
Phase 0 | Start a new loop (mode, goal, verify command, scope, verifiers) |
multiloop_iterate |
Phase 1 | Signal iteration start; captures workspace boundary fingerprint |
multiloop_measure |
Phase 2 | Record metric measurements + verification checks; assess acceptance |
multiloop_decide |
Phase 3 | Apply keep/revert/log/skip decision; append result; advance iteration |
multiloop_log |
Phase 3 alt | Log-only iteration (research/dev/punchlist modes, crashes, blocks) |
multiloop_resume |
Recovery | Resume a paused/stopped/detached loop |
multiloop_pause |
Control | Pause a running loop |
multiloop_stop |
Control | Stop a running loop |
multiloop_archive |
Control | Archive a completed/stopped loop to .multiloop/archive/ |
multiloop_compare |
Analysis | Read-only cross-run comparison with champion verdict |
multiloop_send |
Mesh | Send a directed message to another lane's mailbox |
multiloop_inbox |
Mesh | Read own mailbox (pending messages folded into next iteration) |
multiloop_publish |
Knowledge | Publish a distilled lesson to the shared knowledge board |
multiloop_results |
Peer learning | Read sibling lanes' measured outcomes |
multiloop_propose_lane |
Speciation | Propose a new lane for orthogonal work |
multiloop_proposals |
Speciation | List pending/approved/rejected proposals |
multiloop_approve |
Speciation | Approve a pending lane proposal (starts the lane) |
multiloop_reject |
Speciation | Reject a pending lane proposal |
multiloop_pulse |
Perception | Live swarm overview (lanes, mesh traffic, knowledge, proposals) |
multiloop_sentinel |
Immune | Re-run champions, detect regressions, post immune signals |
Events handled: session_start, input, agent_start, agent_end,
session_before_compact, session_compact, session_shutdown
Slash command: /multiloop with subcommands: status, on, off, ls,
stop, pause, resume, archive, sentinel, prune, gc, rm, compare,
approve, reject, guide, help
Runs autonomous multiloop loops as isolated child Pi sessions. The child loads the multiloop extension and the full tool set, drives the iterate/measure/decide cycle itself until the stop condition, and reports back.
Tools: multiloop_agent (spawn), multiloop_agent_steer (steer),
multiloop_agent_result (poll/collect), multiloop_agent_stop (abort),
multiloop_agents (list)
Widget: LoopFleetWidget — running/terminal agents with spinner animation.
Key function: runLoopAgent — creates a child session, subscribes to tool
events for progress tracking, and re-nudges from durable on-disk state when the
child's turn ends prematurely.
Drives the iterate/measure/decide cadence without an interactive Pi session by
spawning one headless pi -p --mode json session per iteration. The extension's
own state machine does all deciding; this script only orchestrates sessions,
polls state.json for advancement, reaps the child process group, and enforces
stop conditions.
Pure exports for testability: parseArgs, cleanChildEnv, probePi,
readRegistry, resolveLoop, readLoopState, pauseLoop, shouldContinue,
buildIterationPrompt, spawnIteration, winKillArgs, stopIteration,
iterationAdvanced, main
Shared core for spawning fully isolated Pi child sessions. Creates an in-memory
session with a custom ResourceLoader that disables all user extensions, skills,
prompt templates, themes, and context files. Only the caller's system prompt and
explicitly injected inline extensions are visible.
Exports: createIsolatedResourceLoader, createChildAgentSession,
extractLastAssistantText, promptChildAgent
Contains SKILL.md (the interactive setup wizard that a human or agent walks
through to configure a loop) and references/LOOP_GUIDE.md (detailed reference).
The uppercase filename is mandatory: both Pi and OMP skip every directory entry
not named exactly SKILL.md.
.multiloop/
├── registry.json # all loops (active, paused, completed, archived, deleted)
├── active/<LANE>/<RUN_TAG>/
│ ├── results.jsonl # append-only iteration log (authoritative history)
│ ├── state.json # resume snapshot (LoopState)
│ ├── lessons.md # per-lane pivot lessons (optional)
│ └── mesh.jsonl # inter-lane mailbox (optional)
├── shared/
│ ├── knowledge.md # durable cross-lane lessons
│ └── proposals.json # lane proposals awaiting approval
└── archive/ # completed loops moved here
└── <LANE>/<RUN_TAG>_<timestamp>/
| Layer | Storage | Key Question |
|---|---|---|
| Durable registry | .multiloop/registry.json |
"Does this loop exist on disk and is it resumable?" |
| Durable snapshot | state.json per lane |
"What is the loop's current iteration, metric, status?" |
| Append-only results | results.jsonl per lane |
"What has been tried, what worked, what was reverted?" |
| Runtime attachment | activeStates (in-memory) |
"Is this loop loaded in the current Pi process?" |
| Session mode | Session branch replay | "Is this session actively looping?" |
| Per-turn flag | loopTurnActive (in-memory) |
"Did this turn do loop work?" |
iteration,baseline,currentMetric,bestMetricconsecutiveFailures,pivotCount,stallStreaklastLesson,lastWorkspaceFingerprintpendingContinue(durable auto-continue intent)protectedPaths/protectedBaseline(frozen-file protection)auditVerifier/revertVerifier(extension-executed anti-cheat)minMeasurements(noise-robust keep/revert gate)pinnedConfig(frozen verifier/stop-condition fields — tamper detection)
multiloop_iterate → activeIteration.phase = "started"
captures workspace boundary fingerprint
refuses if workspace drifted since last decision
multiloop_measure → activeIteration.phase = "measured"
establishes baseline (first call) or compares
runs auditVerifier (extension-owned) against reported metric
re-hashes protectedPaths against baseline
records acceptance recommendation
multiloop_decide → appends result to results.jsonl
applies keep/revert/log/skip to state
increments iteration
may trigger escalation, pivot, or stop condition
clears activeIteration
multiloop_log → appends log/skip/crash/blocked result
increments iteration
clears activeIteration
37 test files (~6,500 LOC) + 5 support files (~630 LOC).
The suite is built on a mutation-discipline philosophy: a green suite only proves the code you chose to exercise. Before claiming a change is verified, break it on purpose and confirm the suite fails. Three defects in this repo shipped past a green suite because tests replayed a private copy of the logic instead of calling production.
| File | LOC | Category |
|---|---|---|
loop.test.ts |
711 | unit — decide, escalation, stop conditions, applyDecision |
state.test.ts |
508 | unit — save/load, reconstruct, stall detection, atomic writes |
anchors.test.ts |
469 | unit + mutation — protected files, config pin, verifiers, fingerprints |
scenarios.test.ts |
437 | integration — multi-lane, upgrade path, registry desync |
index.test.ts |
408 | unit + integration — prompt builders, compareRuns |
sessions.test.ts |
343 | simulation — 60-turn randomized horizon, durable intent |
mode.test.ts |
293 | unit + property — input classification, continuation gate |
driver.test.ts |
247 | unit + integration — detached driver helpers, poll-and-reap |
properties.test.ts |
246 | property — invariants over seeded random histories |
lanes.test.ts |
232 | unit — validation, registry CRUD, target resolution |
modes.test.ts |
229 | unit — mode configs, keyword detection, punchlist |
tui-stress.test.ts |
219 | simulation — TUI widget rendering under stress |
exhaustive.test.ts |
209 | enumeration — decision space vs oracle.ts |
verifiers.test.ts |
204 | unit — acceptance, check enforcement |
multilane-horizon.test.ts |
192 | simulation — multi-lane long horizon |
sentinel.test.ts |
191 | unit — champion check, immune signals |
longhorizon-env.test.ts |
191 | simulation — 60-turn across 5 simulated platforms |
lifecycle.test.ts |
187 | integration — full run across simulated restart |
homeostasis.test.ts |
173 | integration — swarm homeostasis, convergence |
child-agent.test.ts |
150 | unit + integration — isolated session, abort handling |
metamorphic.test.ts |
141 | metamorphic — relations between related inputs |
peers.test.ts |
139 | unit — peer result reading, decision-only visibility |
adoption.test.ts |
127 | unit — findAdoptable, winning approach |
knowledge.test.ts |
123 | unit — append/read, tail bounding |
mesh.test.ts |
122 | unit — send/read/peek, corrupt-line tolerance |
proposals.test.ts |
119 | unit — propose/resolve, caps, dedup |
swarm.test.ts |
116 | integration — swarm perception, pulse |
metrics.test.ts |
103 | unit — median, MAD, confidence |
autophagy.test.ts |
103 | unit — result compression |
apoptosis.test.ts |
103 | unit — postmortem distillation |
pruning.test.ts |
102 | unit — dedup, reinforcement, capping |
vitality.test.ts |
90 | unit — lane fitness, swarm stress |
ui.test.ts |
85 | unit — dashboard rows, formatting |
bootstrap-fixes.test.ts |
80 | regression — specific bootstrap bugs |
rhythm.test.ts |
63 | unit — temperature derivation |
fleet-tools.test.ts |
61 | integration — MULTILOOP_TOOLS allowlist sync |
child-session-bootstrap.test.ts |
27 | integration — child session extension bootstrap |
| File | LOC | Purpose |
|---|---|---|
global-setup.ts |
18 | Draws/pins the run seed, prints for reproducibility |
seed.ts |
102 | RUN_SEED, rng(), pick(), intBetween() — deterministic randomization |
oracle.ts |
60 | Independent stop-condition model from README/STATE.md (NOT from loop.ts) |
session-harness.ts |
212 | Session simulator mirroring index.ts wiring; composition-only |
stub-pi.mjs |
35 | Test double for pi binary in driver tests |
Every test run draws a fresh seed. MULTILOOP_TEST_SEED=cafe1234 npx vitest run
replays byte-identical. The seed is printed at startup — copy it before debugging.
npm install
npx tsc --noEmit # typecheck across packages/**
npx vitest run # full suite; seed echoed at startup
pi install . # the repo installs itself as a pi package
/multiloop status # in Pi: shows "no active loops" when cleanscripts/e2e-optimize.sh is a manual end-to-end (spawns real pi sessions,
takes minutes). Run it when modifying the core loop or driver.
The codebase uses a colony-organism metaphor. Each module is named after a biological process and behaves accordingly:
| Module | Bio Metaphor | What It Does |
|---|---|---|
apoptosis.ts |
Programmed cell death | Clean lane death with postmortem distillation |
autophagy.ts |
Cellular self-recycling | Compress dead runs to durable signal |
vitality.ts |
Metabolism / endocrine | Lane fitness tags + aggregate swarm stress |
pruning.ts |
Synaptic pruning | Consolidate knowledge board, prune noise |
sentinel.ts |
Immune system | Detect champion regressions over time |
mesh.ts |
Stigmergic communication | File-based inter-lane messaging |
peers.ts |
Shared journal | Read-only cross-lane result learning |
rhythm.ts |
Circadian rhythm | Search temperature oscillation |
adoption.ts |
Horizontal gene transfer | Adopt proven sibling approaches |
knowledge.ts |
Collective memory | Durable cross-lane knowledge board |
proposals.ts |
Speciation | New lane creation with approval gate |
This section makes the map self-improving. The codebase grows and shrinks at will. Any agent or human can detect drift and regenerate the affected sections.
Run this command to check if the map is stale:
# Compare current file inventory + LOC against what the map recorded
cd /home/drvova/pi-multiloop
echo "=== EXTENSION MODULES ==="
for f in packages/multiloop/extensions/pi-multiloop/*.ts; do
printf "%5d %s\n" "$(wc -l < "$f")" "$(basename "$f")"
done | sort -rn
echo "=== TEST FILES ==="
for f in packages/multiloop/tests/*.test.ts; do
printf "%5d %s\n" "$(wc -l < "$f")" "$(basename "$f")"
done | sort -rn
echo "=== STANDALONE PACKAGES ==="
wc -l packages/multiloop-agent/extensions/multiloop-agent/index.ts \
packages/multiloop-run/bin/multiloop-run.mjs \
packages/child-agent/src/index.ts 2>/dev/nullCompare output to the Module Inventory table above. If any file count, LOC, or file name differs, the map needs updating.
To regenerate the entire map from scratch:
-
Gather structure:
find . -type f -not -path '*/node_modules/*' -not -path '*/.git/*' \ -not -path '*/.multiloop/*' -not -path '*/dist/*' | sort
-
Gather module analysis: Dispatch a general-purpose subagent with:
"Analyze every source module in
packages/multiloop/extensions/pi-multiloop/andpackages/*/. For each file: file name, core responsibility (1-2 sentences), key exports, key dependencies, LOC. Also analyze the test suite and support files. Read ALL source files." -
Read architectural docs:
docs/PLAN.md,docs/MONOREPO.md,docs/STATE.mdto capture the vision, decisions, and lifecycle that the code alone cannot convey. -
Update the Map Fingerprint below with the new timestamp and metrics.
-
Append a changelog entry below describing what changed.
| Trigger | What to Refresh |
|---|---|
New .ts file added to extension |
Module Inventory table + dependency graph |
Existing .ts file changes significantly |
Its row in Module Inventory |
| New test file | Test Categories table |
New package under packages/ |
Repository Layout + Standalone Packages section |
| State architecture changes | State Architecture section + read docs/STATE.md |
| New tool or command registered | Key Entry-Point Exports table |
| New biological metaphor module | Biological Metaphor Quick Reference table |
| Package.json changes | Project Identity table |
| North stars or scope changes | Read docs/PLAN.md, update North Stars section |
| Field | Value at Last Update |
|---|---|
| Generated | 2025-01-24 |
| Git commit | e84dd6c |
| Branch | main |
| Extension modules | 21 files, ~6,100 LOC |
| Test files | 37 files, ~6,500 LOC |
| Support files | 5 files, ~630 LOC |
| Standalone packages | 3 files, ~1,279 LOC (agent 807 + run 333 + child 139) |
| Total source LOC | ~13,900 (including tests) |
| Indexed AST symbols | 487 |
| Indexed call graph edges | 6,679 |
| Indexed files | 68 |
| Date | Change |
|---|---|
| 2025-01-24 | Initial generation from commit e84dd6c on main. Full module, test, and architecture analysis via subagent + direct source reading. |