Repository navigation
feat!: Effect-native core on Effect 4.0.0 and remove the legacy runtime - #211
chenxin-yan wants to merge 2 commits into
Conversation
🦋 Changeset detectedLatest commit: c04a855 The changes in this PR will be included in the next version bump. This PR includes changesets to release 12 packages
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 |
There was a problem hiding this comment.
21 issues found across 204 files
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="apps/docs/content/docs/contributing.mdx">
<violation number="1" location="apps/docs/content/docs/contributing.mdx:11">
P3: The `Manual TUI check` section at the end of this file still instructs contributors to run the TUI in a real terminal before each release, but this PR deletes `packages/tui` entirely, so that check cannot be performed. Remove the section and its heading together with the TUI references this change strips out.</violation>
</file>
<file name="scripts/dev-cli.ts">
<violation number="1" location="scripts/dev-cli.ts:9">
P2: The documented `bun run dev:cli init` bootstrap no longer works because this wrapper forwards `init` to the help/version-only CLI, which rejects it. Update ADR-0030 and remove the obsolete sandbox-bootstrap contract, or preserve an explicit bootstrap path.</violation>
</file>
<file name="packages/providers/src/openai-codex/request.ts">
<violation number="1" location="packages/providers/src/openai-codex/request.ts:91">
P1: The optional default Tool choice is now rejected for Codex. Treat `undefined` as the native `"auto"` default, otherwise callers that omit `toolChoice` cannot generate at all.</violation>
</file>
<file name="packages/create-mitome/src/template.ts">
<violation number="1" location="packages/create-mitome/src/template.ts:112">
P2: The Codex scaffold cannot authenticate by following this README: `login` is only a function requiring `configDirectory`, `input`, and `output`, and the generated project provides no caller or auth command. Add a runnable login step or generate an authentication helper before directing users to `node index.ts`.</violation>
</file>
<file name="patches/@effect%2Fai-openai-compat@4.0.0.patch">
<violation number="1" location="patches/@effect%2Fai-openai-compat@4.0.0.patch:9">
P3: This patch changes the generated `dist/OpenAiLanguageModel.js` line layout without updating its source map. Debugger stack locations after this hunk can therefore resolve to the wrong TypeScript lines; regenerate and include `OpenAiLanguageModel.js.map` with the patch.</violation>
</file>
<file name="packages/core/src/provider.ts">
<violation number="1" location="packages/core/src/provider.ts:150">
P2: `providerModel` returns every service produced by the provider layer, exposing provider internals despite its contract to expose only the model and accounting. Return a fresh context containing only those two services.</violation>
</file>
<file name="tools/qualification/sqlite-transactions/checks.ts">
<violation number="1" location="tools/qualification/sqlite-transactions/checks.ts:96">
P2: The peer and temporary directory are created before the cleanup guard, so a module-resolution or identity-check failure leaks the temporary SQLite directory. Move setup and execution under a `try/finally` immediately after creating the peer.</violation>
<violation number="2" location="tools/qualification/sqlite-transactions/checks.ts:213">
P2: `Effect.scoped(sql.reserve)` releases the connection before the injected method and later transactions run, so this test depends on the pool reusing the same connection. Keep the reservation scope around the entire synthetic check to guarantee that `withTransaction` uses `conn`.</violation>
</file>
<file name="docs/research/session-turn-contract-proof.md">
<violation number="1" location="docs/research/session-turn-contract-proof.md:103">
P3: This paragraph says the current pins are rc.118, while this checkout uses Effect and the SQLite adapters at 4.0.0; the plan also names rc.117 patches. Update the version references to match the artifacts qualified by this PR.</violation>
<violation number="2" location="docs/research/session-turn-contract-proof.md:105">
P2: This new section contradicts retained statements in the same file and leaves the document self-inconsistent. It says the owner approved a temporary SQLite patch and that root devDependencies were pinned, but the final "Selected decisions and next proof" section still states "repository dependencies remain unchanged. No backend/schema selection, repository dependency change, fix contribution, local workaround, or production migration is authorized," and the ownership-revalidation section (line ~62) still states "repository dependencies remain rc.108." Update those stale statements to match the newly documented dependency/patch state.</violation>
</file>
<file name="packages/core/src/session/step.ts">
<violation number="1" location="packages/core/src/session/step.ts:397">
P1: The authorization boundary does not fail closed for malformed callback results: only an exact `"deny"` blocks policy, while invalid truthy consent or authority values are accepted. Validate policy results against `"allow" | "ask" | "deny"` and require consent/authority results to equal `true` before dispatch.</violation>
</file>
<file name="packages/providers/src/openai-codex/sse.ts">
<violation number="1" location="packages/providers/src/openai-codex/sse.ts:244">
P2: This locator check rejects an item when the stream switches from an item-id locator to an output-index locator, even though both identify the same item. Adopt the index when it is absent from `byIndex`, and only reject it when it already belongs to another item; otherwise valid mixed-key streams fail with `InvalidOutputError`.</violation>
</file>
<file name=".changeset/effect-4-pin.md">
<violation number="1" location=".changeset/effect-4-pin.md:4">
P3: `create-mitome` is marked `patch` while `@mitome/core` and `@mitome/providers` are `minor`, but all three are in one `fixed` group in `.changeset/config.json`, so changesets cuts them at the same version: create-mitome will be released at the group's minor (0.1.0), not 0.0.1, and its changelog for this changeset will show a bump that never happens. Mark it `minor` to match the group, or drop the `patch` entry here since the group bump governs it.</violation>
</file>
<file name="packages/core/src/session/session.ts">
<violation number="1" location="packages/core/src/session/session.ts:308">
P2: Snapshot the validated execution bounds when `make` creates the Session. Reading the caller-owned `limits` object here lets later mutations raise or lower limits for live Sessions, contrary to the documented per-Session policy.</violation>
</file>
<file name="docs/research/native-baseline-qualification.md">
<violation number="1" location="docs/research/native-baseline-qualification.md:3">
P2: The "current status" line says the catalog and SQLite adapters are pinned to `4.0.0-rc.118`, but the merged result pins stable `4.0.0`: root `package.json` catalog/devDependencies (`effect`, `@effect/ai-openai`, `@effect/ai-openai-compat`, `@effect/platform-bun`, `@effect/vitest`, `@effect/sql-sqlite-node`, `@effect/sql-sqlite-bun`), `patchedDependencies`, and `patches/` all use the `@4.0.0` variants (`effect@4.0.0.patch`, `@effect/ai-openai@4.0.0.patch`, `@effect/ai-openai-compat@4.0.0.patch`); the fixture header also says "installed Effect 4.0.0 (with the retained patches/effect@4.0.0.patch)". Update the status to the 4.0.0 qualification and demote the "Installed rc.118 upgrade" section to dated history so this record matches the repository and the PR's own change.</violation>
<violation number="2" location="docs/research/native-baseline-qualification.md:261">
P3: This reproduction only works on the author's workstation: the `/home/cyan/.../run-v2.sh` runner is not in the repository. Check in the runner and its inputs or provide a checkout-relative reproduction.</violation>
</file>
<file name="packages/core/test/session/step-outcomes.test.ts">
<violation number="1" location="packages/core/test/session/step-outcomes.test.ts:138">
P3: The third script entry `[text("z")]` is never consumed: this test calls `step` only twice (one with a policy, one without), and each `step` performs exactly one generation (`admitted` calls `model.generateText` once), so only the first two generations run. The never-dispatched text generation is dead test data; remove it, or add the third `step` if the intent was to assert that a generation with no Tool Calls records nothing.</violation>
</file>
<file name="packages/providers/test/openai-codex/codex.test.ts">
<violation number="1" location="packages/providers/test/openai-codex/codex.test.ts:191">
P3: The rewritten end-to-end test drops the `response.output_item.added`/`.done` events with an unknown `future_item` type that the old test injected mid-stream. That was the only remaining coverage that a future/unknown Codex item type is silently skipped without corrupting the part stream, and `sse.test.ts` never feeds an unknown item type through `output_item.added`/`.done` (it only uses message/function_call/reasoning items). Since the parser was rebuilt in this PR and Codex is a best-effort unofficial API, keep the unknown-item events in this SSE fixture so the skip path stays covered end to end.</violation>
</file>
<file name="packages/core/test/session/step-lifetime.test.ts">
<violation number="1" location="packages/core/test/session/step-lifetime.test.ts:136">
P3: This assertion treats any Exit failure as success, but an interrupted parked fiber is also an `Exit.failure`, so the test would still pass if the fiber was torn down by scope teardown instead of dying on staging into the already-ended Turn. The point of the test is that the late response cannot stage into the closed Turn, so assert that exact die, as the first test block does, with `defect(outcome.exit)`.</violation>
</file>
<file name="packages/core/test/session/step.test.ts">
<violation number="1" location="packages/core/test/session/step.test.ts:821">
P3: These busy-wait loops spin `Effect.yieldNow` until `write:a:start` appears; if a regression stops the handler from ever starting, the test spins until the runner timeout instead of failing on the actual mismatch (gly letter. Same pattern at line 948). Bound the wait so a regression fails fast with the real assertion error.</violation>
</file>
<file name="docs/plans/effect-native-library.md">
<violation number="1" location="docs/plans/effect-native-library.md:659">
P3: This PR pins and patches the stable Effect `4.0.0` releases (changeset `effect-4-pin.md` moves off `4.0.0-rc.108`; bun.lock resolves `@effect/ai-openai@4.0.0` and `@effect/ai-openai-compat@4.0.0`, and the patch artifacts are named `patches/*@4.0.0.patch`), but the added plan text still identifies the local patches as patches of `4.0.0-rc.117` packages (also in the new `#183` paragraph: 'A development-only patch of `@effect/ai-openai@4.0.0-rc.117`' and 'patches, of `effect@4.0.0-rc.117` and `@effect/ai-openai-compat@4.0.0-rc.117`'). Align the version references in the added text with the pinned 4.0.0 packages so the release-blocking claim points at the packages that actually carry the patches; the sql adapters are pinned `4.0.0` as well and no SQLite adapter patch file exists under `patches/`.</violation>
</file>
Note: This PR contains a large number of files. cubic selects up to 200 of the highest-priority eligible files for this review, so some files may not have been reviewed.
Re-trigger cubic
| ): "auto" | "none" | undefined => { | ||
| if (choice === "auto" || choice === "none") return choice; | ||
| if (Predicate.isObject(choice) && "oneOf" in choice && choice.mode !== "required") return "auto"; | ||
| return undefined; |
There was a problem hiding this comment.
P1: The optional default Tool choice is now rejected for Codex. Treat undefined as the native "auto" default, otherwise callers that omit toolChoice cannot generate at all.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At packages/providers/src/openai-codex/request.ts, line 91:
<comment>The optional default Tool choice is now rejected for Codex. Treat `undefined` as the native `"auto"` default, otherwise callers that omit `toolChoice` cannot generate at all.</comment>
<file context>
@@ -61,10 +61,41 @@ const contentFor = (
+): "auto" | "none" | undefined => {
+ if (choice === "auto" || choice === "none") return choice;
+ if (Predicate.isObject(choice) && "oneOf" in choice && choice.mode !== "required") return "auto";
+ return undefined;
+};
+
</file context>
| return undefined; | |
| return choice === undefined ? "auto" : undefined; |
| : withServices(Effect.suspend(() => policy(request))), | ||
| "deny", | ||
| ); | ||
| if (decision === "deny") return yield* denial("Tool call was denied by policy"); |
There was a problem hiding this comment.
P1: The authorization boundary does not fail closed for malformed callback results: only an exact "deny" blocks policy, while invalid truthy consent or authority values are accepted. Validate policy results against "allow" | "ask" | "deny" and require consent/authority results to equal true before dispatch.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At packages/core/src/session/step.ts, line 397:
<comment>The authorization boundary does not fail closed for malformed callback results: only an exact `"deny"` blocks policy, while invalid truthy consent or authority values are accepted. Validate policy results against `"allow" | "ask" | "deny"` and require consent/authority results to equal `true` before dispatch.</comment>
<file context>
@@ -0,0 +1,618 @@
+ : withServices(Effect.suspend(() => policy(request))),
+ "deny",
+ );
+ if (decision === "deny") return yield* denial("Tool call was denied by policy");
+ if (decision === "ask") {
+ const granted =
</file context>
| "--output-logs=errors-only", | ||
| ], | ||
| const child = Bun.spawn( | ||
| ["bun", join(root, "packages", "cli", "src", "index.ts"), ...process.argv.slice(2)], |
There was a problem hiding this comment.
P2: The documented bun run dev:cli init bootstrap no longer works because this wrapper forwards init to the help/version-only CLI, which rejects it. Update ADR-0030 and remove the obsolete sandbox-bootstrap contract, or preserve an explicit bootstrap path.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At scripts/dev-cli.ts, line 9:
<comment>The documented `bun run dev:cli init` bootstrap no longer works because this wrapper forwards `init` to the help/version-only CLI, which rejects it. Update ADR-0030 and remove the obsolete sandbox-bootstrap contract, or preserve an explicit bootstrap path.</comment>
<file context>
@@ -1,101 +1,18 @@
- "--output-logs=errors-only",
- ],
+const child = Bun.spawn(
+ ["bun", join(root, "packages", "cli", "src", "index.ts"), ...process.argv.slice(2)],
{
- cwd: root,
</file context>
| const credential = | ||
| provider === "openai" | ||
| ? "Set `OPENAI_API_KEY`." | ||
| : "Store a ChatGPT Codex Credential with the `login` export of `@mitome/providers/openai-codex`."; |
There was a problem hiding this comment.
P2: The Codex scaffold cannot authenticate by following this README: login is only a function requiring configDirectory, input, and output, and the generated project provides no caller or auth command. Add a runnable login step or generate an authentication helper before directing users to node index.ts.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At packages/create-mitome/src/template.ts, line 112:
<comment>The Codex scaffold cannot authenticate by following this README: `login` is only a function requiring `configDirectory`, `input`, and `output`, and the generated project provides no caller or auth command. Add a runnable login step or generate an authentication helper before directing users to `node index.ts`.</comment>
<file context>
@@ -94,42 +105,21 @@ const tsconfigSource = `${JSON.stringify(
+ const credential =
+ provider === "openai"
+ ? "Set `OPENAI_API_KEY`."
+ : "Store a ChatGPT Codex Credential with the `login` export of `@mitome/providers/openai-codex`.";
+ return `# Mitome Agent\n\n## Next steps\n\n${credential}\n\n\`\`\`sh\nnpm install\nnode index.ts\n\`\`\`\n`;
};
</file context>
| detail: `Provider "${provider.id}" does not account for requests of Model "${modelId}"`, | ||
| }); | ||
| } | ||
| return Context.add(context, ModelRequestAccounting, accounting.value); |
There was a problem hiding this comment.
P2: providerModel returns every service produced by the provider layer, exposing provider internals despite its contract to expose only the model and accounting. Return a fresh context containing only those two services.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At packages/core/src/provider.ts, line 150:
<comment>`providerModel` returns every service produced by the provider layer, exposing provider internals despite its contract to expose only the model and accounting. Return a fresh context containing only those two services.</comment>
<file context>
@@ -69,19 +91,65 @@ export const makeProvider = <const Id extends string, const ModelIds extends Rea
+ detail: `Provider "${provider.id}" does not account for requests of Model "${modelId}"`,
+ });
+ }
+ return Context.add(context, ModelRequestAccounting, accounting.value);
+ }),
+ );
</file context>
| return Context.add(context, ModelRequestAccounting, accounting.value); | |
| return Context.add( | |
| Context.add(Context.empty(), LanguageModel.LanguageModel, model), | |
| ModelRequestAccounting, | |
| accounting.value, | |
| ); |
| usage: { inputTokens: {}, outputTokens: {} }, | ||
| }, | ||
| ], | ||
| [text("z")], |
There was a problem hiding this comment.
P3: The third script entry [text("z")] is never consumed: this test calls step only twice (one with a policy, one without), and each step performs exactly one generation (admitted calls model.generateText once), so only the first two generations run. The never-dispatched text generation is dead test data; remove it, or add the third step if the intent was to assert that a generation with no Tool Calls records nothing.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At packages/core/test/session/step-outcomes.test.ts, line 138:
<comment>The third script entry `[text("z")]` is never consumed: this test calls `step` only twice (one with a policy, one without), and each `step` performs exactly one generation (`admitted` calls `model.generateText` once), so only the first two generations run. The never-dispatched text generation is dead test data; remove it, or add the third `step` if the intent was to assert that a generation with no Tool Calls records nothing.</comment>
<file context>
@@ -0,0 +1,192 @@
+ usage: { inputTokens: {}, outputTokens: {} },
+ },
+ ],
+ [text("z")],
+ ],
+ [],
</file context>
| @@ -195,48 +188,40 @@ describe("Codex SSE", () => { | |||
| }, | |||
| }); | |||
| try { | |||
| const events: Array<unknown> = []; | |||
| const parts: Array<AiResponse.AnyPart> = []; | |||
There was a problem hiding this comment.
P3: The rewritten end-to-end test drops the response.output_item.added/.done events with an unknown future_item type that the old test injected mid-stream. That was the only remaining coverage that a future/unknown Codex item type is silently skipped without corrupting the part stream, and sse.test.ts never feeds an unknown item type through output_item.added/.done (it only uses message/function_call/reasoning items). Since the parser was rebuilt in this PR and Codex is a best-effort unofficial API, keep the unknown-item events in this SSE fixture so the skip path stays covered end to end.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At packages/providers/test/openai-codex/codex.test.ts, line 191:
<comment>The rewritten end-to-end test drops the `response.output_item.added`/`.done` events with an unknown `future_item` type that the old test injected mid-stream. That was the only remaining coverage that a future/unknown Codex item type is silently skipped without corrupting the part stream, and `sse.test.ts` never feeds an unknown item type through `output_item.added`/`.done` (it only uses message/function_call/reasoning items). Since the parser was rebuilt in this PR and Codex is a best-effort unofficial API, keep the unknown-item events in this SSE fixture so the skip path stays covered end to end.</comment>
<file context>
@@ -195,48 +188,40 @@ describe("Codex SSE", () => {
});
try {
- const events: Array<unknown> = [];
+ const parts: Array<AiResponse.AnyPart> = [];
const { promise: output, resolve: firstOutput } = Promise.withResolvers<void>();
- const turn = Effect.runPromise(
</file context>
| }), | ||
| ); | ||
| // The late response cannot stage into the ended Turn, but its request stays counted. | ||
| expect(Exit.isFailure(outcome.exit)).toBe(true); |
There was a problem hiding this comment.
P3: This assertion treats any Exit failure as success, but an interrupted parked fiber is also an Exit.failure, so the test would still pass if the fiber was torn down by scope teardown instead of dying on staging into the already-ended Turn. The point of the test is that the late response cannot stage into the closed Turn, so assert that exact die, as the first test block does, with defect(outcome.exit).
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At packages/core/test/session/step-lifetime.test.ts, line 136:
<comment>This assertion treats any Exit failure as success, but an interrupted parked fiber is also an `Exit.failure`, so the test would still pass if the fiber was torn down by scope teardown instead of dying on staging into the already-ended Turn. The point of the test is that the late response cannot stage into the closed Turn, so assert that exact die, as the first test block does, with `defect(outcome.exit)`.</comment>
<file context>
@@ -0,0 +1,318 @@
+ }),
+ );
+ // The late response cannot stage into the ended Turn, but its request stays counted.
+ expect(Exit.isFailure(outcome.exit)).toBe(true);
+ expect(outcome.usage).toEqual({ generations: 1, dispatches: 0, physicalRequests: 1 });
+ expect(outcome.history).toEqual([]);
</file context>
| expect(Exit.isFailure(outcome.exit)).toBe(true); | |
| expect(defect(outcome.exit)).toBe(true); |
| turnOf(program, scriptedModel([[call("a", "write"), call("b", "read")]], log)), | ||
| ); | ||
| yield* Effect.yieldNow; | ||
| while (!log.includes("write:a:start")) yield* Effect.yieldNow; |
There was a problem hiding this comment.
P3: These busy-wait loops spin Effect.yieldNow until write:a:start appears; if a regression stops the handler from ever starting, the test spins until the runner timeout instead of failing on the actual mismatch (gly letter. Same pattern at line 948). Bound the wait so a regression fails fast with the real assertion error.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At packages/core/test/session/step.test.ts, line 821:
<comment>These busy-wait loops spin `Effect.yieldNow` until `write:a:start` appears; if a regression stops the handler from ever starting, the test spins until the runner timeout instead of failing on the actual mismatch (gly letter. Same pattern at line 948). Bound the wait so a regression fails fast with the real assertion error.</comment>
<file context>
@@ -0,0 +1,984 @@
+ turnOf(program, scriptedModel([[call("a", "write"), call("b", "read")]], log)),
+ );
+ yield* Effect.yieldNow;
+ while (!log.includes("write:a:start")) yield* Effect.yieldNow;
+ expect(log).toEqual(["generate:0", "write:a:start"]);
+ yield* Fiber.interrupt(fiber);
</file context>
| The first isolated Session/Turn experiment demonstrated exact unary result/error/requirement typing, process-local staging and bounded passive observation, but exposed Session-close/in-flight-save and child/resource lifetime gaps. Parent reruns reproduced 15 checks, four expected compiler failures, a publication-before-save mutant failure, and the explicit post-release-commit counterexample. See [proof evidence](../research/session-turn-contract-proof.md). These are not native AI, Host, restart, or power-loss guarantees. | ||
|
|
||
| The user selected Turn-owned invocation scope and acceptance of non-resumable custom programs with pause/reconciliation after crash. The separate v2 proof now supplies a real Turn Scope and Session-owned admitted execution; parent reruns pass 14 inherited checks, seven ownership checks, two boundary controls and the intended negative/mutation tests. Independent review accepts the bounded proof with explicit sequential/protected shutdown ordering and limited cancelled-caller diagnostic claims. A stuck save still blocks shutdown. The full bounded ownership proof now passes on isolated rc.117 using native scoped cleanup and ordinary join, without the obsolete compensations; parent reruns and independent review agree. The cancellation-intersection test confirms that the execution retains body/cleanup causes while the cancelled caller receives only interruption. These bounded checks do not settle durable outcomes or all lifetime misuse. SQLite is a researched candidate, not a selected backend; rc.108 transaction failures have now been independently reproduced on real SQLite. Parent-verified published rc.117 packages fix the tested BEGIN, Cause-loss and observer regressions, but still leak the active connection after failed COMMIT: the desired cleanup test remains red. A supported upstream resolution is required before transaction integration (see proof evidence). No local workaround or fix contribution is approved. No contract or implementation issue is made ready merely because the scratch checks pass. | ||
| The user selected Turn-owned invocation scope and acceptance of non-resumable custom programs with pause/reconciliation after crash. The separate v2 proof now supplies a real Turn Scope and Session-owned admitted execution; parent reruns pass 14 inherited checks, seven ownership checks, two boundary controls and the intended negative/mutation tests. Independent review accepts the bounded proof with explicit sequential/protected shutdown ordering and limited cancelled-caller diagnostic claims. A stuck save still blocks shutdown. The full bounded ownership proof now passes on isolated rc.117 using native scoped cleanup and ordinary join, without the obsolete compensations; parent reruns and independent review agree. The cancellation-intersection test confirms that the execution retains body/cleanup causes while the cancelled caller receives only interruption. These bounded checks do not settle durable outcomes or all lifetime misuse. rc.108 transaction failures have been independently reproduced on real SQLite. Published rc.117 packages fix the tested BEGIN, Cause-loss and observer regressions, but still leak the active connection after failed COMMIT. The owner has since selected SQLite as the durable-backend direction and approved the qualified minimal upstream fix as a development-only patch of the matching rc.117 packages, for repository development and backend tests only ([development-only integration](../research/session-turn-contract-proof.md#development-only-sqlite-integration)). Portable library delivery and release stay blocked until a fixed upstream release is qualified; schema, recovery policy and limits remain open. No contract or implementation issue is made ready merely because the scratch checks pass. |
There was a problem hiding this comment.
P3: This PR pins and patches the stable Effect 4.0.0 releases (changeset effect-4-pin.md moves off 4.0.0-rc.108; bun.lock resolves @effect/ai-openai@4.0.0 and @effect/ai-openai-compat@4.0.0, and the patch artifacts are named patches/*@4.0.0.patch), but the added plan text still identifies the local patches as patches of 4.0.0-rc.117 packages (also in the new #183 paragraph: 'A development-only patch of @effect/ai-openai@4.0.0-rc.117' and 'patches, of effect@4.0.0-rc.117 and @effect/ai-openai-compat@4.0.0-rc.117'). Align the version references in the added text with the pinned 4.0.0 packages so the release-blocking claim points at the packages that actually carry the patches; the sql adapters are pinned 4.0.0 as well and no SQLite adapter patch file exists under patches/.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At docs/plans/effect-native-library.md, line 659:
<comment>This PR pins and patches the stable Effect `4.0.0` releases (changeset `effect-4-pin.md` moves off `4.0.0-rc.108`; bun.lock resolves `@effect/ai-openai@4.0.0` and `@effect/ai-openai-compat@4.0.0`, and the patch artifacts are named `patches/*@4.0.0.patch`), but the added plan text still identifies the local patches as patches of `4.0.0-rc.117` packages (also in the new `#183` paragraph: 'A development-only patch of `@effect/ai-openai@4.0.0-rc.117`' and 'patches, of `effect@4.0.0-rc.117` and `@effect/ai-openai-compat@4.0.0-rc.117`'). Align the version references in the added text with the pinned 4.0.0 packages so the release-blocking claim points at the packages that actually carry the patches; the sql adapters are pinned `4.0.0` as well and no SQLite adapter patch file exists under `patches/`.</comment>
<file context>
@@ -652,7 +656,7 @@ No gate is waived by a successful internal TUI/HTTP demo. Public native examples
The first isolated Session/Turn experiment demonstrated exact unary result/error/requirement typing, process-local staging and bounded passive observation, but exposed Session-close/in-flight-save and child/resource lifetime gaps. Parent reruns reproduced 15 checks, four expected compiler failures, a publication-before-save mutant failure, and the explicit post-release-commit counterexample. See [proof evidence](../research/session-turn-contract-proof.md). These are not native AI, Host, restart, or power-loss guarantees.
-The user selected Turn-owned invocation scope and acceptance of non-resumable custom programs with pause/reconciliation after crash. The separate v2 proof now supplies a real Turn Scope and Session-owned admitted execution; parent reruns pass 14 inherited checks, seven ownership checks, two boundary controls and the intended negative/mutation tests. Independent review accepts the bounded proof with explicit sequential/protected shutdown ordering and limited cancelled-caller diagnostic claims. A stuck save still blocks shutdown. The full bounded ownership proof now passes on isolated rc.117 using native scoped cleanup and ordinary join, without the obsolete compensations; parent reruns and independent review agree. The cancellation-intersection test confirms that the execution retains body/cleanup causes while the cancelled caller receives only interruption. These bounded checks do not settle durable outcomes or all lifetime misuse. SQLite is a researched candidate, not a selected backend; rc.108 transaction failures have now been independently reproduced on real SQLite. Parent-verified published rc.117 packages fix the tested BEGIN, Cause-loss and observer regressions, but still leak the active connection after failed COMMIT: the desired cleanup test remains red. A supported upstream resolution is required before transaction integration (see proof evidence). No local workaround or fix contribution is approved. No contract or implementation issue is made ready merely because the scratch checks pass.
+The user selected Turn-owned invocation scope and acceptance of non-resumable custom programs with pause/reconciliation after crash. The separate v2 proof now supplies a real Turn Scope and Session-owned admitted execution; parent reruns pass 14 inherited checks, seven ownership checks, two boundary controls and the intended negative/mutation tests. Independent review accepts the bounded proof with explicit sequential/protected shutdown ordering and limited cancelled-caller diagnostic claims. A stuck save still blocks shutdown. The full bounded ownership proof now passes on isolated rc.117 using native scoped cleanup and ordinary join, without the obsolete compensations; parent reruns and independent review agree. The cancellation-intersection test confirms that the execution retains body/cleanup causes while the cancelled caller receives only interruption. These bounded checks do not settle durable outcomes or all lifetime misuse. rc.108 transaction failures have been independently reproduced on real SQLite. Published rc.117 packages fix the tested BEGIN, Cause-loss and observer regressions, but still leak the active connection after failed COMMIT. The owner has since selected SQLite as the durable-backend direction and approved the qualified minimal upstream fix as a development-only patch of the matching rc.117 packages, for repository development and backend tests only ([development-only integration](../research/session-turn-contract-proof.md#development-only-sqlite-integration)). Portable library delivery and release stay blocked until a fixed upstream release is qualified; schema, recovery policy and limits remain open. No contract or implementation issue is made ready merely because the scratch checks pass.
## Pi reference-alignment review
</file context>
| The user selected Turn-owned invocation scope and acceptance of non-resumable custom programs with pause/reconciliation after crash. The separate v2 proof now supplies a real Turn Scope and Session-owned admitted execution; parent reruns pass 14 inherited checks, seven ownership checks, two boundary controls and the intended negative/mutation tests. Independent review accepts the bounded proof with explicit sequential/protected shutdown ordering and limited cancelled-caller diagnostic claims. A stuck save still blocks shutdown. The full bounded ownership proof now passes on isolated rc.117 using native scoped cleanup and ordinary join, without the obsolete compensations; parent reruns and independent review agree. The cancellation-intersection test confirms that the execution retains body/cleanup causes while the cancelled caller receives only interruption. These bounded checks do not settle durable outcomes or all lifetime misuse. rc.108 transaction failures have been independently reproduced on real SQLite. Published rc.117 packages fix the tested BEGIN, Cause-loss and observer regressions, but still leak the active connection after failed COMMIT. The owner has since selected SQLite as the durable-backend direction and approved the qualified minimal upstream fix as a development-only patch of the matching rc.117 packages, for repository development and backend tests only ([development-only integration](../research/session-turn-contract-proof.md#development-only-sqlite-integration)). Portable library delivery and release stay blocked until a fixed upstream release is qualified; schema, recovery policy and limits remain open. No contract or implementation issue is made ready merely because the scratch checks pass. | |
| The owner has since selected SQLite as the durable-backend direction and approved the qualified minimal upstream fix as a development-only patch of the pinned 4.0.0 packages, for repository development and backend tests only |
There was a problem hiding this comment.
Caution
Two reproduced issues need fixes before merging: a special-name Tool can bypass authorization and dispatch limits, and scoped work started during Turn cleanup can outlive commit and Session infrastructure release.
Reviewed changes: Reviewed the Effect-native replacement and its integration boundaries, focusing on executable behavior and retained tests rather than exhaustively rereading deleted legacy tests because the clean cutover is intentional.
- Session ownership: Fresh scoped Sessions, whole-function Turns, save-before-publication, fencing, receipts and passive observation.
- Controlled execution: Serialized Steps, native Tool registration and codecs, policy/consent/authority checks, incomplete results and request/dispatch accounting.
- Provider integration: OpenAI and compatible bindings, Codex SSE reconciliation, redirect handling and the three development dependency patches.
- Cutover and qualification: Legacy package removal, help/version-only CLI, native scaffolding, release fixture changes and native/SQLite qualification.
Verification passed: package typechecks and 537 tests across Core, Providers, CLI and scaffolding; native qualification; SQLite qualification on Bun 1.4.0 and Node 24.19.0. Separate full-core probes on the patched Effect 4.0.0 installation reproduced both findings below. Release tarball fixtures, cross-platform release binaries and live credentialed Provider calls were not rerun; the explicitly documented publication block for development patches remains necessary.
gpt-6-astra | 𝕏
| const output = {} as GatedHandlers<Tools>; | ||
| // SAFETY: the own keys of `handlers` were just checked to be exactly the Toolkit's Tool names. | ||
| for (const name of Object.keys(handlers) as Array<keyof Toolkit.HandlersFrom<Tools>>) { | ||
| const handler = handlers[name]; | ||
| output[name] = (params, handlerContext) => |
There was a problem hiding this comment.
[P1] Preserve special Tool names as own gated-handler properties
For a valid Tool named __proto__, this assignment changes output's prototype instead of creating an own handler binding, so native toHandlers omits the gate. If a same-ID native handler is ambient, controlled dispatch falls back to it and bypasses policy, consent, authority and dispatch reservation; I reproduced a Complete result with policy deny, authority false, a dispatch limit of 0, and neither authorization callback invoked.
Technical details
# Prototype-safe gated handler registration
## Affected sites
- packages/core/src/session/step.ts:146-150 constructs the wrapper map with ordinary property assignment.
- packages/core/src/session/step.ts:421-424 provides the resulting handler context; an omitted binding leaves an ambient same-ID handler available.
- Native Effect 4.0.0 Toolkit.make supports this name using prototype-safe own-property assignment; Toolkit.toHandlers uses Object.entries, and Toolkit.handle resolves the handler by Tool id from Context.
## Reproduction
- Create Toolkit.make(Tool.make("__proto__", { parameters: Schema.Struct({}), success: Schema.String })).
- Register localTools with a computed-property handler map: { ["__proto__"]: localHandler }.
- Provide kit.toLayer({ ["__proto__"]: ambientHandler }) around the controlled Step.
- Return a native Model call named "__proto__" with params {}.
- Set policy to return "deny", authority to return false, and Session limits to { generations: 1, dispatches: 0 }.
- Actual on patched Effect 4.0.0: ambientHandler runs, localHandler/policy/authority do not; Step is Complete and usage.dispatches is 0. Without the ambient handler, the missing binding instead fails dispatch.
## Required outcome
- Every accepted Tool name must have its own gated binding; native ambient handlers must not replace it.
- Use a prototype-safe map or own-property definition and add a special-name regression with an ambient handler and denying authorization.
## Source
- https://github.com/Effect-TS/effect/blob/effect%404.0.0/packages/effect/src/ai/Toolkit.ts
- https://github.com/chenxin-yan/mitome/blob/f15d696dd10b10ea0cd6820c401b72d51683d849/packages/core/src/session/step.ts#L421-L424| const result = yield* Effect.gen(function* () { | ||
| const cleanup = yield* Effect.forkChild(owned); | ||
| return yield* restoreProgram(Fiber.join(cleanup)).pipe( | ||
| Effect.ensuring(Fiber.interrupt(cleanup)), | ||
| ); |
There was a problem hiding this comment.
[P1] Drain scoped children started during Turn cleanup
A Turn finalizer that calls Effect.forkScoped(..., { startImmediately: true }) uses its captured Turn Scope after that Scope has been marked closed, so Effect interrupts the new daemon fiber without awaiting it. Joining cleanup therefore does not cover that child's held cleanup: I reproduced Session.run returning and publishing history, followed by Session infrastructure release, while the child was still running.
Technical details
# Scoped cleanup work escapes the Turn completion barrier
## Affected sites
- packages/core/src/session/session.ts:329-345 relies on joining the cleanup fiber to settle all cleanup-created children.
- packages/core/test/session/session.test.ts:258-295 and 339-395 cover finalizer-created forkChild children; forkScoped is covered only when started in the program body.
## Reproduction
Allocate Deferred<void> values cleaning and gate outside the Turn, stage one message, then register:
```ts
yield* Effect.addFinalizer(() =>
Effect.forkScoped(
Effect.never.pipe(
Effect.ensuring(
Deferred.succeed(cleaning, undefined).pipe(
Effect.andThen(Deferred.await(gate)),
),
),
),
{ startImmediately: true },
).pipe(Effect.asVoid),
);
```
Keep the returned child handle when probing its state. Actual on patched Effect 4.0.0: session.run completes; history contains the staged message; child.pollUnsafe() is undefined. Closing the allocating Session Scope releases infrastructure before gate is opened. Opening gate finally drains the child.
## Cause
Effect.addFinalizer restores the registration Context, including the Turn Scope. Native Scope.close marks that Scope Closed before invoking finalizers. Native forkScoped delegates to forkIn, which creates a daemon and, for a closed Scope, only calls interruptUnsafe; it does not join the child. That child is neither a native child of the cleanup fiber nor registered with the Session execution Scope.
## Required outcome
- Supported Turn-owned scoped work started during cleanup must settle before save, commit, invocation completion and Session dependency release.
- Add a held-cleanup regression covering forkScoped during a finalizer, not just forkChild.
- This does not require changing native reverse finalizer order or propagating unjoined child failures; those intentional contracts can stay unchanged.
## Source
- https://github.com/Effect-TS/effect/blob/effect%404.0.0/packages/effect/src/internal/effect.ts#L5624-L5665
- https://github.com/chenxin-yan/mitome/blob/f15d696dd10b10ea0cd6820c401b72d51683d849/packages/core/src/session/session.ts#L325-L348… 4.0.0 Adopt the stable Effect 4.0.0 release and the approved TypeScript 7.0.2 + @effect/tsgo compiler, and add the Effect-native execution core. - Native baseline (#181): pin effect and every @effect/* package to 4.0.0, migrate to the stable module paths, and add compile-time and runtime qualification fixtures for native Tool/Toolkit behavior. - Sessions and whole-function Turns (#182): ordinary Effect programs run through makeSession and Turn, with staged history committed once, owned cleanup and live queries. - SQLite transaction qualification for the Node and Bun adapters (#170). - Controlled Tool Steps (#183): step and loop with serial native Tools, fail-closed authorization, incomplete-response handling, generation and dispatch limits, and physical request accounting across the openai, openai-compatible and openai-codex providers. The legacy Promise SDK and Extension runtime no longer build against this core; the next commit removes them.
Delete the Promise SDK, the Agent Definition/Extension/Hook framework, the old Host model, the old Transcript and Route stores, the HTTP channel host and the old TUI, so the repository builds on the native Session/Turn/Step core alone. Pre-release: no compatibility layer. - Remove @mitome/sdk, @mitome/channels and @mitome/tui. - Core exports only the native Session, Turn, Step and Provider surface. - The CLI keeps only --help/--version until native application loading lands (#184); the TUI is rebuilt in #185. - Port the provider tests from the removed engine to providerModel with native LanguageModel generation. - create-mitome scaffolds a native Effect program. - Release fixtures smoke-test the native packed exports. - Move the models.dev filter used by hint generation into scripts.
f15d696 to
c04a855
Compare
|
Run failed. View the logs →
|

Replaces #206, #208 and #209. Stacked on #207 → #205.
Moves Mitome onto the stable Effect 4.0.0 release and the new Effect-native execution core, and deletes the legacy runtime. Pre-release, so there's no compatibility layer.
Delivers #181, #182 and #183, and the SQLite qualification part of #170. The CLI (#184) and TUI (#185) are rebuilt next on this core.
Commits
feat!: run native Sessions, Turns and controlled Tool Steps on Effect 4.0.0@effect/tsgocompiler.makeSession/Turn: whole-function Turns that commit staged history once, with owned cleanup and live queries.step/loop/localTools: serial native Tools, fail-closed authorization, incomplete-response handling, generation/dispatch limits and physical request accounting.openai,openai-compatibleandopenai-codex.tools/qualification.refactor!: remove the legacy runtime@mitome/sdk,@mitome/channelsand@mitome/tui, plus the Agent Definition, Extension, Hook, Host and old store code in Core.--help/--version.providerModel+LanguageModel.create-mitomescaffolds a native program, and the release fixtures smoke-test the native packed exports.Merge with "Create a merge commit" so the stack's commit SHAs carry through. Only the merged result builds green; commit 1 alone doesn't.
No local dependency patches
Effect and
@effect/*are used exactly as published at 4.0.0. There is nopatches/directory and nopatchedDependencies. Earlier drafts patched three Effect modules; those patches were removed by owner decision, and Mitome accepts upstream behavior:@effect/ai-openaidoes not validate the Responsesstatus, unfinished item statuses orcomputer_callitems. An incomplete response without a reason, or one stopped bymax_output_tokens, is not reported as incomplete.Tests that only covered the patched behavior were removed (4 core, 51 provider cases). See the dated note in
docs/research/native-baseline-qualification.md.Verification (local, Bun 1.4.0 / Node 26.7.0)
bun run build -- --force,bun run check -- --forceandbun run test -- --forceall exit 0:tools/qualificationchecks pass: native baseline, and SQLite on Node and Bun.Not covered: the scaffold's runtime
node index.tsneeds credentials, so only its typecheck is gated. The CLI binary release-artifact path wasn't exercised locally.