Skip to content

feat(opencode): /claude-start — warm a session's cache lane with one synthetic turn - #160

Open
iceteaSA wants to merge 2 commits into
cortexkit:mainfrom
iceteaSA:feat/claude-start-upstream
Open

feat(opencode): /claude-start — warm a session's cache lane with one synthetic turn#160
iceteaSA wants to merge 2 commits into
cortexkit:mainfrom
iceteaSA:feat/claude-start-upstream

Conversation

@iceteaSA

@iceteaSA iceteaSA commented Aug 20, 2026

Copy link
Copy Markdown
Contributor

Adds /claude-start (OpenCode-only): fire one synthetic, one-token turn through the current session's normal pipeline, so a resumed session's prompt cache gets re-warmed and its TTL clock refreshed without the operator typing a turn. The manual habit it replaces is typing "stand by" into a session after a restart.

Stacked on #159 — the branch is #159's head plus one squashed lane-start commit (the start turn emits source:"start" diagnostics records and rides #159's dump-handle machinery, so it cannot build without it). Review here = the single commit after feat(opencode): capture cache-diagnostics…; once #159 merges, this rebases to a lane-start-only diff.

How it works

  1. /claude-start (palette or typed) queues one synthetic prompt into the session via the SDK — parts:[{type:"text", text:"[lane start] — automated cache warm; no response needed.", synthetic:true}], with the session's current model/agent/variant resolved from message history. OpenCode assembles the request exactly as a real turn — no stored bodies, no staleness.
  2. The fetch layer correlates the request by its synthetic message id (exact-match, one-shot, session-scoped, bounded map) and shapes it: max_tokens: 1, thinking stripped, streaming kept. Correlation is by message identity, not text, so a real operator turn in flight can never be shaped — the race is pinned by tests binding two sessions and an interleaved real turn with different payloads.
  3. The turn rides the full pipeline: sticky routing, killswitch/quota checks, cache strategy, relay or direct, cch signing, diagnostics opt-in. The response's terminal stop_reason: "max_tokens" is rewritten to end_turn for the correlated OAuth-served turn only, so the session records a clean micro-turn instead of a length error.
  4. CacheKeep adopts the session afterward, so keepalive continues without any operator turn. Dumps (when enabled) tag start requests with a distinct -start- marker on both direct and relay paths.

/claude-start automatic is reserved: it replies Automatic lane start is not yet wired in this build; no setting was changed. (no racy event-hook trigger shipped). /claude-start off persists the flag and emits the same command-audit INFO record as the other persistent claude-* actions. Pi gets no command — it has no session SDK to inject a turn.

Verified live

Two production runs on a ~460K-token session, records from the cache-diagnostics stream:

start  read=459,602  write=419   server_null   (hit-shaped; TTL refreshed, no operator turn)
turn   read=460,021 = 459,602+419             (next real turn reads exactly what start warmed)

The second line is the acceptance gate for Anthropic's prompt-affecting-config caveat: shaping (max_tokens/thinking changes) does not fork the cache key — the real turn hit everything the start turn touched. Cost per start on that session: ~$0.48 (prefix read + marker-delta write) vs ~$9.26 for a cold rewrite if the entry had expired.

API-key boundary

Shaping, dump tags, diagnostics source, and the finish rewrite apply to OAuth routes only. API-key fallback routes send ordinary bodies even with a start pending, and their responses are never rewritten — pinned by tests (fallback-first ordinary body; API-key-fails→OAuth-fallback leg does shape; API-key-served response bytes untouched).

Gates

bun run test full suite green (0 fail) · typecheck clean · lint clean · e2e 26/27 (the one failure is the pre-existing tool-prefix.test.ts stale-Opus-bridge case, which fails identically at the base commit — unrelated to this diff).

Tests written red-first throughout; every guard was proven to redden by breaking its seam (correlation module absent, shaping reversed, one-shot leak into the API-key path, no-op clearSession, finish-rewrite removed).

Greptile Summary

The PR adds an OpenCode-only /claude-start command that injects a correlated synthetic one-token turn to warm the current session’s cache lane.

  • Adds lane-start command parsing, persistence, session-context resolution, and synthetic prompt injection.
  • Correlates synthetic message IDs so shaping and response rewriting apply only to the intended OAuth request.
  • Extends cache diagnostics and request/response dump artifacts for ordinary turns, lane starts, and CacheKeep prewarms.
  • Adds focused coverage for routing, fallback, diagnostics, dump tagging, command logging, and concurrent sessions.

Confidence Score: 5/5

The PR appears safe to merge.

No blocking failure remains; both previously reported issues are fixed at the current HEAD.

Important Files Changed

Filename Overview
packages/opencode/src/index.ts Integrates /claude-start into command handling and emits the persistent-off audit record after successful persistence.
packages/opencode/src/lane-start.ts Resolves session context, queues the synthetic prompt, and tracks bounded one-shot message correlations.
packages/opencode/src/transform.ts Shapes correlated OAuth lane-start requests and preserves ordinary behavior for unrelated or API-key traffic.
packages/opencode/src/cache-diagnostics.ts Adds versioned cache-diagnostic request tracking, response parsing, and source classification.
packages/core/src/cachekeep.ts Adds body preparation, response observation, and sanitized dump artifacts to CacheKeep prewarms.
packages/core/src/dump.ts Adds tagged dump handles and sanitized response metadata artifacts with sweep support.
packages/core/src/start.ts Defines lane-start command parsing and user-facing status, usage, queued, and disabled responses.
README.md Documents the OpenCode lane-start command and contains no remaining conflict markers.
packages/opencode/README.md Documents lane-start behavior and cache-diagnostic records without remaining conflict markers.

Sequence Diagram

sequenceDiagram
  participant User
  participant Command as /claude-start
  participant SDK as OpenCode SDK
  participant Fetch as Plugin fetch pipeline
  participant Route as OAuth routing/relay
  participant Anthropic
  User->>Command: Invoke explicit lane start
  Command->>SDK: Queue synthetic prompt with current session context
  SDK->>Fetch: Send normal session request
  Fetch->>Fetch: Match synthetic message ID once
  Fetch->>Fetch: "Set max_tokens=1 and strip thinking"
  Fetch->>Route: Run normal quota, routing, cache, and signing pipeline
  Route->>Anthropic: Stream OAuth request
  Anthropic-->>Route: Terminal response
  Route-->>Fetch: Response and cache diagnostics
  Fetch->>Fetch: Rewrite correlated max_tokens stop to end_turn
  Fetch-->>SDK: Clean synthetic micro-turn
  Fetch->>Fetch: Adopt session for CacheKeep
Loading

Reviews (4): Last reviewed commit: "feat(opencode): /claude-start — warm a s..." | Re-trigger Greptile

Comment thread README.md Outdated
Comment thread packages/opencode/src/index.ts
@iceteaSA
iceteaSA force-pushed the feat/claude-start-upstream branch from 0ba748d to 64ee83f Compare August 20, 2026 20:01

@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.

Review completed against the latest diff

Tip: cubic can generate docs of your entire codebase and keep them up to date. Try it here.

Re-trigger cubic

Comment thread packages/core/src/dump.ts Outdated
Comment thread packages/opencode/src/index.ts
Comment thread packages/opencode/src/cache-diagnostics.ts Outdated
Comment thread packages/opencode/src/tests/index.test.ts
Comment thread packages/opencode/src/tests/cachekeep.test.ts
…per request

The plugin has sent cache-diagnosis-2026-04-07 in its beta list on every
request without enabling the feature: the beta requires a request-body
opt-in (diagnostics.previous_message_id), and nothing sent it or read
the response. Turn the dormant channel on, measure-only.

Request side: eligible OAuth requests gain the diagnostics opt-in inside
the fail-closed rewriteRequestBody pipeline. The id sent is only ever
one captured from a prior Anthropic response (bounded per-session
tracker) - opencode mints its own msg_01-shaped ids for every provider,
and a foreign id fails silently as previous_message_not_found.

Response side: the existing SSE wrapper exposes message_start.message
through a typed callback; per valid eligible response one MC-CACHE-DIAG
single-line JSON record (schema v:2) is emitted via the logger with
verbatim usage, TTL-bucket accounting, diag_state
(absent|server_null|pending|populated, always a string), populated
cache_miss_reason.type, and attribution fields: source (open set) +
synthetic (closed machinery/traffic split with published mapping),
account_id (opaque persisted identifier; consumer timelines key on
(account_id, prefix) because sticky routing migrates sessions across
account-scoped caches), betas_hash (xxh64 of the sorted sent beta list,
resolved by a once-per-hash MC-CACHE-DIAG-BETAS side-channel line), and
requested_model present only on request/served divergence.

Dumps: responses gain artifacts (status/id/model/usage/diagnostics,
never content); CacheKeep prewarms are dumped tagged -prewarm-cachekeep-
and emit records through the same chain, since any write resets the
server TTL clock.

Canary: short-gap previous_message_not_found with a genuinely-sent id
logs a warn - the id capture broke, not a fingerprint expiry.

Every per-chunk stateful consumer in the response wrapper is bounded at
8 MiB with drain-before-cap semantics; complete frames are processed
before overflow passthrough engages.

Verified live: null(write=24831) -> null(read=24831, hit) ->
system_changed(cache_missed_input_tokens=23963) on a forced system
change against a warm prefix. Comparison engages only on cacheable
requests; consumers classify hit-first (documented in README, which is
the record contract).

Closes cortexkit#157
…synthetic turn

Adds an OpenCode-only /claude-start command: queue one synthetic
one-token turn through the current session's normal model, agent,
variant, quota, routing, cache, relay, signing, and response pipeline,
so a resumed session's prompt cache is re-warmed and its TTL clock
refreshed without an operator turn.

The synthetic prompt ([lane start] - automated cache warm; no response
needed.) is injected via the session SDK with synthetic:true and the
session's context resolved from message history; OpenCode assembles the
request exactly as a real turn, so the warm is byte-exact by
construction. The fetch layer correlates the request by its synthetic
message id (exact-match, one-shot, session-scoped, bounded) and shapes
it fail-closed: max_tokens 1, thinking stripped, streaming kept. The
terminal max_tokens stop is rewritten to end_turn for the correlated
turn only, so the session records a clean micro-turn. CacheKeep adopts
the session afterward; dumps tag start requests -start- on direct and
relay paths; diagnostics records carry source:start with synthetic:true.
API-key routes are unreachable by all of this. /claude-start off is
persisted (and logged to the command audit trail); automatic is
reserved and replies honestly that it is not yet wired.

Verified live on a ~460K-token session: start read=459,602 write=419;
next real turn read=460,021 = start read + write exactly - shaping does
not fork the cache key. ~$0.48 per start vs ~$9.26 for a cold rewrite.

Stacked on the cache-diagnostics capture branch (cortexkit#159).
@iceteaSA
iceteaSA force-pushed the feat/claude-start-upstream branch from 64ee83f to c1f9bfb Compare August 20, 2026 20:19
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