Skip to content

feat(ci): working aimock drift detection - #846

Merged
blove merged 8 commits into
mainfrom
blove/aimock-drift-detection
Aug 29, 2026
Merged

feat(ci): working aimock drift detection#846
blove merged 8 commits into
mainfrom
blove/aimock-drift-detection

Conversation

@blove

@blove blove commented Aug 29, 2026

Copy link
Copy Markdown
Contributor

The drift check had never run. drift.ts shelled out to llmock --record ... --out, and --out is not a declared CLI option (strict parseArgs; the set is port, host, fixtures, latency, watch, metrics, record, strict), so every invocation died with ERR_PARSE_ARGS_UNKNOWN_OPTION before comparing anything. Two deeper problems sat underneath: a fixture entry is only match + response, so it cannot be "re-recorded" in isolation — the request is built by the graph at runtime; and byte-size comparison cannot see meaning drift.

Design: docs/superpowers/specs/2026-08-29-aimock-drift-detection-design.md

The two-stage replacement

Stage 1 — the gate. Three contract-only specs tagged @drift (non-empty reply, research dispatch surfaces a subagent card, interrupt panel renders) run against the live provider through aimock's record-proxy — LLMock supports record: { providers, fixturePath } natively, no CLI. The assertions are the semantic contract: if the graph's prompts stop eliciting the tool call, a spec fails. Content-asserting specs (canned text, exact counts) are deliberately untagged — they fail live regardless of drift.

Stage 2 — the diagnostic. A rewritten differ pairs the run's recordings against committed fixtures by match discriminator and reports tool-name sets, response kind, and a coarse length bucket. Always exit 0 — the specs are the gate; this explains a red run.

Workflow: weekly cron (Mon 09:00 UTC) + workflow_dispatch, recordings uploaded as an artifact on every run, issue opened on failure with the diff in the body (and the trigger named, fixing the old always-says-scheduled text). Advisory only — never a merge gate. Committed fixtures are never modified.

Found while building it

The first record run reported toolCalls -> text on both tool-calling entries — while the specs proving those tools fired were green. The aimock recorder's stream-collapse couldn't parse our client's tool-call deltas and saved { content: "" } with its own "fixture may be incomplete" warning. The differ now reports these in a dedicated incompleteRecordings category so a recorder artifact can't masquerade as model drift. (Recordings do preserve real per-chunk timings — interChunkDelaysMs — which may be useful later.)

Acceptance (spec requirement: one green run + one artifact before merge)

  • Local record run: 3/3 passed against the live model, 5 recordings, changed=0 incompleteRecordings=2 unmatchedCommitted=15 unmatchedRecorded=0 (unmatched-committed are fixtures for untagged specs — a report, not a failure).
  • CI dispatch on this branch: run 33269989119success, aimock-recordings artifact (5,080 bytes), identical differ output.
  • Differ unit tests: 7/7 (npx tsx --test e2e/scripts/drift-lib.test.ts).
  • Replay path untouched: @drift subset passes in replay (3/3); default mode unchanged.

🤖 Generated with Claude Code

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Automated approval: this PR received an intelligent (AI) code review. See the review comments on this PR.

@vercel

vercel Bot commented Aug 29, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
threadplane Ready Ready Preview Aug 29, 2026 7:20pm

Request Review

@github-actions

github-actions Bot commented Aug 29, 2026

Copy link
Copy Markdown
Contributor

Claude finished @blove's task in 0s —— View job


I'll analyze this and get back to you.

blove and others added 8 commits August 29, 2026 12:16
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…from drift

The aimock recorder emits { content: '' } with a 'fixture may be
incomplete' warning when it cannot parse tool-call deltas from a stream.
Observed live: both tool-calling entries in the first record run collapsed
to empty text while the @drift specs passed. A recorder artifact, not
model drift — reported in its own category so it cannot masquerade as a
kind change.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@blove
blove force-pushed the blove/aimock-drift-detection branch from d4b2258 to f59af54 Compare August 29, 2026 19:16
@blove
blove enabled auto-merge (squash) August 29, 2026 19:16
@github-actions

github-actions Bot commented Aug 29, 2026

Copy link
Copy Markdown
Contributor

Claude finished @blove's task in 0s —— View job


I'll analyze this and get back to you.

@blove
blove merged commit 3afa63e into main Aug 29, 2026
24 checks passed
blove added a commit that referenced this pull request Aug 29, 2026
* docs: design spec for the fixture-replay blog post

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* docs: tighten the scale claim in the fixture-replay spec

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* docs: implementation plan for the fixture-replay post

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* feat(website): scaffold the fixture-replay post

* feat(website): write the fixture-replay post

Body across all sections, plus two validation fixes:
- Rewrote the transcript-settling sentence, which had 6 eight-grams of
  overlap with the subgraphs docs guide (same author, two days apart).
- Restored Let's transitions and trimmed demonstrative tics.

Dated 2026-08-29 (plan said 08-28; the date rolled and siblings date to
their merge day).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* fix(website): apply editorial review to the fixture-replay post

- Remove a self-contradiction the thesis forbids: the conclusion claimed
  'both have a plan' while the body says the drift check cannot see meaning
  drift. Only one of the two has a plan; say so.
- Answer the obvious objection (replay the recorded chunk boundaries):
  faithful boundaries make the fence failure deterministic, not absent.
- Defend the trade by displacement, not documentation — progressive
  rendering moved to the unit-level variance tables, which do test it.
- Name the drift check as the second instance of the law, earning 'every'
  in the opening line.
- Attribute affordability to replacing the model, not to the seam position.
- Link the subgraphs post as the source of the worked example; 1 link -> 4.
- Register: trim idiom, intensifiers, and doubled phrasing.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* fix(website): correct three false claims in the fixture-replay post

Spec-compliance review found the post overstated its own thesis. All
verified in source:

- chunkSize 4096 is a DEFAULT, not a law. A second harness serves the two
  example apps, and its comment says targeted streaming regressions opt
  into smaller per-fixture chunks — those fixtures set 3/4/6/23/36 with
  25-750ms latency, and there are e2e tests sampling the mid-stream DOM.
  'We deleted time' was true of the cockpit tier only. Reframed around
  which tier opted back in, which is a better question anyway.
- 'spawns a real langgraph dev subprocess' is false for 8 of 34 apps; the
  ag-ui setup spawns uvicorn. The seam generalizes, the server doesn't.
- The drift check does not run at all: it shells out to ,
  which is not a declared option under strict parseArgs, so it exits
  before comparing anything. The post said it re-records and compares.
  Now says what is true.
- Quote the full source comment rather than half of it; the omitted lines
  were what the following paragraphs presented as original analysis.
- Drop the 60ms/56-samples figures — real, but from an ad-hoc session and
  unverifiable from the repo.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* feat(website): drift section now describes the shipped solution

The check went from 'has never run' to merged and proven (#846) between
drafts. §5 now carries the real design: a @drift-tagged contract-only
subset of the e2e suite runs weekly against the live provider through the
record proxy — the assertions are the drift check, so meaning drift is
caught by construction. Includes the first run's finding: the differ
reported tool-call drift that was actually the recorder failing to parse
tool-call deltas — the instrument's own blind spot, now its own category.

Conclusion updated to match: both gaps covered by the same move, widening
is tagging specs, not new design.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* fix(website): apply re-review findings to the drift section

- Keystone stands alone as its own paragraph; third restatement cut.
- Anecdote no longer announces itself; actor fixed (the recorder warns,
  not the check); adds that it has run clean against the live model since.
- Conclusion scoped honestly: widening drift = tagging specs plus porting
  record mode to the shared harness; widening streams = per-fixture chunk
  opt-in. Both reviews independently flagged the original overclaim.
- Tagging rule examples now match what the specs actually assert
  (prompt-derived patterns, not shape-only).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* style(website): pull the fixture-replay post back to Brian's register

- Soften first-person discovery narration to impersonal constructions.
  The events are real, but they were my discoveries narrated under
  Brian's byline; the claims now belong to the work, not a narrator.
- Restore the flagged opinion on the streaming trade ('I think that's
  the right trade') — the post's strongest judgment was reading as fact,
  and siblings carry 1-2 flags.
- Fix bold in body prose (italics-only convention).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
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