SCHWAI runs a handful of internal AI copilots. One writes summaries, one helps devs, one handles admin, one works sales. Each is a model wrapped in a system prompt, and the prompt is where most of the quality lives. Change the wording and the answers change. The hard part is knowing whether a change actually helped, or whether you just talked yourself into liking it.
So I built a sandbox that improves a copilot by rewriting its system prompt against a measured reward. No retraining. No weight updates, no gradients. It’s model-agnostic too, so swapping the underlying model is a config change, not a rebuild.
The loop is simple to say and finicky to get honest.
First it generates a bank of about 300 realistic questions, either from seeds or from real logged usage. Then it runs the copilot over all of them and scores every answer 1 to 5 on 5 quality dimensions, with a second model acting as judge. On top of the judge sit deterministic hard-gates: a leaked client name or a broken output format is an automatic veto that no high score can buy back.
Then a strong model proposes one small edit to the system prompt, aimed at whichever dimension scored worst. The edit is an anchored diff: an exact snippet to find plus its replacement. If the anchor isn’t in the prompt verbatim, the edit is rejected. That one rule stops the model from quietly rewriting the whole prompt when you asked for a nudge.
Keep the edit only if it measurably beats questions the proposer never saw, and only if it regresses no strong dimension. Then repeat, drilling one weakness at a time. I call it focus mode; it’s coordinate ascent by another name. When the loop settles on a winner, it scores that winner once on a held-out test set and writes a scorecard a human can read.
It runs locally against a copy of the data, streams a live dashboard, and never touches the production prompt. What comes out is a reviewed suggestion. A human stays the gatekeeper.
The honest part is the whole point, so let me spend a paragraph on it.
Train, validation, test. If you keep adopting rounds against the same validation set, that set slowly turns into a target you’re optimizing straight at, and the score stops meaning anything. Winner’s curse, multiple comparisons, whatever you want to call it. So the winner gets scored once on a test set it was never selected against.
That discipline caught the system fooling itself. One run looked like a big jump on the validation set. On the never-optimized test questions it came out lower than the baseline it started from. Most tools would have reported the validation number and called it a win. This one reported the truth and killed the run.
There’s also a noise floor. Score the baseline twice, treat the wobble between the two runs as noise, and express every candidate’s gain as an effect size in units of that noise. A gain several times the noise is real. A gain smaller than the noise is luck wearing a nice outfit.
Sometimes the most useful output is a shrug backed by data. On the grounded dimension the loop tried twice and moved the score by nothing. That flat result was itself the finding: the gap lived in retrieval, so the fix belonged in the corpus rather than the prompt. Knowing which lever to pull next saved days of guessing at the wrong one.
I also gave the proposer a memory. Early on it guessed fresh every round and kept re-proposing near-duplicates of edits it had just been told didn’t work, so very few landed. Now every edit it tries, and whether it landed, gets logged to a persistent ledger, and each round it studies the ones that worked before proposing the next. In-context few-shot, experience replay, same idea. Acceptance should climb the longer it runs.
The results came out the honest way. The dimensions that started weakest, conciseness and tone, moved the most. The ones that were already strong barely budged, which is what you’d expect when there’s less room to gain. And grounded didn’t move at all, for the reason above.
I ran the same sandbox across all 4 copilots, summary, dev, admin, and sales, and on held-out questions it lifted answer quality by roughly 10% on average, with the whole program running a good bit more efficiently within a couple of days.
None of the shape is new, and I’d rather say so. The anchored-diff-from-critique idea echoes ProTeGi and the automatic-prompt-optimization line, where the judge’s critique reads as a textual gradient and the diff is the step. The generate-score-select loop is the shape APE, OPRO, and DSPy all use. The judge is LLM-as-judge. What none of them touch is weights, and that’s the next frontier.
Because that’s what this really is: a self-improvement environment. A measured reward, held-out proof, a human gate. Point that same environment at an open-source model and let it update weights, and you have an RL-style loop where the model changes how it thinks inside the sandbox, not just the instructions wrapped around it. Once the edit ledger holds a few hundred context-edit-outcome tuples, you can fine-tune an open model to emit better edits directly. Real weight-learning, but only once the in-context version plateaus. The ledger is already the dataset.
That’s the direction I want to take into research: sandboxes where powerful models improve themselves under honest measurement. This one was the first step. I want to be straight about where it sits, though. Today it optimizes prompts and touches no weights, and the earlier stages carry the weight. The rest is where it’s headed.