Repository navigation
T3 fork: Add a compiled plugin host with a stable host interface #1
Description
Problem Statement
I want to extend a T3 Code fork with strict workflows while continuing to pull upstream improvements to providers, orchestration and the everyday client experience. T3 has useful internal service boundaries but no documented, supported application plugin API in the inspected source. Adding each feature directly to server composition, MCP registration, RPC handlers, navigation and client stores would spread dependencies on upstream internals throughout my implementation.
Agent skills, MCP servers and provider-native plugins do not solve application extensibility: they do not provide application pages, authenticated client APIs, background services, persistence or coherent navigation. The fork needs a small application-owned boundary that supports the planned workflow features and concentrates future upstream adaptation.
Solution
Provide a plugin host for trusted server and web modules compiled with the fork. A plugin contributes its services, agent tools, authenticated APIs, pages and a small set of UI integrations through one registration contract. A narrow host adapter exposes supported T3 operations. Plugins depend on that contract rather than T3 implementation services, database tables, provider adapters or client stores.
The first consumer will be the workflow plugin specified separately. This issue delivers and proves the extension boundary with a small fixture plugin; it does not implement workflow routing or a workflow editor. Installing or changing compiled plugins requires a rebuild and environment restart.
User Stories
- As a fork maintainer, I want to implement a feature in its own package, so that upstream changes do not spread through its domain code.
- As a plugin author, I want one documented registration contract, so that I can discover every supported integration point without copying core wiring.
- As a plugin author, I want a small host interface for launching and controlling threads, so that I can reuse native providers and normal T3 workspaces.
- As a plugin author, I want normalized lifecycle subscriptions and reconciliation, so that I can recover without parsing provider-specific output.
- As a plugin author, I want stable command identities and durable launch receipts, so that retrying after a crash does not start duplicate work.
- As a plugin author, I want to register schema-backed agent tools, so that real native tool invocations can reach my services.
- As a plugin author, I want authenticated tool caller context, so that I can enforce ownership without trusting caller-supplied thread identifiers.
- As a user, I want plugin tools to respect my provider and permission settings, so that extensions remain consistent with the environment I selected.
- As a user, I want to see which providers support a plugin's required tools, so that unsupported execution fails with an actionable explanation.
- As a plugin author, I want authenticated commands, queries and subscriptions, so that local and remote clients use the same service behavior.
- As a plugin author, I want environment-scoped startup and shutdown, so that my background services work without a connected browser and stop cleanly.
- As a plugin author, I want to register scheduled work and a schedule dispatch target, so that I can reuse T3 timing and scheduling controls.
- As a plugin author, I want private persistence and migration identities, so that upstream migrations cannot collide with plugin state.
- As a plugin author, I want environment-local project, workspace and skill discovery, so that I use the selected server's actual capabilities.
- As a plugin author, I want to contribute a page and navigation entry, so that users can reach my feature within the existing application shell.
- As a plugin author, I want to associate feature context and actions with an existing thread, so that users can move between my feature and its native conversation.
- As a user, I want plugin attention items to link to a clear action, so that I can identify and resolve work requiring me.
- As a user connected to multiple environments, I want plugin actions bound to the intended environment and project, so that I never operate on another machine by accident.
- As a user, I want an unavailable or incompatible plugin to be identified clearly, so that I can keep using the rest of T3.
- As a user, I want plugin state retained when a compiled plugin is temporarily absent, so that upgrading or changing a build does not delete my work.
- As a client developer, I want a capability descriptor for installed plugins, so that independently upgraded clients can hide or explain unsupported features.
- As a fork maintainer, I want import rules and a host contract suite, so that tight coupling is detected before it spreads.
- As a fork maintainer, I want ordinary threads and schedules to work with no plugins registered, so that the extension host preserves the default application.
- As a plugin author, I want consistent cancellation, errors and subscription cleanup, so that a plugin does not leave hidden background work after its scope ends.
Implementation Decisions
- Delivery model: trusted, first-party modules are explicitly included in the build. Server contributions and web contributions have separate entry points, joined by a unique plugin identity and an interface version. Use static composition and normal type checking. The host does not discover arbitrary executable modules from project files or download code at startup.
- Single boundary: define a host-contract package and an adapter package. Plugin implementations may import their own contracts, the host contract, supported UI primitives and ordinary platform/library dependencies. They may not import T3 orchestration implementations, provider adapters, core persistence repositories or raw client state stores. Enforce this restriction with the existing lint/import tooling. Only the adapter owns those upstream dependencies.
- Registration: the manifest declares the plugin ID, display name, plugin version, supported host interface version, server/client contributions and required host capabilities. Register each plugin once. Reject duplicate IDs, duplicate contribution identifiers, incompatible interface versions and missing capabilities before publishing that plugin's contributions. Expose an environment-level descriptor for available, unavailable and incompatible plugins with a concise reason. An optional plugin's failed registration must leave ordinary T3 boot usable.
- Execution interface: expose project discovery, workspace metadata, prepare/launch at an exact ref, send/queue instructions, interrupt, inspect thread/work state and navigate to a thread. Operations use environment-owned identifiers and normalized schemas. Forward durable command IDs to the existing launch/command machinery; expose the recorded result or reconciliation lookup. An acknowledgement means committed intent, not finished execution. Do not copy core orchestration into the host.
- Lifecycle interface: expose scoped, resumable subscriptions to the normalized events needed by consumers, including turn settlement, outstanding work, provider interruption, requests and checkpoints. Return a cursor and make replay gaps explicit. On a gap, supply a snapshot/reconciliation operation; never suggest that every intermediate event was delivered. Filter by requested project/thread scope, and release subscriptions when their consumer scope closes.
- Environment data: expose provider capabilities, runtime modes, installed native skills and the workspace/Git/PR metadata needed by the known consumers. Fresh PR-head verification must be available where cached metadata is insufficient. Every operation executes in the selected environment. A disconnected or missing target cannot fall back to another environment or the client's filesystem.
- Agent tools: allow a plugin to register tool schemas, handlers, descriptions and permission metadata through the existing app-owned MCP connection. Validate requests and responses with the project's Effect/Schema conventions. Pass authenticated environment, thread and provider-session context to the service. Let the plugin resolve its own domain ownership from that context. Displayed tool-call events are observations, not acknowledgement that the handler accepted a result.
- Provider policy: maintain an explicit tool-injection capability matrix for every selectable provider and connection mode. The host owns the adapter-specific injection and allowlist changes, including start, resume and refreshed credentials. At minimum prove the interface with managed Codex, Claude and OpenCode sessions. Externally owned OpenCode servers remain unsupported until a tested injection path exists. Mutating plugin tools must retain truthful permission metadata; supporting a report tool in a read-only review session requires an explicit permission allowance rather than classifying it as a read-only operation.
- Client API: plugin exports statically composed request/response schemas, handlers and required authorization scopes under its namespace. The host joins these at one composition point using the existing authenticated RPC transport. Commands, queries, subscriptions and agent tools delegate to the same plugin services. Socket authentication does not bypass per-operation authorization. This is a compiled API, not a dynamically typed invocation endpoint for arbitrary installed code.
- Lifecycle and scheduling: acquire plugin services in the environment's managed scope after required storage and core recovery are ready. Registration returns scoped resources with deterministic cleanup. Reuse the existing scheduler for plugin due-work sources. Also provide a generic schedule-target registration so the existing schedule pipeline can dispatch a plugin operation with a stable occurrence ID. Persist dispatch identity before invocation and retain its receipt; retries use the same identity. The scheduler records dispatch results while the plugin owns the eventual domain result. No workflow-specific branch belongs in the scheduler clock.
- Persistence: give each plugin an environment-local namespace below T3 home and a separate migration ledger. Plugin state can use its own SQLite store with transaction support. The host grants no direct access to core projection tables. Migration failure makes that plugin unavailable with a useful reason, without deleting state or corrupting core boot. Removing a plugin from the build retains its data. Core transactions and plugin transactions are separate; cross-boundary work must use durable intent and idempotent host operations rather than assuming a shared transaction.
- UI contributions: provide a thin route container for plugin pages within the existing generated routing system, plus finite slots for navigation, project run actions, thread context/actions and attention summaries. Preserve the normal application shell, connection selection and design primitives. A plugin supplies feature components and read models; host components consume generic contributions without importing a workflow package or naming workflow node types. Do not manually edit generated route output.
- UI lifecycle: make registration and cleanup independent of page mount. Feature data subscriptions run only while their views or shared attention controllers need them. Shared client connection/authentication state stays in the normal client runtime; the adapter exposes an environment-bound plugin client. Match server capability descriptors before showing or invoking contributions, including cached data after an environment downgrade.
- Attention interface: plugins provide stable item IDs, a bounded summary, severity/reason, related project/thread references and an actionable deep link. The server/plugin owns whether the item is unresolved; opening a page or marking a thread read does not resolve it. Preserve native approval and input requests as native interactions. This interface supplies integration points; a plugin owns its feature-specific attention model.
- Failure semantics: validation, unsupported capability, authorization, unavailable environment and plugin-service errors have typed, actionable responses. A failed optional plugin can be unavailable while core T3 remains usable. Trusted in-process modules share the process; this issue does not claim to isolate arbitrary hangs or malicious code.
- Acceptance: the fixture plugin must exercise a real tool call, authenticated API command and subscription, durable state surviving restart, a scheduled occurrence, a hosted page, a related-thread link and an attention item entirely through the public boundary. Ordinary thread execution and prompt scheduling must also work with zero plugins. Document only the public contract, its ownership rules and the verified provider/client matrix.
Testing Decisions
- The primary seam is a registered plugin exercised through the real host. Assert externally visible capabilities, tool responses, API results, persisted state, navigation and cleanup rather than how many registration callbacks ran.
- Use one small fixture plugin that imports only the host contract. Run the real server composition, authorization, MCP transport, core persistence and plugin persistence. Substitute only external provider transport/processes, filesystem locations, identifiers and time where determinism requires it.
- Test tool discovery and invocation through supported adapters, including new sessions, resume, server restart with refreshed credentials, and permission modes. Verify wrong-session calls, unauthorized API calls, unsupported external OpenCode and truthful mutating-tool policy. A direct handler unit call is insufficient integration evidence.
- Test exact command retries, an interrupted launch acknowledgement, subscription replay and snapshot fallback, scope cleanup, schedule occurrence redelivery and restart. Assert one resulting host action and retained plugin state, using persisted receipts/events and drainable workers rather than sleeps.
- Test duplicate contribution IDs, incompatible versions, migration failure, no registered plugins and a missing plugin after previously having one. Assert usable core behavior and explicit plugin unavailability with retained data.
- Test local and authenticated remote targets, two environments with similar project names, descriptor downgrade and a client that lacks the plugin page. Assert no fallback to a different environment and no unsupported method invocation.
- At the client seam, verify that the fixture page is hosted in the normal shell and its contributed actions open the intended environment/project/thread. Verify mounted subscriptions are released and a shared attention controller does not multiply with readers.
- Prior art includes T3's MCP toolkit and HTTP integration tests, RPC authorization tests, thread launch receipt/recovery tests, provider transport replay framework, scheduler integration tests and client-runtime connection compatibility tests. Follow their production persistence semantics and focused scope. Add only the contract/import guard needed to preserve this new boundary; avoid tests that mirror component wiring.
Out of Scope
- Runtime plugin installation, git/npm plugin downloads, hot reloading, a marketplace, dependency resolution and arbitrary third-party execution.
- A sandbox, per-plugin process isolation, plugin billing, budgeting or a new concurrency-admission policy.
- A replacement orchestrator, provider protocol or scheduler clock.
- Workflow definitions, report acceptance rules, graph execution, a workflow editor and workflow-specific attention rules.
- A promise of compatibility with future upstream internal APIs. The adapter is the deliberately maintained integration point.
- Full React Native plugin rendering. This first interface supports server plus web, including the desktop web shell and authenticated remote web clients. Other clients must handle capability absence without breaking ordinary features.
Further Notes
This is specification 1 of 3 for a T3 Code fork. The workflow backend spec depends on this boundary; the workflow UI spec consumes its client contribution points. Implementation and planning belong to the iglo.code fork.
Source inspection was pinned to T3 main cf3e714b0f58e29c8fa8660db50d2187e3263b65; the subsequent checked commit 7812230572f2d7e042d8d14ada2381fc71377560 did not change these integration points. Recheck the fork's base before implementation. Architecture, glossary, testing strategy.
The closed, unmerged plugin proposal #6158 is background evidence, not a dependency or shipped API. Begin by proving the reporting-tool and page contribution paths before broadening the host contract.
Activity