corago/codex-keep/policy.go

192 lines
3.8 KiB
Go
Raw Permalink Normal View History

package main
import "time"
type goalStatus string
const (
active goalStatus = "active"
paused goalStatus = "paused"
blocked goalStatus = "blocked"
)
2026-10-22 22:05:33 +09:00
func (s goalStatus) waiting() bool {
return s == blocked || s == "usageLimited"
}
type goal struct {
Objective string `json:"objective"`
Status goalStatus `json:"status"`
CreatedAt int64 `json:"createdAt"`
UpdatedAt int64 `json:"updatedAt"`
}
func sameGoal(a, b *goal) bool {
return a != nil && b != nil && a.Objective == b.Objective && a.CreatedAt == b.CreatedAt
}
func sameState(a, b *goal) bool {
return sameGoal(a, b) && a.Status == b.Status && a.UpdatedAt == b.UpdatedAt
}
type watch struct {
Goal *goal `json:"goal,omitempty"`
Turn turn `json:"turn"`
Armed bool `json:"armed"`
Known bool `json:"known"`
Pending bool `json:"pending,omitempty"`
Hold *goal `json:"hold,omitempty"`
Source *intentLog `json:"source,omitempty"`
Failures int `json:"failures"`
Due time.Time `json:"due"`
revision uint64
echo *goal
seed *goal
}
func (w *watch) stop() {
w.Armed = false
w.Due = time.Time{}
}
func (w *watch) update(g *goal) {
w.revision++
if w.Hold != nil {
if !sameGoal(w.Hold, g) {
w.Hold = nil
} else if g.Status == active && g.Status == w.Hold.Status && !w.Pending && w.echo == nil {
w.Hold = nil
if g.Status == active {
w.Armed = true
}
} else {
if g.Status == w.Hold.Status {
w.Goal = g
}
w.stop()
return
}
}
previous := w.Goal
same := sameGoal(previous, g)
if !same {
w.stop()
w.Known = false
w.Failures = 0
} else if w.Armed || previous.Status == active {
w.Known = true
}
w.Goal = g
if g == nil {
w.stop()
return
}
switch g.Status {
case active:
w.Known = true
if !same || previous.Status != active {
w.Due = time.Time{}
w.Armed = true
} else if w.Turn.Status == "inProgress" {
w.Armed = true
}
2026-10-22 22:05:33 +09:00
case blocked, "usageLimited":
if g.Status == "usageLimited" && (!same || previous.Status != g.Status) {
w.Due = time.Time{}
}
if !same && w.Pending {
w.Hold = g
}
default:
w.stop()
w.Hold = g
}
}
func (w *watch) notice(g *goal) {
if w.seed != nil {
w.seed = nil
return
}
if sameState(w.echo, g) {
w.echo = nil
return
}
if w.Hold != nil && sameGoal(w.Hold, g) {
w.revision++
2026-10-22 22:05:33 +09:00
if g.Status.waiting() || g.Status == active {
return
}
w.Goal, w.Hold = g, g
w.stop()
return
}
if g != nil && g.Status == active && sameGoal(w.Goal, g) {
w.revision++
return
}
w.update(g)
}
func (w *watch) observe(t turn) {
if t.ID == "" {
return
}
w.revision++
fresh := t.ID != w.Turn.ID
2026-10-22 22:05:33 +09:00
if w.Known && (fresh || w.Turn.Status == "inProgress") && t.Status == "failed" {
w.Armed = true
}
if fresh {
w.Due = time.Time{}
if w.Goal != nil && t.Status == "inProgress" && w.Goal.Status == active {
w.Armed = true
}
}
w.Turn = t
switch t.Status {
case "completed":
w.Due = time.Time{}
w.Failures = 0
case "interrupted":
w.stop()
if w.Pending && w.Goal != nil {
desired := *w.Goal
desired.Status = paused
w.Goal, w.Hold = &desired, &desired
}
}
if w.Hold != nil {
w.stop()
}
}
func (w *watch) eligible() bool {
2026-10-22 22:05:33 +09:00
return w.Hold == nil && w.Armed && w.Goal != nil && w.Goal.Status.waiting() &&
w.Turn.Status == "failed"
}
func (w *watch) schedule(now time.Time, base, cap time.Duration, jitter func(time.Duration) time.Duration) bool {
if !w.eligible() || !w.Due.IsZero() {
return false
}
2026-10-22 22:05:33 +09:00
if w.Goal.Status == "usageLimited" {
w.Due = now
return true
}
w.Failures++
w.Due = now.Add(min(cap, jitter(backoff(base, cap, w.Failures))))
return true
}
func backoff(base, cap time.Duration, failures int) time.Duration {
delay := base
for i := 1; i < failures && delay < cap; i++ {
if delay > cap/2 {
return cap
}
delay *= 2
}
return min(delay, cap)
}