Skip to content

feat(runtime): Effect v4 foundation — vendored source, boundary module, conventions (#wave 3.5 stage 0) - #153

Merged
ScriptedAlchemy merged 3 commits into
mainfrom
wave35/effect-foundation
Sep 1, 2026
Merged

feat(runtime): Effect v4 foundation — vendored source, boundary module, conventions (#wave 3.5 stage 0)#153
ScriptedAlchemy merged 3 commits into
mainfrom
wave35/effect-foundation

Conversation

@ScriptedAlchemy

Copy link
Copy Markdown
Owner

Summary

Wave 3.5 stage 0: the Effect v4 foundation plus the agentic feedback loop, with zero behavior change to any published entry.

  • Dependency: exact-pinned effect@4.0.0-rc.112 on @agent-bundle/runtime only. The brief named rc.113, which is not on the registry (latest published RC is rc.112); the deviation is documented in docs/effect-conventions.md for the re-pin chore. Nothing imports it from an entry graph yet — verified no effect code lands in dist/{index,state,state/sqlite,plugin}.js.
  • Vendored source: repos/effect via git subtree (Effect-TS/effect main, squashed). Editor excludes in .vscode/settings.json (search/files/watcher/auto-import); repos/** also excluded from rslint (globalIgnores) and the root tsconfig. Test-pool and pnpm-workspace globs are root-anchored (packages/*, packages/**/tests/**) so they never swept repos/ in.
  • AGENTS.md: "Vendored repos" section — read-only, read repos/effect/LLMS.md (verified it exists) + agent-patterns before writing Effect code, never import from repos/**.
  • Agent patterns: agent-patterns/effect-{stream,scope,concurrency,errors}.md, written from the vendored v4 source/tests, each < 70 lines.
  • Boundary module: packages/rsc-runtime/src/effect/boundary.ts is the only legal Effect.run* site: runPromise(effect, { signal }), interruptWhenAborted (AbortSignal→interruption), scopedAbortSignal (interruption→AbortSignal), and cause mapping onto the existing typed errors (AgentRequestError/AgentContractError by instanceof, AgentStateError by name so the state kernel stays off the root graph; interrupt-only causes → AbortError). Unit-tested: abort mid-effect interrupts, interruption rejects as AbortError, typed failures round-trip identically.
  • Boundary enforcement: custom rslint rule effect-boundary/no-ad-hoc-run (rslint supports TS plugin modules — chosen over the grep stand-in), banning run* member calls and named imports outside src/effect/boundary.ts, plus unit tests of the rule.
  • Language service: @effect/language-service wired in tsconfig.base.json with outdatedApi/serviceNotAsClass as warnings. Wired manually rather than npx @effect/tsgo setup because setup's prepare patch mutates the workspace typescript on every install; noted in the conventions doc.
  • Cold-start baseline (stage-2 budget gate, generated claude SessionStart stdio hook, 7 bare-process runs, Node v22.23.1): median 39.74 ms, min 36.41, max 43.06. Recorded in docs/effect-conventions.md + docs/effect-cold-start-baseline.json; re-check via pnpm bench:hook-cold-start -- --check.
  • Changeset: patch on @agent-bundle/runtime (public dependency metadata changed).

Verification (scoped per landing policy)

  • rslint . green; negative test confirmed the boundary rule reports violations
  • root + runtime typecheck green
  • full unit pool green (128 runtime tests incl. new boundary/lint suites)
  • built artifacts byte-identical in behavior: no entry imports effect

git-subtree-dir: repos/effect
git-subtree-split: 81485ef0288a3321e75d7d4ae32b7e0e06b6f86b
…bfff'

git-subtree-dir: repos/effect
git-subtree-mainline: ce55a67
git-subtree-split: 08cc0a6
…ule, conventions (wave 3.5 stage 0)

- exact-pin effect@4.0.0-rc.112 (rc.113 is unpublished; documented) as a
  runtime-package dependency only; no entry imports it yet
- src/effect/boundary.ts: sole Effect.run* edge, AbortSignal <-> interruption
  bridges, error-channel mapping onto the existing typed Agent* errors
- rslint effect-boundary/no-ad-hoc-run custom rule + unit tests
- agent-patterns/effect-{stream,scope,concurrency,errors}.md written from the
  vendored source; docs/effect-conventions.md with pin/boundary/error tables
- @effect/language-service wired via tsconfig.base.json (outdatedApi and
  serviceNotAsClass as warnings); .vscode + tsconfig + rslint exclude repos/**
- hook cold-start baseline: median 39.74ms (scripts/measure-hook-cold-start.mjs)
@changeset-bot

changeset-bot Bot commented Sep 1, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 341a970

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
@agent-bundle/runtime Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@ScriptedAlchemy
ScriptedAlchemy merged commit b4afb74 into main Sep 1, 2026
2 of 9 checks passed
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Sep 1, 2026

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review Completed 2026-09-01T11:19:08.158812Z 341a970 PR opened
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@chatgpt-codex-connector

Copy link
Copy Markdown

💡 Codex Review

if (signal.aborted) return interruptAs();

P2 Badge Recheck aborted state when the Effect starts

When this helper is created while the signal is live but the returned lazy Effect is not run until after the controller aborts, this eager check sees false and the callback subsequently registers a listener on an already-aborted signal. Abort events are not replayed to late listeners, so a wrapped operation such as Effect.never hangs instead of being interrupted; perform the aborted check during Effect execution as well as handling future events.


}) {
if (node.computed === true) return;
const name = node.property?.name;

P2 Badge Track aliased Effect namespaces in the lint rule

When callers rename the Effect namespace, for example import { Effect as Fx } from 'effect'; Fx.runPromise(program), this check accepts the call because it only recognizes objects literally named Effect or Runtime. That allows the exact ad-hoc runner this rule is intended to prevent to pass pnpm lint; resolve the member expression back to its import rather than relying on the local identifier spelling.

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@ScriptedAlchemy

Copy link
Copy Markdown
Owner Author

Codex P2s addressed in #164 (merged as 7a9887bed714030572dac9736f18d39f612959e6).

Recheck aborted state when the Effect starts. Both boundary bridges now share the same pattern: abortToInterrupt defers the check with Effect.suspend, registers the abort listener first, then rechecks signal.aborted immediately after addEventListener so a signal that aborts between composition and run interrupts instead of hanging. Regression coverage is in both effect-boundary.test.ts files (abort-after-composition-before-start).

Track aliased Effect namespaces. effect-boundary/no-ad-hoc-run now records import { Effect as Fx } / import * as E from 'effect' (and Runtime) and flags Fx.runPromise / E.runSync / siblings. Negative fixture: aliased E.succeed is still allowed.

@ScriptedAlchemy
ScriptedAlchemy deleted the wave35/effect-foundation branch September 3, 2026 05:29
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant