Skip to content

refactor(opencode): cut per-step turn prep - #29

Merged
PierrotAWB merged 8 commits into
devfrom
turn-instruction-once
Aug 24, 2026
Merged

PierrotAWB merged 8 commits into
devfrom
turn-instruction-once

Conversation

@PierrotAWB

@PierrotAWB PierrotAWB commented Aug 24, 2026 •

Copy link
Copy Markdown
Collaborator

Issue for this PR

Issues are disabled on this fork. Consolidates #23, #25, #26, #27, #28 (closed in favor of this) — one commit per concern, individually revertable.

Type of change

  • Bug fix
  • Refactor / code improvement

What does this PR do?

Five commits attacking per-step turn prep, in order:

  1. prep_ms + ttft_step on turn.done — the derived overhead (ttft − provider_ttft) silently absorbed tool wall-clock on tool-first turns (firstTokenAt only set on text/reasoning, handle resets per step): prod sample showed a 192s "overhead" on a 217.9s turn. prep_ms (turnStart → first provider request, first-attempt stamped so retry backoff is excluded — cubic review, addressed) is correct by construction; ttft_step flags cross-step pairs. Additive; no existing field changes.
  2. Phase breakdown — history_ms / tools_ms / context_ms / snapshot_ms, so each following commit has a fleet-wide before/after.
  3. History once per turn — runLoop reloaded and re-hydrated the whole session from SQLite every step (O(history × steps)). The runner lock makes the turn the single writer, so: load once, then fetch only rows with id > last-seen (new MessageV2.after, ids are monotonic) — which also preserves mid-turn queued user messages exactly as today — with a full reload whenever the tail contains compaction/summary rows.
  4. Snapshot overlap — the pre-stream snapshot.track() git chain forks and runs during provider TTFT; an idempotent awaitSnapshot joins at the real constraint points (tool execute wrapper + step-start). track is mutex-locked so the fiber serializes safely; orDie + forkIn(scope) keep failure/interruption semantics.
  5. Instructions once per turn — the AGENTS.md findUp walk (and any URL instruction fetch with its 5s timeout) ran per step; now per turn. Deliberate: a turn's own instruction edits apply next turn, like skills/config.

How did you verify your code works?

Full packages/opencode suite over the stack: 3129 pass; only failures are the pre-existing Truncate date bug (fixed in #24) and a pty test that passes 9/9 in isolation (full-suite load flake). Purpose-built coverage: new turn.done fields test (scripted tool-first two-step turn: ttft_step === 2, prep_ms ≤ ttft_ms, phase fields ≥ 0); snapshot-tool-race.test.ts guards the barrier; multi-step/subtask/question/compaction suites guard history threading. bun run typecheck clean, bun run lint 0 errors.

Checklist

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

🤖 Generated with Claude Code


Summary by cubic

Cuts per-step turn prep and makes turn.done timing accurate. Previously we reloaded history and instructions every step and misattributed prep/TTFT on tool-first turns; now we cache per turn, overlap the snapshot with provider TTFT, use fresh per-step snapshots, serialize the snapshot barrier so concurrent tools share one baseline, and emit honest, additive timing fields.

  • Adds prep_ms (turn start → first provider request), ttft_step (step index for TTFT), and per-phase timings: history_ms, tools_ms, context_ms, snapshot_ms. Stamps the first request once so retry backoff is excluded.
  • Loads session history once per turn and appends new rows via MessageV2.after(sessionID, time, id) (index-aligned on (time_created, id)); falls back to full recompute if the tail contains compaction/summary rows.
  • Overlaps the pre-turn snapshot.track() with the provider request; tools (including MCP) and step-start events block on an awaitSnapshot barrier. Uses a fresh snapshot per step after the first join and serializes awaitSnapshot so concurrent tools share one baseline.
  • Resolves instruction files once per turn; instruction edits during a turn take effect next turn.
  • Caches before reminders to avoid duplicating synthetic reminder parts across steps.

Review focus

  • packages/opencode/src/session/prompt.ts: per-turn history cache with after(), pre-reminder cache, timing aggregation and turn.done fields, per-turn instruction resolution, TTFT step capture.
  • packages/opencode/src/session/processor.ts: snapshot overlap and serialized awaitSnapshot barrier (KeyedMutex); fresh per-step snapshots; first-request stamping to exclude retry backoff; exposes snapshotMs.
  • packages/opencode/src/session/message-v2.ts: new after(sessionID, time, id) query using (time_created, id) for index alignment.
  • packages/opencode/src/session/tools.ts: built-in and MCP tools await the snapshot before execution.

Rollout notes

  • No migrations. Event fields are additive; update dashboards to consume prep_ms, ttft_step, and phase metrics.
  • Behavior change: per-turn instructions and a serialized snapshot barrier. Edits made during a turn apply next turn; tools wait for the snapshot before mutating files.

Written for commit 243bcaa. Summary will update on new commits.

Review in cubic

PierrotAWB and others added 6 commits August 23, 2026 18:46
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ry backoff

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…l per step

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown

Hey! Your PR title perf(opencode): cut per-step turn prep — honest timing fields, history cache, snapshot overlap, per-turn instructions doesn't follow conventional commit format.

Please update it to start with one of:

  • feat: or feat(scope): new feature
  • fix: or fix(scope): bug fix
  • docs: or docs(scope): documentation changes
  • chore: or chore(scope): maintenance tasks
  • refactor: or refactor(scope): code refactoring
  • test: or test(scope): adding or updating tests

Where scope is the package name (e.g., app, desktop, opencode).

See CONTRIBUTING.md for details.

@github-actions

Copy link
Copy Markdown

The following comment was made by an LLM, it may be inaccurate:

@cubic-dev-ai cubic-dev-ai Bot left a comment •

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All reported issues were addressed across 6 files

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

Comment thread packages/opencode/src/session/tools.ts Resolved
Comment thread packages/opencode/src/session/prompt.ts Resolved
…che, index-aligned after()

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@PierrotAWB PierrotAWB changed the title perf(opencode): cut per-step turn prep — honest timing fields, history cache, snapshot overlap, per-turn instructions refactor(opencode): cut per-step turn prep Aug 24, 2026

@cubic-dev-ai cubic-dev-ai Bot left a comment •

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All reported issues were addressed across 4 files (changes from recent commits).

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

…baseline

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@PierrotAWB
PierrotAWB merged commit e58b47c into dev Aug 24, 2026
11 of 13 checks passed
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