yoi/.yoi/tickets/00001KSXRQ4G8/item.md

13 KiB

title state created_at updated_at assignee
Plugin: define runtime, surface, and minimal host API model closed 2026-05-31T01:00:05Z 2026-06-19T13:29:26Z null

Goal

Design Yoi's Plugin model as a user-facing package/config/runtime layer built on top of pod::feature and a deliberately small host API.

Use three stable terms:

  • Plugin runtime: how Plugin code/config is executed.
  • Plugin surface: what the Plugin adds to Yoi.
  • Plugin host API: what Yoi-provided capabilities a Plugin runtime may call while implementing a surface.

pod::feature remains the internal substrate for contribution registration, lifecycle diagnostics, and Worker/ToolRegistry/HookRegistry integration. Plugin owns package identity, enablement, runtime selection, Plugin-layer permissions, trust policy, and host API grants.

MCP is not the Plugin model and should not be treated as a Plugin permission backend. MCP is a separate protocol-backed integration layer that also uses pod::feature and owns MCP-specific enablement/trust policy.

Background

Yoi already has internal extension-like surfaces:

  • Tools via llm_worker::ToolRegistry / ToolDefinition
  • Hooks via HookRegistry / HookEvent / HookAction
  • Feature contribution declarations in pod::feature
  • Built-in features such as task and ticket tools

Plugin design must not let external packages bypass Worker history, prompt/context invariants, scoped tool permissions, restore snapshot authority, or launch-policy authority.

Prior Hook hardening work constrains model-visible context mutation so Plugin exposure can stay safe. Plugin design should reuse those safe surfaces rather than creating parallel prompt/history/tool paths.

A concrete future use case is a chat bridge such as Slack/Discord. The preferred architecture is not that Yoi starts or bundles a full Slack/Discord SDK process. An externally managed bridge service, such as a discord.js service, may expose a small HTTPS API; a Yoi Plugin can call that API using configured URL/auth data. A Plugin may also run a Pod-lifetime service loop and submit external events through a host-mediated Ingress surface, but those semantics must stay concrete and bounded.

Plugin runtime

Runtime answers: how is Plugin code/config executed?

Supported design vocabulary:

  • declarative: config-only Plugin behavior, no arbitrary code execution.
  • wasm: sandboxed WASM Plugin module with explicit host APIs.
  • external_process: future/optional runtime. Do not assume it for the initial Plugin model.

The initial runtime direction is:

runtime: declarative and/or wasm
host APIs: https + fs, plus surface-intrinsic ingress/diagnostics where required
surfaces: Tool + Hook + Service + Ingress

Plugin surface

Surface answers: what does the Plugin add to Yoi?

The Plugin surfaces are:

  • Tool
  • Hook
  • Service
  • Ingress

Do not use Outbound as a separate surface. External writes are represented as Tool surface entries with side-effect metadata.

Tool surface

A Tool surface adds model/workflow-callable operations through ordinary Yoi ToolRegistry paths.

External side effects such as chat sends are Tools:

discord_send_message
slack_reply_thread
github_create_issue_comment

Requirements:

  • registers through ordinary ToolRegistry / pod::feature paths
  • uses normal model-visible schema exposure
  • passes normal PreToolCall permission policy
  • commits tool call/result through normal history plumbing
  • uses bounded result serialization
  • declares side-effect metadata such as effect = "external_write" where applicable
  • validates configured destination allowlists before executing external writes

Hook surface

A Hook surface reacts to supported Yoi lifecycle events using the hardened public Hook API.

Requirements:

  • no raw hidden Item injection
  • no prompt/context mutation outside durable history-aware paths
  • explicit supported HookAction subset only
  • Hook-triggered external writes must still go through Tool or another explicit host-approved path, not hidden side effects

Service surface

A Service surface is Pod-lifetime Plugin work managed by Yoi's Plugin runtime.

The initial Service contract is concrete and limited:

  • Services start during Pod startup after explicit Plugin enablement is resolved.
  • Services stop when the Pod stops or when the Plugin instance is replaced during restore/relaunch.
  • Services report lifecycle state: starting, ready, degraded, failed, stopping, stopped.
  • Services may use only granted Plugin host APIs, initially https and fs, plus surface-intrinsic diagnostics and ingress submission where granted.
  • Services must be cancellable and must not block Pod shutdown indefinitely.
  • Service diagnostics must be bounded and must redact secrets.

For chat bridge Plugins, Service is the WASM-side client loop that communicates with an externally managed bridge service over HTTPS polling or other explicitly designed future event mechanisms. WebSocket/SSE are not implied by the word Service; they require separate host API design.

Ingress surface

Ingress is the host-mediated surface for external events entering Yoi.

Plugins do not directly choose Notify, Run, target Pod, or hidden context insertion. A Plugin submits a typed external event to the host:

host.ingress.submit(event)

Then host routing policy decides delivery:

submit external event -> route/dedup/bounds/policy -> notify | run | drop | diagnostic

Initial typed event target for chat bridges:

ExternalMessageEvent {
  source_plugin,
  provider,
  external_workspace_id/team_id/guild_id,
  channel_id,
  thread_id?,
  message_id,
  actor_id,
  actor_display_name?,
  text,
  attachments[],
  timestamp,
  permalink?,
  dedup_key,
}

Requirements:

  • external content is untrusted
  • event size is bounded before entering Yoi history/model-visible context
  • host performs deduplication and loop prevention
  • host routing config decides target Pod and delivery mode
  • accepted events are appended through durable history/notification paths before they can affect model context
  • rejected/dropped events produce bounded diagnostics without unnecessarily storing message content

Plugin host APIs

Host API answers: what Yoi-provided capabilities can a Plugin runtime call while implementing a surface?

Host APIs are runtime capabilities controlled by Plugin-layer grants. Keep this set narrow. Do not add a broad API to avoid deciding concrete semantics.

The initial general-purpose host API set is:

  • https: bounded HTTPS client only.
  • fs: scoped filesystem read/write under explicit Plugin grants.

Surface-intrinsic host calls also exist where required:

  • ingress.submit: available only to Plugins granted the Ingress surface.
  • diagnostics: bounded lifecycle/error/health reporting for Plugin runtime and Service surface.

Use https, not web, for the initial network API. The first network host API should not imply arbitrary browser-like web capabilities, raw TCP, local network access, HTTP without TLS, WebSocket, SSE, DNS policy, or remote fetching semantics beyond the explicitly designed HTTPS client.

https host API

Initial requirements:

  • HTTPS only
  • explicit allowlist of origins or exact endpoints
  • bounded request/response size
  • timeout/cancellation
  • configured headers/auth data only through Plugin enablement policy
  • no implicit access to ambient environment variables or host credentials
  • diagnostics must redact credentials and sensitive headers

WebSocket, SSE, long polling helpers, and inbound server/webhook APIs are out of the initial host API unless a later Ticket designs them explicitly.

fs host API

Initial requirements:

  • scoped paths explicitly granted by Plugin-layer policy
  • no automatic inheritance from Pod workspace scope
  • bounded read/write operations
  • path traversal protection
  • diagnostics that identify denied paths without leaking file contents

Requirements

  • Define Plugin as a user-facing layer over pod::feature, not as the feature API itself.
    • Plugin package/config/runtime code eventually contributes through pod::feature.
    • pod::feature remains responsible for contribution declarations, lifecycle, diagnostics, and registration plumbing.
    • Plugin remains responsible for package enablement, provenance, runtime selection, host API grants, and Plugin-layer permission/trust policy.
  • Use the terms Plugin runtime, Plugin surface, and Plugin host API; avoid durable/user-facing use of contribution category.
  • Plugin surfaces are Tool / Hook / Service / Ingress.
  • Service has a Pod-lifetime lifecycle contract; it does not imply WebSocket/SSE/raw network support.
  • Ingress is host.ingress.submit(typed external event); host routing decides notify/run/drop/diagnostic.
  • External outbound side effects are Tool surface entries with external-write metadata, not a separate Outbound surface.
  • Initial general-purpose Plugin host APIs are only https and fs.
  • ingress.submit and diagnostics are surface-intrinsic host calls, not broad ambient capabilities.
  • Do not expose a generic web API in the initial Plugin model.
  • Define Plugin-layer permission and trust model separately from pod::feature.
    • Do not rely on feature-level HostAuthority / authority grants for Plugin permissions.
    • Plugin grants are tied to package identity/runtime/user or workspace config, not feature install reports.
    • Plugin policy decides which surfaces and host APIs a Plugin runtime can use.
  • Define runtime families separately.
    • Declarative/config-only Plugins
    • WASM Plugins
    • External process Plugins only as an explicit future/optional runtime
    • MCP remains a separate feature-backed protocol integration, not the Plugin permission model.
  • Plugin metadata, config, external data, and package content are untrusted input.
  • Plugin enablement must be explicit.
    • package presence/discovery alone cannot enable or execute Plugin code
    • workspace/user config must opt in to package/runtime/surface activation and host API grants
  • The design must document the boundary among Plugin, MCP, and built-in Features.

Acceptance criteria

  • A design note or Ticket plan defines Plugin as a user-facing layer over pod::feature.
  • The plan uses runtime, surface, and host API terminology and avoids relying on contribution category as the durable term.
  • The plan states that Plugin surfaces are Tool / Hook / Service / Ingress.
  • The plan states that Service has concrete Pod-lifetime lifecycle semantics and does not imply unspecified network/event APIs.
  • The plan states that Ingress is typed external-event submission to the host, not direct Notify/Run/context injection.
  • The plan states that external outbound side effects are Tool surface entries with external-write metadata.
  • The plan states that initial general-purpose Plugin host APIs are https and fs only.
  • The plan states that https is HTTPS-only and does not imply generic web/browser/raw-network capabilities.
  • The plan states that filesystem access is a Plugin host API controlled by Plugin-layer grants and does not inherit Pod workspace scope automatically.
  • The plan states that Plugin permissions are Plugin-layer policy and are not implemented by pod::feature HostAuthority grants.
  • The plan states that MCP is a separate feature-backed integration with MCP-specific enablement/trust policy.
  • The public Hook safety invariants from 00001KT6Q08R8 remain intact.
  • Tool Plugins are required to use ordinary ToolRegistry / permission / history / bounded-result paths.
  • No design path allows Plugin package presence alone to execute code or mutate context.
  • The design identifies which follow-up Tickets own package format, runtime implementation, host APIs, and Plugin permission details.

Suggested decomposition

  1. Public Hook surface hardening. Completed by 00001KT6Q08R8.
  2. Feature contribution API substrate. Completed by 00001KT6Q08R9 and 00001KTR81P9X.
  3. Plugin package/discovery format. Tracked by 00001KT0Z4BK8.
  4. Plugin enablement plan + metadata snapshot restore integration.
  5. WASM/declarative runtime slice with Tool and Hook surfaces.
  6. Plugin host API slice for https and fs only.
  7. Service lifecycle surface.
  8. Ingress external-message submission and routing policy.
  9. Tool external-write metadata and destination allowlist enforcement.
  10. Future Ticket: streaming event APIs such as SSE/WebSocket/long polling, if HTTPS polling is insufficient.
  11. MCP local stdio integration remains tracked separately by 00001KTR82RB7.

Notes

  • This Ticket is a design coordination Ticket; implementation should be split into concrete Tickets rather than landing a broad Plugin platform in one change.
  • Keep Plugin permission terms distinct from removed feature-layer HostAuthority terminology.
  • Avoid requiring Yoi to manage arbitrary external SDK processes for chat bridge support.
  • Avoid broad host APIs. Add host APIs only when a concrete Plugin surface and use case require them.