Skip to content

feat: add production plugin runtime - #7480

Closed
t3-code[bot] wants to merge 5 commits into
mainfrom
feat/plugin-runtime-production
Closed

t3-code[bot] wants to merge 5 commits into
mainfrom
feat/plugin-runtime-production

Conversation

@t3-code

@t3-code t3-code Bot commented Aug 19, 2026 •

Copy link
Copy Markdown
Contributor

what changed

  • adds the internal, effect-native @t3tools/plugin-runtime service and layer
  • separates a deterministic, stack-safe reconciliation planner from the effect scope executor
  • validates plugin manifests and publishes immutable contribution snapshots
  • carries over transactional staging, rollback, targeted restarts, callback boundaries, and cleanup reporting from the comparison poc

why

this is the production foundation selected by the plugin runtime comparison. effect already owns t3's async resource lifetimes, while the pure planner keeps graph validation and reconciliation deterministic. cordis is not added as a dependency.

this pr intentionally does not load plugin packages or connect contributions to product ui. the next slice will add an environment-owned declarative command catalog through the existing typed rpc and authorization layers.

validation

  • npx -y -p node@24.13.1 -c './node_modules/.bin/vp test run packages/plugin-runtime'
  • vp run --filter @t3tools/plugin-runtime typecheck
  • vp lint --deny-warnings packages/plugin-runtime
  • vp fmt --check packages/plugin-runtime
  • pnpm install --frozen-lockfile
  • git diff --check

41 tests pass, including layer-owned cleanup, interruption safety, and a 20,000-plugin dependency chain.

checklist

  • this pr is small and focused
  • i explained what changed and why

request provenance


Note

Medium Risk
New foundational infrastructure with complex concurrency and lifecycle semantics; behavior is heavily tested but not yet wired into product loading or UI.

Overview
Adds a new internal @t3tools/plugin-runtime package as the production foundation for t3 product plugins, built on Effect (no Cordis).

Contracts and manifests: contract.ts defines plugin definitions, activation context (resolve / register / onDispose), and frozen runtime snapshots. A separate ./manifest export validates plugin package descriptors (namespaced IDs, SemVer, capability IDs, safe relative entrypoints, contribution catalogs).

Planner: planComposition deterministically orders plugins by requires/provides, blocks missing deps, rejects duplicate IDs/capabilities and cycles, and affectedPluginIds expands restarts to dependents when definitions change.

Runtime: PluginRuntime is exposed via make / layer with per-plugin Effect scopes, serialized reconcile/dispose, candidate staging with rollback on activation failure, targeted restarts for unchanged plugins, reentrancy guards (AsyncLocalStorage), and optional lifecycle/cleanup observers.

Tests: Manifest decoding rules, a shared runtime contract suite, layer-owned shutdown, interruption/disposal edge cases, and a 20k-plugin dependency chain.

Reviewed by Cursor Bugbot for commit 72d3a0a. Bugbot is set up for automated code reviews on this repo. Configure here.

Note

Add production plugin runtime with deterministic reconciliation and manifest validation

  • Introduces the @t3tools/plugin-runtime package, a full plugin lifecycle runtime built on Effect, providing reconcile, snapshot, and dispose via a PluginRuntime Context service and Layer.
  • The planner performs topological ordering of plugin definitions, detects dependency cycles and duplicate ids/capabilities, and computes the minimal set of plugins to restart on changes.
  • The runtime manages plugin activation in child scopes, serializes transitions via a semaphore, publishes atomic immutable snapshots, and guards against reentrancy from activate/finalizer callbacks.
  • The manifest module provides Effect Schema validators for plugin manifests, enforcing namespaced ids, semver, safe relative entrypoints, and capability declarations.
  • Rollback semantics preserve the previous composition when staging fails; cleanup errors are aggregated and reported via lifecycle observer hooks rather than surfaced as hard failures.

Macroscope summarized 72d3a0a.

Co-authored-by: Utkarsh Patil <73941998+UtkarshUsername@users.noreply.github.com>
@github-actions github-actions Bot added vouch:trusted PR author is trusted by repo permissions or the VOUCHED list. size:XL 500-999 changed lines (additions + deletions). labels Aug 19, 2026

@macroscopeapp macroscopeapp Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Effect Service Conventions

packages/plugin-runtime introduces a new Effect-backed runtime, but it is packaged as a Promise-returning factory rather than an Effect service. Three convention violations are flagged inline:

  • the service shape lives in a standalone PluginRuntime interface plus a PluginRuntimeFactory type instead of an inline interface on a Context.Service tag with make/layer;
  • the root Scope is created lazily and held in a closure, so the runtime's lifetime is not represented in the Effect environment;
  • Effect.runPromise is executed inside the service constructor rather than at an application/framework boundary.

Everything else (subpath namespace imports, Schema.TaggedErrorClass failures with structural attributes and derived messages, Schema.is predicate export, Effect.catch on a fully handled channel) matches the conventions.

Posted via Macroscope — Effect Service Conventions

Comment thread packages/plugin-runtime/src/contract.ts Outdated
Comment thread packages/plugin-runtime/src/runtime.ts Outdated
Comment thread packages/plugin-runtime/src/runtime.ts Outdated
@github-actions

github-actions Bot commented Aug 19, 2026 •

Copy link
Copy Markdown
Contributor

Thread transfer impact

✅ Thread transfer remains within every enforced ceiling.

Provider Metric Main baseline This PR Impact PR ceiling
Codex Total thread wire 12.6 KiB 12.6 KiB +10 B (+0.1%) 15.1 KiB ✅
Codex Thread snapshot wire 6.3 KiB 6.3 KiB +7 B (+0.1%) 7.3 KiB ✅
Codex Live turn WebSocket wire 6.3 KiB 6.3 KiB +3 B (+0.0%) 7.8 KiB ✅
Codex Live turn WebSocket decoded 51.8 KiB 51.8 KiB 0 B (0.0%) 66.4 KiB ✅
Codex Live turn messages 16 16 0 (0.0%) 21 ✅
Claude Total thread wire 12.6 KiB 12.6 KiB −3 B (−0.0%) 15.1 KiB ✅
Claude Thread snapshot wire 6.3 KiB 6.3 KiB +1 B (+0.0%) 7.3 KiB ✅
Claude Live turn WebSocket wire 6.3 KiB 6.3 KiB −4 B (−0.1%) 7.8 KiB ✅
Claude Live turn WebSocket decoded 52.7 KiB 52.7 KiB 0 B (0.0%) 66.4 KiB ✅
Claude Live turn messages 16 16 0 (0.0%) 21 ✅

Baseline: 36f4314 · PR result: 72d3a0a · Source CI: success

Scenario and decoded snapshot size

10 historical turns, 5 command tools per turn, 878.9 KiB retained MCP result per historical turn, and a 1.05 MiB retained result in the measured turn.

  • Codex decoded thread snapshot: 99.9 KiB
  • Claude decoded thread snapshot: 100.6 KiB

Updated in place by a trusted workflow. PR artifacts are strictly validated and never executed.

@macroscopeapp

macroscopeapp Bot commented Aug 19, 2026 •

Copy link
Copy Markdown
Contributor

Approvability

Verdict: Not approved

Macroscope's review found this PR not approvable — This PR introduces a complete new plugin runtime system (~2000 lines across multiple new files) with lifecycle management, dependency resolution, and Effect-based concurrency. New capabilities of this scope warrant human review. Additionally, there is an open High severity finding about potential resource leaks during interrupted reconciliation.

You can add or adjust custom eligibility rules. Learn more.

Co-authored-by: Utkarsh Patil <73941998+UtkarshUsername@users.noreply.github.com>
@t3-code

t3-code Bot commented Aug 19, 2026

Copy link
Copy Markdown
Contributor Author

addressed the effect convention findings:

  • PluginRuntime is now a Context.Service with a deterministic service key
  • make() acquires the caller's Effect.scope, and layer() owns shutdown
  • reconcile, snapshot, and dispose now return effects
  • removed the package-owned promise queue and all manual effect runners from production code
  • serialization now uses an effect semaphore
  • layer release runs the same lifecycle shutdown path and reports deactivation
  • converted the contract suite to @effect/vitest

focused validation passes with 38 tests on node 24.13.1, package typecheck, warning-free lint, formatting, frozen install, and whitespace checks.

Comment thread packages/plugin-runtime/src/runtime.ts Outdated

@macroscopeapp macroscopeapp Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Effect service conventions review: the service definition, dependency acquisition, and error modeling all look consistent with the conventions (inline Context.Service interface, scope acquired from the environment, make + layer, Schema.TaggedErrorClass failures with structural attributes and a direct Schema.is predicate). Two remaining findings concern module-namespace exposure of the service module.

Posted via Macroscope — Effect Service Conventions

Comment thread packages/plugin-runtime/test/runtime.test.ts Outdated
Comment thread packages/plugin-runtime/src/index.ts Outdated
Comment thread packages/plugin-runtime/src/runtime.ts Outdated
if (Exit.isFailure(candidateExit)) {
const failures = yield* closePlugins([...staged.values()], false);
yield* reportCleanupErrors("rollback", failures);
return yield* Effect.failCause(candidateExit.cause);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Interrupted reconcile leaks plugin scopes

High Severity

reconcile is now an interruptible Effect, but plugin scopes are forked onto the composing scope before staging commits. Rollback only runs after Effect.exit completes. An interrupt during activatePlugin or the staging loop skips that rollback, so the forked scope and in-flight activate promise can stay alive while current is unchanged. The previous Promise adapter did not expose this cancellation path.

Additional Locations (1)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 14cd84c. Configure here.

Co-authored-by: Utkarsh Patil <73941998+UtkarshUsername@users.noreply.github.com>
@t3-code

t3-code Bot commented Aug 19, 2026

Copy link
Copy Markdown
Contributor Author

fixed the interruption finding and the related shutdown race:

  • disposal is now uninterruptible once cleanup starts
  • an interrupted caller cannot mark the runtime disposed before scopes finish closing
  • layer release acquires the same transition semaphore as reconcile and explicit dispose
  • interrupted activation expires its activation context immediately
  • added regressions for interrupted dispose retry, interrupted activation context expiry, and layer release racing an in-flight reconcile

focused validation passes with 41 tests on node 24.13.1, package typecheck, warning-free lint, formatting, and whitespace checks.

@macroscopeapp macroscopeapp Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One finding on the new interruption-safe shutdown path: the layer finalizer discriminates two statically known tagged errors by property sniffing instead of Effect.catchTags. Previously reported items on src/index.ts and test/runtime.test.ts are unchanged and not repeated here.

Posted via Macroscope — Effect Service Conventions

Comment thread packages/plugin-runtime/src/runtime.ts Outdated

@cursor cursor Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes using high effort and found 1 potential issue.

There are 2 total unresolved issues (including 1 from previous review).

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 431c7c7. Configure here.

Comment thread packages/plugin-runtime/src/runtime.ts Outdated
Co-authored-by: Utkarsh Patil <73941998+UtkarshUsername@users.noreply.github.com>
@t3-code

t3-code Bot commented Aug 19, 2026

Copy link
Copy Markdown
Contributor Author

addressed the latest findings:

  • interrupted activation now expires its context and rolls back the forked child scope
  • staging, publication, retirement, and rollback run in an interruption-safe region
  • disposal remains uninterruptible once cleanup starts
  • layer release uses the same transition semaphore as reconcile and explicit dispose
  • runtime imports and package exports now preserve the PluginRuntime module namespace
  • finalizer failures use explicit Effect.catchTags handling

focused validation passes with 41 tests on node 24.13.1, package typecheck, warning-free lint, formatting, and whitespace checks.

Co-authored-by: Utkarsh Patil <73941998+UtkarshUsername@users.noreply.github.com>
@t3-code

t3-code Bot commented Aug 19, 2026

Copy link
Copy Markdown
Contributor Author

addressed the latest findings:

  • interrupted activation expires its context while retaining the callback reentrancy guard until the javascript promise settles
  • staging, publication, retirement, and rollback run in an interruption-safe region
  • disposal remains uninterruptible once cleanup starts
  • layer release uses the same transition semaphore as reconcile and explicit dispose
  • runtime imports and package exports now preserve the PluginRuntime module namespace
  • finalizer failures use explicit Effect.catchTags handling

focused validation passes with 41 tests on node 24.13.1, package typecheck, warning-free lint, formatting, and whitespace checks.

@t3-code

t3-code Bot commented Aug 23, 2026

Copy link
Copy Markdown
Contributor Author

Consolidated into #8007 as the single plugin-system PR targeting main.

@t3-code t3-code Bot closed this Aug 23, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:XL 500-999 changed lines (additions + deletions). vouch:trusted PR author is trusted by repo permissions or the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

0 participants