perf(opencode): overlap the pre-turn snapshot with the provider request

Closed
PI
2mo ago
+25-10

Issue for this PR

Issues are disabled on this fork; motivation below. Stacked on #26; re-target after it merges.

Type of change

  • Refactor / code improvement

What does this PR do?

SessionProcessor.create awaited snapshot.track() — a chain of git subprocesses (init/add/write-tree) over the worktree — before the provider stream could start, putting the whole git cost inside time-to-first-token on every step. The original eager await existed because the AI SDK can execute tools before emitting step events, so the snapshot must exist before any tool mutates files — a data-ordering constraint, not a "before the provider request" constraint.

Now the track forks (Effect.forkIn(scope)) and the stream starts immediately; an idempotent awaitSnapshot on the processor handle is the barrier, taken at exactly the consumption points: the shared tool execute wrapper (before any tool runs — the case the original comment warned about) and step-start. The provider's TTFT absorbs the git work. snapshot.track is internally mutex-locked, so the forked fiber serializes safely against the step-finish re-track; orDie keeps a git failure failing the turn as before, and forkIn(scope) ties the fiber to the turn's scope so interruption still cancels it. snapshot_ms (#25) now reports the true concurrent git cost rather than serial prep.

How did you verify your code works?

snapshot-tool-race.test.ts (the purpose-built test for this exact barrier) passes, plus test/snapshot 55/55, and prompt.test.ts + compaction.test.ts + retry.test.ts 145/145. bun run typecheck clean, bun run lint 0 errors. Fleet-wide effect visible post-release as prep_ms dropping while snapshot_ms holds steady.

Checklist

  • I have tested my changes locally
  • I have not included unrelated changes in this PR

šŸ¤– Generated with Claude Code


Summary by cubic

Overlaps the pre-turn snapshot with the provider request to reduce time-to-first-token while preserving tool ordering. Previously we awaited the snapshot before starting the provider stream; now we track the snapshot concurrently and gate tool execution and step-start behind an idempotent awaitSnapshot.

  • Adds awaitSnapshot to SessionProcessor.Handle; tracks the snapshot in a forked fiber bound to the turn scope, preserving failure and cancellation semantics.
  • Gates tool execution via SessionTools.resolve and step-start on awaitSnapshot, ensuring tools cannot mutate files before the snapshot lands.
  • Maintains snapshot serialization via the internal mutex; snapshot_ms measures the concurrent git cost, and prep_ms should drop accordingly.

Review notes

  • Focus on concurrency boundaries: forkIn(scope), Fiber.join, and the cached result on the handle.
  • If tests construct fake processors, provide awaitSnapshot (e.g., Effect.succeed(undefined)).