perf(tools): prefetch streamed filesystem calls - #445
Open
mpscholten wants to merge 50 commits into
Open
Conversation
mpscholten
force-pushed
the
2026-08-25-speculative-read-file
branch
2 times, most recently
from
August 25, 2026 08:39
d434b09 to
d5aa4f8
Compare
mpscholten
force-pushed
the
2026-08-25-speculative-read-file
branch
from
August 25, 2026 11:36
d5aa4f8 to
77d2ea0
Compare
mpscholten
force-pushed
the
2026-08-25-speculative-read-file
branch
from
August 25, 2026 13:18
77d2ea0 to
136ba3f
Compare
Keep the Runtime.hs facade and port speculative-read-file wiring onto Runtime/Internal.hs. Combine OpenAI WebSocket speculation with the HTTP transport fallback and Codex turn-state recovery.
Replace the callback-based speculation API with a fold over argument text: type ToolInterpreter s = s -> ToolInput -> IO (Either ToolResult s) The runtime still correlates provider events, but tools only see accumulated prefixes and one ToolDone after approval. Speculation is now an implementation detail of read_file rather than a core protocol.
ToolInterpreter now returns Either (args, state) state. After approval, streamedConsume runs on those parsed args so later stages do not re-decode JSON. read_file uses ReadFileArgs for prefetch consume and live fallback.
…ive-read-file # Conflicts: # packages/agent-cli/src/Agent/CLI/Runtime/Internal.hs
jsonTool, raw JSON tools, and apply_patch now attach a default interpreter that decodes arguments once on ToolDone and runs the typed handler. Resource claims take those parsed records via withTypedResourceClaims. read_file still replaces the default with its prefetching interpreter. Streaming tools keep snapshot output through takeToolSpeculationEmitting.
…ive-read-file # Conflicts: # packages/agent-cli/src/Agent/CLI/Provider/OpenAI.hs # packages/agent-core/agent-core.cabal # packages/agent-core/package.nix
Stop threading ToolSpeculationRuntime through CLI session types, the OpenAI backend constructors, and LoopConfig. The loop now builds the runtime from the tool registry, backends only emit ToolArgumentEvent, and --speculative-read-file is gone. Prefetch is an interpreter detail again, not a session feature.
The runtime now always opens a streamed interpreter, defaulting to the typed handler when a tool does not supply its own factory. JSON tools decode on prefixes as well as ToolDone. MCP and test AppTool records use the same default. A QuickCheck suite covers chunked prefixes, handler-once consume, misses, call-aware tools, and snapshot emit.
list_dir predicts unique directories from streamed target_directory prefixes. search_replace and apply_patch prefetch the target file read while later arguments still stream, then write only after approval. Shared path/fingerprint helpers live in PathPrefix and FilePrefetch.
Add speculative-tools-bench: a sequential, LLM-free replay of list_dir, read_file, search_replace, and apply_patch against a unique-prefix fixture. Baseline waits out the stream tail then dispatches; speculative feeds prefixes so reads overlap that tail. Reports p50/p95 wall.
Prefetch no longer stores raw bytes plus a formatted copy. Default offset/limit reads stop after 1000 lines, and prefix JSON is not run through Aeson. Negative offsets still slurp so last-line seeks work.
Replace remaining Aeson.decodeStrict' in PathPrefix and read_file speculation so complete objects and JSON string unescaping go through Agent.Json.Decode.
Honor an explicit read_file limit so a full-file read is possible. The mixed replay now sizes edit payloads from FILE_KIB, keeps reads under the token cap, and reports io_* as wall minus the tail delay.
…29-105db0e9 # Conflicts: # packages/agent-claude/src/Agent/Claude/Internal/Messages.hs # packages/agent-codex-dialect/src/Agent/Codex/Dialect/Tools.hs # packages/agent-codex-dialect/test/Agent/Codex/DialectSpec.hs # packages/agent-core/agent-core.cabal # packages/agent-core/package.nix # packages/agent-core/src/Agent/Loop/Internal.hs # packages/agent-core/src/Agent/MCP/Client.hs # packages/agent-core/test/Spec.hs # packages/agent-openai/src/Agent/OpenAI/LoopBackend.hs # packages/agent-openai/test/Agent/OpenAI/LoopBackendSpec.hs
# Conflicts: # packages/agent-core/src/Agent/Loop/Internal.hs
… 2026-08-29-105db0e9
… 2026-08-29-105db0e9
… 2026-08-29-105db0e9
…29-105db0e9 # Conflicts: # packages/agent-cli/src/Agent/CLI/NativeAgents.hs # packages/agent-cli/src/Agent/CLI/Provider/OpenAI.hs # packages/agent-codex-dialect/test/Agent/Codex/DialectSpec.hs # packages/agent-core/package.nix # packages/agent-core/src/Agent/Tools/FileSystem/ReadFile.hs
# Conflicts: # packages/agent-core/src/Agent/Loop/Internal.hs # packages/agent-core/src/Agent/ToolDispatch.hs
…29-105db0e9 # Conflicts: # flake.nix
…29-105db0e9 # Conflicts: # packages/agent-core/src/Agent/Tools/FileSystem/ListDir.hs
# Conflicts: # packages/agent-codex-dialect/test/Agent/Codex/DialectSpec.hs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
ToolCallremaining authoritativeread_file,list_dir,search_replace, andapply_patchDesign
Every tool now runs through a small
StreamedToolfold:The provider adapter emits provider-neutral argument events.
ToolSpeculationRuntimecorrelates provider item IDs, output indexes, and call IDs, owns one scoped worker per call, and retains work only for calls in the final response. The final call is checked before a prepared result can be consumed.Filesystem interpreters can start only when a path is complete or a streamed prefix uniquely identifies a Git-indexed workspace path. Prepared reads are bounded (four concurrent files, 16 MiB maximum), preserve the normal PDF/NUL checks, and are revalidated with a file fingerprint immediately before use. Approval and resource claims still run before consumption, including external-root access.
Correctness audit
This revision fixes and covers:
The edge cases are covered in dedicated speculation, interpreter, filesystem, loop, dialect, provider, and CLI tests.
Benchmarks
Built with GHC 9.10.3 and
-O2; each result is forced and measured with+RTS -T -N1. Fixtures are created outside the measured interval, while the configured tail deliberately models argument-streaming time still available for overlap. All compared modes produced identical checksums.Isolated
read_file15 samples with a 10 ms remaining stream tail:
Completed speculation reduced median wall time by 8.8% at 1 MiB and 42.1% at 8 MiB. The representative 8 MiB miss path remained effectively at baseline.
Mixed coding turn
The benchmark replays
list_dir, fourread_filecalls,search_replace, andapply_patchagainst 8 MiB edit targets (31 samples):Speculation allocated about 2.9 MiB more per mixed turn (roughly 2.9%). Median CPU varied from +0.3 ms to +7.8 ms, as expected for work moved ahead and overlapped rather than eliminated.
Validation
masteratcd37be69; it is an ancestor of headea29944fagent-core: 542agent-cli: 1,459agent-tui: 195packages/agent-core/package.nixregenerated and checkedgit diff --check