OMP Workbook

Read the source. Follow the evidence.

Discovery, installation and reload

Theo can now distribute a working folder, but another maintainer cannot find the tool. The obstacle is not TypeScript: it is discovery scope.

He separates four questions:

  1. Is the package installed or linked?
  2. Is its entry selected by discovery?
  3. Did the factory bind successfully?
  4. Is the tool enabled and presented in the expected way?

Creation and loading formats

FormatExact entry contractTypical use
Single .ts or .js fileDefault factoryFirst extension or tightly focused integration
Async default factoryReturns Promise<void>Read adjacent assets before registration
Directory with index.ts / index.jsDefault factory in the selected indexEntry plus ordinary helpers
Directory with package.jsonomp.extensions; legacy pi.extensions fallbackSeveral explicit entries
Explicit .mjs / .cjs fileESM default factory or compatible CommonJS factory exportJavaScript distribution without TypeScript
Installed plugin manifestBase entries plus enabled feature entriesManaged distribution
SDK inline factoryFunction passed to extensionsHost-owned composition
Prepared factory rebindingImported factory called against a fresh API/runtimeAvoid repeated module evaluation without sharing bound state

For normal ESM modules, an arbitrary named export is not enough.

Complete JavaScript exercise—save as format-note.mjs:

Discovery, installation and reload · source excerpt 1; read surrounding instructions
export default function formatNote(pi) {
    pi.registerCommand("format-note", {
        description: "Identify the explicitly loaded module format",
        async handler(_args, ctx) {
            ctx.ui.notify("Explicit ESM extension loaded.", "info");
        },
    });
}

Complete CommonJS exercise—alternative file format-note.cjs; do not load both alternatives together:

Discovery, installation and reload · source excerpt 2; read surrounding instructions
module.exports = function formatNote(pi) {
    pi.registerCommand("format-note", {
        description: "Identify the explicitly loaded module format",
        async handler(_args, ctx) {
            ctx.ui.notify("Explicit CommonJS extension loaded.", "info");
        },
    });
};

Terminal shell—choose one explicit file:

Discovery, installation and reload · source excerpt 3; read surrounding instructions
omp --no-extensions -e ./format-note.mjs

Scanning and manifest resolution are not identical

Native and configured directory scanning automatically discover direct .ts/.js files and immediate child entry packages. They do not recursively import arbitrary helpers.

Installed-plugin manifest expansion is broader: it supports .ts, .js, .mjs and .cjs, and corresponding index files.

There are additional implementation differences worth knowing:

  • Native discovery uses globbing with gitignore/hidden filtering; explicit configured-directory scanning uses readdir and does not apply that same filtering.
  • Native child manifests with declared entries suppress index fallback.
  • Installed-plugin directory manifests are authoritative even when a declared entry is missing; a stale index is not substituted.
  • The explicit-directory resolver falls back to an index when no declared entry survives its existence checks.
  • Installed-plugin scanning excludes declaration files. Do not place .d.ts files in loose native/configured scan directories and assume every scanner excludes them.
  • For portable manifests, list concrete entry files. Do not depend on every loader resolving directory-valued manifest entries in exactly the same way.

Where native extension factories come from

The ambient native project root is cwd-only:

  • <cwd>/.omp/extensions
  • the active agent directory’s extensions folder

The default user agent directory is ~/.omp/agent. A named profile uses ~/.omp/profiles/<name>/agent; PI_CODING_AGENT_DIR can override it.

Native discovery also reads legacy settings.json extension lists. The main startup path adds merged extensions settings, normally from the active agent config.yml and project configuration.

Plain <project>/extensions is not the same as <project>/.omp/extensions. The former needs explicit/configured loading; the latter is a native ambient root.

Legacy pi package metadata and some .pi compatibility lookups remain supported. .pi/extensions is not a native ambient module root in this snapshot.

Skill ancestor traversal must not be used to infer extension-module traversal.

Ordered inputs and two layers of deduplication

The module discovery pipeline appends:

  1. native extension-module capability items;
  2. discovered JS/TS hook factories;
  3. enabled installed-plugin extension entries;
  4. explicit/configured paths.

Within the SDK’s configured lane, CLI additional paths precede settings paths.

There are two different identity operations:

  • Capability discovery can deduplicate by derived extension name. Native project items precede user items.
  • The final path list deduplicates normalized absolute paths, first seen wins.

The final path deduplication is not a universal realpath-equivalence guarantee. Two different symlink spellings are not necessarily one path-list identity.

Imports may be prepared concurrently, but factories are bound sequentially in path order. Do not rely on module-scope side effects occurring in factory order.

Explicit-only and trusted-file loading

--no-extensions means explicit-only for this extension-factory path and its OMP extension-package sibling roots. Explicit -e, --extension and --hook entries remain eligible.

It is not full isolation. Other tools, skills, rules, prompts and MCP discovery families have their own controls.

--trusted-extension is stricter about entry selection:

  • it requires an existing absolute module file path;
  • it rejects directories;
  • it canonicalizes the selected file’s real path;
  • it cannot be combined with -e, --extension or --hook;
  • a trusted entry load failure aborts that startup path rather than merely becoming an ordinary per-module warning.

Terminal shell—trusted-file selection using the actual absolute example path:

Discovery, installation and reload · source excerpt 4; read surrounding instructions
omp --trusted-extension "$EXAMPLES/package-lab/single/field-notes.ts"

Do not copy the relative trusted-path form from older example prose: the supplied current parseArgs() requires an absolute path.

Trusted-file selection does not:

  • sandbox the factory;
  • authenticate its dependencies;
  • restrict ordinary imports;
  • remove in-process filesystem or network authority;
  • create a universal prohibition on unrelated discovery systems.

Disable an entry without deleting it

A derived ID looks like:

  • extension-module:field-notes for field-notes.ts;
  • extension-module:multi for multi/index.ts.

Config file—an example setting in the active agent’s config.yml:

Discovery, installation and reload · source excerpt 5; read surrounding instructions
disabledExtensions:
  - extension-module:multi

In normal settings-aware discovery, this filters the directory-discovered multi/index.ts entry. An explicitly configured file remains an override in this build.

Observed: the disabled directory case selected zero entries; the explicit file case selected one.

Under disableExtensionDiscovery, the SDK does not use the normal ambient disabled-ID list for the explicit lane. Do not use --no-extensions to test whether normal ambient disabling works.

Other capability families use their own IDs in disabledExtensions. A hook’s disabled identity is not automatically its extension-module name.

Installation is an opt-in change to your environment

The workbook requires no plugin installation. Explicit -e is enough.

If you intentionally want to try native plugin management, these commands change the user plugin installation state. The scratch agent directory above is not a guarantee that plugin storage is project-local.

Terminal shell—optional developer installation:

Discovery, installation and reload · source excerpt 6; read surrounding instructions
omp plugin link "$EXAMPLES/package-lab/manifest"
omp plugin list --json
omp plugin features omp-workbook-field-notes
omp plugin features omp-workbook-field-notes --enable summary

A linked plugin starts with feature defaults. summary defaults off.

Launch normally, without --no-extensions, when checking ambient installed-plugin discovery.

omp plugin install ./local-directory routes to the same link operation. It is not a package copy into the current project.

Current installation boundaries:

OperationWhat it does
plugin link <directory>Symlinks into the user plugin node_modules tree and records runtime state
plugin install <local-directory>Routes to link
npm or git installationRuns package-manager work and may fetch dependencies
plugin install name@marketplace --scope projectUses the distinct marketplace project-scope route
plugin disable <name>Disables an installed plugin without removing its source
plugin enable <name>Re-enables it
plugin uninstall <name>Removes installation/runtime registration through the appropriate route

Adding --scope project does not make local/npm installation project-scoped. The local/npm install handler warns that scope is supported only for marketplace installs.

Runtime discovery can read both user and project plugin roots. Enabled project packages shadow user packages with the same package name. That does not imply every installer writes to the project root.

Features and settings

Install-spec feature syntax is:

  • package: default features;
  • package[summary]: selected features;
  • package[*]: all optional features;
  • package[]: no optional features.

Quote bracketed shell arguments so your shell does not expand them. Use a real package spec; the workbook’s private teaching package is not claimed to exist in a registry.

Plugin feature metadata supports description, default, and additional extensions, tools, hooks and commands.

Plugin settings support:

  • string, number, boolean and enum types;
  • descriptions and defaults;
  • secret display masking;
  • an env fallback declaration;
  • numeric min, max, step;
  • enum values.

A settings declaration is not executable behavior. The supplied getPluginSettings() helpers merge saved global/project values; they do not inject a settings object into ExtensionAPI or magically apply all defaults/environment fallbacks to extension code. A consumer must resolve and validate its settings deliberately.

The CLI supports plugin config list|get|set|delete|validate. In this snapshot, the parsed --local flag is not forwarded into the manager’s setter: do not advertise it as a reliable project-local write route.

Project overrides have their own file:

Config-file exercise—.omp/plugin-overrides.json, selecting the real example feature:

Discovery, installation and reload · source excerpt 7; read surrounding instructions
{
  "features": {
    "omp-workbook-field-notes": ["summary"]
  }
}

The same override structure supports disabled package names and per-package settings.

secret: true is not secure credential storage. Human CLI display may mask a value while JSON output still exposes it. Keep real credentials out of source, screenshots, transcript details and published config.

Collisions have family-specific rules

Registration familyCurrent behavior
Same tool/command name inside one factoryLater Map entry replaces earlier
Tools across extension instancesRegistrations are retained; effective lookup is later-extension-wins
Commands across extension instancesEffective command lookup is later-extension-wins
Aggregated built-in command collisionFiltered with diagnostics
Event handlersAppend and run in registration/load order, subject to event-specific short-circuiting
Custom message renderer lookupFirst extension with that type wins
Assistant thinking renderersAll returned components append in registration order
Flags and composer-shape IDsAggregate last-wins
Nonreserved shortcut collisionWarning; later normalized key wins
Reserved shortcutRejected from the effective shortcut set

Do not load all Seed Desk stages together and infer which version won from one notification.

Registered CLI flags can shadow same-named built-ins in the extension-aware parser. Prefer namespaced flags so startup behavior is not surprising.

The current CLI validates requested tool names against the fully discovered session registry. Older custom-tool documentation saying only built-in names are validated is stale. Still, --tools/--no-tools is not a universal isolation switch: unrestricted extension/custom tools have their own inclusion behavior.

Reload means what the host actually reloads

This snapshot contains a particularly important distinction:

  • AgentSession.reload() reopens the current session file through switchSession().
  • The supplied TUI command-context reload() calls that session reload and rebuilds presentation.
  • RPC/ACP plugin-refresh code clears caches and refreshes skills/file-command metadata, but does not itself reimport every extension factory.
  • The module loader supports cache-busted imports when a host actually invokes it again.

Therefore:

For editing extension code, restarting the explicit launch is the reliable workbook procedure. Do not promise that ctx.reload() hot-rebinds the factory.

Treat await ctx.reload(); return; as the end of the current command handler. Factory-local notebook state need not reset on a session reload.

The loader’s same-process source cache-busting also has platform-specific behavior. In particular, the supplied compatibility loader notes a Windows file:// query limitation. A fresh process avoids treating an unverified hot-reload path as proof.

Failure recovery and removal

An ordinary invalid export, import failure or factory exception becomes a path-specific loader error; later modules still bind.

Factory failure restores the queued provider-registration list, including earlier registrations the failed factory removed. It is not a transaction over arbitrary side effects. Recorded checks showed a failed factory’s flag default surviving that rollback.

Inline loadExtensionFromFactory() rejects its promise instead of returning a per-path loader error record.

For recovery:

  1. Restart with only one explicit reviewed entry.
  2. Read the named load error.
  3. Check adjacent assets and dependencies.
  4. Restore a known-good source version.
  5. Restart and inspect tool/command metadata before invoking a mutation.

For removal:

  • remove the explicit/configured path, or move the authored entry outside discovery;
  • disable/uninstall an installed plugin through its installation route;
  • restart and verify that no second path still loads it;
  • keep session files unless you intentionally want to discard their data.

Terminal shell—optional installed-plugin lifecycle checks:

Discovery, installation and reload · source excerpt 8; read surrounding instructions
omp plugin disable omp-workbook-field-notes
omp plugin enable omp-workbook-field-notes
omp plugin uninstall omp-workbook-field-notes
omp plugin list --json

Inspect the result. A linked source directory remains your source; uninstalling is not a request to delete that checkout.

Exercise: an extension remains active after disabling its plugin. What is the first likely duplicate route to inspect?

Answer: an explicit -e or extensions: path, or a loose copy in a native extension directory.

Source, snapshot 2026-08-29: packages/coding-agent/src/extensibility/extensions/loader.ts; packages/coding-agent/src/discovery/builtin.ts, helpers.ts; packages/coding-agent/src/extensibility/plugins/loader.ts, manager.ts, types.ts; packages/coding-agent/src/cli/args.ts, plugin-cli.ts; packages/coding-agent/src/main.ts, buildSessionOptions; packages/coding-agent/src/session/agent-session.ts, reload.

Extensions inside those boundaries · Source chapter: extensions/discovery-installation-and-reload. Original evidence remains scoped to its recorded snapshot.

Read this chapter as Markdown

Your lesson ticks

A self-reported reading checklist, not proof of real OMP behavior. Only these ticks are saved in this browser. Reading a milestone does not resume, fork, reset or export a session.

Chapters I have worked through
Start here 1
Sessions, resets, and reviewable history 19
Memory and reusable knowledge 14
Tangent work and live control 17
Tool permissions and approvals 15
Extensions inside those boundaries 23
Connections and next steps 8
0 of 97 checked

Checklist saving needs JavaScript and available browser storage.