# Extension kernel and event bus

## What it does

The extension kernel loads host-provided `Extension` objects in order and gives each extension a runtime `ExtensionAPI`. The API can register contributions into explicit registries, register middleware, subscribe to lifecycle events, and emit events.

APIs:

- `createExtensionKernel()` / `ExtensionKernel`
- `createExtensionEventBus()` / `ExtensionEventBus`
- `forwardAgentEvents()` / `AgentEventBridgeOptions`
- `ExtensionAPI`, `ExtensionEvent`, and `extension_error` events
- Shared `MiddlewareRegistry` access and `api.use()` registration

## When to use it

Use the extension kernel when a host wants packages to contribute provider packages, providers, models, auth methods, provider request policies, system prompt contributions, tools, context providers, skills, commands, agents, builders, strategies, stores, resources, settings providers, or credential resolvers without editing Prism internals.

Skip the kernel and use contribution registries directly when the host does not need extension setup lifecycle or events.

## Inputs / request

```ts
createExtensionKernel(options?: ExtensionKernelOptions): ExtensionKernel
createExtensionEventBus(options?: { errorPolicy?: "event" | "throw"; secrets?: readonly string[] }): ExtensionEventBus
```

`ExtensionKernelOptions`:

| Field | Type | Purpose |
| --- | --- | --- |
| `registries` | `ContributionRegistries` | Optional host-created registry bundle. |
| `middleware` | `MiddlewareRegistry` | Optional host-created middleware registry. |
| `errorPolicy` | `"event" | "throw"` | Defaults to `"event"`; use `"throw"` for fail-fast setup/listener/middleware errors. |
| `secrets` | readonly strings | Known secret values to redact from extension error events. |

`ExtensionAPI` includes `registries`, `middleware`, `on()`, `emit()`, `use()`, and registration methods for all contribution categories, including provider packages, auth methods, provider request policies, and system prompt contributions.

## Outputs / response / events

- `kernel.load(extensions)` calls each extension's `setup(api)` in host-provided order and resolves to `LoadedExtension[]` (`{ name, dispose() }`). A failed `setup` unwinds that extension's partial registrations (no orphaned half-loads). `dispose()` removes the extension's registry contributions and middleware/event subscriptions via each registry's `unregister(key)` — best-effort, idempotent, and limited to registries/subscriptions: side effects outside the registries (files, network, spawned work) are not unwound, and load-order/dependency graphs between extensions are out of scope.
- `kernel.registries` exposes the explicit contribution registry bundle.
- `kernel.events.on(type, handler)` registers ordered event handlers and returns an unsubscribe function.
- `kernel.events.emit(event)` calls matching handlers in registration order.
- `kernel.middleware.run(hook, value)` runs matching middleware in registration order.
- `forwardAgentEvents(source, events, options?)` is host-invoked wiring for a live `AgentEvent` stream: it maps `agent_started` → `before_agent_start`, `turn_started`/`turn_finished` → `turn`, `tool_execution_started` → `tool_call`, and `tool_execution_finished` → `tool_result`, carrying the original event as read-only `payload`. Other events are ignored. Handlers run in event order and never in the run's path, so a slow or throwing listener cannot stall or fail the observed run; the returned function stops forwarding and releases the source iterator. Bridge failures go to `options.onError` (or become `extension_error` under the bus's own policy) — never to the run.
- `activateKernel(kernel)` copies the `createAgent()` array slots into one config: `{ tools, skills, instructionInjectors, context, stopHooks, commands, middleware }`. Contributions stay inert until the host passes them into runtime config; single-slot builders, `compaction`/`retry`, provider/model selection, and skill activation remain host-owned decisions.
- With default `errorPolicy: "event"`, setup/listener/middleware errors become `extension_error` events with redacted `ErrorInfo`.
- With `errorPolicy: "throw"`, setup/listener/middleware errors reject/throw.

## Request/response example

```json
{
  "loaded": ["demo-extension"],
  "events": [{ "type": "extension_error", "extension": "demo-extension" }]
}
```

## Implementation example

```ts
import { activateKernel, createAgent, createExtensionKernel, forwardAgentEvents, type Extension } from "@arnilo/prism";

const extension: Extension = {
  name: "demo-extension",
  setup(api) {
    api.registerProviderPackage({ name: "demo-provider", setup: () => undefined });
    api.registerModel({ provider: "mock", model: "demo", capabilities: { input: ["text"] } });
    api.registerAuthMethod({ provider: "mock", kind: "api_key", credentialName: "apiKey" });
    api.registerProviderRequestPolicy({ name: "cache", apply: ({ request }) => request });
    api.registerSystemPromptContribution({ id: "demo-prompt", source: "package", mode: "append", text: "Use demo rules." });
    api.registerTool({ name: "echo", execute: (args, ctx) => ({ toolCallId: ctx.toolCallId, name: "echo", value: args }) });
    api.registerContextProvider({ name: "project", resolve: () => [{ title: "Project", content: "Context" }] });
    api.registerInputBuilder({ name: "input", build: async () => [{ role: "user", content: [{ type: "text", text: "Hello" }] }] });
    api.registerPromptBuilder({ name: "prompt", build: async (request) => request.messages });
    api.registerSkill({ name: "brief", instructions: "Answer briefly.", toolNames: ["echo"] });
    api.registerAgent({ name: "demo", create: () => createAgent({ model, provider }) });
    api.registerCompactionStrategy({ name: "compact", compact: () => ({ summary: "summary" }) });
    api.registerRetryPolicy({ name: "retry", decide: () => ({ retry: false }) });
    api.registerStopHook({ name: "checklist", decide: (ctx) => (ctx.stopHookActive ? { action: "stop" } : { action: "continue", reason: "Verify the checklist." }) });
    api.on("demo:ready", (event) => {
      console.log(event.type);
    });
    api.use("session_start", (payload) => payload);
    api.use("provider_request", (request) => request);
    api.use("compaction", (payload) => payload);
    api.use("retry", (payload) => payload);
  },
};

const kernel = createExtensionKernel({ errorPolicy: "event" });
await kernel.load([extension]);

console.log(kernel.registries.models.resolve("mock", "demo").model);
console.log(kernel.registries.tools.resolve("echo").name); // contributed only; host must activate before dispatch
console.log(kernel.registries.contextProviders.resolve("project").name); // contributed only; host must select before context resolution
console.log(kernel.registries.inputBuilders.resolve("input").name); // contributed only; host must pass it to assembly
console.log(kernel.registries.promptBuilders.resolve("prompt").name); // contributed only; host must pass it to assembly
console.log(kernel.registries.skills.resolve("brief").name); // contributed only; host must select before prompt use
console.log(kernel.registries.agents.resolve("demo").name); // contributed only; host must create/select before runtime use
console.log(kernel.registries.systemPromptContributions.resolve("demo-prompt").text); // contributed only; host must select before prompt use
await kernel.middleware.run("provider_request", { metadata: {} });

// Host activation: copy the array slots into createAgent() fields.
const activated = activateKernel(kernel);
const agent = createAgent({
  model: { provider: "mock", model: "demo" },
  tools: activated.tools,
  skills: activated.skills,
  instructionInjectors: activated.instructionInjectors,
  stopHooks: activated.stopHooks,
  context: activated.context,
  middleware: activated.middleware,
});

// Forward live AgentEvents onto the bus; stop() ends forwarding and releases the subscription.
const session = agent.createSession();
const stop = forwardAgentEvents(session.subscribe(), kernel.events, { onError: (error) => console.warn(error) });
// const run = await session.run("Hi");
// stop();
```

## Extension and configuration notes

- Extension loading is explicit. Prism does not discover packages, read manifests, or load filesystem config in the kernel.
- Extensions stay host-owned outside `AgentConfig`. `createAgent()` and `session.run()` do not load extension lists or call `Extension.setup()`. Load extensions with `createExtensionKernel().load(...)`, then pass selected contributions (`tools`, `context`, `skills`, middleware, etc.) into `createAgent()`.
- Setup order is the order provided by the host.
- The kernel writes only to explicit registries returned by `createContributionRegistries()` or provided by the host.
- `api.registerTool()` contributes an inert `ToolDefinition` to `registries.tools`; it does not add the tool to an active tool registry, allow list, or dispatch loop.
- `api.registerInputBuilder()`, `api.registerPromptBuilder()`, and `api.registerContextProvider()` contribute inert builders/providers; they do not replace defaults or run until the host passes selected entries to Phase 5 helpers.
- `api.registerSkill()` contributes an inert `Skill` to `registries.skills`; it does not disclose instructions, activate referenced tools, or grant permissions until the host selects it.
- `api.registerInstructionInjector()` (Phase 30) contributes an inert `InstructionInjector` to `registries.instructionInjectors`; it grants no tools, skills, or permissions and is only applied when the host selects it via `AgentConfig.instructionInjectors`/`RunOptions.instructionInjectors`. See [Instruction injection](instruction-injection.md).
- `api.registerStopHook()` contributes an inert run-end `StopHook` to `registries.stopHooks`; `activateKernel()` copies it into `stopHooks` for `createAgent({ stopHooks })`, and `LoadedExtension.dispose()` unwinds it. Hooks decide at a natural loop end only — see [Hooks](hooks.md).
- `forwardAgentEvents()` is host-invoked wiring, not a runtime default, and it observes only: the bus never transforms what the run sees. Prefer `session.subscribe()` directly when the host wants the raw stream; use the bridge when extension packages already listen on the bus.
- Session lifecycle middleware (`session_start`/`session_shutdown`) is dispatched by the agent/session runtime when the host passes its registry to `AgentConfig.middleware` — see [Middleware hooks](middleware-hooks.md).
- `api.registerProviderPackage()`, `api.registerAuthMethod()`, `api.registerProviderRequestPolicy()`, and `api.registerSystemPromptContribution()` contribute inert provider-package data; they do not load packages, resolve credentials, mutate provider payloads, or change prompts until selected by a host/runtime helper that documents that behavior.
- `api.registerAgent()` contributes an inert `AgentDefinition`; its `create()` can call `createAgent()`, but the runtime is not started until host code resolves the definition and creates/runs a session.
- The kernel registers middleware only into the explicit registry returned by `createMiddlewareRegistry()` or provided by the host.
- `api.use("compaction", middleware)` and `api.use("retry", middleware)` observe or adjust runtime compaction/retry payloads only when the host passes that middleware registry to `createAgent({ middleware })`; compaction strategy and retry policy contributions remain inert until selected by the host.
- Manifest contribution `kind` values for provider packages, auth methods, provider request policies, and system prompt contributions match the registry keys populated by the extension API. See [Configuration and manifests](configuration-and-manifests.md) for data-only declaration examples.

## Security and performance notes

- No hidden global extension kernel, provider registry, credential resolver, settings provider, store, or resource loader is created.
- Extension packages do not auto-execute from `createAgent()`, so constructing or running an agent cannot unexpectedly run extension code.
- Error events use `ErrorInfo` and redact only known secret values passed in `secrets`.
- Do not put resolved credential values in extension events, registry metadata, docs, logs, prompts, or session stores.
- Event and middleware dispatch are ordered and dependency-free. They use no timers, background workers, filesystem discovery, network calls, provider calls, or tool execution.
- Extension middleware cannot bypass host tool permissions: tool dispatch re-checks active registry lookup, filters, and object arguments after `tool_call` middleware. Skills that reference `toolNames` are checked against host-active tools by `resolveActiveSkills()`.
- Optional `loadPolicy: { allowList?, verifySignature? }` on `createExtensionKernel` runs before `setup`. Unallowlisted or unsigned (when `verifySignature` is set) extensions fail closed. Put host-attested digests on `Extension.signature`.

## Related APIs

- [Extension authoring guide](extension-authoring.md): package-author checklist for inert contributions, host activation, trust, permissions, no sandbox, and redaction.
- [Middleware hooks](middleware-hooks.md): ordered hook registry populated by `ExtensionAPI.use()`.
- [Provider packages](provider-packages.md): provider package and model metadata registration through `ExtensionAPI`.
- [Contribution registries](contribution-registries.md): registry bundle populated by `ExtensionAPI`.
- [Contribution discovery (workspace)](contribution-discovery.md): filesystem-driven complement to extension registration — opt-in scan without `import()` or activation.
- [Tools](tools.md): host activation, filtering, and dispatch for contributed tool definitions.
- [Middleware hooks](middleware-hooks.md): hook names, payloads, and the dispatched `session_start`/`session_shutdown` call sites.
- [Agent events](agent-events.md): the `AgentEvent` union the bridge forwards.
- [Instruction injection](instruction-injection.md): package injectors that layer instructions and context blocks for `first_turn`/`every_turn`/`on_input` without granting tools.
- [Hooks](hooks.md): the hook model and event map, plus run-end stop hooks contributed through `ExtensionAPI.registerStopHook()`.
- [Input and prompt assembly](input-and-prompt-assembly.md): host selection for contributed input/prompt builders.
- [System prompts](system-prompts.md): host selection for contributed system prompt layers.
- [Context and skills](context-and-skills.md): host selection and tool checks for contributed context providers and skills.
- [Agent/session runtime](agent-session-runtime.md): `AgentDefinition.create()` can return agents built with `createAgent()` from explicit host-selected config.
- [Compaction and retry policies](compaction-and-retry.md): compaction strategy/retry policy contributions and `compaction`/`retry` middleware runtime behavior.
- [LLM compaction package](compaction-llm.md): optional extension helper that registers a provider-backed compaction strategy.
- [Observational memory compaction package](compaction-observational-memory.md): optional extension helper that registers an inert fast memory compaction strategy.
- [Caveman behavior integration](caveman.md): optional `@arnilo/prism-coding-tools/caveman` upstream Caveman skills, commands, level injector, and session `caveman-level` persistence.
- [Ponytail behavior integration](ponytail.md): optional `@arnilo/prism-coding-tools/ponytail` upstream Ponytail skills, commands, mode injector, and session `ponytail-mode` persistence.
- [Impeccable behavior integration](impeccable.md): optional `@arnilo/prism-coding-tools/impeccable` upstream Impeccable skill and `load_skill` command.
- [Public contracts](public-contracts.md): `Extension`, `ExtensionAPI`, and contribution contract types.
- [Credentials and redaction](credentials-and-redaction.md): secret-redaction behavior used for extension errors.

`createExtensionKernel({ permission })` checks `extension:<name>:setup` before each extension `setup()`. Denied extensions do not run. Prism does not sandbox extension code or auto-load project-local extensions. See [Security/auth/trust](settings-auth-trust-security.md).
