Most bad coding-agent prompts are too short in the wrong places. They say what to add, but not what scientific contract must remain true. For research code, that is risky. A small implementation detail can change a convergence rate, a sign convention, a nondimensional number, or the meaning of a validation result.

The prompt template I keep coming back to is deliberately structured:

Objective
  Add a manufactured-solution convergence test for <equation/method>.

Context to read first
  Read src/<operator>, test/<nearest existing test>, docs/MATH.md.

Scientific constraints
  The exact solution is ... Evaluate L2 error at refinements [...].

Implementation contract
  Start with a plan. Make the smallest patch. Run <command>.

Final response
  List changed files, commands run, numerical results and uncertainty.

The important part is not the formatting. The important part is that the model receives the same information a careful collaborator would need before touching the code.

Objective

The objective should be narrow. “Improve the solver” is too vague. “Add a manufactured-solution convergence test for the diffusion operator” is better because it defines a target and suggests what kind of evidence will count.

For numerical code, I try to include the equation or method name directly in the objective. That helps the model search locally and prevents it from drifting toward unrelated tests.

Context to read first

This section is the antidote to generic code. The model should read the operator, the nearest existing test, and the mathematical notes before writing anything. If the repository already has a pattern for grids, tolerances, fixtures, or error norms, the new test should follow it.

The phrase “nearest existing test” is useful. It tells the model to adapt local style instead of inventing a new testing framework.

Scientific constraints

This is the part that normal software prompts often miss. Scientific code needs more than input/output behavior. It needs the mathematical claim:

  • the exact solution;
  • the forcing term;
  • the boundary conditions;
  • the refinement sequence;
  • the norm used for the error;
  • the expected convergence rate;
  • the tolerance and why it is reasonable.

If these are not written down, the model may choose convenient values that make the test pass without testing the intended property.

Implementation contract

I usually ask for the smallest patch. Not because small patches are morally better, but because they are easier to audit. If the goal is a convergence test, I do not want an unrelated refactor of the solver, the plotting code, and the documentation in the same change.

The command matters too. A coding agent should know how success will be checked:

Run julia --project=. -e 'using Pkg; Pkg.test()'

or, for a narrower pass:

Run julia --project=. test/test_diffusion_operator.jl

The command is part of the task, not an afterthought.

Final response

The final response should be boring and factual:

  • changed files;
  • commands run;
  • numerical results;
  • uncertainty.

That last item is important. A convergence rate estimated on three grids is not the same thing as a proof. A passing smoke test is not a paper-level validation. A result that depends on a coarse geometry should say so.

Why this works

The template forces the model to operate inside a scientific boundary. It tells it what to read, what to preserve, how to make the change, and how to report evidence.

It also makes review faster. If the final answer does not list numerical results, something is missing. If no uncertainty is mentioned, the result is probably oversold. If the patch is large, it probably ignored the implementation contract.

For research software, a good prompt is not just a request. It is a small protocol for keeping the code, the mathematics, and the evidence aligned.