beval-pi-lazy-loader
Pi extension that defers loading of non-critical extensions until after the interactive session is ready, keeping startup fast.
Package details
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.
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:
- Reads its config from the
lazyLoaderkey in the agentsettings.json. - Scans
<agentDir>/extensions/lazy/(override withlazyDir). - Filters by whitelist/blacklist, then loads every surviving extension in parallel via
Promise.allSettled. - Guards each load with a per-extension timeout so one slow extension can't stall the rest (see Timeout semantics below).
- Reports a one-line status (
N/M extensions loaded in Xms) and clears it after 2s. Failures go toconsole.error. - 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 astimedOut, 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
completedLateflag (visible in the error log asTimeout (>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
ExtensionAPIinstance 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 synchronousextensions/directory, notextensions/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