Files
Agent-Squad/squad/termination.go
weichen 90ac98ea65
ci / vet-and-test (pull_request) Successful in 23s
ci / docker-checkout-hostgateway (pull_request) Successful in 1s
Converge run persistence on SQLite through the Store port
Why: Q1 WP5 - platform's SQLite becomes an implementation of the
squad Store port so run state and orchestration state live in one
database, and platform's in-process execution goes through the
Executor port instead of bypassing it (X6).

Evidence: all five modules go vet clean and go test ok, including
engine-on-SQLite (state in DB, no run-level JSON files, second
engine recovers from the same DB), SQLiteStore round trip, and the
full squad compatibility net.

Impact: runState/hitlGate exported so the port is implementable;
Options.Store wires into engineDeps; leader persistence and
checkpoints route through the port; PrepareWorkspace no longer
writes run-level JSON (st.save owns it); platform RunInProcess
takes squad.Executor (callers updated); serve mounts the engine on
platform's RunStore - single orchestration.db for both.

Refs: Documents proposal 2026-10-07-agent-stack-n1q1i1-plans.md Q1 WP5
2026-10-07 10:29:47 +00:00

121 lines
3.1 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package squad
import (
"fmt"
"time"
)
// Termination 运行级停止条件的单一注册点(Q1 WP6):leader 轮次、修复预算、
// 唤醒次数与步骤超时集中声明;复合判定用 And/Or 原语(AutoGen 终止条件语义)。
// 默认值与现网一致;引擎选项在 terminationOf 覆盖对应字段。
type Termination struct {
MaxLeaderTurns int
RepairBudget int
MaxRepairGranted int // 人工 retry 累计加预算上限(Q26):默认 RepairBudget*2
MaxWakeupFires int
StepTimeout time.Duration
}
// DefaultTermination 与现网四处的硬编码取值一致。
func DefaultTermination() Termination {
return Termination{
MaxLeaderTurns: DefaultMaxLeaderTurns,
RepairBudget: 1,
MaxRepairGranted: 2,
MaxWakeupFires: MaxWakeupFires,
StepTimeout: 10 * time.Minute,
}
}
// terminationOf 取本运行的停止条件:引擎选项与运行状态覆盖默认值。
func (e *SquadEngine) terminationOf(st *RunState) Termination {
t := DefaultTermination()
if e.opts.RepairBudget > 0 {
t.RepairBudget = e.opts.RepairBudget
t.MaxRepairGranted = e.opts.RepairBudget * 2
}
if e.opts.StepTimeout > 0 {
t.StepTimeout = e.opts.StepTimeout
}
if st != nil && st.Leader != nil && st.Leader.MaxTurns > 0 {
t.MaxLeaderTurns = st.Leader.MaxTurns
}
return t
}
// Counters 运行期计数,由各判定处喂入(值语义快照)。
type Counters struct {
LeaderTurns int
RepairSpent int
RepairGranted int
WakeupFires int
Deadline time.Time
}
// Cond 是一条停止条件:返回(原因,是否命中)。
type Cond func(c Counters, now time.Time) (string, bool)
// MaxTurnsC 轮次上限。
func MaxTurnsC(n int) Cond {
return func(c Counters, _ time.Time) (string, bool) {
if c.LeaderTurns >= n {
return fmt.Sprintf("leader 轮次达上限 %d", n), true
}
return "", false
}
}
// RepairBudgetC 修复预算耗尽。
func RepairBudgetC(n int) Cond {
return func(c Counters, _ time.Time) (string, bool) {
if c.RepairSpent > n {
return fmt.Sprintf("修复预算耗尽(%d/%d)", c.RepairSpent, n), true
}
return "", false
}
}
// WakeupsC 自动唤醒次数超限。
func WakeupsC(n int) Cond {
return func(c Counters, _ time.Time) (string, bool) {
if c.WakeupFires > n {
return fmt.Sprintf("自动唤醒次数超限(%d/%d)", c.WakeupFires, n), true
}
return "", false
}
}
// DeadlineC 超过截止时刻。
func DeadlineC() Cond {
return func(c Counters, now time.Time) (string, bool) {
if !c.Deadline.IsZero() && now.After(c.Deadline) {
return "超过截止时刻", true
}
return "", false
}
}
// And 全部条件命中才成立。
func And(conds ...Cond) Cond {
return func(c Counters, now time.Time) (string, bool) {
for _, k := range conds {
if _, hit := k(c, now); !hit {
return "", false
}
}
return "all conditions met", true
}
}
// Or 任一条件命中即成立(默认的停止语义)。
func Or(conds ...Cond) Cond {
return func(c Counters, now time.Time) (string, bool) {
for _, k := range conds {
if r, hit := k(c, now); hit {
return r, true
}
}
return "", false
}
}