Skip to content

Load and run plugins with the tw-plugin sandbox - #258

Closed
fylorn wants to merge 1 commit into
feat/pluginsfrom
plugins-engine
Closed

fylorn wants to merge 1 commit into
feat/pluginsfrom
plugins-engine

Conversation

@fylorn

@fylorn fylorn commented Oct 2, 2026

Copy link
Copy Markdown
Contributor

Replaces the "engine unavailable" stand-in with the tw-plugin sandbox (#254). The adapter is tw_gateway::plugin::sandbox, and it implements the gateway's Engine, PluginHost and ReplyHost seams.

Runtime lifetime

  • The runtime starts on the first plugin load: a configured plugin or a PluginInspect.
  • It is reused across config reloads. A core without plugins never starts it.
  • If it fails to start, every plugin gets status error with that reason, and the requests it covers follow on_error.

Loading

  • Each load runs on its own thread with an 8 MiB stack. tw-plugin needs at least 2 MiB, and loads come from config reloads, the plugin-directory watcher, spawn_blocking (inspect, create, approve) and, at startup, the main thread, which has 1 MiB on Windows.
  • Plugins are compiled from exactly the bytes that were hashed (I9).
  • Load errors, including module top-level errors and evaluation limits (LoadError::Syntax with line and column), reach PluginInspection.error and status error.

Hooks

on_request, reply → on_text, on_text_end and on_tool_call delegate to tw_plugin::Plugin and tw_plugin::Reply. Results, logs and CPU time are mapped one to one.

Tests

  • Unit tests load real plugins:
    • manifest mapping
    • hash of the bytes
    • a syntax error and its line
    • a manifest that asks for an unused permission
    • a caller thread with a 256 KiB stack
  • Unit tests also run a real request hook and a real reply hook through the adapter.
  • A control-plane test takes a real plugin through inspect, install, a changed file and approval.

Checks

  • cargo fmt --all --check
  • cargo clippy --workspace --all-targets -- -D warnings, and --lib
  • cargo test --workspace

🤖 Generated with Claude Code

The gateway's plugin engine was the "engine unavailable" stand-in. It is
now tw-plugin's runtime (QuickJS in Wasmtime) behind `plugin::Engine`,
`plugin::PluginHost` and `plugin::ReplyHost` (`plugin::sandbox`):

- The runtime starts on the first plugin load (a configured plugin, or an
  inspect), not with the gateway, and is reused across reloads. A core
  without plugins, and every test that builds a gateway, never starts it.
  If it cannot start, every plugin fails to load with that reason, and the
  requests it covers follow `on_error`.
- Each load runs on its own thread with an 8 MiB stack: the sandbox needs
  more than 2 MiB, and a load can come from any thread (a config reload,
  or the main thread at startup, which has 1 MiB on Windows).
- Plugins are compiled from exactly the bytes that were hashed. Manifests,
  permissions (in `tw_api::Permission` order), scope, reply mode, settings
  (in the author's order) and hooks map onto the gateway's types; load
  errors, including top-level evaluation errors, keep their line and
  column, so they reach `PluginInspection.error` and status `error`.
- `onRequest`, the per-reply instance, `onReplyText`, `onReplyTextEnd`
  and `onToolCall` delegate to the sandbox, with their logs and CPU time.

Tests load real plugins (manifest, hash of the bytes, a syntax error with
its line, a manifest that asks for more than it uses, a caller with a
small stack), run a request hook and a reply hook through the adapter,
and take a real plugin through the control plane: inspect, install, a
changed file and approval.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@fylorn

fylorn commented Oct 2, 2026

Copy link
Copy Markdown
Contributor Author

Superseded by #259, which carries the same sandbox adapter (lazy runtime, loads on an 8 MiB thread, hooks delegated one to one) plus the data-plane fixes that go with it. Closing this one so the two do not collide in plugin/sandbox.rs. The control-plane test that takes a real plugin through inspect, install, a changed file and approval will follow separately once #259 is in.

@fylorn fylorn closed this Oct 2, 2026
@fylorn
fylorn deleted the plugins-engine branch October 2, 2026 12:04
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant