Making the first change
A coding assistant changes the form of the conversation. It can inspect a file, edit it, and run a check. The engineer can respond to a concrete diff, and the next exchange can be grounded in what the execution environment reported.
That feedback cycle is the mechanism described in Coding Agents. In this part of the adoption journey, the engineer remains actively involved: choosing the change, resolving ambiguity, and deciding whether the result is ready for review. The assistant can carry out several actions within that discussion without owning the entire delivery task.
The first useful change benefits from a boundary that both participants can inspect. The notification example gives us one: the application supplies an idempotency key, but the adapter fails to forward it.
The change boundary #
The previous article exercised a submission function using a fake provider. It established that the application reused the notification identity when retrying. The real adapter still needed inspection.
Suppose that inspection finds the following implementation. The client and its method names are illustrative; they represent an existing integration with a provider that supports the required idempotency behaviour.
interface EmailRequest {
to: string;
subject: string;
body: string;
}
interface EmailProvider {
send(
request: EmailRequest,
options: { idempotencyKey: string },
): Promise<{ messageId: string }>;
}
interface ProviderInput {
recipient: string;
subject: string;
text: string;
idempotencyKey?: string;
}
interface ProviderClient {
submitEmail(input: ProviderInput): Promise<{ id: string }>;
}
function createEmailAdapter(client: ProviderClient): EmailProvider {
return {
async send(request) {
const response = await client.submitEmail({
recipient: request.to,
subject: request.subject,
text: request.body,
});
return { messageId: response.id };
},
};
}
The interface accepts a key. The implementation ignores the options argument. Type checking permits a function to leave an argument unused, so the interface alone cannot establish that the value reaches the provider client.
The relevant change is now specific. The key already exists, its lifetime is controlled elsewhere, and the client already accepts it. Forwarding it should preserve the request fields, receipt mapping, and rejection behaviour. Adding a retry loop or generating another key would change responsibilities outside this defect.
The engineer can make that scope explicit in the conversation:
The submission function already supplies the stable notification ID. Check how the adapter handles that value, then patch the forwarding and add a regression check at the client boundary. Preserve the current request mapping and error behaviour.
This gives the assistant a reason to inspect the caller and adapter together. It also identifies which existing behaviours the patch must preserve.
The proposed patch #
In the illustrative repository, the working area is small:
src/notifications/
submit-notification.ts
email-adapter.ts
test/
email-adapter.test.ts
The assistant can read both source files and the existing tests before editing. It may discover that the tests replace the whole adapter, explaining why the earlier retry exercise could pass while this defect remained. The new check belongs below that replacement, where the adapter invokes the client.
The source change is two lines:
function createEmailAdapter(client: ProviderClient): EmailProvider {
return {
- async send(request) {
+ async send(request, options) {
const response = await client.submitEmail({
recipient: request.to,
subject: request.subject,
text: request.body,
+ idempotencyKey: options.idempotencyKey,
});
return { messageId: response.id };
},
};
}
The small diff makes an important part of review straightforward: the engineer can see that the adapter passes through the supplied key. There is no new identity generation, persistence, or scheduling behaviour hidden in the fix.
An assistant might propose an additional retry around submitEmail because the surrounding discussion concerns retries. The engineer has enough context to question that expansion. A second retry mechanism could interact with the queue's attempts and change latency or request volume. Its value would need a separate discussion.
Keeping that decision with the engineer is a concrete form of human oversight: a scope question gets resolved before it becomes additional implementation to maintain.
Behavioural evidence #
A regression check can observe what crosses the client boundary. The following framework-free example uses the types above and the patched factory. In a repository, it would use the existing test runner and assertion conventions.
function assert(condition: boolean, message: string): asserts condition {
if (!condition) throw new Error(message);
}
async function verifyAdapterMapping() {
const calls: ProviderInput[] = [];
const adapter = createEmailAdapter({
submitEmail: async (input) => {
calls.push(input);
return { id: "provider-17" };
},
});
const receipt = await adapter.send(
{ to: "reader@example.test", subject: "Updated", body: "Request changed" },
{ idempotencyKey: "N42" },
);
assert(calls.length === 1, "Expected one client call");
assert(calls[0].idempotencyKey === "N42", "Key must reach the client");
assert(calls[0].recipient === "reader@example.test", "Recipient changed");
assert(calls[0].subject === "Updated", "Subject changed");
assert(calls[0].text === "Request changed", "Body changed");
assert(receipt.messageId === "provider-17", "Receipt mapping changed");
}
async function verifyProviderFailure() {
const failure = new Error("Provider unavailable");
const adapter = createEmailAdapter({
submitEmail: async () => { throw failure; },
});
try {
await adapter.send(
{ to: "reader@example.test", subject: "Updated", body: "Request changed" },
{ idempotencyKey: "N42" },
);
throw new Error("Expected the provider rejection to propagate");
} catch (error) {
assert(error === failure, "Provider failure must reach the caller");
}
}
await verifyAdapterMapping();
await verifyProviderFailure();
Running the mapping check against the original factory fails at the missing key. Running it against the patch passes. That difference connects the check to the reported defect. The other assertions exercise behaviour the engineer asked to preserve, including rejection reaching the caller so the surrounding workflow can handle the failed attempt.
The result has a limited scope. It verifies the arguments sent to this client interface and the response returned to the application. It does not verify how an actual SDK serialises the key or how the remote provider treats it. Those are the integration questions identified in the earlier articles.
The assistant's report should preserve that limit. “The adapter forwards the supplied key, and the targeted checks pass” is supported by this evidence. A claim that duplicate notifications are now impossible would exceed it.
The working copy #
The interaction also has a repository boundary. The engineer may already have unrelated edits, and the assistant needs to distinguish those from the task's changes. Inspecting the working tree before editing gives both participants a baseline for the final diff.
Reading the local setup matters for execution as well. The relevant test might require a workspace command rather than running one file directly. If a command fails because a dependency is unavailable, that output says the check could not complete. It gives the conversation a setup problem to resolve, rather than evidence that the patch is correct or incorrect.
Once the targeted check establishes the behaviour, the repository's required type, lint, and test checks provide the next layer of feedback. A new failure needs investigation in relation to the change and the baseline. Broad edits to unrelated tests would make this small fix harder to assess.
The engineer continues to direct these decisions while the assistant handles the mechanics of inspection, editing, and execution. That division lets the team learn what the assistant does well without delegating every judgement involved in finishing the task.
The review record #
The reviewable result consists of the patch, the regression check, and an explanation of the evidence. A useful description identifies the omitted key, states that the adapter now forwards it unchanged, and records which checks ran against the proposed revision. It also leaves the external provider guarantee as a separate dependency.
A reviewer can then inspect a narrow behavioural change without reconstructing the entire AI conversation. The engineer submitting the work can explain why the test belongs at this boundary and why the patch avoids a new retry policy.
That is a meaningful first use of coding assistance. The assistant has produced and exercised a change, while the person has kept the task aligned with the system's intended behaviour.