Files
esengine 2a9743d790 feat(extension): split the session capability, and bound one queued prompt
session.read and session.submit are separate capabilities, so a mod that only
reads usage cannot put words in front of the model. One prompt a mod queues is
bounded by [mods] submit_bytes (8 KiB, 0 for none) and refused with a typed
identity that does not spend the per-turn count. The docs say the turn a queued
prompt starts is held to this session's own approvals and sandbox.
2026-10-18 04:01:29 -07:00

38 KiB


owner: @SivanCola backup: @esengine status: active reviewed: 2026-09-29

Reasonix Plugin Packages

Reasonix plugin packages bundle skills, hooks, MCP servers, prompts, themes, and code extensions behind one installable unit.

For a copyable local package and the community review path, see the community author guide. The multi-file skill example shows how a plugin keeps a required reference file with its skill. For an issue-to-PR-draft exercise with a deliberately failing local fixture, see the repository workflow example. For a packaged read-only subagent, local inputs and lifecycle verification, see the repository orientation example. For a frontend task with a brief, sample data, and browser acceptance steps, see the local interface example.

The API documentation example adds local OpenAPI inputs, a reference checker, and a source-linked guide format.

The stdio MCP example bundles a local Go server with build, connection, tool-call, and lifecycle checks.

The Studio theme author guide uses a copyable pure-theme package, the current token vocabulary, and Appearance activation and cleanup.

The compatible command example shows a copied Claude- or Codex-format package, qualified command names, argument substitution, and the disable, re-enable and removal checks.

CLI Mode

Use reasonix plugin when installing or managing plugin packages from a terminal. Plugin packages are installed globally under the Reasonix home directory.

Install From CLI

install accepts one source:

  • A GitHub repository, such as git:github.com/obra/superpowers or https://github.com/obra/superpowers.
  • A GitHub branch or subdirectory URL, such as https://github.com/owner/repo/tree/main/path/to/plugin.
  • A local directory that contains reasonix-plugin.json, .codex-plugin/plugin.json, or .claude-plugin/plugin.json.

Preview the install plan without writing files:

reasonix plugin install git:github.com/obra/superpowers --dry-run

Install a plugin after reviewing the plan:

reasonix plugin install git:github.com/obra/superpowers --yes

Install with an explicit name or replace an installed plugin with the same name:

reasonix plugin install git:github.com/obra/superpowers --name superpowers --replace --yes

Use a local directory in developer mode:

reasonix plugin install /path/to/plugin --link --replace --yes

CLI install flags:

  • --dry-run plans and validates the install without writing files.
  • --yes is required for any install that writes files.
  • --replace allows the source to replace an installed plugin with the same name.
  • --name <name> or --name=<name> overrides the name from the plugin manifest for this install.
  • --link links a local plugin directory instead of copying it into Reasonix's plugin storage. Moving or deleting that directory breaks the linked plugin.

Running reasonix plugin install <source> without --dry-run or --yes refuses to write files and prints a reminder to rerun with one of those flags. Install and remove commands print the structured JSON response from the same install-source backend used by the desktop UI.

Installed plugin state is stored in:

~/.reasonix/plugin-packages.json
~/.reasonix/plugins/<name>/

On Windows, copied replacements are validated in a fresh sibling directory under plugins/, then published by updating the package's recorded root. On other platforms the validated copy is swapped into the existing directory with a backup-protected rename and the recorded root does not change.

Directory watches from another process cannot block a rename of the old tree because that tree is left intact. Failed publication discards the new copy. Use the recorded root to locate the active version after an update.

Previous copies remain on disk so processes still using them keep a complete version. Removal deletes the active copy; retained copies require manual cleanup after every process using or watching them has stopped. An external lock on the package state file can still prevent publication.

Approval snapshot

Copy-mode preview parses a bounded, materialized snapshot. Its file-content hashes contribute to contentDigest and the approval planId.

  • Apply checks the copied tree against that snapshot before installation and before every registry rename attempt; a failed check ends publication.
  • The digest covers all copied bytes: skills, hooks, runtime binaries, prompts, themes and assets. .git is excluded.
  • Internal regular-file links are materialized by the existing copy rules. Link mode remains mutable and has no content pin.
  • Failed actions carry a typed errorCode (install.digest_mismatch or install.publication_failed) alongside diagnostic text.

Publication and durability

Registry publication uses a synced temporary file and strict rename. Filesystems or Windows filter drivers that reject rename with a cross-device error fail closed; publication never truncates and copies over the previous registry.

Copied files are synced before publication; directory syncing is best effort. Windows directory-entry persistence and power-loss durability are not guaranteed.

Restart and recovery

The registry is the sole authority on restart. A process exit before its rename keeps the old root active; an exit after rename loads the new root.

  • Unregistered complete or incomplete siblings and registry temporary files are never adopted automatically. Recovery never guesses which orphan was approved.
  • A corrupt registry fails closed with a read error. The package loader excludes invalid registered roots and reports a repair warning.
  • Retained copies may include user edits. After removal, a retained canonical directory can require manual cleanup or an explicit replacement for reinstall.

Writable generations

Publication-time digest checks do not prevent later edits or confine a same-authority external writer that can replace directories or alter bytes after the last check that precedes the successful rename.

Manage From CLI

List installed plugins:

reasonix plugin list

Show one plugin's metadata, root, source, and exported capability counts:

reasonix plugin show superpowers

show also prints the concrete capability inventory when available:

  • skills include suggested /<plugin>:<skill> invocations and descriptions.
  • commands include /<plugin>:<command> invocations, argument hints, and descriptions.
  • hooks list lifecycle events, matchers, and commands or context files.
  • mcpServers list server names, transports, and launch targets.

Check that the manifest and skill roots are readable:

reasonix plugin doctor superpowers

For a workspace-wide capability report (skills, hooks, MCP merge, package roots), see Capability diagnostics:

reasonix doctor capabilities --json
# Agent:   /reasonix-guide

Enable or disable a plugin without uninstalling it:

reasonix plugin disable superpowers
reasonix plugin enable superpowers

Remove a plugin:

reasonix plugin remove superpowers --yes

remove also accepts uninstall as an alias. It requires --yes because it writes state and removes copied plugin content. For linked local plugins, the external source directory is left in place.

Use Installed Plugins From CLI

Installed plugins do not open a separate chat surface. When a plugin is enabled, Reasonix loads its capabilities into normal interactive sessions:

  • Run /plugins inside an interactive session to list installed plugin packages. Run /plugins show <name> to inspect a plugin's exported skills, hooks, MCP servers, and usage hints without leaving the chat.
  • Skills appear in /skills. Invoke a plugin skill with /<plugin>:<skill> [args], or ask naturally and let the agent choose a matching skill by description.
  • Hooks run automatically at their configured lifecycle events, such as SessionStart, UserPromptSubmit, PreToolUse, or PostToolUse.
  • MCP servers join the normal MCP/tool flow. Ask for the task you want done; Reasonix can call the plugin's tools when they are relevant.

After installing, enabling, disabling, or updating a plugin from a separate terminal while a session is already running, start a new reasonix session or reopen /skills to verify the current session sees the expected skills.

Desktop Settings

Open Settings → Extension → Installed (扩展 → 已安装) to install and manage plugin packages. In Plugin packages, choose Add to open the installer.

Install Plugins

The installer takes one source:

  • A repository address, such as https://github.com/obra/superpowers, a branch, or a subdirectory of one.
  • A local folder: drag it onto the installer or click 选文件夹 (Choose folder).

A local .zip archive, such as one made by Export, installs through the CLI (reasonix plugin install ./pkg.zip --dry-run, then rerun with --yes after reviewing the plan) and the install_source tool.

Installing is always two steps: enter or choose the source, then select See what it is (查看内容) to read its contributions and requirements without writing anything. Install then applies exactly that plan. A changed source between the two is refused rather than installed.

The Studio installer copies local packages into the user's plugin storage.

To replace an installed package from its recorded source, choose Update on its row and review the new plan before applying it.

For a different replacement source or a development link, use the CLI's --replace or --link flags described above; these are not checkbox options in the Studio installer.

Preview is the safest first step for a new Git source or local plugin directory.

Manage Installed Plugins

The installed-plugin list shows each package's contributions and executable capabilities.

After editing plugin files outside the app, use Reload runtime in the Runtime group above the package list. A successful reload refreshes the list and makes the new extensions available for the next turn; a refused reload reports its reason.

Expand a plugin row to manage it:

  • Enable or disable the plugin.
  • Read its contribution rows, including the qualified invocations for skills, commands, and agents, and the details of hooks, servers, and runtimes.
  • Update pulls or refreshes an installed plugin when an update source is available.
  • Read package errors and warnings in the expanded row. For CLI diagnostics, run reasonix plugin doctor <name>; there is no per-package Doctor button.
  • Export packages the plugin for sharing, with credentials stripped.
  • Remove uninstalls the package after confirmation.

MCP displays mask userinfo, credential query/fragment values, credential path suffixes, and opaque fragments. Credential-bearing or uncertain shell text is hidden in display; operational install values remain intact. Parse errors redact input values. Approval tickets bind operational credentials before projection.

Surface Values
Operator MCP parse/install url, env, headers, command, args: operational values for the install round-trip.
MCP confirmation UI Render only displayUrl, displayEnv, displayHeaders, displayCommand, displayArgs.
Public install plans and package inventories Display values; operational entries stay internal.
Export Mask URL credentials and credential flag arguments; replace placeholders before connecting. Env/header literals remain environment references.

Use Installed Plugins From Desktop

The desktop settings page uses the same runtime model as the CLI:

  • Expand an installed plugin to read its contribution rows and warnings.
  • In any desktop session, type /plugins to list installed plugins, or /plugins show <name> to see the same usage details from the chat surface.
  • Skills are shown with package-qualified direct commands such as /superpowers:writing-plans; they are also discoverable from /skills in a session.
  • Plugin commands are shown and invoked with package-qualified names such as /superpowers:plan.
  • Hooks and MCP servers are listed for transparency. They do not need a manual "run" button: enabled hooks trigger automatically, and MCP tools are available through ordinary tool use.
  • If an install or update reports that files were saved but the runtime did not reload, use Reload runtime to retry and follow any refusal it reports. A successful reload takes effect on the next turn.

Native Manifest

Reasonix plugins can declare reasonix-plugin.json at the plugin root:

{
  "apiVersion": "reasonix.io/plugin/v2",
  "name": "example",
  "version": "1.0.0",
  "description": "Example plugin",
  "contributes": {
    "skills": ["skills"],
    "hooks": {
      "SessionStart": [
        {
          "command": "hooks/session-start",
          "args": [],
          "description": "Load startup context"
        },
        {
          "command": "printf 'ready' && ./hooks/audit",
          "shell": "bash",
          "description": "Run a compound shell script"
        }
      ]
    },
    "mcpServers": {
      "helper": {
        "command": "bin/helper"
      }
    }
  }
}

Relative paths are resolved inside the plugin root. Reasonix does not run third-party install scripts during plugin installation.

A SessionStart hook can load packaged text without launching a command:

"hooks": {
  "SessionStart": [{"contextFile": "context/startup.md"}]
}

Put this block inside contributes and include the referenced file in the package. Reasonix reads the installed copy and adds its text to the first user turn as hook context. It keeps that text out of the cached system prompt.

Disabling or removing the package stops this context in newly built sessions. Use the existing reload flow to refresh an already running session's hooks; earlier conversation messages keep context that was already sent.

Plugin hook execution is explicit:

  • A hook with contextFile reads that package-relative file directly; it does not run a command. The session-start context example includes a complete package, copy/link instructions, and provider-boundary verification of first-turn delivery and session rotation.
  • When args is present, including "args": [], the hook uses exec form. command is the executable and every argument is passed literally, without shell parsing or interpolation.
  • When args is absent and shell is present, the hook uses shell form. The complete command is handed unchanged to bash, powershell/pwsh, cmd (Windows only), or auto. On Windows, auto prefers Git Bash and falls back to PowerShell.
  • Existing native hooks that declare neither field keep the historical Reasonix shell-command behavior. shellCommand: true remains supported as the legacy spelling of shell form.

Manifest v2 (Extensions)

Native Reasonix extensions use the exact v2 apiVersion:

{
  "apiVersion": "reasonix.io/plugin/v2",
  "name": "example",
  "version": "1.0.0",
  "description": "Example extension",
  "requires": [],
  "provides": [
    {
      "namespace": "plugin/example",
      "kind": "interceptors",
      "id": "default",
      "version": "1.0.0"
    }
  ],
  "contributes": {
    "skills": ["skills"],
    "agents": ["agents"],
    "commands": ["commands"],
    "prompts": ["prompts"],
    "hooks": {},
    "mcpServers": {},
    "themes": ["themes/*/theme.json"]
  },
  "runtime": {
    "command": "${REASONIX_PLUGIN_ROOT}/bin/example",
    "args": [],
    "env": {},
    "required": true,
    "priority": 0,
    "intercepts": ["input.receive", "tool.before"],
    "replaces": [],
    "capabilities": ["interceptors", "tools"],
    "tools": [
      {
        "name": "glossary_lookup",
        "description": "Look a term up in the team glossary",
        "inputSchema": {
          "type": "object",
          "properties": { "term": { "type": "string" } },
          "required": ["term"]
        },
        "readOnly": true
      }
    ]
  }
}

Parsing rules:

  • Native reasonix-plugin.json manifests must declare the exact reasonix.io/plugin/v2 value. v1 and missing versions are rejected; there is no v1 dual-read or automatic migration path.
  • v2 is strict: any unknown field — at the root or nested under contributes/runtime — is an error naming the field path, so typos fail loudly instead of silently disabling a capability.
  • v2 resource discovery is explicit. Reasonix loads only the skills, agents, commands, prompts, hooks, MCP servers, themes, and runtime declared by the native manifest. Host-specific sidecars such as a root CLAUDE.md, hooks/hooks.json, .claude/settings.json, or .mcp.json are not imported implicitly.
  • Minor aliases (reasonix.io/plugin/v2.0, v2.1, …) and unknown major versions are rejected.
  • requires and provides declare dependency constraints and the capability ceiling enforced against the Sidecar handshake.
  • v2 may mix the supported top-level resource fields (skills, hooks, mcpServers, …) with contributes: identical paths are deduplicated; the same key with two different definitions is a manifest error naming the key.
  • All relative paths and globs must stay inside the plugin root: traversal, absolute paths, escaping symlinks, and non-regular theme files are rejected.

New resource types:

  • prompts are prompt templates using the same semantics and argument substitution as commands, invoked as /<plugin>:<name>. The two are discovered together; commands remains a compatible alias.
  • themes are Studio theme packs:
    • each entry names a pack's theme.json, as a path or a per-segment glob such as themes/*/theme.json; its background and preview images sit beside it;
    • a pack appears in appearance settings as plugin:<plugin>:<directory>;
    • packs are read in place, never copied into the user's library, and leave with the plugin;
    • any other file listed under themes is ignored.

The native prompt example shows a template-only package, nested names, argument substitution, and the install/update/disable/remove lifecycle without a compiler or sidecar.

The runtime block declares a code extension — a sidecar process Reasonix launches and talks to over the Extension Protocol (JSON-RPC 2.0 over stdio; see docs/EXTENSION_PROTOCOL.generated.md for the method index and sdk/go/README.md for the Go SDK):

  • command/args/env are exec form only — the command is the executable (never shell-interpreted); ${REASONIX_PLUGIN_ROOT} expands to the installed plugin root.
  • intercepts lists the events the extension wants to intercept (for example input.receive, tool.before, permission.decision); replaces declares the replacement slots it may own (system_prompt, context, provider_request, provider_response, compaction, session_policy, permission, frontend_events, tool:<name>, provider:<ref>). Each slot has exactly one owner across all installed plugins; a collision fails the build with both sources named.
  • capabilities gates whole feature families: interceptors, strategies, providers, ui, tools, session. Anything the sidecar announces beyond the manifest is rejected during the handshake.
  • tools declares the model-callable tools the runtime serves; it needs the tools capability. Each tool has:
    • a name of 1-48 letters, digits, _ or -, unique in the package;
    • a description the model chooses the tool by;
    • an inputSchema, a JSON schema whose type is object;
    • an optional readOnly, the author's claim that the tool changes nothing.
  • The model sees a tool as ext__<plugin>__<name> and calls it through use_capability; every call passes the normal permission check. The install plan and the package list show the tools a runtime brings.
  • Provider models contributed by an extension appear as plugin/<plugin>/<provider>/<model> in the model picker; the ref also works as default_model (including on first boot) and in /model, Desktop, and ACP model switches.

Full trust. A code extension runs outside the sandbox with the unfiltered inherited environment. It can read the full session and environment, bypass permissions, and operate the machine directly; a permission.decision "allow" from an extension overrides a host deny. Installing, updating, replacing, or --linking a plugin with a runtime block is the authorization — there is no second confirmation prompt, and --link keeps trusting changed content automatically. The install preview, reasonix plugin show, capability diagnostics, and the Desktop installer therefore display a prominent FULL TRUST block with the runtime command, interceptors, replacement slots, and provider/UI capabilities. Review that block before installing, and only install runtimes you trust completely. Only plugins installed through the plugin flow can start a runtime; project configuration can never declare one.

Codex & Claude Compatibility

Reasonix also reads Codex plugin manifests at .codex-plugin/plugin.json and Claude plugin manifests at .claude-plugin/plugin.json. The install preview reports full, partial, or none compatibility, lists mapped capabilities, and identifies every skipped entry. A non-native package with no mapped capabilities is blocked instead of being recorded as an unusable installation. full means every declared capability in the manifest parsed and mapped to a Reasonix construct; it does not by itself guarantee every runtime decision an imported hook can make is honored. PreToolUse/PermissionRequest "deny" and PermissionRequest "allow" are implemented, but a hook's updatedInput or PreToolUse's ask/defer decisions are chosen by the script's stdout at call time, not by anything in the manifest, so they can't be flagged during install; see the hook bullet below for what's implemented.

GitHub-hosted multi-plugin marketplaces with a .claude-plugin/marketplace.json can be installed from the repository root when their plugin entries use relative string sources such as ./plugins/example or plugins/example; preview shows one action per plugin before anything is written. Set the optional install name to select one plugin.

Object sources support source: "url" with a GitHub repository URL pinned to a full commit SHA, and source: "git-subdir" with a GitHub repository url and a relative path.

git-subdir uses the repository's default branch unless ref or a full 40-character sha is supplied; sha takes precedence. All git-subdir sources require a working Git installation, including default-branch and SHA-pinned entries. The no-Git tarball fallback used by other GitHub plugin sources does not resolve these entries. In a bulk install, an unavailable or invalid git-subdir entry is skipped with a warning; selecting it by name returns the error.

The preview binds the resolved commit and subdirectory to its plan ID, and installation still requires approval of that plan. The installed source URL is pinned to that commit: updating the plugin keeps the same commit, rather than following subsequent changes to the default branch or ref. Preview the marketplace again to select a newer commit.

Copied plugin packages use the same 256 MiB total budget as plugin archives; individual skill installs retain their 20 MiB budget.

Unpinned external strings, npm, strict: false, and other advanced marketplace protocols are skipped in a bulk install and rejected when selected by name.

For packages such as Superpowers and Claude-style skill packs, Reasonix maps the following compatibility conventions. Native v2 manifests use only their explicit declarations and do not apply these fallbacks:

  • skills to Reasonix skill roots. A Claude manifest that declares no skills field falls back to the conventional skills/ (or .claude/skills/) directory, matching Claude's own auto-discovery. Plugin skills are displayed and invoked canonically as /<plugin>:<skill>. An unambiguous /<skill> is still accepted as a hidden compatibility alias; project and user skills keep their short names, while same-name skills from multiple plugins remain independently addressable only by their qualified names. This user-facing namespace does not change the bare skill identifiers in the model skill index or the run_skill tool.
  • commands/ (and .claude/commands/) to Reasonix custom slash commands: each <name>.md prompt template is displayed and invoked canonically as /<plugin>:<name>, with frontmatter description / argument-hint and $ARGUMENTS / $1..$N substitution honored. An unambiguous /<name> remains accepted as a hidden compatibility alias, but it is omitted from completion, help, desktop menus, ACP command discovery, and the model-visible command list. User- and project-authored commands own their short names, and no short alias is created when multiple plugins export the same command name. An explicit custom command can also occupy the qualified name; desktop plugin details report that conflict. Native reasonix-plugin.json manifests can declare the same thing explicitly with a "commands" path list.
  • agents/*.md to manually invoked, plugin-owned subagent profiles. Claude model aliases inherit the active Reasonix model; inline tools lists map to Reasonix tool names, including wildcard MCP names such as mcp__*__search. Agents use /<plugin>:agent:<name>, so an upstream agent and skill may share the same name without shadowing one another.
  • hooks/session-start-codex to the Reasonix SessionStart hook when present.
  • For Codex compatibility packages, a plugin-root CLAUDE.md file to a built-in SessionStart context hook. The file is read directly by Reasonix, without spawning a shell command. Claude plugin manifests ignore this file, matching Claude Code's plugin contract.
  • .claude/settings.json and hooks/hooks.json command hooks to Reasonix hook events when the event names match. matcher, args, shell, async, env, and timeout are preserved. Claude's execution contract is retained: an args field (even an empty array) selects exec form and preserves every argument literally; omitting args selects shell form and passes the raw command to the declared Bash or PowerShell interpreter. matcher and the tool_name a hook script sees are translated between Reasonix's own tool names and Claude's (bash ↔ Bash, write_file ↔ Write, ...), so a matcher like "Bash" fires correctly; every Reasonix subagent-spawning tool (task, read_only_task, parallel_tasks, and the dedicated explore/research/review/ security_review wrappers) maps to Claude's single Agent tool, and a matcher can still use the legacy Task name. Every mapped Agent payload includes Claude's required prompt and description; Reasonix supplies a stable operation label when its tool call omitted the optional description. tool_input keys that Reasonix names differently from Claude are renamed too — path becomes file_path for Read/Write/Edit/MultiEdit and notebook_path for NotebookEdit, name/arguments become skill/args for Skill, job_id becomes task_id for the current TaskOutput/TaskStop, the dedicated subagent wrappers' task becomes Agent's prompt, and parallel_tasks synthesizes Agent's prompt from its sub-task prompts (keeping tasks alongside) — so a guard reading .tool_input.file_path or .tool_input.prompt sees the target instead of failing open on an empty value. Legacy BashOutput/KillShell matchers still fire while the emitted names and fields use current Claude vocabulary. bash_output supplies TaskOutput's required non-blocking fields; wait also maps to TaskOutput, including task_id when it waits for exactly one job, and omits TaskOutput's optional timeout for an unbounded wait rather than claiming a 0ms budget. AskUserQuestion supplies omitted multiSelect:false and empty option descriptions, while TodoWrite derives an omitted activeForm from the task content. NotebookEdit also supplies new_source from Reasonix's accepted aliases, or an empty string for delete/empty-cell operations. Relative file_path/notebook_path values are resolved absolute against the payload cwd, matching Claude's file-tool contract, so prefix-matching guards inspect the path the tool actually accesses. A Bash tool_response is delivered in Claude's {stdout, stderr, interrupted} shape (Reasonix combines both streams into stdout; the failure error text becomes stderr), which the official security-guidance plugin's commit/push checks read; other tools' responses pass through as the raw result. Imported hooks receive Claude-compatible snake_case stdin payloads, including hook_event_name. Before process launch, the host expands ${CLAUDE_PLUGIN_ROOT} and ${REASONIX_PLUGIN_ROOT} (plus their unbraced $NAME and Windows %NAME% spellings), so plugin-relative paths do not depend on the target shell's environment-variable syntax. On Windows, shell-form hooks without an explicit shell use the same Git Bash-first, PowerShell-fallback selection as Reasonix's shell tool; when a hook points to a POSIX-shebang script file, the host also converts Windows paths to a Bash- compatible form. Explicit Bash hooks and legacy bare sh -c/bash -c hooks are routed through a discovered Git for Windows Bash even when it is not on cmd.exe's PATH; an explicit interpreter path remains untouched. If no usable Bash is installed, the hook reports a clear prerequisite error instead of the localized sh is not recognized output. A non-standard or portable Bash configured with [tools.shell] prefer = "bash" and path = ".../bash.exe" is reused by explicit Bash hooks. reasonix plugin doctor <name> and reasonix doctor capabilities report a missing required shell before the first hook invocation. Captured legacy-code-page output is normalized to UTF-8 before it reaches the UI. A PreToolUse or UserPromptSubmit hook can still deny via exit code 2 or its JSON deny shape on exit 0 (hookSpecificOutput.permissionDecision for PreToolUse, top-level decision:"block" for UserPromptSubmit); an imported PermissionRequest hook additionally answers the permission dialog itself (deny or auto-allow, rather than only notifying) via exit code 2 or hookSpecificOutput.decision.behavior, matching Claude's own contract. updatedInput is not yet applied to the tool call, and a hook's if condition or asyncRewake field is not evaluated. A package reports partial compatibility with a structured warning when it declares either field, a Stop/SubagentStop hook (which cannot block the turn in Reasonix), or a matcher that covers one of three inputs Reasonix cannot losslessly express: WebFetch.prompt, NotebookEdit.cell_id for a Reasonix cell_number call, or TaskOutput.task_id when Reasonix wait covers multiple/all jobs. Each structural gap is reported once per hooks file, so a wildcard-matcher plugin sees one warning per gap instead of one per hook.
  • A plugin-root .mcp.json maps to installed MCP entries. Claude local maps to stdio, non-ASCII display names receive stable internal IDs, and duplicate declarations are deduplicated. Imported servers default to auto_start=false and remain inactive until enabled or explicitly connected.
  • With no usable cached schema, enabled servers connect for catalog discovery. With a cached schema, deferred servers connect on demand. Discovery and connection do not change the current session's provider-visible tool schema.

Unsupported Claude hook item types are skipped with a warning. Reasonix does not run third-party install scripts.

Plugin hooks receive these environment variables:

  • REASONIX_PLUGIN_ROOT
  • REASONIX_PLUGIN_NAME
  • REASONIX_PLUGIN_VERSION
  • REASONIX_HOME
  • REASONIX_WORKSPACE_ROOT
  • CLAUDE_PROJECT_DIR
  • CLAUDE_PLUGIN_ROOT

Claude Code mods

A Claude Code mod is a plugin whose hooks/hooks.json names one ES module ("modules": ["./register.js"]) that exports register(on, options). Reasonix installs it like any Claude plugin.

The module runs only while Run Claude Code mods is on, in Settings or as [mods] enabled = true in the user file. It is off by default, and a project file cannot turn it on.

A mod is full trust:

  • Its module runs on this machine as you, outside the sandbox.
  • It can read files, start processes and use the network.
  • The install preview says so and lists the module's events and $ calls from its syntax tree.
  • That listing is an aid, not a guarantee. Code the module imports, and names it builds at run time, are not listed, and the preview shows them as such.

How it runs:

  • The host script runs under Studio's own binary in Node mode, or under the node on PATH for the terminal and serve. Node 22 or newer is required, and a node inside the open project is never used.
  • .js .mjs .cjs modules run everywhere. .ts .mts .cts run where the runtime strips types. .jsx and .tsx do not run.
  • The process is a tighten-only extension runtime. A mod can deny a call or ask the user about it. The host refuses an allow, or a rewrite of a permission decision, whatever the process writes.
  • A hook gets [mods] hook_budget_ms of its own running time, 10 s by default. A hook stuck in a loop ends the process and the turn goes on without it.
  • The process restarts at the next reload. After three deaths the mod is disabled, and its status says why.
  • Nothing a mod does changes the cached system prompt or the tool list. Its text reaches the model only as data from the mod.
  • While a mod runs, the completion receipt carries the gap mod_effects_unobserved: what a full-trust process wrote is outside what the host observes, so a change it made cannot be proven.

Events served:

  • tool.call: { deny }, argument rewrite, and { result }. A result answers the call in place of the tool: the tool does not run, the model gets a successful result under the host's header naming the mod, and the receipt counts no change.
  • tool.check: deny and ask. An allow is ignored. It applies to installed MCP tools as well, and can only tighten.
  • prompt.submit: text rewrite, { drop } and context, a list of strings or { label, text } blocks. Blocks follow the prompt on the turn's tail under the host's header; they never touch the cached prefix. A block is cut at [mods] context_bytes (8192) and a turn keeps context_blocks (8).
  • session.start, session.end, turn.start, command.run, ui.press.
  • ui.render for the AbovePrompt band.

$ served:

  • plugin, store, clock, env.get, fs, process.run, http.fetch.
  • state, with atom, read, update, derive and memberOf imported from claude-code.
  • session.id, session.cwd, session.root.
  • session.messages({ limit }) and session.usage(): the last transcript items (credentials redacted, up to [mods] messages_max, 200) and the last response's token counts. A mod that calls them is listed in the install preview as reading the session.
  • prompt.submit(text): queues a prompt as the mod's own words, under the host's header, never as yours; asUser is not served.
  • It rejects with mod.submit_rate over [mods] submit_per_turn (3 between two prompts you write), with mod.submit_too_long over [mods] submit_bytes (8 KiB, 0 for none), and with mod.submit_refused while the session has no saved file.
  • A call that needs an answer made while session.start runs rejects with mod.not_ready: that hook runs before the session is ready.
  • tool.register/call/list and command.register/run/list. Both register while session.start runs.
  • ui.toast, status, notice, log, ask, resolve, invalidate.

The turn a queued prompt starts is held to this session's own approvals and sandbox like any other turn; that, not the label, is what keeps it safe.

The ceilings above are in the user file only, and 0 removes one.

Anything else is registered and named in the mod's status as unserved; a call to an unserved $ member rejects with the typed code mod.unsupported.

$.ui.ask is a question from the mod, shown with its name. It can never approve a tool call or a permission.

Studio HTTP routes

Studio drives plugin packages through these kernel routes:

  • GET /plugins lists installed packages and what each contributes
  • POST /plugins/plan reads a source and returns the install plan
  • POST /plugins/install applies a plan, echoing its planId; with replace set it is also the update path
  • POST /plugins/enabled enables or disables a package
  • DELETE /plugins/{name} removes a package
  • GET /plugins/{name}/export packs a package as a .zip, credentials stripped
  • POST /extensions/reload rebuilds the runtime from what is on disk

reasonix plugin doctor has no route; run it from the CLI.

Both reasonix plugin doctor <name> and GET /plugins report warnings for declared theme.json files that the theme reader rejects, and for individual tokens it drops. Warnings include the file's path relative to the package root. They also appear for disabled packages, so authors can diagnose a theme before enabling it.

Theme warnings do not reject an otherwise valid plugin or change the doctor's exit status. A rejected theme is absent from the theme picker; a pack with some invalid tokens still loads with its valid tokens. Other declared theme files are not theme.json packs and are not validated by this reader.