13 KiB
| title | state | created_at | updated_at | assignee |
|---|---|---|---|---|
| Plugin: define runtime, surface, and minimal host API model | planning | 2026-05-31T01:00:05Z | 2026-06-14T17:22:23Z | 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:
ToolHookServiceIngress
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::featurepaths - 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
Iteminjection - no prompt/context mutation outside durable history-aware paths
- explicit supported
HookActionsubset 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
httpsandfs, 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::featureremains 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.
- Plugin package/config/runtime code eventually contributes through
- Use the terms
Plugin runtime,Plugin surface, andPlugin host API; avoid durable/user-facing use ofcontribution 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
httpsandfs. ingress.submitanddiagnosticsare surface-intrinsic host calls, not broad ambient capabilities.- Do not expose a generic
webAPI 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.
- Do not rely on feature-level
- 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, andhost APIterminology and avoids relying oncontribution categoryas 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
httpsandfsonly. - The plan states that
httpsis HTTPS-only and does not imply genericweb/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::featureHostAuthoritygrants. - The plan states that MCP is a separate feature-backed integration with MCP-specific enablement/trust policy.
- The public Hook safety invariants from
00001KT6Q08R8remain 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
- Public Hook surface hardening. Completed by
00001KT6Q08R8. - Feature contribution API substrate. Completed by
00001KT6Q08R9and00001KTR81P9X. - Plugin package/discovery format. Tracked by
00001KT0Z4BK8. - Plugin enablement plan + metadata snapshot restore integration.
- WASM/declarative runtime slice with Tool and Hook surfaces.
- Plugin host API slice for
httpsandfsonly. - Service lifecycle surface.
- Ingress external-message submission and routing policy.
- Tool external-write metadata and destination allowlist enforcement.
- Future Ticket: streaming event APIs such as SSE/WebSocket/long polling, if HTTPS polling is insufficient.
- 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
HostAuthorityterminology. - 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.