Make 'Propose a Job' and 'Launch a Job' Different Tools
"Propose a training job" and "launch a training job on 64 GPUs" are different operations. So are "draft a cleanup of old checkpoints" and "delete them." An agent in a research lab will get tools with real costs, and the interface should make the difference between suggesting and doing impossible to blur.
OpenAI's function calling guide describes the flow: the model returns a function call with a name and arguments, your application executes code with that input, and you send the result back. The model never runs anything itself. What a call is allowed to do is decided entirely by your code.
Use that. Give the agent two tools:
propose_job(config, gpus, hours, reason)validates the arguments, stores the proposal and returns a proposal ID and a hash of the exact arguments;launch_job(proposal_id)launches only if a human approved that ID, and only if the stored hash still matches.
The model can assemble the proposal, estimate its cost (GPUs × hours × price per GPU-hour, shown so a reviewer can check it) and explain why. It cannot grant itself the approval.
The check, as three tests against a fake scheduler:
launch_jobon an unapproved proposal is refused, and nothing reaches the scheduler.- A proposal edited after approval (64 GPUs becomes 128) no longer matches its hash and is refused.
- Calling
launch_jobtwice with the same approved ID, as a retry would, starts one job, not two.
Inspect the recorded state transitions (proposed, approved, launched), not the agent's final chat message. The book's approval chapter builds an exact-approval gate like this one from scratch; the chapter after it, on ambiguous orders, covers the retry case.