Luigit
repositories / smith

smith

There are many coding harnesses - but this one is fast

owned by admin

.system/specs/SMH-SPEC-PLUG0001-plugin-system/index.md

Raw
Rendered preview

id: SMH-SPEC-PLUG0001 type: spec title: "Plugin System" research: [SMH-RESEARCH-TBKDJHVX, SMH-RESEARCH-WASM0001]

Plugin System

Intent

One typed plugin system where unprivileged wasm components extend Smith. Built-ins and user plugins share the same contracts, effects, and sandbox; parity is law, not aspiration.

Engine mechanics (determinism, fuel, memory ceilings, artifact validation) live in SMH-SPEC-WASM0001. Product integration points live in SMH-SPEC-SPEC0001.

Runtime and parity

Plugins run in a restricted runtime without direct operating-system, filesystem, environment, network, or clock access. Plugin execution is resource-bounded. Host effects are available only through typed Smith SDK component interfaces granted by manifest and trust decisions. Built-in plugins have no privileged effect path.

Built-in coding tools are embedded component artifacts loaded through the normal sandbox; they call the same typed host primitives available to user plugins.

Contributions

Plugins may contribute:

  • tools,
  • commands,
  • providers and models,
  • credential sources,
  • prompts and context transforms,
  • event handlers,
  • themes and keybindings,
  • layout and render descriptions,
  • VCS workflows.

Provider protocol

A provider plugin is a pure codec. The guest builds request templates carrying secret references; the host substitutes plaintext at the effect boundary, executes transport, and records the redacted effect. Raw response chunks flow back to the guest, which decodes bytes into normalized events as pure computation. Replay re-executes decoding over recorded bytes. Custom transports and routing policy arrive later as additive granted interfaces or host-executed plans, never by redesigning this pipeline.

Generations

Contribution identities are namespaced by their plugin and coexist. Short names resolve through the alias layer; ambiguity fails deterministically, naming every candidate and the override path; no implicit winner is selected.

Plugin events are ordered and typed. Contributions are declared at instantiation through a registration entry point; the host validates the resulting complete set before publication. Manifest statics stay minimal and curated: requested capabilities plus hints concerning safety, performance, and robustness. Registration performs no operational effects and mutates no active runtime. A candidate generation can declare and validate contributions but cannot invoke operational effects or mutate the active runtime before publication. A failed load leaves no registrations or subscriptions active. Reload publishes one complete generation or retains the previous generation.

A generation owns every plugin instance, registration, subscription, binding, and contribution. Instances are interchangeable dispatch targets: pooled freely for parallel calls, immutable after instantiation, with local state as scratch carrying no semantic guarantee. Per-instance configuration binds at instantiation and never mutates after. All durable state lives in the host; guests address it through per-call resource handles. Instance affinity, where genuinely needed, is expressed through resources that pin calls to a serving instance. Session, branch, queue, secrets, cancellation, and active-turn state live outside the generation and survive reload. Guest-local implementation state resets on reload. Removed contributions disappear at publication. Calls already running complete against their pinned generation; later calls resolve against the published generation.

Each provider request pins one generation through its response and complete tool-call/result batch. The pinned generation supplies both advertised definitions and executable handlers.

Budgets and failure

Smith SDK calls may catch ordinary typed guest errors, but not cancellation, budget exhaustion, sandbox violations, or host-effect recording failures. Smith SDK child work is structured and host-orchestrated: it shares the parent invocation budget and generation pin, serializes per plugin instance, and finishes or cancels before the parent handler returns. Fan-out cannot multiply resource limits. The budget covers deadline, guest fuel, component memory and table ceilings, host allocations, logs, conversions, and output. Budget knobs resolve by precedence: explicit user or trust-time configuration, then manifest hints, then product defaults. Manifest hints may only tighten; raising any ceiling is a trust decision made at trust or install time.

Event boundary

Plugin handlers run synchronously in deterministic registration order under one bounded invocation budget shared with structured child tasks. Event domains — turn, provider stream, tool, session, and plugin lifecycle — are delivered under these laws; concrete event records are world surface and evolve under the world's lifecycle rules. They return typed actions rather than mutable runtime ownership. Actions may request cancellation, queued input, reload, or runtime-only model, provider, tool, prompt, and configuration changes. Cancellation applies immediately. Other actions are validated as one batch and apply atomically between provider requests, after the current stream and complete tool-result batch. Conflicting mutations are rejected; no implicit winner is selected.

A failed or over-budget handler emits one typed diagnostic, is disabled for the current generation, and does not abort the turn unless its event contract requires a decision. Terminal agent events remain singular.

sequenceDiagram
  participant P as Provider round
  participant A as Agent boundary
  participant H as Ordered handlers
  participant G as Runtime generation
  P-->>A: Stream and complete tool batch
  A->>H: Typed lifecycle events
  H-->>A: Typed action batch
  A->>A: Validate and reject conflicts
  A->>G: Publish coherent runtime changes
  G-->>P: Next request snapshot

Discovery and modules

Version 1 discovers plugin packages by path convention. Each package carries a manifest declaring its plugin artifact, world version, and requested capabilities. Package, artifact, and manifest identities are deterministic and validated before instantiation. Artifacts failing validation never execute. Manifest and artifact reads are bounded before materialization, and canonical paths cannot escape their declared root.

A module contract is a typed, major-versioned component interface. A package that exports a contract ships the contract WIT alongside its artifact; the artifact exports both the contract interface and its implementation constructors in one component. Remote updates deliver implementations only; interfaces change only with host world releases. Undeclared exports remain private. A module can have zero, one, or multiple implementations. One implementation binds automatically. Zero or ambiguous implementations fail only when consumed, with sorted candidates and the consumer reported. Explicit user binding overrides automatic binding and must satisfy the requested contract.

Module consumers use logical major-versioned handles. A new invocation resolves each handle against the published generation; nested accesses during an active invocation resolve within its pinned generation. Reload can rebind only to an implementation satisfying the same major contract; guest-local state resets.

Precedence is built-in, global user, project, then explicit override. Later registration replaces earlier registration only where the earlier contract explicitly permits replacement. Installed capabilities are validated as a complete set before atomic replacement or removal.

Project trust

Project-local executable configuration, plugins, modules, and managed packages are not enumerated or loaded before trust is resolved. A project trust identity is its lossless, platform-tagged canonical native path. Symlink aliases share one decision; distinct native paths cannot collide through text conversion. Interactive mode can ask and persist the decision. Non-interactive modes require an explicit or previously persisted decision to load project-local executable behavior. An explicit plugin path grants one-run trust only to that exact canonical source. Capability grants resolve at trust time for project plugins and at install time for user plugins. Ignoring untrusted project behavior produces a visible diagnostic.

Trust does not remove the plugin sandbox or tool boundaries.

System plugins

System plugins ship inside the smith release artifact, hash-pinned at build time; their trust is the release signature, transitively. They get no runtime privilege: same sandbox, loader, fuel, and grant model as user plugins. The smith: package prefix is reserved; external artifacts exporting smith: contracts fail validation with a concrete fix. Product default grants — for example net-scope for the OAuth plugin — are configuration shipped by the product, not runtime privilege.

Trust tiers: embedded (signed with the release), installed user (hash recorded at install, grants at install), project (trust-gated before enumeration, grants at trust time).

World surface

The smith:plugin world is the entire plugin API. No non-WIT construct is required to build or consume a plugin; raw wit-bindgen guests are first-class before and after any SDK exists.

Design laws:

  • Two layers: a core world every plugin sees, plus granted capability interfaces — HTTP via net-scope, credential exchange via secret-proxy, logging, and UI intents via the UI grant — imported only with matching grants. Grants gate not only ambient reach but also resource and presentation decisions: the plugin manager can load, pool, or skip UI-granted plugins per frontend.
  • Flat interface set inside one world: http, secrets, log, stream, contrib; resources carry methods, no prefix hierarchies. Grouping pressure means an interface is too wide.
  • One world major: smith:plugin@N; breaking changes bump the world, additive changes stay inside it. Plugin-published contracts carry their own majors (owner:name@major). The host embeds the previous world during a deprecation window.
  • Deprecations are metadata: @deprecated with since and removal versions plus a named replacement; deprecated items stay functional until the next world major; smith plugin check fails new builds targeting them.
  • New items enter as @unstable: usable but exempt from the append-only law until stabilized; a world major ships only with zero unstable items — each is stabilized or removed first.
  • Append-only inside a major applies to stabilized items; unstable items may reshape or disappear freely until stabilized.
  • One naming grammar, one error grammar, one documentation shape across all interfaces.
  • Names follow verb-noun functions, noun types, no abbreviations; symmetric verb pairs; open for acquired handles, create for pure values; faults are nouns.
  • Parameter order is data first, options record last.
  • One world-wide glossary distinguishes near-synonyms: secret, credential, chunk, frame, provider, effect.
  • One-purpose interfaces; an interface needs two real consumers before entering the world.
  • Explicit parameters: buffers, bounds, cancellation, and provenance are arguments, never ambient state.
  • Precise failure sets: typed result variants per operation family; no single generic error case.
  • Resources are the polymorphism unit: module contracts are resource types, implementations export constructors, the host mediates binding.
  • Pull, never callback: the host pulls chunks and re-invokes renewal; function values never cross the boundary.
  • Plugin-to-plugin calls route through host effects; guests never link each other directly.
  • Values cross by copy: calls are batched; chatty interfaces fail review.
  • Evolution is additive-only with an unknown catch-all; world versions are computed from interface diffs, not asserted by authors.
  • No SDK requirement: a smith-authored SDK may add ergonomics after 1.0; the world alone stays complete and comfortable without it.

Guest languages: Rust first, TypeScript via jco as maintained compatibility target, per SMH-SPEC-WASM0001.

Acceptance criteria

  • Project plugins cannot execute before trust resolution.
  • Built-in and user plugins use the same sandboxed effect APIs.
  • Event handlers are bounded, ordered, and publish conflict-free runtime actions only at safe points.
  • Reload atomically replaces one complete generation while active provider rounds remain pinned.
  • Callable module handles rebind only to conforming implementations of the same major contract.
  • Structured child work cannot suppress cancellation or exceed its shared budget.
  • A failed or over-budget handler disables only itself for the generation.
---
id: SMH-SPEC-PLUG0001
type: spec
title: "Plugin System"
research: [SMH-RESEARCH-TBKDJHVX, SMH-RESEARCH-WASM0001]
---

# Plugin System

## Intent

One typed plugin system where unprivileged wasm components extend Smith.
Built-ins and user plugins share the same contracts, effects, and sandbox; parity is law, not aspiration.

Engine mechanics (determinism, fuel, memory ceilings, artifact validation) live in `SMH-SPEC-WASM0001`.
Product integration points live in `SMH-SPEC-SPEC0001`.

## Runtime and parity

Plugins run in a restricted runtime without direct operating-system, filesystem, environment, network, or clock access.
Plugin execution is resource-bounded.
Host effects are available only through typed Smith SDK component interfaces granted by manifest and trust decisions.
Built-in plugins have no privileged effect path.

Built-in coding tools are embedded component artifacts loaded through the normal sandbox; they call the same typed host primitives available to user plugins.

## Contributions

Plugins may contribute:

- tools,
- commands,
- providers and models,
- credential sources,
- prompts and context transforms,
- event handlers,
- themes and keybindings,
- layout and render descriptions,
- VCS workflows.

## Provider protocol

A provider plugin is a pure codec.
The guest builds request templates carrying secret references; the host substitutes plaintext at the effect boundary, executes transport, and records the redacted effect.
Raw response chunks flow back to the guest, which decodes bytes into normalized events as pure computation.
Replay re-executes decoding over recorded bytes.
Custom transports and routing policy arrive later as additive granted interfaces or host-executed plans, never by redesigning this pipeline.

## Generations

Contribution identities are namespaced by their plugin and coexist.
Short names resolve through the alias layer; ambiguity fails deterministically, naming every candidate and the override path; no implicit winner is selected.

Plugin events are ordered and typed.
Contributions are declared at instantiation through a registration entry point; the host validates the resulting complete set before publication.
Manifest statics stay minimal and curated: requested capabilities plus hints concerning safety, performance, and robustness.
Registration performs no operational effects and mutates no active runtime.
A candidate generation can declare and validate contributions but cannot invoke operational effects or mutate the active runtime before publication.
A failed load leaves no registrations or subscriptions active.
Reload publishes one complete generation or retains the previous generation.

A generation owns every plugin instance, registration, subscription, binding, and contribution.
Instances are interchangeable dispatch targets: pooled freely for parallel calls, immutable after instantiation, with local state as scratch carrying no semantic guarantee.
Per-instance configuration binds at instantiation and never mutates after.
All durable state lives in the host; guests address it through per-call resource handles.
Instance affinity, where genuinely needed, is expressed through resources that pin calls to a serving instance.
Session, branch, queue, secrets, cancellation, and active-turn state live outside the generation and survive reload.
Guest-local implementation state resets on reload.
Removed contributions disappear at publication.
Calls already running complete against their pinned generation; later calls resolve against the published generation.

Each provider request pins one generation through its response and complete tool-call/result batch.
The pinned generation supplies both advertised definitions and executable handlers.

## Budgets and failure

Smith SDK calls may catch ordinary typed guest errors, but not cancellation, budget exhaustion, sandbox violations, or host-effect recording failures.
Smith SDK child work is structured and host-orchestrated: it shares the parent invocation budget and generation pin, serializes per plugin instance, and finishes or cancels before the parent handler returns.
Fan-out cannot multiply resource limits.
The budget covers deadline, guest fuel, component memory and table ceilings, host allocations, logs, conversions, and output.
Budget knobs resolve by precedence: explicit user or trust-time configuration, then manifest hints, then product defaults.
Manifest hints may only tighten; raising any ceiling is a trust decision made at trust or install time.

## Event boundary

Plugin handlers run synchronously in deterministic registration order under one bounded invocation budget shared with structured child tasks.
Event domains — turn, provider stream, tool, session, and plugin lifecycle — are delivered under these laws; concrete event records are world surface and evolve under the world's lifecycle rules.
They return typed actions rather than mutable runtime ownership.
Actions may request cancellation, queued input, reload, or runtime-only model, provider, tool, prompt, and configuration changes.
Cancellation applies immediately.
Other actions are validated as one batch and apply atomically between provider requests, after the current stream and complete tool-result batch.
Conflicting mutations are rejected; no implicit winner is selected.

A failed or over-budget handler emits one typed diagnostic, is disabled for the current generation, and does not abort the turn unless its event contract requires a decision.
Terminal agent events remain singular.

```mermaid
sequenceDiagram
  participant P as Provider round
  participant A as Agent boundary
  participant H as Ordered handlers
  participant G as Runtime generation
  P-->>A: Stream and complete tool batch
  A->>H: Typed lifecycle events
  H-->>A: Typed action batch
  A->>A: Validate and reject conflicts
  A->>G: Publish coherent runtime changes
  G-->>P: Next request snapshot
```

## Discovery and modules

Version 1 discovers plugin packages by path convention.
Each package carries a manifest declaring its plugin artifact, world version, and requested capabilities.
Package, artifact, and manifest identities are deterministic and validated before instantiation.
Artifacts failing validation never execute.
Manifest and artifact reads are bounded before materialization, and canonical paths cannot escape their declared root.

A module contract is a typed, major-versioned component interface.
A package that exports a contract ships the contract WIT alongside its artifact; the artifact exports both the contract interface and its implementation constructors in one component.
Remote updates deliver implementations only; interfaces change only with host world releases.
Undeclared exports remain private.
A module can have zero, one, or multiple implementations.
One implementation binds automatically.
Zero or ambiguous implementations fail only when consumed, with sorted candidates and the consumer reported.
Explicit user binding overrides automatic binding and must satisfy the requested contract.

Module consumers use logical major-versioned handles.
A new invocation resolves each handle against the published generation; nested accesses during an active invocation resolve within its pinned generation.
Reload can rebind only to an implementation satisfying the same major contract; guest-local state resets.

Precedence is built-in, global user, project, then explicit override.
Later registration replaces earlier registration only where the earlier contract explicitly permits replacement.
Installed capabilities are validated as a complete set before atomic replacement or removal.

## Project trust

Project-local executable configuration, plugins, modules, and managed packages are not enumerated or loaded before trust is resolved.
A project trust identity is its lossless, platform-tagged canonical native path.
Symlink aliases share one decision; distinct native paths cannot collide through text conversion.
Interactive mode can ask and persist the decision.
Non-interactive modes require an explicit or previously persisted decision to load project-local executable behavior.
An explicit plugin path grants one-run trust only to that exact canonical source.
Capability grants resolve at trust time for project plugins and at install time for user plugins.
Ignoring untrusted project behavior produces a visible diagnostic.

Trust does not remove the plugin sandbox or tool boundaries.

## System plugins

System plugins ship inside the smith release artifact, hash-pinned at build time; their trust is the release signature, transitively.
They get no runtime privilege: same sandbox, loader, fuel, and grant model as user plugins.
The `smith:` package prefix is reserved; external artifacts exporting `smith:` contracts fail validation with a concrete fix.
Product default grants — for example net-scope for the OAuth plugin — are configuration shipped by the product, not runtime privilege.

Trust tiers: embedded (signed with the release), installed user (hash recorded at install, grants at install), project (trust-gated before enumeration, grants at trust time).

## World surface

The `smith:plugin` world is the entire plugin API.
No non-WIT construct is required to build or consume a plugin; raw `wit-bindgen` guests are first-class before and after any SDK exists.

Design laws:

- Two layers: a core world every plugin sees, plus granted capability interfaces — HTTP via net-scope, credential exchange via secret-proxy, logging, and UI intents via the UI grant — imported only with matching grants.
Grants gate not only ambient reach but also resource and presentation decisions: the plugin manager can load, pool, or skip UI-granted plugins per frontend.
- Flat interface set inside one world: `http`, `secrets`, `log`, `stream`, `contrib`; resources carry methods, no prefix hierarchies. Grouping pressure means an interface is too wide.
- One world major: `smith:plugin@N`; breaking changes bump the world, additive changes stay inside it. Plugin-published contracts carry their own majors (`owner:name@major`). The host embeds the previous world during a deprecation window.
- Deprecations are metadata: `@deprecated` with since and removal versions plus a named replacement; deprecated items stay functional until the next world major; `smith plugin check` fails new builds targeting them.
- New items enter as `@unstable`: usable but exempt from the append-only law until stabilized; a world major ships only with zero unstable items — each is stabilized or removed first.
- Append-only inside a major applies to stabilized items; unstable items may reshape or disappear freely until stabilized.
- One naming grammar, one error grammar, one documentation shape across all interfaces.
- Names follow verb-noun functions, noun types, no abbreviations; symmetric verb pairs; `open` for acquired handles, `create` for pure values; faults are nouns.
- Parameter order is data first, options record last.
- One world-wide glossary distinguishes near-synonyms: secret, credential, chunk, frame, provider, effect.
- One-purpose interfaces; an interface needs two real consumers before entering the world.
- Explicit parameters: buffers, bounds, cancellation, and provenance are arguments, never ambient state.
- Precise failure sets: typed result variants per operation family; no single generic error case.
- Resources are the polymorphism unit: module contracts are resource types, implementations export constructors, the host mediates binding.
- Pull, never callback: the host pulls chunks and re-invokes renewal; function values never cross the boundary.
- Plugin-to-plugin calls route through host effects; guests never link each other directly.
- Values cross by copy: calls are batched; chatty interfaces fail review.
- Evolution is additive-only with an unknown catch-all; world versions are computed from interface diffs, not asserted by authors.
- No SDK requirement: a smith-authored SDK may add ergonomics after 1.0; the world alone stays complete and comfortable without it.

Guest languages: Rust first, TypeScript via jco as maintained compatibility target, per `SMH-SPEC-WASM0001`.

## Acceptance criteria

- Project plugins cannot execute before trust resolution.
- Built-in and user plugins use the same sandboxed effect APIs.
- Event handlers are bounded, ordered, and publish conflict-free runtime actions only at safe points.
- Reload atomically replaces one complete generation while active provider rounds remain pinned.
- Callable module handles rebind only to conforming implementations of the same major contract.
- Structured child work cannot suppress cancellation or exceed its shared budget.
- A failed or over-budget handler disables only itself for the generation.