Files
weichenandCursor da0b3d812b feat(intentflow): persist plans with CAS lease and fencing
Own plan/step/decision writes behind UoW so two workers cannot both execute the same step.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-15 23:43:46 +00:00

56 lines
2.1 KiB
Go

package intentflow
import (
"context"
"time"
"chat-agent-system/internal/app/uow"
)
// Store is the intent_plans* owner port. Callers pass the command TxScope.
type Store interface {
InsertPlan(ctx context.Context, scope uow.TxScope, plan *Plan) error
GetPlan(ctx context.Context, scope uow.TxScope, planID string) (*Plan, error)
GetPlanForUpdate(ctx context.Context, scope uow.TxScope, planID string) (*Plan, error)
GetByTurn(ctx context.Context, scope uow.TxScope, env, sessionID, clientTurnID string) (*Plan, error)
GetActiveWrite(ctx context.Context, scope uow.TxScope, p Principal) (*Plan, error)
GetActive(ctx context.Context, scope uow.TxScope, p Principal) (*Plan, error)
CASPlan(ctx context.Context, scope uow.TxScope, planID string, expectedRev int64, from []PlanStatus, to PlanStatus, now time.Time) (newRevision int64, err error)
InsertDecision(ctx context.Context, scope uow.TxScope, d *Decision) error
GetDecisionByNonce(ctx context.Context, scope uow.TxScope, planID, nonce string) (*Decision, error)
ReplaceSteps(ctx context.Context, scope uow.TxScope, planID string, steps []Step) error
ClaimStep(ctx context.Context, scope uow.TxScope, planID, stepID, worker string, until, now time.Time, idempotencyKey string) (*Step, int64, error)
WriteStepReceipt(ctx context.Context, scope uow.TxScope, worker string, token int64, step Step, now time.Time) (bool, error)
ExpireWaiting(ctx context.Context, scope uow.TxScope, now time.Time) (int, error)
PurgeClosed(ctx context.Context, scope uow.TxScope, before time.Time) (int, error)
}
func waitingStatuses() []PlanStatus {
return []PlanStatus{PlanProposed, PlanAwaitingClarification, PlanAwaitingConfirmation}
}
func activeStatuses() []PlanStatus {
return []PlanStatus{
PlanProposed, PlanAwaitingClarification, PlanAwaitingConfirmation,
PlanExecuting, PlanPartiallyCompleted,
}
}
func isActiveStatus(s PlanStatus) bool {
for _, x := range activeStatuses() {
if x == s {
return true
}
}
return false
}
func isWaitingStatus(s PlanStatus) bool {
for _, x := range waitingStatuses() {
if x == s {
return true
}
}
return false
}