beval-pi-lazy-loader

Pi extension that defers loading of non-critical extensions until after the interactive session is ready, keeping startup fast.

Packages

Package details

extension

Install beval-pi-lazy-loader from npm and Pi will load the resources declared by the package manifest.

$ pi install npm:beval-pi-lazy-loader
Package
beval-pi-lazy-loader
Version
1.1.0
Published
Aug 11, 2026
Downloads
494/mo · 494/wk
Author
bevalz
License
MIT
Types
extension
Size
31.4 KB
Dependencies
0 dependencies · 1 peer
Pi manifest JSON
{
  "extensions": [
    "./extensions/lazy-loader.ts"
  ],
  "image": "https://img.shields.io/badge/pi--lazy--loader-deferred%20load-blue"
}

Security note

Pi packages can execute code and influence agent behavior. Review the source before installing third-party packages.

README

beval-pi-lazy-loader

A pi extension that defers loading of non-critical extensions until after the interactive session is ready, keeping startup fast.

npm version License: MIT

Why

When your pi setup accumulates many extensions (cost trackers, runtime tracers, web tooling, sound notifiers, …), loading them all synchronously at startup blocks the prompt. This package moves that work to the background: it listens to session_start, then discovers and loads every extension in the agent's extensions/lazy/ directory after a configurable delay — so you get a responsive prompt first, and the rest loads in ~20–50ms without blocking.

pi startup
  ├─ load core extensions (sync)
  ├─ scan skills
  ├─ prompt ready ← user can type immediately
  └─ background: lazy-loader loads deferred extensions

Install

pi install npm:beval-pi-lazy-loader

Or add it to ~/.pi/agent/settings.json manually:

{
  "packages": ["npm:beval-pi-lazy-loader"]
}

Then move the extensions you want deferred into the lazy directory (create it if missing):

mkdir -p ~/.pi/agent/extensions/lazy
# e.g. move a deferred extension
mv ~/.pi/agent/extensions/cost-tracker ~/.pi/agent/extensions/lazy/

Each lazy extension is either a directory with index.ts (or index.js) or a standalone .ts/.js file (not named index.*) inside extensions/lazy/. Type declaration files (.d.ts/.d.js) are skipped automatically.

How it works

On session_start, the loader:

  1. Reads its config from the lazyLoader key in the agent settings.json.
  2. Scans <agentDir>/extensions/lazy/ (override with lazyDir).
  3. Filters by whitelist/blacklist, then loads every surviving extension in parallel via Promise.allSettled.
  4. Guards each load with a per-extension timeout so one slow extension can't stall the rest (see Timeout semantics below).
  5. Reports a one-line status (N/M extensions loaded in Xms) and clears it after 2s. Failures go to console.error.
  6. Optionally watches the lazy directory for new files and hot-loads them.

All paths resolve against the agent config directory (~/.pi/agent by default, overridable via PI_CODING_AGENT_DIR), so this package behaves identically whether installed globally or per-project.

⚠️ What not to defer (first-turn visibility)

Lazy extensions are loaded after session_start — i.e. after the UI is ready but in the background. The system prompt for the first user turn is assembled around that same point in time, so any tool, command, or prompt snippet a lazy extension registers is not guaranteed to be present in that first turn if the user types fast.

Keep in the synchronous extensions/ directory (not extensions/lazy/):

  • extensions that register tools the LLM must be able to call from the very first message (e.g. a web-access tool the user expects to use immediately),
  • provider proxies or model overrides that must change the available model list before the first turn,
  • anything that patches the system prompt and needs to be visible immediately.

Safe to defer into extensions/lazy/:

  • passive listeners (cost trackers, runtime tracers, evolve trackers),
  • notifiers and non-critical helpers whose absence on turn 1 is harmless,
  • tools the user won't reach for until later in the conversation.

Timeout semantics

The timeout is a decision deadline, not a cancellation. A running extension factory generally cannot be safely aborted (it may have already registered side-effects). Therefore:

  • If a factory has not resolved within timeout, the result is reported as timedOut, but the underlying load is kept running and awaited.
  • If it eventually completes successfully, it is still recorded as loaded — so hot reload will not attempt to register it a second time. The result carries a completedLate flag (visible in the error log as Timeout (>Nms) but completed later).
  • This avoids the previous bug where a timed-out extension would silently load in the background yet be missing from the loaded set, causing hot reload to double-register it.

Configuration

Add a lazyLoader object to ~/.pi/agent/settings.json:

{
  "lazyLoader": {
    "enabled": true,
    "startDelay": 0,
    "timeout": 5000,
    "lazyDir": "~/.pi/agent/extensions/lazy",
    "hotReload": false,
    "whitelist": [],
    "blacklist": ["sound-notifier"]
  }
}
Option Type Default Description
enabled boolean true Master switch. Set false to disable lazy loading entirely.
startDelay number (ms) 0 Delay before the background load begins. 0 loads as soon as the session is ready.
timeout number (ms) 5000 Per-extension load decision deadline. See Timeout semantics above.
lazyDir string <agentDir>/extensions/lazy Override the directory scanned for lazy extensions.
whitelist string[] [] If non-empty, only these extension names are loaded.
blacklist string[] [] Extensions to skip.
hotReload boolean false Watch the lazy directory and hot-load newly added .ts files.

Notes

  • Deferred extensions must be self-contained. They are loaded with the same ExtensionAPI instance as core extensions, so they can register tools, commands, event handlers, etc. — but anything that must be ready before the first prompt (e.g. a provider proxy that changes available models) should stay in the synchronous extensions/ directory, not extensions/lazy/.
  • Failures are isolated. One broken lazy extension logs an error and is skipped; the rest still load.
  • No bundled extensions. This package ships only the loader. Your extensions/lazy/ content is your own.

Compatibility

Requires pi (@earendil-works/pi-coding-agent) with the getAgentDir export (0.80+). The pi core package is declared as an optional peer dependency — pi provides it at runtime, so you don't need to install it separately.

License

MIT © BevalZ