- Migrated global service tier settings to a per-model-family architecture (OpenAI, Anthropic, Google).
- Implemented `ServiceTierByFamily` mapping to allow independent configuration and resolution per provider.
- Added automatic migration logic for legacy service tier and fast-mode application settings.
- Updated telemetry, session management, and task execution to support provider-specific tier resolution.
Concurrent graph-root loads in the JS eval kernel segfaulted Bun
(SIGSEGV at 0xFFFFFFFFFFFFFFF8, getImportedModule on a null record in
JSC::AbstractModuleRecord::innerModuleLinking). Two roots loaded at once
over an overlapping local-import graph — e.g.
Promise.all([import("./a.ts"), import("./b.ts")]) sharing a dependency —
each launched its own module.link() over the same shared
vm.SourceTextModule instances. The async link resolver yields
mid-instantiation, letting the two link passes interleave and re-enter
Bun's node:vm linker, which crashes instead of throwing.
The earlier single-pass fix (15.7.2) only serialized linking within one
graph root; it did not guard two concurrent roots over shared modules.
LocalModuleLoader now routes every module.link() through a promise-chain
mutex (#serializeLink), so the linker is never re-entered
mid-instantiation. The lock is held only across link(), not evaluate(),
so a dynamic import during evaluation re-acquires it without deadlock,
and the chain swallows rejections so a failed link cannot wedge later
imports. Reproduced and verified against the user crash trace: the
concurrent-diamond repro went from 5/6 crashes to 0/N with correct
namespaces.
- Renamed the `find` and `search` tools to `glob` and `grep` respectively across the codebase to improve command clarity.
- Implemented full-stack support for the renamed tools, including CLI arguments, system prompts, SDK exports, and tool registration.
- Added automated migration logic in `settings` to transform legacy `find` and `search` configuration keys to their new equivalents.
- Updated the `collab-web` renderer registry to ensure backwards compatibility with legacy tool outputs.
- Surface the exception type and message in the error display by prepending the formatted error string to the traceback array.
- Prevent the host from hiding the actual error by ensuring the traceback is not empty, consistent with other language runners.
- Introduced `serviceTierSubagent` and `serviceTierAdvisor` settings to allow independent service tier control for subagents and the advisor model.
- Enabled `"inherit"` mode for these settings, allowing subagents and the advisor to track the main session's live effective service tier, including dynamic toggles like `/fast`.
- Added a resolution layer to ensure service tier propagation from parent sessions to spawned task agents and evaluators.
- Removed deprecated eval prelude helpers `append`, `tree`, `diff`, `sort`, `uniq`, and `counter` from all supported runtimes.
- Cleaned up runtime implementations, protocol definitions, and UI rendering logic associated with the removed helpers.
- Updated project documentation, prompts, and test suites to reflect the reduced helper API surface.
- Recorded functional changes in the package changelog.
- Added headless plot configuration for Julia to prevent GUI popup windows during execution.
- Implemented robust mime-bundle serialization in Julia using `invokelatest` to handle runtime-loaded library methods.
- Added IRuby-protocol and magic-byte image sniffing support to the Ruby runner to enable inline rendering for graphics gems like Gruff, ChunkyPNG, and RMagick.
The eval agent() helper used `agent_type`/`return_handle` (snake_case) in
Python/Ruby/Julia and `agentType`/`returnHandle` (camelCase) in JS, forcing
the prelude docs to repeat every option twice ("JS same but camelcased").
Both are now single lowercase words identical across all four runtimes, and
`agent` matches the `task` tool's existing agent-selection parameter.
- Renamed across py/js/rb/jl preludes (signatures, forwarding, docstrings).
- Renamed the `__agent__` bridge wire protocol + `EvalAgentArgs` (`agentType`
→ `agent`, `returnHandle` → `handle`) so no prelude-side remap is needed.
- Updated prompt docs (workflow-notice.md, tools/eval.md), repo docs
(docs/tools/eval.md, docs/python-repl.md), and all bridge/prelude tests.
- CHANGELOG: Breaking Changes entry under [Unreleased].
Two Codex P2 findings landed against 978d2a76d0 that were not in the previously delivered review event:
1) prepareIsolationContext() (which runs captureBaseline → walks nested repos and untracked diffs) was running OUTSIDE withBridgeTimeoutPause; on dirty/large repos the baseline walk can exceed the eval idle timeout while the runtime is blocked. Moved the prep call into the pause closure so the watchdog is suspended for the whole bridge call from prep through cleanup.
2) applyNestedPatches() swallowed git stash pop failures with only a logger.warn, so a stash-pop conflict after a successful agent commit was invisible to the workflow. Changed the helper to return Promise<string[]> of warnings; applyEligibleNestedPatches now wraps them in a <system-notification> appended to the merge summary so the caller actually sees the partial-success case.
Added regression tests:
- bridge: prepare fires after timeout-pause and before timeout-resume.
- runner: applyEligibleNestedPatches surfaces stash-restore warnings as a system-notification.
- worktree (real git): a pre-existing dirty edit on the same file the agent patches causes stash pop to conflict; the helper returns a warning naming the nested repo and the stash entry is preserved for manual recovery.
Fixes#3196
Resolves conflict in test/task/worktree.test.ts by keeping both the
getRepoRoot (main) and applyNestedPatches (PR) describe blocks.
Extends the PR's Python/JS work to the remaining workflow runtimes:
- eval/rb/prelude.rb, eval/jl/prelude.jl: agent() now accepts and
forwards isolated/apply/merge (as booleans) plus returnHandle, and the
return_handle node carries isolated/patch_path/branch_name/
nested_patches/changes_applied/isolation_summary.
Post-merge fixups:
- task/index.ts: drop dead commitStyle var (the dedup refactor reads
task.isolation.commits inside makeIsolationCommitMessage).
- CHANGELOG: move the misplaced Added entry under [Unreleased], correct
the stale "defaults track task.isolation.mode" wording to the final
strict opt-in behavior, and note all four runtimes.
Fixes#3196
TaskTool and the eval agent() bridge each held a private copy of the nested-repo patch eligibility gate and the AI commit-message factory; isolation policy could drift between the two callers.
Moved both into task/isolation-runner.ts:
- applyEligibleNestedPatches(opts) — single nested-patch gate (skip on patch-mode parent failure, skip on branch-mode unmerged root, fail non-fatally with a system-notification suffix).
- makeIsolationCommitMessage(session) — single factory that yields the AI commit-message callback when task.isolation.commits === "ai" and a model registry is wired, undefined otherwise.
Both call sites now invoke the helpers; behavior is unchanged. Removed the now-dead generateCommitMessage/applyNestedPatches imports from each caller.
Added unit tests for the new helper covering the skip-on-patch-failure, skip-on-unmerged-branch, success, and failure-suffix paths.
Fixes#3196
The two julia-prelude tests pay a ~11-12s Julia kernel cold-start
(JIT + package precompile) when run with reset: true, exceeding
Bun's default 5000ms per-test timeout. CI exposed this since 33e2594f0
(Julia eval support) ran on PR runners (ubuntu-22.04, Julia
preinstalled) rather than the omp-kata self-hosted runners that lack
Julia and skip the suite.
Bumped both tests to 30_000ms, matching the precedent in
agent-bridge.test.ts:540 for similar persistent-kernel tests.
Fixes#3274
Previously withBridgeTimeoutPause only wrapped the subagent subprocess; mergeIsolatedChanges, applyNestedPatches, nested commit-message generation, and artifact cleanup ran with the eval watchdog re-armed. A cherry-pick or large patch apply could trip the cell timeout and abort successful post-processing.
Moved the entire bridge work (subprocess + merge + nested apply + cleanup + usage recording) inside one withBridgeTimeoutPause block. The pause helper still resumes via its finally on success and on throw, so existing failure paths are unchanged.
Added a regression that captures the emitted op order and asserts merge fires after timeout-pause and before timeout-resume.
Fixes#3196
Per maintainer ruling on #3196, eval agent() now defaults to non-isolated regardless of task.isolation.mode, mirroring the task tool. isolated=true is the only way to turn it on; isolated=true while task.isolation.mode === "none" still throws the same clear error.
Updated tests, workflow-notice.md, and Python agent() docstring to reflect the strict opt-in contract. Existing isolation tests now pass isolated:true explicitly; the inherit-from-settings assertion is replaced with a default-off + isolated=true opt-in regression.
Fixes#3196
- Extracted common kernel and executor logic into `BaseKernel` and `executor-base` to eliminate duplicated implementations for Julia, Python, and Ruby.
- Migrated shared operational workflows--including session namespacing, environment filtering, and result mapping--to centralized backend helpers.
- Consolidated runtime discovery and resolution logic into a unified `runtime-env` utility module.
- Simplified language-specific modules by delegating subprocess lifecycle, IPC, and configuration management to the newly established base classes.
When an isolated apply fails the bridge throws a ToolError and never returns details, so the nested-patch payload that previously lived in details.nestedPatches was unrecoverable after the isolation worktree was torn down.
The bridge now writes each captured nested patch to a file under the per-call artifacts dir (e.g. <agentId>.nested-<index>-<slug>.patch) before throwing and includes the resolved paths in the error message so the caller can apply them manually.
Added a regression test verifying the persisted file exists with the original patch contents and that the path is surfaced in the thrown error.
Fixes#3196
A failed isolated apply (changesApplied === false) previously only set details.isolationSummary and returned the subagent text. Schema-backed agent() calls then parsed the JSON and returned the object, so workflows saw a successful structured result while none of the edits had landed.
Throw a ToolError when mergeIsolatedChanges reports a failed apply, with the merge summary plus a recovery hint pointing at the preserved patch/branch/nested artifacts so the caller can apply manually.
Added regression tests for the schema and non-schema apply-failure paths.
Fixes#3196
- Implemented persistent execution backends for Ruby and Julia using dedicated kernel processes and NDJSON-based IPC.
- Integrated language-specific prelude environments, runtime path resolution, and security-focused environment variable filtering.
- Exposed configuration options, tool schema updates, and lifecycle management for seamless agent interaction with both languages.
- Added comprehensive integration tests and updated prompt documentation to support the new evaluation capabilities.
Branch-mode isolation can capture nested repository changes without creating a root branch. Eval agent() with apply=false previously treated that shape as no captured changes and returned no recoverable nested patch payload after the isolation worktree was removed.
Expose captured nested patches in EvalAgentResult details and copy them onto JS/Python returnHandle nodes (nestedPatches / nested_patches). Document the return_handle escape hatch and add regression coverage for branch-mode nested-only apply=false runs.
Fixes#3196
Eval preludes now forward returnHandle to the bridge so no-session eval runs can preserve the temp artifacts backing returned agent:// handles. The bridge keeps those temporary artifact directories whenever returnHandle is requested, including non-isolated runs and successful isolated applies.
Branch-mode isolation now treats nested-only changes as merge-eligible even when no root branch was produced, letting callers apply nested patches instead of dropping them when the root repo had no diff.
Added regression coverage for returnHandle artifact preservation and nested-only branch isolation.
Fixes#3196
When agent() ran with schema and apply=false, the bridge correctly returned the captured patch/branch in details, but the preludes only forwarded id/agent/handle/data on the returnHandle node. Structured workflows had no way to recover the artifact for a manual apply.
Both runtimes now copy isolated, patchPath/branchName, changesApplied, and isolationSummary onto the returnHandle node (snake_case in Python, camelCase in JS), keeping null changesApplied so apply=false stays distinguishable from a successful apply. Updated the workflow notice and the Python agent() docstring to point callers at return_handle as the artifact escape hatch for isolated+apply=false runs. Added prelude tests locking the new node shape in both runtimes.
Fixes#3196
The cleanup gate was treating changesApplied===null (apply=false) the same as a clean apply, deleting the temp artifacts dir before returning details.patchPath. Sessions without a session file (which fall back to a per-call tmp dir) ended up with a patchPath pointing at a removed file, defeating the documented manual-apply path.
Tightened the cleanup condition to remove the temp dir only on a confirmed clean apply (changesApplied===true); apply=false and failed applies both keep the artifact for the caller.
Added regression tests for the apply=false preserve case and the apply-succeeds cleanup case.
Fixes#3196
Kept isolation merge/apply summaries out of agent() text when a schema is supplied so Python and JS eval helpers can still parse the JSON payload. The summary now lands in details.isolationSummary for callers that need the human-readable apply state.
Added a regression test that exercises an isolated schema-backed eval agent with a merge summary.
Fixes#3196
The workflowz eval path bypasses the task tool's isolation wrapper and
calls runSubprocess() directly, so parallel agent() fan-outs that edit
overlapping files all land in the parent worktree.
Extends the eval agent bridge schema with isolated/apply/merge, forwards
them through the Python and JS preludes, and adds a shared
task/isolation-runner.ts so the lifecycle (prepare context → run in
worktree → capture patch/branch → merge → cleanup) is implemented once
for both TaskTool and the bridge.
Default mirrors task.isolation.mode: isolated by default when settings
allow it, off when mode === 'none'. isolated=False explicitly disables;
isolated=True with mode === 'none' errors out to match the task tool.
apply=false keeps captured changes inside the worktree and surfaces the
patch path / branch name in details. merge=false forces patch mode even
when task.isolation.merge === 'branch'.
Fixes#3196
- Renamed the global `INTENT_FIELD` constant from `_i` to `i`.
- Updated documentation strings, type annotations, and test expectations across packages to reflect the new field name.
- Ensured consistent usage of the constant in tool schema construction and intent serialization.
- Moved the `INTENT_FIELD` constant from `@oh-my-pi/pi-agent-core` to the specialized `@oh-my-pi/pi-wire` package to permit broader usage across the monorepo.
- Updated all references across `agent`, `ai`, `coding-agent`, `collab-web`, and `snapcompact` packages to import the constant from the new location.
- Added `@oh-my-pi/pi-wire` as a dependency to all affected packages.
- Added `sessionId` and `cwd` tracking to JS evaluator session instances.
- Updated `acquireSession` in the JS context manager to update existing session identity and directory metadata on reuse.
- Forwarded the current working directory from `executePerCall` and `executeOnSession` callers to the underlying Python kernel tasks.
- Migrated all wire protocol, schema definitions, and tools validation from Zod to ArkType across multiple packages.
- Updated extension runtimes, custom tools loader, and TypeBox compatibility shim to expose and use ArkType instances.
- Added a comprehensive ArkType migration guide, validation parity tests, and helper utilities.
- Removed redundant PDF asset routing and parsing implementations from the read tool.
- Added Matplotlib figure PNG rendering and display tracking in Python runner to emit PNG output immediately when figures are displayed via display(fig).
- Extended session persistence to externalize oversized image payloads in both content and details.images, enabling tool result images to survive session reload.
- Enhanced session loader to resolve image data payloads and blob references across content and details.images during session reconstruction.
- Added image cache invalidation in TUI image component when image protocol, cell dimensions, or Kitty Unicode placeholder mode changes.
- Added comprehensive test coverage for Matplotlib display, image persistence across reload, and TUI image rendering with protocol and dimension changes.
- Made `JsRuntime` track the realm globals it installs (`#globalOwner`, `#ownedGlobalKeys`, `PRELUDE_GLOBAL_KEYS`) and reclaim them in `dispose()`, re-binding ownership through `#activateGlobals()` on cwd/run-scope/run; disposing an older inline/direct runtime no longer deletes a newer runtime's helper globals, and overlapping same-realm runs are serialized via `enterGlobalRun`.
- Added `WorkerCore.dispose()` (rejects pending tool calls with `ToolError` and disposes the runtime) and invoked it from `spawnInlineWorker` teardown in `context-manager.ts`.
- Updated the `js-static-import-rewrite` test to snapshot and restore any pre-existing `__omp_import__` global instead of unconditionally deleting it.
- Added `runtime-global-dispose.test.ts` covering newer-runtime global survival, older-runtime reactivation, and cross-runtime mutation rejection.
- Clarified the `runWorkerEntrypoint` comment in `cli.ts` about `parentPort` sync-prefix buffering for tab/eval workers.
- Forwarded `parentAgentId` through task and eval launch paths when spawning subagents.
- Mapped `parentAgentId` to `parentId` in `createAgentSession`.
- Passed each caller's session `getAgentId` (or `MAIN_AGENT_ID`) as the parent for spawned agents.
- Added a return_handle (Python) / returnHandle (JS) option to the eval agent() helper that returns a DAG node dict { text, output, handle, id, agent } instead of bare text, where handle is the spawned agent's recoverable agent:// URI.
- Enabled downstream pipeline/parallel stages to reference a large transcript by handle/output instead of re-inlining it; the default path stays backward compatible (bare text, or the parsed object under schema).
- Documented return_handle and the acyclic DAG-wiring pattern in the eval tool description and added a VM-level prelude regression test for the node shape and the no-details fallback.
- Added `WorkerInbox` and `installWorkerInbox(port)` to queue worker messages before bind.
- Added `consumeWorkerInbox()` to replay buffered messages and clear one active inbox.
- Added buffered inbox consumption in JS and tab worker transports before direct message handlers.
- Normalized worker selector arguments to the `__omp_worker_*` naming across workers and tests.
- Replaced Bun.sleep and wall-clock timing with fake timers (vi.useFakeTimers), release gates, and deterministic polling across 15+ test files to eliminate flakiness and improve speed.
- Consolidated per-test fixture setup into beforeAll/afterAll lifecycle hooks across 20+ test files, reducing redundant initialization and improving test performance by reusing shared immutable fixtures.
- Stubbed network calls in ModelRegistry and test discovery to prevent unintended outbound requests during test execution.
- Replaced subprocess-based test coordination (file markers, Bun.sleep polling) with in-memory fakes (FakeWebSocket, FakeLspServer, VirtualClock) for deterministic, fast test execution.
- Updated worker core so it emitted `ready` only after receiving an `init` message instead of on construction.
- Changed worker startup to send `init` before waiting for readiness, enabling startup errors to fail fast and trigger fallback.
- Expanded JS worker tests with startup-error simulation and verified execution falls back to the inline worker when spawning fails.
- Initialized JS workers through a new helper that attaches message and error listeners before initialization handshake and enforces the existing ready timeout.
- Handled startup failures by rejecting on init or error events, cleaning up listeners and replacing dead workers with a retry path.
- Retried session creation with an inline worker after non-inline init failure so requests no longer stall on worker startup timeouts.
- Added runtime hook resolvers so each `JsRuntime` instance can expose hooks for its active run.
- Patched `process.stdout` and `process.stderr` writes once per stream to route output chunks through active run text hooks and preserve existing worker logging when no run is active.
- Added chunk-to-string conversion for write payloads and encoding-aware forwarding while keeping callback semantics intact.