# Future Improvements

Roadmap for `pi-lazy-loader`, ranked by utility-to-effort ratio. Items at the
top buy the most user value per line of code; items at the bottom are nice-to-
have polish.

## High-value

### 1. Eager tool stubs with cached metadata

**Problem.** Today the LLM cannot autonomously call a lazy target's tools
because they're invisible in the system prompt until first activation.
This is fine for extensions whose UX is entirely command-driven, but it
disqualifies any target that exposes tools the LLM is meant to call on
its own initiative.

**Solution.** Capture each tool's metadata (`name`, `label`, `description`,
`parameters`, `promptSnippet`, `promptGuidelines`) during the discovery pass
and persist it to the cache. At startup, register tool **stubs** with that
metadata and an `execute` that does:

```ts
async execute(toolCallId, params, signal, onUpdate, ctx) {
  const cap = await ensureLoaded(target, pi, loadStates);
  const real = cap.tools.find(t => t.name === toolName);
  return real.execute(toolCallId, params, signal, onUpdate, ctx);
}
```

Cache-key strategy: `parameters` schema is a `typebox` Type, which serializes
fine. `renderCall` / `renderResult` functions can't be serialized — when those
exist, register a stub that delegates to the real renderer after activation.

Opt-in per target via `lazyTools: true` (default off) so behavior of v1 is
preserved.

### 2. Synthetic event replay on activation

**Problem.** A target's `session_start` (and to a lesser extent
`before_agent_start`, `agent_start`, `turn_start`) handlers won't fire for
events that already happened. If a target depends on those for state
initialization, mid-session activation leaves it broken.

**Solution.** When `ensureLoaded` runs mid-session, synthetically fire the
captured handlers with reconstructed event objects:

```ts
// After replay, before returning cap:
if (sessionAlreadyStarted) {
  for (const [event, handler] of cap.handlers) {
    if (event === "session_start") {
      await handler({ reason: "lazy-activation" } as any, lazyCtx);
    }
  }
}
```

The `lazy-activation` reason is new and tells handlers "this is a synthetic
replay, behave appropriately". Targets opt into respecting it; ones that don't
just see a regular `session_start`.

Risk: some handlers do irreversible work (write to state files); replaying
must be off-by-default per target via config (`replayOnLoad: ["session_start"]`).

### 3. Schema validation of `lazy.json`

Use a small zod or typebox schema validated at config-load time so users get
clear error messages instead of silent ignores. Cheap, ~30 lines, big UX win
for first-time setup.

### 4. Auto-discovery via package manifest hint

If a pi package declares `"piLazyLoadable": true` in its `package.json` and
ships a tool/command manifest, the lazy-loader could pick it up automatically
without an entry in `lazy.json`. Encourages upstream packages to participate
in lazy-loading instead of requiring downstream config maintenance.

Spec sketch:

```json
{
  "name": "some-pi-package",
  "piLazyLoadable": {
    "manifest": "./pi-lazy-manifest.json"
  }
}
```

Where `pi-lazy-manifest.json` declares commands and tools with their static
metadata. The lazy-loader auto-imports the manifest at startup, registers
stubs, and never has to do a fake-load if the manifest is fresh.

This is essentially the VS Code `contributes` model.

## Medium-value

### 5. Test harness

Unit tests for:
- Fake API: every `register*` method captures correctly; live-mode forwards
  correctly; action methods always forward.
- Replay: every captured registration ends up on the real API; stubs are
  overwritten not duplicated.
- Cache I/O: round-trip; staleness detection (mtime, version, neither);
  graceful corruption recovery.
- Match logic: prefix matching with `=`, `-`, longest-prefix-wins.
- Input-event fallback: only intercepts owned commands; doesn't loop on
  extension-injected messages.

Pi exports `loadExtensionFromFactory` from `@mariozechner/pi-coding-agent` for
in-process testing. Use that to spin up a real `ExtensionAPI` against a fixture
target in the test suite.

### 6. Hash-based staleness instead of mtime

**Problem.** `mtime` can lie — some package managers preserve mtimes from
publish time, so reinstalling an updated package looks unchanged. Symlinked
dev installs also break mtime semantics.

**Solution.** Hash the entry file (and optionally a few imported files) and
store the hash in the cache. Slightly slower staleness check but bulletproof.

### 7. Background warmup with idle detection

Today the warmup runs in a `setImmediate` after factory return. For a heavy
warmup, this can produce visible latency on the first user keystroke. Better
to wait for an explicit idle signal (e.g. after `session_start` settles, or
after first user input arrives — whichever is later).

Alternative: run warmup on a worker thread so the main loop stays responsive.

### 8. `/lazy-status` command for diagnostics

Show, for each target:
- Whether stubs are registered, from which cache, whether stale
- Whether the target has been activated this session
- How long the activation took (cold-load timing)
- The captured registration counts (commands, tools, handlers, etc.)

Useful for debugging "why isn't my command registered?" type issues.

### 9. Multi-extension package support

Today, `entry` is a single file. Some pi packages declare multiple extensions
in `package.json`'s `pi.extensions` array (or via globs). Support targeting
the package as a whole; the lazy-loader resolves all entries and proxies each
independently.

```json
{
  "name": "big-package",
  "package": "npm-name",
  "packageRoot": "/path/to/big-package",
  "prefixes": ["bp"]
}
```

The loader would read `packageRoot/package.json`, expand `pi.extensions` globs,
and treat each as a sub-target.

### 10. Hot-reload integration

Pi has `/reload`. Today the lazy-loader's load state and cache survive within
a single process; on `/reload` everything resets and we replay startup. Verify
that `loadStates` and any captured event handlers don't leak between reloads.

If a target was activated and then pi reloads, its replayed handlers should be
torn down so they're not double-subscribed.

### 11. Programmatic API

Export a function so other extensions can register lazy targets at runtime:

```ts
import { registerLazyTarget } from "pi-lazy-loader";

registerLazyTarget({
  name: "my-target",
  entry: "...",
  prefixes: ["mt"],
});
```

Useful for meta-extensions (extension managers, settings UIs).

### 12. Demo media in `media/` and `pi.image` / `pi.video` in `package.json`

Most popular pi extensions ship a screenshot or short terminal recording
in a `media/` directory and reference it from `pi.image` / `pi.video` in
`package.json`. Pi UIs that show package previews use these to give
users a glance of what the extension does before installing.

For pi-lazy-loader, a useful screenshot would show: (a) autocomplete
listing the lazy-loaded extension's slash commands, (b) the brief
"Loading…" footer indicator on first invocation, (c) the target
extension's UI rendering normally. Could also demo `bench candidates`
output ranking packages.

Skipped for v0.1 because adding `pi.image` pointing at a not-yet-existing
asset would 404 in pi UIs.

## Low-value / wait-and-see

### 12. Telemetry hooks

Emit per-target activation timing to pi's `PI_TIMING` channel so users can
profile their lazy-loading setup the same way they profile cold start.

### 13. Eager-warmup-on-idle preference

Per-target opt-in: "warmup as soon as session is idle, even if the user hasn't
asked for the target yet". Splits the load cost off the cold-start path
without making the user wait for the first command.

### 14. Strip target on unload

Symmetric API: if a target is removed from `lazy.json`, also tear down its
captured registrations on next pi run. (Requires tracking which extensions
registered which symbols — non-trivial because the public API doesn't expose
unregister methods for tools/commands.)

### 15. Glob-style prefix matching

Today prefixes are exact strings. Some targets might want regex-like patterns
(e.g. `git-*`). Easy to add; haven't seen a use case yet.

### 16. Windows path support

Code uses POSIX-style absolute paths in examples and validation. A pass
through with `path.resolve` and tests on Windows would make this portable.

## Won't-do

### Lazy-load _everything_ by heuristic

Tempting but dangerous. Some extensions need their `session_start` handlers
to fire eagerly (e.g. permission gates, audit logging, environment setup).
Auto-detecting safe-to-defer extensions would require either a manifest hint
from upstream or runtime introspection that's bound to misclassify. Better
to require explicit opt-in, even if it means more configuration work.

### Replace pi's loader

Some users will ask for a flag that makes pi itself lazy-load extensions.
That's a pi core change, not an extension. The right place for that
discussion is the upstream `pi-coding-agent` repo. This package solves the
problem from userland.
