Skip to content

Commit 393de1e

Browse files
skill: add Partial Prefetching optimizer (#96471)
## Summary - add a Partial Prefetching optimizer Skill for selected source Links and destinations - keep prefetch stages and Link policy in the public [Optimizing prefetching guide](https://nextjs.org/docs/app/guides/optimizing-prefetching), and rely on the same static-shell documentation as the Cache Components optimizer for cache placement, Suspense, loading UI, and authorization - keep the Skill focused on the product decision, production `instant()` loop, differential verification, and mutation freshness when an optimization adds a cache around mutable data - reference the canonical [`unstable_prefetch()`](https://nextjs.org/docs/app/api-reference/functions/prefetch) and [`unstable_navigation()`](https://nextjs.org/docs/app/api-reference/functions/navigation) docs instead of explaining the APIs again in the Skill - link cache-writing optimizations to the canonical [Revalidating](https://nextjs.org/docs/app/getting-started/caching-and-revalidating#revalidating) documentation and verify a populated-cache mutation in the Skill and eval - bundle a self-contained production rig aligned with the related adoption and optimizer Skills, tuned to record each source-link and prefetch-stage contract - add an eval that compares baseline, the bundled guide, and the local Skill against the same prefetched-UI and cache-freshness goal - document the optimizer in the AI agents guide This follows the same guide/Skill split as #97737. Partial Prefetching adoption preserves existing behavior; this optimizer is the later decision about what is worth preparing before a click and what should wait for navigation. Related: #97712, #97737, #97759, #98122, #98175 ## Eval One run per treatment with Claude Opus 4.8 and Claude Sonnet 4.5, using a fixed Claude Haiku 4.5 judge. The bundled-guide and Skill treatments use a local Next.js tarball containing the new guide and API references. | Model | Treatment | Criteria | Score change | Overall | Duration | Tokens processed | Total run cost | Cost change | | --- | --- | ---: | ---: | ---: | ---: | ---: | ---: | ---: | | Opus 4.8 | Baseline | 10/10 (100%) | — | Pass | 18m 42s | 3.39M | $3.46 | — | | Opus 4.8 | Bundled guide | 9/10 (90%) | -10 pp | Fail | 13m 37s | 3.02M | $2.84 | -18.0% | | Opus 4.8 | Local Skill | 10/10 (100%) | +10 pp | Pass | 17m 22s | 2.80M | $2.54 | -10.6% | | Sonnet 4.5 | Baseline | 2/10 (20%) | — | Fail | 15m 55s | 1.97M | $1.06 | — | | Sonnet 4.5 | Bundled guide | 5/10 (50%) | +30 pp | Fail | 15m 49s | 2.17M | $1.11 | +4.5% | | Sonnet 4.5 | Local Skill | 8/10 (80%) | +30 pp | Fail | 15m 31s | 1.41M | $0.77 | -30.4% | Token totals count each unique model message once and include uncached input, cache creation, cache reads, and output. Cache reads account for most of the total in every treatment. Total run cost applies the current AI Gateway base rates to each token category: Opus 4.8 costs $5/M input, $6.25/M cache writes, $0.50/M cache reads, and $25/M output; Sonnet 4.5 costs $3/M input, $3.75/M cache writes, $0.30/M cache reads, and $15/M output. The six agent runs cost $11.78 in total. Compared with baseline, the Opus Skill run maintained a 100% criteria pass rate while reducing cost by 26.7%. The Sonnet Skill run improved the criteria pass rate from 20% to 80%, a 60 percentage-point increase, while reducing cost by 27.3%. The stronger model could solve the fixture without the Skill, although it independently searched the bundled Next.js documentation and took slightly longer than the Skill treatment. The Opus guide-only run implemented the correct behavior but missed the required RED-before-GREEN proof. The Sonnet treatments show a clearer progression. Baseline did not implement the required caching, navigation stage, `instant()` contract, or cache invalidation. The bundled guide added the cached summary and production test, but missed the navigation stage, deferred-state assertions, tag invalidation, and RED-before-GREEN proof. The Skill treatment completed the `instant()` RED-to-GREEN loop and final production behavior, but left `connection()` in the related-sessions read and used path revalidation instead of tagging the cached read. It therefore passed 8 of 10 criteria but failed the overall eval. All six measured runs completed through grading. Chromium and its system dependencies are provisioned before the agent starts, so missing browser libraries did not determine these results. ## Verification - `pnpm --filter=next build` - `pnpm prettier --check` for the changed guide, prompt, Skill, and eval fixture - `pnpm exec tsc -p evals/evals/agent-051-optimize-partial-prefetching/tsconfig.json --noEmit` - `node --check run-evals.js` - `git diff --check` - packed a local Next.js tarball and confirmed it contains the optimizer guide plus the `navigation()` and `prefetch()` references - verified the `navigation()` contract in the minimal reproduction and NextBeats on Next.js 16.4.0-canary.22 - merged the latest `canary`, preserving both generic fixture setup and Playwright provisioning <!-- NEXT_JS_LLM -->
1 parent 6fbba16 commit 393de1e

29 files changed

Lines changed: 1276 additions & 126 deletions

File tree

‎docs/01-app/01-getting-started/08-caching.mdx‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ The [`use cache`](/docs/app/api-reference/directives/use-cache) directive caches
4747
- **Data-level**: Cache a function that fetches or computes data (e.g., `getProducts()`, `getUser(id)`)
4848
- **UI-level**: Cache an entire component or page (e.g., `async function BlogPosts()`)
4949

50-
A cache directive gives a result a lifetime, information Next.js uses to apply rendering optimizations. See [Prerendering](#prerendering) for how cached results become part of the static shell and may be included in a [prefetch](#prefetching).
50+
A cache directive gives a result a lifetime, which Next.js uses for time-based revalidation and rendering optimizations. For on-demand revalidation after a mutation, see [Revalidating](/docs/app/getting-started/revalidating). See [Prerendering](#prerendering) for how cached results become part of the static shell and may be included in a [prefetch](#prefetching).
5151

5252
> **Good to know:** We recommend pairing every cache directive with a [`cacheLife`](/docs/app/api-reference/functions/cacheLife). Without one, the implicit `default` profile applies.
5353

‎docs/01-app/02-guides/adopting-partial-prefetching.mdx‎

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,30 @@ A legacy full prefetch can include more UI than the navigation needs. In most ca
8989
9090
### Verify prefetched UI with tests
9191

92-
To verify the migration programmatically, an [`instant()`](/docs/app/guides/instant-navigation#prevent-regressions-with-e2e-tests) test can capture the UI that should stay available from an existing full prefetch. Run this baseline against a production build with `partialPrefetching` disabled because automatic prefetching doesn't run in `next dev`. Then rerun the same assertions after adopting the destination. A failure identifies UI the App Shell no longer carries, so you can cache the data or subtree that restores it. The passing test provides regression coverage.
92+
To verify the migration programmatically, an [`instant()`](/docs/app/guides/instant-navigation#prevent-regressions-with-e2e-tests) test can capture the UI that should stay available from an existing full prefetch. Test the exact source link because different links to the same destination can use different prefetch settings:
93+
94+
```ts filename="e2e/product-navigation.test.ts" highlight={7-16}
95+
import { expect, test } from '@playwright/test'
96+
import { instant } from '@next/playwright'
97+
98+
test('preserves the prefetched product summary', async ({ page }) => {
99+
await page.goto('/products')
100+
101+
await instant(page, async () => {
102+
const link = page.getByRole('link', { name: 'Baseball Cap' })
103+
await expect(link).toBeVisible()
104+
await link.click()
105+
await page.waitForURL((url) => url.pathname === '/products/baseball-cap')
106+
107+
await expect(
108+
page.getByRole('heading', { name: 'Baseball Cap' })
109+
).toBeVisible()
110+
await expect(page.getByTestId('product-summary')).toBeVisible()
111+
})
112+
})
113+
```
114+
115+
Run this baseline against a production build with `partialPrefetching` disabled because automatic prefetching doesn't run in `next dev`. Then rerun the same test after adopting the destination. If it fails, the selected UI is no longer prefetched. Cache the reusable data or subtree that should stay available, then keep the passing test as regression coverage.
93116

94117
### Static or cached content
95118

‎docs/01-app/02-guides/ai-agents.mdx‎

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -237,3 +237,23 @@ Then give the agent this prompt:
237237
```prompt
238238
Adopt Partial Prefetching in this project using the next-partial-prefetching-adoption Skill.
239239
```
240+
241+
### `next-partial-prefetching-optimizer`
242+
243+
The [`next-partial-prefetching-optimizer`](https://www.skills.sh/vercel/next.js/next-partial-prefetching-optimizer) Skill takes a target navigation or set of navigations and works toward the UI you want ready before each click:
244+
245+
1. Turns the requested behavior for each navigation into a contract for what is prefetched, what waits for navigation, and when prefetching begins.
246+
2. Captures each contract in an [`instant()`](/docs/app/guides/instant-navigation#prevent-regressions-with-e2e-tests) test.
247+
3. Applies the [Optimizing prefetching](/docs/app/guides/optimizing-prefetching) patterns and keeps the passing tests as regression coverage.
248+
249+
It needs Cache Components and Partial Prefetching already adopted.
250+
251+
```bash filename="Terminal"
252+
npx skills add vercel/next.js --skill next-partial-prefetching-optimizer
253+
```
254+
255+
Then give the agent a prompt like:
256+
257+
```prompt
258+
Make the product title instant when navigating from /products to /products/[id]. Use the next-partial-prefetching-optimizer Skill.
259+
```

‎docs/01-app/02-guides/optimizing-prefetching.mdx‎

Lines changed: 45 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,15 @@ export default function Home() {
6161
}
6262
```
6363

64-
The destination renders a static heading and a `<Results>` list whose contents depend on the query. Each query is cached, computed once and reused.
64+
`'auto'` is equivalent to omitting `prefetch` or passing `undefined`. In a conditional expression, return `'auto'` for links that should keep the default App Shell prefetch:
65+
66+
```tsx filename="app/page.tsx"
67+
<Link href={href} prefetch={isFeatured ? true : 'auto'} />
68+
```
69+
70+
Use `prefetch={false}` only when you want to disable prefetching for a link.
71+
72+
The `/search` page renders a static heading and a `<Results>` list based on the query. The `search()` function caches results by query so later requests can reuse them.
6573

6674
```tsx filename="app/search/page.tsx"
6775
import { Suspense } from 'react'
@@ -215,6 +223,40 @@ Without it, keeping a subtree out of a prefetch means leaving it uncached, which
215223

216224
For a subtree that would otherwise sit in the App Shell, but that a `<Link prefetch={true}>` should still resolve before the click, use [`unstable_prefetch()`](/docs/app/api-reference/functions/prefetch) instead.
217225

226+
Unlike [`connection()`](/docs/app/api-reference/functions/connection), `unstable_navigation()` does not make the subtree request-dependent. This lets reusable content keep its cache lifetime while waiting for navigation. See [How `unstable_navigation()` differs from `connection()`](/docs/app/api-reference/functions/navigation#how-unstable_navigation-differs-from-connection) for the full comparison.
227+
228+
## Test prefetched and deferred content
229+
230+
The [Instant navigation guide](/docs/app/guides/instant-navigation#prevent-regressions-with-e2e-tests) explains how to install and configure `@next/playwright`. For a client navigation, use `instant()` to inspect the destination's prefetched UI.
231+
232+
When [adopting Partial Prefetching](/docs/app/guides/adopting-partial-prefetching#verify-prefetched-ui-with-tests), run this test before enabling the feature and rerun the same assertions afterward. This preserves the UI selected from the legacy full prefetch while its implementation moves to the App Shell or a per-link prefetch.
233+
234+
Inside the callback, click the link, wait for the destination URL, and assert which content is available. After the callback releases the navigation, assert that deferred content eventually renders:
235+
236+
```ts filename="e2e/product-navigation.test.ts" highlight={8-15,17}
237+
import { expect, test } from '@playwright/test'
238+
import { instant } from '@next/playwright'
239+
240+
test('prefetches the product summary but not reviews', async ({ page }) => {
241+
await page.goto('/products')
242+
243+
await instant(page, async () => {
244+
await page.click('a[href="/products/baseball-cap"]')
245+
await page.waitForURL((url) => url.pathname === '/products/baseball-cap')
246+
247+
await expect(
248+
page.getByRole('heading', { name: 'Baseball Cap' })
249+
).toBeVisible()
250+
await expect(page.getByTestId('product-summary')).toBeVisible()
251+
await expect(page.getByTestId('reviews')).toHaveCount(0)
252+
})
253+
254+
await expect(page.getByTestId('reviews')).toBeVisible()
255+
})
256+
```
257+
258+
While the `instant()` callback is running, dynamic content is paused. In this example, the product summary is part of the prefetched UI, while reviews wait for navigation. The final assertion confirms that the reviews render after the pause is released.
259+
218260
## Trade-offs
219261

220262
Use `prefetch={true}` on routes where:
@@ -223,6 +265,8 @@ Use `prefetch={true}` on routes where:
223265
- That part of the tree has a known cache lifetime (it can be expressed with `"use cache"` or `"use cache: private"`)
224266
- The traffic justifies the per-link server invocation
225267

268+
Prefetching cached data does not change its freshness behavior. If a mutation can update the data, [tag and invalidate the cached result](/docs/app/getting-started/revalidating).
269+
226270
Skip it when the prefetch can't produce a better UI than the App Shell. Each visible `<Link prefetch={true}>` can wake a server, and that cost only pays off if more of the page is ready before the click:
227271

228272
- The route has little or no URL-data dependency. The App Shell already makes the navigation instant.

‎docs/01-app/03-api-reference/04-functions/navigation.mdx‎

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ version: experimental
55
related:
66
links:
77
- app/api-reference/functions/prefetch
8+
- app/api-reference/functions/connection
89
- app/guides/adopting-partial-prefetching
910
- app/api-reference/functions/io
1011
- app/api-reference/config/next-config-js/partialPrefetching
@@ -118,6 +119,14 @@ The slug and the cookie are both read below `unstable_navigation()` and passed a
118119
119120
Put the directive on a function called below it, as `getComments` does here. Awaiting `unstable_navigation()` inside a cached scope, such as [`use cache`](/docs/app/api-reference/directives/use-cache) or [`use cache: private`](/docs/app/api-reference/directives/use-cache-private), is not allowed.
120121
122+
## How `unstable_navigation()` differs from `connection()`
123+
124+
The [`connection()`](/docs/app/api-reference/functions/connection) function waits for a real request. It excludes the code that follows from prerendering and prefetching, making that work request-dependent.
125+
126+
`unstable_navigation()` only excludes the code that follows from route prefetches. It resolves when the user navigates without making that work request-dependent. A function called after `await unstable_navigation()` can therefore use [`"use cache"`](/docs/app/api-reference/directives/use-cache) and keep its cache lifetime without generating its result during prefetching.
127+
128+
Use `connection()` when the work must run for a real request. Use `unstable_navigation()` when the work is reusable but should wait until the user navigates.
129+
121130
## When to use it
122131
123132
Reach for it when the content below `unstable_navigation()` is expensive to produce, in compute, in upstream requests, or in latency, and you do not want every prefetch of the route to generate it.
@@ -158,7 +167,6 @@ async function Stories() {
158167
## Good to know
159168
160169
- The function has no effect on the initial load of a page.
161-
- Unlike [`connection()`](/docs/app/api-reference/functions/connection), it does not mark the subtree as request-dependent. Content below `await unstable_navigation()` stays fully cacheable and can be wrapped in [`use cache`](/docs/app/api-reference/directives/use-cache). It also keeps its cache lifetime.
162170
- Reading `cookies()` or `headers()` below `unstable_navigation()` does not make the route's prefetches render per request, so they can still be served from static output. Those accesses are ignored, because they would not have resolved in a prefetch either.
163171
- A call outside a `<Suspense>` boundary surfaces a validation insight in development. It is reported even on a page that is currently prerendered in full, because the same page starts deferring content as soon as it switches to a runtime shell.
164172
- Calling it inside `use cache`, `use cache: private`, [`unstable_cache()`](/docs/app/api-reference/functions/unstable_cache), [`after()`](/docs/app/api-reference/functions/after), [`generateStaticParams`](/docs/app/api-reference/functions/generate-static-params), or a Client Component throws.

‎docs/01-app/03-api-reference/04-functions/prefetch.mdx‎

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ version: experimental
55
related:
66
links:
77
- app/api-reference/functions/navigation
8+
- app/api-reference/functions/connection
89
- app/guides/adopting-partial-prefetching
910
- app/api-reference/functions/io
1011
- app/api-reference/config/next-config-js/partialPrefetching
@@ -122,6 +123,14 @@ The greeting reads the session and stays in the App Shell. The recommendations r
122123
123124
Put the directive on a function called below it, as `getRecommendations` does here. Awaiting `unstable_prefetch()` inside a cached scope, such as [`use cache`](/docs/app/api-reference/directives/use-cache) or [`use cache: private`](/docs/app/api-reference/directives/use-cache-private), is not allowed.
124125
126+
## How `unstable_prefetch()` differs from `connection()`
127+
128+
The [`connection()`](/docs/app/api-reference/functions/connection) function waits for a real request. It excludes the code that follows from prerendering and prefetching, making that work request-dependent.
129+
130+
`unstable_prefetch()` only excludes the code that follows from the App Shell. It can resolve during a per-link prefetch without making that work request-dependent. A function called after `await unstable_prefetch()` can therefore use [`"use cache"`](/docs/app/api-reference/directives/use-cache) and keep its cache lifetime without generating its result for every link into the route.
131+
132+
Use `connection()` when the work must run for a real request. Use `unstable_prefetch()` when the work is reusable but should only run for a selected per-link prefetch or a navigation.
133+
125134
## When to use it
126135
127136
Reach for it when the content below `unstable_prefetch()` is expensive to produce, in compute, in upstream requests, or in latency, and you do not want it generated in the App Shell that every `<Link>` into the route prefetches.
@@ -139,7 +148,6 @@ When every read of [`cookies()`](/docs/app/api-reference/functions/cookies) and
139148
## Good to know
140149
141150
- The function has no effect on the initial load of a page.
142-
- Unlike [`connection()`](/docs/app/api-reference/functions/connection), it does not mark the subtree as request-dependent. Content below `await unstable_prefetch()` stays fully cacheable and can be wrapped in [`use cache`](/docs/app/api-reference/directives/use-cache).
143151
- A call outside a `<Suspense>` boundary surfaces a validation insight in development. It is reported even on a page that is currently prerendered in full, because the same page starts deferring content as soon as it switches to a runtime shell.
144152
- Calling it inside `use cache`, `use cache: private`, [`unstable_cache()`](/docs/app/api-reference/functions/unstable_cache), [`after()`](/docs/app/api-reference/functions/after), [`generateStaticParams`](/docs/app/api-reference/functions/generate-static-params), or a Client Component throws.
145153

‎evals/eval.config.json‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,10 @@
77
"skills": ["next-cache-components-adoption"],
88
"timeout": 1800
99
},
10+
"agent-051-optimize-partial-prefetching": {
11+
"skills": ["next-partial-prefetching-optimizer"],
12+
"timeout": 1800
13+
},
1014
"agent-054-cache-components-empty-static-params": {
1115
"skills": ["next-cache-components-adoption"],
1216
"timeout": 1800
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
/**
2+
* Optimize one Partial Prefetching navigation
3+
*
4+
* The fixture starts after Cache Components and Partial Prefetching adoption.
5+
* The agent must turn one accepted, URL-specific product goal into an
6+
* instant() RED-to-GREEN loop without expanding the cost to every card.
7+
*/
8+
9+
import { expect, test } from 'vitest'
10+
import { existsSync, readFileSync, readdirSync, statSync } from 'fs'
11+
import { join } from 'path'
12+
import { environment, transcript } from '@vercel/agent-eval/eval'
13+
14+
const IGNORE_DIRS = new Set([
15+
'.git',
16+
'.next',
17+
'node_modules',
18+
'dist',
19+
'build',
20+
'coverage',
21+
])
22+
23+
function readSourceFiles(dir: string): string[] {
24+
if (!existsSync(dir)) return []
25+
26+
return readdirSync(dir).flatMap((entry) => {
27+
if (IGNORE_DIRS.has(entry)) return []
28+
const path = join(dir, entry)
29+
if (statSync(path).isDirectory()) return readSourceFiles(path)
30+
if (entry === 'EVAL.ts' || !/\.(ts|tsx|js|jsx)$/.test(entry)) return []
31+
return readFileSync(path, 'utf-8')
32+
})
33+
}
34+
35+
const source = readSourceFiles(process.cwd()).join('\n')
36+
37+
test('keeps Cache Components and Partial Prefetching enabled', () => {
38+
const config = readFileSync(join(process.cwd(), 'next.config.ts'), 'utf-8')
39+
40+
expect(config).toMatch(/cacheComponents\s*:\s*true/)
41+
expect(config).toMatch(/partialPrefetching\s*:\s*true/)
42+
})
43+
44+
test('adds a click-driven instant navigation regression test', () => {
45+
expect(source).toMatch(/from\s+['"]@next\/playwright['"]/)
46+
expect(source).toMatch(/\binstant\s*\(/)
47+
expect(source).toMatch(/\.click\s*\(/)
48+
})
49+
50+
test('adds reusable cached work', () => {
51+
expect(source).toMatch(/['"]use cache['"]/)
52+
})
53+
54+
test('uses the navigation stage', () => {
55+
expect(source).toMatch(/\b(?:unstable_)?navigation\s*\(/)
56+
})
57+
58+
test('optimizes only the selected featured navigation', async () => {
59+
await expect(environment).toSatisfyCriterion(
60+
`Only the featured Aurora keynote Link from /sessions opts into per-link prefetching with prefetch={true} or the equivalent bare prefetch prop. The other session-card Links retain the default or auto prefetch behavior. The solution does not add prefetch={false} or enable per-link prefetching for every card.`
61+
)
62+
})
63+
64+
test('assigns the destination UI to the intended stages', async () => {
65+
await expect(environment).toSatisfyCriterion(
66+
`The final /sessions/aurora-keynote implementation makes the public title "Aurora Keynote", speaker "Mina Park", and summary reusable through a focused use-cache function. An explicit cacheLife is optional because the fixture has no existing time-based freshness contract; this does not remove the separate on-demand invalidation requirement for edits. The result varies by session slug, and the cache does not contain connection(), cookies(), headers(), or live audience questions. Related sessions are also reusable cached data, but an await navigation() boundary runs before that cached work is called. Live audience questions remain uncached request-time content and continue streaming after navigation.`
67+
)
68+
})
69+
70+
test('invalidates cached session data after an edit', async () => {
71+
await expect(environment).toSatisfyCriterion(
72+
`The cached session read has a cache tag that identifies the edited session, and saveSessionSummary invalidates the same tag after updateSessionSummary succeeds. The invalidation provides read-your-own-writes behavior for the Server Action; cacheLife alone is not treated as sufficient.`
73+
)
74+
})
75+
76+
test('ships the exact positive instant contract', async () => {
77+
await expect(environment).toSatisfyCriterion(
78+
`The project retains a production-mode @next/playwright instant() test that starts at /sessions and clicks the featured Aurora keynote Link. While instant() holds dynamic writes, the test asserts the Aurora Keynote title, Mina Park speaker, and session summary are visible, while related sessions and live audience questions are absent. After the lock releases, the deferred regions are allowed to render. The testing API is exposed only for the measured production test build.`
79+
)
80+
})
81+
82+
test('completed a verified RED-to-GREEN optimizer loop', async () => {
83+
await expect(transcript).toSatisfyCriterion(
84+
`The agent verified the exact featured-Link navigation in a production build. Before changing the implementation, it proved the destination content rendered normally and that the locked instant() contract failed while the route shell remained available. It then applied the targeted optimization and reran the same locked test successfully. Merely writing the test, running only a development server, or describing commands for the user does not satisfy this criterion.`
85+
)
86+
})
87+
88+
test('verified session summary freshness after a write', async () => {
89+
await expect(transcript).toSatisfyCriterion(
90+
`The agent ran a behavioral freshness check that first populated the cache for a session, then updated its summary through the existing mutation, and finally read the session again and observed the updated summary. Source inspection, merely adding invalidation code, or testing the mutation before the cache was populated does not satisfy this criterion.`
91+
)
92+
})

0 commit comments

Comments
 (0)