## Prepare a reversible lab

Before adding convenience\,&#32;make failure inexpensive\.

Mara\,&#32;a seed\-library volunteer\,&#32;wants to experiment without filling her normal project with extension state\.&#32;She chooses an explicit entry path and a scratch working directory\.&#32;That gives her a reproducible launch command and a clear place to inspect the session file\.

### Prerequisites

You need\:

- The workbook\-compatible custom&#32;`omp`&#32;on&#32;`PATH`\.
- Bun\;&#32;the recorded checks used&#32;**Bun 1\.3\.14**\.
- The complete example directory structure\,&#32;preferably from&#32;[the ZIP](<https://present-sketch-tp94.here.now/downloads/extensions-examples.zip>)\.
- Matching runtime dependencies where an example imports them\.

Check the installed versions\,&#32;but do not treat a matching version string as proof that an unrelated distribution contains the same custom APIs\.

**Terminal shell—inspect your installed programs\:**

~~~sh
omp --version
bun --version
~~~

The Seed Desk stages are complete\,&#32;independent entries\.&#32;Load&#32;**one at a time**\:&#32;all three intentionally own&#32;`/seeds`\.

Package Lab’s command\-line entries use type\-only SDK imports and injected schema builders\.&#32;Seed Desk stage 3 additionally imports matching&#32;`@oh-my-pi/omptype`&#32;and&#32;`@oh-my-pi/pi-tui`&#32;runtime packages\.&#32;Review Desk imports the TUI package through the compatible host’s extension loader\.

Do not fix a missing custom package by silently substituting an incompatible upstream package\.

### Establish the workspace once

Start in the extracted directory that contains&#32;`seed-desk`\,&#32;`review-desk`&#32;and&#32;`package-lab`\.

**Terminal shell—create the workbook workspace\:**

~~~sh
EXAMPLES="$PWD"
LAB="$(mktemp -d)"
mkdir -p "$LAB/work" "$LAB/agent"
export PI_CODING_AGENT_DIR="$LAB/agent"
export PI_PROFILE=
cd "$LAB/work"
~~~

Later commands assume these shell variables remain available\.

This is a&#32;**reversible workspace**\,&#32;not complete isolation\:

- `PI_CODING_AGENT_DIR`&#32;selects the lab’s agent directory\.
- `--no-extensions`&#32;suppresses ambient extension\-factory discovery\,&#32;while explicit entries still load\.
- Other discovery families\,&#32;environment credentials\,&#32;package installation and host startup activity have their own behavior\.
- Neither an empty working directory nor&#32;`--no-session`&#32;is an OS security boundary\.

The recorded isolated scenarios used an additional OS policy denying network access and restricting writes\.&#32;Ordinary reader launch commands below do not install that policy\.

### An extension is a factory\,&#32;not a command\-line program

A module loaded with&#32;`-e`&#32;exports a&#32;**default function**\.&#32;OMP calls that function with&#32;`ExtensionAPI`\.

**Complete TypeScript exercise—save as&#32;`lab-status.ts`&#32;in the lab working directory\:**

~~~ts
import type { ExtensionAPI } from "@oh-my-pi/pi-coding-agent";

export default function labStatus(pi: ExtensionAPI): void {
    pi.setLabel("Workbook lab");

    pi.on("session_start", (_event, ctx) => {
        pi.logger.debug("Workbook lab session started");
        if (ctx.hasUI) {
            ctx.ui.notify("Workbook lab loaded.", "info");
        }
    });
}
~~~

**Terminal shell—load that exercise\:**

~~~sh
omp --no-extensions -e ./lab-status.ts
~~~

**Expected checkpoint\:**&#32;in a TUI\,&#32;the session\-start handler produces&#32;`Workbook lab loaded.`&#32;In a default headless context\,&#32;the notification is absent\;&#32;that absence is not a load failure\.

Running&#32;`bun lab-status.ts`&#32;merely evaluates a module that exports a function\.&#32;It does not supply OMP’s extension runtime\.

### Registration first\,&#32;initialized actions later

The lifecycle has two important phases\:

1. **Factory binding\:**&#32;register commands\,&#32;tools\,&#32;flags\,&#32;handlers\,&#32;renderers and fallback handlers\.
2. **Runtime initialization\:**&#32;the host wires session actions and UI\,&#32;then dispatches events and invocations\.

Methods such as&#32;`sendMessage()`\,&#32;`appendEntry()`\,&#32;`getAllTools()`&#32;and&#32;`setModel()`&#32;depend on the initialized runtime\.&#32;Calling them during factory loading raises&#32;`ExtensionRuntimeNotInitializedError`\.

An async factory is valid\.&#32;Seed Desk uses one to read&#32;`tool.txt`&#32;before registering a tool\.&#32;Async initialization does not make runtime actions available early\.

Register providers at factory time if needed\:&#32;their registrations are queued for the model registry\.&#32;That is a special registration path\,&#32;not an exception allowing arbitrary session actions during loading\.

### Keep the lifetimes separate

| Lifetime | What belongs here | What does not follow automatically |
| --- | --- | --- |
| Process | Loaded libraries\,&#32;process\-wide registries\,&#32;explicitly shared buses | One user\,&#32;one session or one authorization scope |
| Module evaluation | Static fixtures and helper definitions | Fresh mutable state for every SDK session |
| Factory binding | Closures created when the factory is called | Automatic reset on&#32;`/new`&#32;or session switching |
| Session\/transcript | A session ID\,&#32;header and journal | A global shared database |
| Current branch | The path from root to the active leaf | Every entry in the session file |
| Invocation | The current handler\/tool context and abort signal | A context object safe to cache indefinitely |

The Field Notes selection later demonstrates the distinction\:&#32;it survives a transcript change because the factory binding survives\.

### Trust is already being granted

**Source\-backed\:**&#32;`ExtensionContext.isProjectTrusted()`&#32;always returns&#32;`true`&#32;in this build\.&#32;It is a compatibility method reflecting that project\-local inputs are already trusted by default\.&#32;It is not a prompt\,&#32;sandbox or per\-directory permission store\.

Review the selected module’s entire import graph\.&#32;Package dependencies and helper modules have the same in\-process JavaScript authority as the entry\.

**Exercise\:**&#32;move&#32;`pi.sendMessage()`&#32;into the factory body of a copy of&#32;`lab-status.ts`\.&#32;What should happen\?

**Answer\:**&#32;loading should report an uninitialized\-runtime error\.&#32;Move the action into a handler\;&#32;do not add a delay and hope startup finishes first\.

*Source\,&#32;snapshot 2026\-08\-29\:&#32;`packages/coding-agent/src/extensibility/extensions/loader.ts`\,&#32;`getExtensionFactory`\,&#32;`ExtensionRuntime`\,&#32;`ConcreteExtensionAPI`\;&#32;`packages/coding-agent/src/extensibility/extensions/runner.ts`\,&#32;`initialize`\,&#32;`createContext`\.*
