Right-Size the Agent Request
You've still got order-api open from the last lesson: the tiny order-lookup service with orders.ts reading from an in-memory Map, client.ts wrapping it in an OrderClient class, and server.ts wiring three Express routes to that client. Last lesson you wrote .cursor/rules/order-api.mdc, a project rule file that Cursor's Agent mode (the multi-file mode from lesson 3, sometimes labeled Composer) reads automatically every session, and it has already shown it can cite that file back to you unprompted. Today the rule file earns its keep for real, on an actual request.
Two rules from that file matter for what's coming: OrderClient methods must stay synchronous with orders.ts's interface, and the project uses named exports, never default exports. Keep both in the back of your mind. You won't have to repeat them in the prompt. That's the entire point of having written them down last lesson.
The task: cache OrderClient.get(id)
OrderClient.get(id) currently hits orders.ts on every single call, even if the same order was looked up two seconds ago. The fix is an in-memory cache: repeated calls to get(id) for the same order, within 60 seconds, should return the cached result instead of reading through to orders.ts again.
That's a real task. It's also a task with a wrong way and a right way to hand it to Agent, and the difference between them is not politeness or prompt length. It's scope.
Two requests, same intent, different blast radius
Make this API faster.
Three words. Not one of them tells Agent where to look, what "faster" means, or where the change should stop. Agent has to guess, and every guess it makes is a reasonable one: maybe the fix is a cache, maybe it's a connection pool, maybe it's swapping the in-memory Map in orders.ts for something else, maybe it's touching server.ts to batch requests, maybe it decides the real fix is a caching library and adds a dependency to package.json. None of those guesses is wrong given the prompt. All of them are wrong given what you actually wanted, which is one specific change to one specific method.
Read the broad version again with lesson one's reframe in mind: accepting whatever Agent hands back for "make this API faster" is not reviewing a small diff, it's merging a pull request from a contributor who was given no ticket, no acceptance criteria, and free rein across the repo. You'd never approve that PR without a much longer read than a caching change deserves. The scoped version is what lets the diff you're about to see stay small enough to actually check in the time it takes to read it.
It's tempting to read the broad prompt as somehow more generous, more trusting of the agent, closer to how you'd brief a senior engineer. It isn't. A senior engineer handed "make this API faster" with no other context would ask three clarifying questions before touching a file. Agent doesn't ask by default. It picks an interpretation and executes it, and the interpretation it picks is whichever one the model finds most statistically plausible, not the one you were picturing.
That's not a guess about how these tools behave in general. It's the same gap a later lesson walks through in full, when an unscoped ask ("matching the pattern our team already uses elsewhere," with no file named) gets a plausible-sounding answer built from nothing this project actually said. A broad prompt and a vague context reference fail for the identical reason: the model fills a gap you left open with its own best guess, not with the thing you had in mind but never wrote down.
What the scoped request actually constrains
Line up what each sentence in the right-sized prompt rules out:
- "In
src/client.ts" rules out Agent deciding the cache belongs inorders.tsinstead, or that a newcache.tsmodule would be cleaner. - "
OrderClient.get(id)" rules out touchinglistForCustomer, which wasn't part of this request and doesn't need caching yet. - "Cache each order by its id" rules out a cache key Agent invents on its own, like a serialized version of the whole request.
- "Within 60 seconds... after 60 seconds, treat the entry as stale" rules out no expiry at all (a memory leak by another name) and rules out Agent picking its own TTL because none was stated.
- "Don't change the method's signature or return type" rules out
get(id)suddenly returningPromise<Order>or a wrapper object, which would break every caller inserver.tssilently. - "Don't touch
server.tsororders.ts" rules out exactly the kind of wandering the broad prompt invited.
None of this is about distrusting Agent. It's the same reason a well-written Jira ticket gets a better pull request than "fix the slowness." Bounded inputs, a defined output, a stated rule, a stated stop: the request earns a diff you can actually verify, because you already know what verifying it means before you've read a line of the response.
Running it
Paste the right-sized prompt into Composer/Agent mode with src/client.ts open. It reads the file, reads .cursor/rules/order-api.mdc on its own (no @-mention needed for a project rule that's already scoped to apply), and comes back with a diff to one file.
The plausible result: a cache field on OrderClient, a Map keyed by order id, storing each cached order alongside the timestamp of the lookup that produced it.
export class OrderClient {
private cache = new Map<string, { order: Order; cachedAt: number }>();
private static readonly CACHE_TTL_MS = 60_000;
get(id: string): Order {
const cached = this.cache.get(id);
if (cached && Date.now() - cached.cachedAt < OrderClient.CACHE_TTL_MS) {
return cached.order;
}
const order = getOrder(id);
this.cache.set(id, { order, cachedAt: Date.now() });
return order;
}
listForCustomer(customerId: string): Order[] {
return listOrders(customerId);
}
}Walk it against the prompt, the same way you'd walk a colleague's diff against its ticket. The cache is a Map<string, { order: Order; cachedAt: number }> keyed by id, exactly what "cache each order by its id" asked for. CACHE_TTL_MS is 60_000, and the check on the read path, Date.now() - cached.cachedAt < CACHE_TTL_MS, is the 60-second window stated in the prompt, not a number Agent picked. get(id) still takes a string and returns an Order, same as before; nothing downstream in server.ts has to change to keep compiling. listForCustomer is untouched. orders.ts and server.ts don't appear in the diff at all, because the diff is one file, which is the entire reason this took thirty seconds to check instead of ten minutes.
The request paid off exactly where it was supposed to.
Checkpoint
What's ahead
You've got a diff now, sitting in Composer, waiting on your accept or reject. Next lesson takes this exact client.ts change and walks through reviewing it properly: Cursor's per-file diff view, what a checkpoint actually restores, and what to do when a diff looks reasonable but isn't quite what you asked for.
Reviewing the cache diff the way you'd review a colleague's pull request, and using a checkpoint to roll back when something's off.
Reply here and it goes straight to Rod. Same as replying to one of his emails.