The Copilot Planning Loop
A reason-act-observe loop with an explicit step budget and replanning, driving bounded tool calls toward a grounded, cited answer.
Reason, act, observe, repeat
A copilot answers by running a bounded planning loop. Given a request and assembled context, the planner proposes the next tool call, executes it, observes the result, and decides whether to continue, replan, or compose an answer. The loop is capped by an explicit step and resource budget so it always terminates.
Loop structure
def plan_and_act(request, ctx, budget):
trace = []
for step in range(budget.max_steps):
thought, call = planner(ctx, request, trace)
if call is None: # planner ready to answer
return compose(ctx, trace)
if not schema_valid(call): # bounded action check
trace.append(error(call)); continue
obs = execute(call) # read / simulate / propose
trace.append((thought, call, obs))
ctx = update(ctx, obs)
return compose(ctx, trace, note='budget exhausted')
Why the trace matters
The full reasoning trace — each thought, tool call, and observation — is retained. It is the substrate for citations (every claim in the answer maps to an observation in the trace), for the evaluation harness (which grades intermediate steps, not just final answers), and for audit. A copilot that reaches a right answer by an unsound path is caught by trace grading.
- Explicit step and compute budget guarantees termination
- Replanning on new observations, not a fixed script
- Malformed tool calls become observations the planner must handle
- The trace grounds citations, evaluation, and audit
Replanning is what makes the loop useful for messy, real problems: if a scenario simulation reveals an unexpected instability, the planner can pivot to disruption-margin analysis rather than blindly continuing. But replanning never expands the toolbox — the planner may only call declared, schema-bound tools, and any side-effecting proposal still exits the loop into L4 for authorization. The loop reasons; it does not act on the machine.