feat(good-fences): code-mode prompt; the model writes code to mow

codeModeMessages turns what I say plus `about` into a prompt: a
neighbor persona, rules for the reply (one async arrow function over
`env`), and the lent things' types. Things are remote and calls on one
are delivered in order, so a lamp example shows pipelining: await only
for a value, or on a batch's last call. With only the rule, the model
awaited every call; with the example, it pipelines.

aboutMower gives the Mower type with Promise returns. The model test
checks the code calls only Mower methods, pushes, and awaits less often
than it calls; it saves the code to itest-output/ via a reporter, since
the test runs in workerd, which can't write files. Model tests get 60s.

Green: both model tests.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
dckcandClaude Opus 5.5 committed 2026-10-09 00:21:44 -05:00
1 parent 4a85143a66
commit a2ca59a27e
6 files changed
+113 -4

No files matched your search

+63
View File
@@ -0,0 +1,63 @@
/** @file Code mode: the model answers with code against the things it was lent. */
import type { Message } from "./model.ts";
/** what the neighbor knows about each thing: its TypeScript type */
export type About = Readonly<Record<string, Readonly<{ ts: string }>>>;
/** a template literal with `~` for each backtick, as in Markdown's `~~~`
* fences; interpolated values are left as they are */
const md = (strings: TemplateStringsArray, ...values: unknown[]) => {
const tick = (s: string) => s.replaceAll("~", "`");
return tick(strings[0]) + values.map((value, i) => String(value) + tick(strings[i + 1])).join("");
};
const persona = md`
You are my neighbor. You have 50 acres of lawn that need mowing.
You act by writing code. When I lend you things, your code gets them as ~env.<name>~.
`.trim();
const rules = md`
Reply with exactly one JavaScript arrow function, ~async (env) => { ... }~,
in a ~~~js block, and nothing else. Things are remote: each call on one
returns a promise, and calls on a thing are delivered in order, so there's
no need to wait for one before making the next. Await only when you need a
value back, or on the last call of a batch, to wait for the whole batch.
Your code can use only ~env~: no network, timers, imports, or console.
The function returns a short string saying what it did.
For example, lent ~env.lamp~ with ~on()~ and ~dim()~:
~~~js
async (env) => {
const { lamp } = env;
lamp.on();
lamp.dim();
await lamp.dim(); // the whole batch
return 'dimmed the lamp twice';
}
~~~
`.trim();
/** the prompt for `say`, with the types of the things lent alongside it */
export const codeModeMessages = ({ say, about }: { say: string; about: About }): Message[] => {
const types = Object.entries(about)
.map(
([name, { ts }]) => md`
~env.${name}~:
~~~ts
${ts}
~~~
`.trim(),
)
.join("\n\n");
return [
{ role: "system", content: `${persona}\n\n${rules}` },
{ role: "user", content: `${say}\n\nWhat I lent you:\n\n${types}` },
];
};
/** the code in a reply: the first fenced block, or the whole reply if none */
export const extractCode = (reply: string): string => {
const fenced = reply.match(/```(?:js|javascript)?\n([\s\S]*?)```/);
return (fenced?.[1] ?? reply).trim();
};