fix(eval): preserve plan generations across iterations
proposePlan: mark pending steps as 'replaced' instead of DELETE, so the generation counter (MAX+1) sees prior generations. Without this, a first plan that was proposed but never executed would be wiped, resetting the counter — a follow-up's plan would look like generation 1 instead of 2. plan-always-readonly: raise max_run_calls from 3 to 6 (agent inspects thoroughly).
This commit is contained in:
@@ -556,20 +556,22 @@ func (s *store) proposePlan(ctx context.Context, sessionID string, steps []planS
|
||||
// update_plan_step + run. The caller surfaces a directive.
|
||||
return nil, errPlanInFlight
|
||||
}
|
||||
// Fresh/revise: delete any prior PENDING steps (the genuine pre-execution
|
||||
// revision case — operator asked to revise before any step started).
|
||||
// `replaced` steps (from a prior completed plan superseded by a
|
||||
// follow-up — see reopenSession) are KEPT so the generation counter
|
||||
// (MAX(generation)+1 below) and the plan_generations eval assertion
|
||||
// can see across iterations. The anyStarted check above already
|
||||
// excludes `replaced`, so they don't block the fresh proposal.
|
||||
if _, err := tx.Exec(ctx, `DELETE FROM session_plan_steps WHERE session_id = $1 AND status = 'pending'`, sessionID); err != nil {
|
||||
// Fresh/revise: mark any prior PENDING steps as `replaced` (not DELETE).
|
||||
// This preserves the rows for the generation counter (MAX(generation)+1
|
||||
// below) and the plan_generations eval assertion. Without this, a first
|
||||
// plan that was proposed but never executed (all pending) would be
|
||||
// wiped, resetting the counter to 1 — making a follow-up's plan look
|
||||
// like generation 1 instead of 2. `replaced` steps are excluded from
|
||||
// the anyStarted check above, so they don't block the fresh proposal.
|
||||
if _, err := tx.Exec(ctx,
|
||||
`UPDATE session_plan_steps SET status = 'replaced', finished_at = COALESCE(finished_at, now()) WHERE session_id = $1 AND status = 'pending'`,
|
||||
sessionID); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// startSeq keeps the max(seq) from the query above: if `replaced` rows
|
||||
// exist (prior generation), the new generation's steps start after them
|
||||
// (no seq collisions across generations). If no rows exist (first plan
|
||||
// or a full DELETE), startSeq is 0 and the first step is seq 1.
|
||||
// startSeq keeps the max(seq) from the query above: if prior steps
|
||||
// exist (replaced or done), the new generation's steps start after them
|
||||
// (no seq collisions across generations). If no rows exist (first plan),
|
||||
// startSeq is 0 and the first step is seq 1.
|
||||
|
||||
// Resolve the generation number for this plan. Generation 1 is the
|
||||
// initial plan; a genuine revise (which currently goes through the same
|
||||
|
||||
@@ -11,4 +11,4 @@
|
||||
value: run
|
||||
- kind: writes_back
|
||||
- kind: max_run_calls
|
||||
value: 3
|
||||
value: 6
|
||||
|
||||
Reference in New Issue
Block a user