Skip to content

revamp plugin system with entry points - #36

Merged
abc8747 merged 16 commits into
mainfrom
pr-better-plugin
Aug 1, 2026
Merged

revamp plugin system with entry points#36
abc8747 merged 16 commits into
mainfrom
pr-better-plugin

Conversation

@abc8747

@abc8747 abc8747 commented Aug 1, 2026

Copy link
Copy Markdown
Collaborator

Previously, plugins are 1) not independently distributable, 2) uses ast/sys.path hacks to scan plugin metadata in a local directory and 3) lacks a way to properly manage background threads. Following #19 (comment) and #28, this PR implements and closes #24.

Following the approach in tangram, plugins are now normal Python packages exposing a Plugin value through the minisky.plugins entry-point group:

[project.entry-points."minisky.plugins"]
example = "minisky_example:plugin"

which points to a build function inside the plugin:

from minisky import plugin as plugin_api

# executed on "plugin startup"
def build(
    context: plugin_api.PluginContext[ExampleConfig],
) -> plugin_api.PluginSpec:
    # context here owns construction for one load attempt.
    component = context.mount(Example(context.config, context.python_random))
    return context.finish()


plugin = plugin_api.Plugin(
    build=build,
    config_class=ExampleConfig,
)

Mounted components declare their contributions:

class Example(plugin_api.Entity):
    @plugin_api.command(arguments="txt,[int]")
    def passengers(self, callsign: str, count: int = -1) -> tuple[bool, str]:
        ...

    @plugin_api.hook("update", interval=5.0)
    def update(self) -> None:
        ...

Replaceable implementations are also now explicitly contributed by the plugin:

@plugin_api.replacement
class CustomAutoPilot(Autopilot):
    ...

# then in the build function, register it so the SELECTIMPL command works...
return context.finish(replacements=(CustomAutoPilot,))

Plugins can also provide an FastAPI-style async lifespan to manage resources like httpx/postgres like:

@asynccontextmanager
async def lifespan(runtime: plugin_api.PluginRuntime):
    resource = await open_resource()
    try:
        yield
    finally:
        await resource.aclose()

The plugin_path and enabled_plugins settings are removed. The [plugins.<id>] now stores the plugin-specific configuration, much like tangram, and is validated before calling its build function.

Note that plugins are not loaded by default and must be done via LOAD PLUGIN or await runtime.plugins.load_configured().

Future work planned but not included in this PR:

  • replacing the command arguments DSL with Annotated[...]
  • typed migration of core stack commands
  • explicit replaceable-slot declarations
  • preserving the plugin config generic through entry-point metadata

abc8747 added 16 commits July 30, 2026 05:18
- move core and runtime assets to `packages/minisky`
- move tangram to `packages/tangram-minisky`
- old plugin loader and behaviour remains unchanged.
- keep synchronous `init_plugin` for now
- make legacy cmd dict and decorated module commands use it
- check plugin state names
- keep `init_plugin` and `TimedFunctionManager`
- stack owns at most one pending awaitable command, others are suspended until
  it completes
- for `Simulation.step` return true when timestep is complete and false while
  awaitable owns boundary
- add command pending and wait for pending for future async plugin loading
…inSpec`

- make `Plugin` the "package declaration"
- move entry point to private `_PluginRecord`
- validate `config_class` with `pydantic.TypeAdapter` and pass that to plugins
- `ctx.mount()`
- setup new test infra
- bind command and hook declarations to instance passed to mount
- reject static method and classmethods
- use shared public name validation for commands
- infer docstring -> help, method signature -> brief text
- allow stacked hooks and validate them
- add `@plugin.replacement` and `PreparedReplacement`
- extend `PluginSpec` and `PluginContext.finish()` with explicit replacements
- add prepared replacement records and runtime-local validation
- remove `TrafficArrays.__subclasses__()` discovery from active selection
- migrate `minisky-example-customautopilot` plugin
- register `MVP` (though it should be isolated to a new plugin soon)

addresses 47a1e2d
- accumulate simulated time directly
- clear reset typed-hook elapsed state
- add restricted `PluginRuntime` capabilities and read-only `PluginStatus`
- add async lifespans to `PluginSpec`
- serialize plugin loading with a manager-owned lock
- complete lifespan startup before publishing registrations
- remove registrations and revoke capabilities before teardown
- add runtime-owned console subscriptions
- migrate `minisky-tangram` and `PLUGINS LOAD` to the async lifecycle
- remove legacy initializer, command scanner, and timed-hook
…atch

- remove `CommandStack.addcommand()`, its reimplementation and
  custom-command-class compatibility
- remove unused `Command.parent` and `Command.notimplemented()`
@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Aug 1, 2026

Copy link
Copy Markdown

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

Status Name Latest Commit Updated (UTC)
✅ Deployment successful!
View logs
minisky 37ab076 Aug 01 2026, 02:24 PM

@abc8747
abc8747 merged commit 9fa800e into main Aug 1, 2026
2 checks passed
@abc8747
abc8747 deleted the pr-better-plugin branch August 1, 2026 14:24
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.

Replace directory-scanning plugin system with entry points

1 participant