| 1 | # Reasonix Extensions |
| 2 | |
| 3 | Extensions let a plugin package change what Reasonix does at runtime — |
| 4 | rewrite input, intercept tool calls, replace the system prompt, contribute |
| 5 | streaming model providers, publish structured UI, and ship prompts and |
| 6 | themes — using a stable, versioned contract. |
| 7 | |
| 8 | Two kinds of plugin capabilities exist: |
| 9 | |
| 10 | - **Declarative** (any plugin package): skills, agents, commands, prompts, |
| 11 | hooks, MCP servers, and themes. These are files and configuration; they |
| 12 | run with the host's normal permissions. |
| 13 | - **Code runtime** (Manifest v1 `runtime` block): a sidecar process speaking |
| 14 | the Extension Protocol. Code extensions are **full trust** — see the |
| 15 | security section below before installing one. |
| 16 | |
| 17 | ## Installing and managing |
| 18 | |
| 19 | Extensions install exactly like any plugin package: |
| 20 | |
| 21 | ```bash |
| 22 | reasonix plugin install git:github.com/owner/extension --dry-run # preview |
| 23 | reasonix plugin install git:github.com/owner/extension --yes # install |
| 24 | reasonix plugin show <name> # details |
| 25 | reasonix plugin doctor <name> # validate |
| 26 | ``` |
| 27 | |
| 28 | For a plugin with a `runtime` block, the preview and `show` output include a |
| 29 | **FULL TRUST** block: the runtime command, the events it intercepts, the |
| 30 | replacement slots it owns, and its provider/UI capabilities. Installing, |
| 31 | updating, replacing, or `--link`ing is the authorization — there is no |
| 32 | second confirmation, and `--link` keeps trusting changed content. Only |
| 33 | install runtimes you trust completely. |
| 34 | |
| 35 | ## What extensions can do |
| 36 | |
| 37 | - **Interceptors** — observe and rule on 17 hook points (input, tool calls, |
| 38 | permission decisions, provider requests/responses, compaction, session |
| 39 | lifecycle, frontend events). An interceptor can `continue`, `block` with a |
| 40 | user-visible reason, or `replace` the payload; the host re-validates every |
| 41 | replacement. |
| 42 | - **Replacement strategies** — single-owner slots (`system_prompt`, |
| 43 | `context`, `provider_request`, `provider_response`, `compaction`, |
| 44 | `session_policy`, `permission`, `frontend_events`, `tool:<name>`, |
| 45 | `provider:<ref>`). One owner per slot across all installed plugins; a |
| 46 | collision fails the runtime build with both sources named. |
| 47 | - **Streaming providers** — new models appear as |
| 48 | `plugin/<plugin>/<provider>/<model>` in the model picker, streamed with |
| 49 | the same text/reasoning/tool-call/usage semantics as built-in providers. |
| 50 | The ref works everywhere a built-in ref does: `default_model`, `--model`, |
| 51 | the CLI/Desktop/ACP pickers, and mid-session model switches — including on |
| 52 | the very first boot. |
| 53 | - **Structured UI** — status entries, cards, forms, and notifications |
| 54 | rendered natively in the CLI transcript, the Desktop app, and ACP clients |
| 55 | (with text fallbacks), plus `/<plugin>:<action>` actions in the slash |
| 56 | menu, the Desktop command palette, and ACP's discoverable commands. |
| 57 | - **Prompts and themes** — `/<plugin>:<name>` prompt templates and |
| 58 | read-only plugin themes (`plugin:<plugin>:<theme>`) in Desktop Settings. |
| 59 | |
| 60 | ## Runtime reload |
| 61 | |
| 62 | Changing an installed extension (install, update, enable/disable, or |
| 63 | `--link` content changes) never mutates a running turn. Reloading is one |
| 64 | fail-atomic operation through every interactive frontend — CLI `/reload`, |
| 65 | Desktop **Reload Runtime** (command palette), Serve `/reload`, and the ACP |
| 66 | vendor method `_reasonix.io/session/reloadExtensions`: |
| 67 | |
| 68 | 1. If a turn or background work is running, CLI/Desktop/ACP queue exactly one |
| 69 | reload; Serve rejects the request so the browser can retry once idle. |
| 70 | 2. When idle, Reasonix starts new sidecars and builds a new runtime |
| 71 | snapshot. |
| 72 | 3. On full success it swaps atomically, carrying over the session path, |
| 73 | transcript, approval grants, and goal/recovery state. |
| 74 | 4. If the new build fails, the old runtime keeps working untouched. |
| 75 | 5. Only after the swap are the old sidecars retired. |
| 76 | |
| 77 | Each turn pins one runtime generation for the whole turn, tool batch, and |
| 78 | compaction — extension changes apply to the *next* turn, and a no-op reload |
| 79 | leaves the provider prompt-cache prefix byte-identical. |
| 80 | |
| 81 | ## Performance and prompt cache |
| 82 | |
| 83 | With no code runtime installed, the Agent takes the existing nil-dispatcher |
| 84 | path: no sidecar process, JSON encoding, RPC, or event queue is involved. |
| 85 | When runtimes are installed, Reasonix initializes at most four sidecars at once |
| 86 | inside one shared 30-second generation startup budget. A stalled optional |
| 87 | runtime therefore cannot multiply boot or reload time by the number of installed |
| 88 | packages. Packages that do not start inside that budget degrade or fail according |
| 89 | to their `runtime.required` setting. |
| 90 | Enabled synchronous interceptors are deliberately on the matching hot path and |
| 91 | run serially, so their RPC and handler latency is additive; keep input, tool, |
| 92 | permission, and provider interceptors small and deterministic. Observation |
| 93 | events use a bounded non-blocking queue and are dropped with a warning under |
| 94 | backpressure instead of stalling the turn. |
| 95 | |
| 96 | An observation-only extension does not change the provider-visible cache |
| 97 | prefix. A stable system-prompt or tool replacement creates one intentional |
| 98 | cold prefix after install/reload and remains cacheable afterwards. A strategy |
| 99 | that injects timestamps, random values, session IDs, or other per-turn data |
| 100 | into the system prompt, tool schemas, context prefix, or provider request can |
| 101 | destroy cache reuse; dynamic data should stay in the current turn tail when |
| 102 | possible. Maintainers can measure host overhead with: |
| 103 | |
| 104 | ```bash |
| 105 | go test ./internal/extension/... -run '^$' -bench 'Extension|Dispatch' -benchmem |
| 106 | ``` |
| 107 | |
| 108 | ## Developing an extension |
| 109 | |
| 110 | Start with the complete |
| 111 | [`starterextension`](../sdk/go/examples/starterextension/README.md) package. |
| 112 | It keeps the manifest, Sidecar source, cross-platform build commands, linked |
| 113 | installation, and first observable intercept in one directory. The normal |
| 114 | development loop is: |
| 115 | |
| 116 | 1. Add `apiVersion: "reasonix.io/plugin/v1"` to `reasonix-plugin.json` and |
| 117 | declare `contributes` and (optionally) `runtime` — see |
| 118 | [Plugin Packages](./PLUGIN_PACKAGES.md#manifest-v1-extensions). |
| 119 | 2. Implement the Sidecar. The [Go SDK](../sdk/go/README.md) (standard library |
| 120 | only) handles transport, handshake, sequencing, content references, and |
| 121 | shutdown; the [wire contract](./EXTENSION_PROTOCOL.md) and |
| 122 | [generated method index](./EXTENSION_PROTOCOL.generated.md) are the |
| 123 | language-neutral references. |
| 124 | 3. Build the runtime binary, preview its trust and capabilities with |
| 125 | `reasonix plugin install /path/to/plugin --dry-run`, then install it with |
| 126 | `--link --yes`. |
| 127 | 4. Validate with `reasonix plugin doctor <name>`, run `/reload` while idle, |
| 128 | and exercise the contributed intercept, Provider, UI action, or resource. |
| 129 | |
| 130 | SDK releases use immutable `sdk/go/vX.Y.Z` tags. The first public version is |
| 131 | `sdk/go/v1.0.0`; until that tag exists, use the starter from a source checkout |
| 132 | instead of relying on an unversioned module. |
| 133 | |
| 134 | ## Compatibility |
| 135 | |
| 136 | - Manifests without `apiVersion` parse exactly as before. |
| 137 | - Older Reasonix versions ignore extension-only state: the per-session |
| 138 | `<session>.extensions.json` sidecar file, `plugin/...` model refs (they |
| 139 | simply resolve as unavailable models), and the `extension_surface` / |
| 140 | `extension_status` event kinds (older frontends drop unknown kinds; ACP |
| 141 | clients without `reasonix.extensionSurface` get text fallbacks). |
| 142 | - `plugin-packages.json` keeps its existing schema; an enabled installed |
| 143 | runtime *is* the trust record. |
| 144 | |
| 145 | ## Security model |
| 146 | |
| 147 | A code extension runs outside the Reasonix sandbox with the unfiltered |
| 148 | inherited environment. It can read the full session and environment, bypass |
| 149 | permissions and workspace restrictions, and operate the machine directly; |
| 150 | its `permission.decision` "allow" overrides a host deny. In return the host |
| 151 | enforces: |
| 152 | |
| 153 | - only plugins installed through the plugin flow can start a runtime — |
| 154 | project configuration can never declare one; |
| 155 | - the handshake rejects any capability beyond the manifest; |
| 156 | - replacements are re-validated against each point's DTO and schema; |
| 157 | - sidecar diagnostics, structured UI, interceptor reasons, and provider errors |
| 158 | are credential-redacted by the host before they reach the UI, logs, or error |
| 159 | surfaces; ordinary provider/model content is preserved as product data; |
| 160 | - a crashed sidecar fails its own operations explicitly — Reasonix never |
| 161 | silently falls back to another model or strategy. |
| 162 |