Skip to content

Commit 06d6471

Browse files
authored
Merge pull request #592 from phantom5099/rebuild-verify
refactor(runtime):验收机制重建
2 parents fb5f51c + 2e98c14 commit 06d6471

116 files changed

Lines changed: 3171 additions & 6345 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

docs/guides/configuration.md

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,6 @@ tool_timeout_sec: 20
2626
generate_start_timeout_sec: 90
2727

2828
runtime:
29-
max_no_progress_streak: 5
3029
max_repeat_cycle_streak: 3
3130
max_turns: 90
3231
hooks:
@@ -108,9 +107,8 @@ context:
108107

109108
| 字段 | 说明 |
110109
|------|------|
111-
| `runtime.max_no_progress_streak` | 连续“无进展”轮次提醒阈值,默认 `5`;达到 `limit-1` 起会向模型注入纠偏提示,不会直接终止运行 |
112-
| `runtime.max_repeat_cycle_streak` | 连续“重复调用同一工具参数”提醒阈值,默认 `3`;达到阈值后触发重复循环提醒,不会直接终止运行 |
113-
| `runtime.max_turns` | 单次 Run 的最大推理轮数上限,默认 `40`;达到上限后直接终止并返回明确 stop reason |
110+
| `runtime.max_repeat_cycle_streak` | 连续“相同工具签名 + 相同结果指纹 + 相同子目标”阈值,默认 `3`;达到阈值后先注入重复循环提醒,提醒后仍重复则终止为 `repeat_cycle` |
111+
| `runtime.max_turns` | 单次 Run 的最大推理轮数上限,默认 `90`;达到上限后直接终止并返回明确 stop reason |
114112
| `runtime.hooks.enabled` | hooks 总开关;关闭后不执行 runtime hooks |
115113
| `runtime.hooks.user_hooks_enabled` | user hooks 开关;关闭后不加载 `runtime.hooks.items` |
116114
| `runtime.hooks.default_timeout_sec` | user hook 默认超时秒数,需 `> 0` |

docs/stop-reason-and-decision-priority.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,11 @@
77
- `max_turn_exceeded`
88
- `verification_failed`
99
- `accepted`
10+
- `missing_completion_signal`
11+
- `accept_check_failed`
1012
- `todo_not_converged`
1113
- `todo_waiting_external`
12-
- `no_progress_after_final_intercept`
14+
- `repeat_cycle`
1315
- `max_turn_exceeded_with_unconverged_todos`
1416
- `max_turn_exceeded_with_failed_verification`
1517
- `verification_config_missing`

internal/cli/gateway_runtime_bridge.go

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1703,19 +1703,16 @@ func convertRuntimeSnapshot(snapshot agentruntime.RuntimeSnapshot) gateway.Runti
17031703
RunID: strings.TrimSpace(snapshot.RunID),
17041704
SessionID: strings.TrimSpace(snapshot.SessionID),
17051705
Phase: strings.TrimSpace(snapshot.Phase),
1706-
TaskKind: strings.TrimSpace(snapshot.TaskKind),
17071706
UpdatedAt: snapshot.UpdatedAt,
17081707
Todos: convertRuntimeTodoSnapshot(snapshot.Todos),
17091708
Facts: map[string]any{
17101709
"runtime_facts": snapshot.Facts.RuntimeFacts,
17111710
},
17121711
Decision: map[string]any{
1713-
"status": strings.TrimSpace(snapshot.Decision.Status),
1714-
"stop_reason": strings.TrimSpace(snapshot.Decision.StopReason),
1715-
"missing_facts": snapshot.Decision.MissingFacts,
1716-
"required_next_actions": snapshot.Decision.RequiredNextActions,
1717-
"user_visible_summary": strings.TrimSpace(snapshot.Decision.UserVisibleSummary),
1718-
"internal_summary": strings.TrimSpace(snapshot.Decision.InternalSummary),
1712+
"status": strings.TrimSpace(snapshot.Decision.Status),
1713+
"stop_reason": strings.TrimSpace(snapshot.Decision.StopReason),
1714+
"summary": strings.TrimSpace(snapshot.Decision.Summary),
1715+
"details": append([]string(nil), snapshot.Decision.Details...),
17191716
},
17201717
SubAgents: map[string]any{
17211718
"started_count": snapshot.SubAgents.StartedCount,

internal/cli/gateway_runtime_bridge_test.go

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1009,7 +1009,6 @@ func TestGatewayRuntimePortBridgeListSessionTodosAndSnapshot(t *testing.T) {
10091009
RunID: "run-1",
10101010
SessionID: "session-2",
10111011
Phase: "acceptance",
1012-
TaskKind: "workspace_write",
10131012
Decision: agentruntime.DecisionSnapshot{Status: "continue", StopReason: "unverified_write"},
10141013
SubAgents: agentruntime.SubAgentSnapshot{StartedCount: 1, CompletedCount: 1, FailedCount: 0},
10151014
},

internal/config/config_test.go

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1721,7 +1721,6 @@ func TestValidateSnapshotPropagatesCompactError(t *testing.T) {
17211721
},
17221722
},
17231723
Runtime: RuntimeConfig{
1724-
MaxNoProgressStreak: 3,
17251724
MaxRepeatCycleStreak: 3,
17261725
},
17271726
Context: ContextConfig{
@@ -1830,7 +1829,7 @@ func TestParseCurrentConfigRoundTripRuntimeConfig(t *testing.T) {
18301829
t.Parallel()
18311830

18321831
snapshot := testDefaultConfig().Clone()
1833-
snapshot.Runtime.MaxNoProgressStreak = 5
1832+
snapshot.Runtime.MaxRepeatCycleStreak = 5
18341833

18351834
data, err := marshalPersistedConfig(snapshot)
18361835
if err != nil {
@@ -1841,8 +1840,8 @@ func TestParseCurrentConfigRoundTripRuntimeConfig(t *testing.T) {
18411840
if err != nil {
18421841
t.Fatalf("parseCurrentConfig() error = %v", err)
18431842
}
1844-
if parsed.Runtime.MaxNoProgressStreak != 5 {
1845-
t.Fatalf("expected max_no_progress_streak=5, got %d", parsed.Runtime.MaxNoProgressStreak)
1843+
if parsed.Runtime.MaxRepeatCycleStreak != 5 {
1844+
t.Fatalf("expected max_repeat_cycle_streak=5, got %d", parsed.Runtime.MaxRepeatCycleStreak)
18461845
}
18471846
}
18481847

@@ -1854,7 +1853,7 @@ selected_provider: openai
18541853
current_model: gpt-4.1
18551854
shell: bash
18561855
runtime:
1857-
max_no_progress_streak: -2
1856+
max_repeat_cycle_streak: -2
18581857
`)
18591858

18601859
parsed, err := parseCurrentConfig(raw, StaticDefaults().Context, StaticDefaults().Memo)
@@ -1866,9 +1865,9 @@ runtime:
18661865
if err := parsed.ValidateSnapshot(); err != nil {
18671866
t.Fatalf("ValidateSnapshot() error = %v", err)
18681867
}
1869-
if parsed.Runtime.MaxNoProgressStreak != DefaultMaxNoProgressStreak {
1870-
t.Fatalf("expected default max_no_progress_streak=%d, got %d",
1871-
DefaultMaxNoProgressStreak, parsed.Runtime.MaxNoProgressStreak)
1868+
if parsed.Runtime.MaxRepeatCycleStreak != DefaultMaxRepeatCycleStreak {
1869+
t.Fatalf("expected default max_repeat_cycle_streak=%d, got %d",
1870+
DefaultMaxRepeatCycleStreak, parsed.Runtime.MaxRepeatCycleStreak)
18721871
}
18731872
}
18741873

internal/config/context_budget_migration.go

Lines changed: 63 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,14 @@ func MigrateContextBudgetConfigContent(raw []byte) ([]byte, bool, []string, erro
134134
if verificationChanged {
135135
changed = true
136136
}
137+
legacyRuntimeChanged := migrateLegacyRuntimeConfigFields(doc)
138+
if legacyRuntimeChanged {
139+
changed = true
140+
}
141+
legacyMemoChanged := migrateLegacyMemoConfigFields(doc)
142+
if legacyMemoChanged {
143+
changed = true
144+
}
137145

138146
if !changed {
139147
return raw, false, nil, nil
@@ -146,6 +154,60 @@ func MigrateContextBudgetConfigContent(raw []byte) ([]byte, bool, []string, erro
146154
return out, true, notes, nil
147155
}
148156

157+
// migrateLegacyRuntimeConfigFields 清理 runtime 下已废弃且会导致严格解析失败的历史字段。
158+
func migrateLegacyRuntimeConfigFields(doc map[string]any) bool {
159+
runtimeValue, ok := doc["runtime"]
160+
if !ok {
161+
return false
162+
}
163+
runtimeMap, ok := migrationStringMap(runtimeValue)
164+
if !ok {
165+
return false
166+
}
167+
168+
changed := false
169+
for _, key := range []string{"max_no_progress_streak"} {
170+
if _, exists := runtimeMap[key]; exists {
171+
delete(runtimeMap, key)
172+
changed = true
173+
}
174+
}
175+
if !changed {
176+
return false
177+
}
178+
doc["runtime"] = runtimeMap
179+
return true
180+
}
181+
182+
// migrateLegacyMemoConfigFields 清理 memo 下已移除且可安全丢弃的历史字段。
183+
func migrateLegacyMemoConfigFields(doc map[string]any) bool {
184+
memoValue, ok := doc["memo"]
185+
if !ok {
186+
return false
187+
}
188+
memoMap, ok := migrationStringMap(memoValue)
189+
if !ok {
190+
return false
191+
}
192+
193+
changed := false
194+
for _, key := range []string{"extract_recent_messages"} {
195+
if _, exists := memoMap[key]; exists {
196+
delete(memoMap, key)
197+
changed = true
198+
}
199+
}
200+
if !changed {
201+
return false
202+
}
203+
if len(memoMap) == 0 {
204+
delete(doc, "memo")
205+
} else {
206+
doc["memo"] = memoMap
207+
}
208+
return true
209+
}
210+
149211
// migrateVerificationConfig 清理已废弃的 verification 字段,并将安全的旧 command string 收敛成 argv。
150212
func migrateVerificationConfig(doc map[string]any) (bool, error) {
151213
runtimeValue, ok := doc["runtime"]
@@ -166,7 +228,7 @@ func migrateVerificationConfig(doc map[string]any) (bool, error) {
166228
}
167229

168230
changed := false
169-
for _, key := range []string{"enabled", "default_task_policy", "final_intercept", "max_retries", "hooks"} {
231+
for _, key := range []string{"enabled", "default_task_policy", "final_intercept", "max_retries", "hooks", "max_no_progress"} {
170232
if _, exists := verificationMap[key]; exists {
171233
delete(verificationMap, key)
172234
changed = true

internal/config/context_budget_migration_test.go

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -206,6 +206,97 @@ runtime:
206206
}
207207
}
208208

209+
func TestMigrateContextBudgetConfigContentRemovesLegacyRuntimeNoProgressField(t *testing.T) {
210+
t.Parallel()
211+
212+
input := []byte(strings.TrimSpace(`
213+
runtime:
214+
max_no_progress_streak: 5
215+
max_repeat_cycle_streak: 3
216+
`) + "\n")
217+
218+
out, changed, notes, err := MigrateContextBudgetConfigContent(input)
219+
if err != nil {
220+
t.Fatalf("MigrateContextBudgetConfigContent() error = %v", err)
221+
}
222+
if !changed {
223+
t.Fatal("expected migration change")
224+
}
225+
if len(notes) != 0 {
226+
t.Fatalf("expected no migration notes, got %v", notes)
227+
}
228+
229+
text := string(out)
230+
if strings.Contains(text, "max_no_progress_streak") {
231+
t.Fatalf("expected max_no_progress_streak removed, got:\n%s", text)
232+
}
233+
if !strings.Contains(text, "max_repeat_cycle_streak: 3") {
234+
t.Fatalf("expected max_repeat_cycle_streak preserved, got:\n%s", text)
235+
}
236+
}
237+
238+
func TestMigrateContextBudgetConfigContentRemovesLegacyVerificationNoProgressField(t *testing.T) {
239+
t.Parallel()
240+
241+
input := []byte(strings.TrimSpace(`
242+
runtime:
243+
verification:
244+
max_no_progress: 3
245+
verifiers:
246+
test:
247+
timeout_sec: 30
248+
`) + "\n")
249+
250+
out, changed, notes, err := MigrateContextBudgetConfigContent(input)
251+
if err != nil {
252+
t.Fatalf("MigrateContextBudgetConfigContent() error = %v", err)
253+
}
254+
if !changed {
255+
t.Fatal("expected migration change")
256+
}
257+
if len(notes) != 0 {
258+
t.Fatalf("expected no migration notes, got %v", notes)
259+
}
260+
261+
text := string(out)
262+
if strings.Contains(text, "max_no_progress") {
263+
t.Fatalf("expected max_no_progress removed, got:\n%s", text)
264+
}
265+
if !strings.Contains(text, "timeout_sec: 30") {
266+
t.Fatalf("expected verifier config preserved, got:\n%s", text)
267+
}
268+
}
269+
270+
func TestMigrateContextBudgetConfigContentRemovesLegacyMemoExtractRecentMessagesField(t *testing.T) {
271+
t.Parallel()
272+
273+
input := []byte(strings.TrimSpace(`
274+
memo:
275+
auto_extract: true
276+
extract_recent_messages: 4
277+
extract_timeout_sec: 9
278+
`) + "\n")
279+
280+
out, changed, notes, err := MigrateContextBudgetConfigContent(input)
281+
if err != nil {
282+
t.Fatalf("MigrateContextBudgetConfigContent() error = %v", err)
283+
}
284+
if !changed {
285+
t.Fatal("expected migration change")
286+
}
287+
if len(notes) != 0 {
288+
t.Fatalf("expected no migration notes, got %v", notes)
289+
}
290+
291+
text := string(out)
292+
if strings.Contains(text, "extract_recent_messages") {
293+
t.Fatalf("expected extract_recent_messages removed, got:\n%s", text)
294+
}
295+
if !strings.Contains(text, "auto_extract: true") || !strings.Contains(text, "extract_timeout_sec: 9") {
296+
t.Fatalf("expected supported memo fields preserved, got:\n%s", text)
297+
}
298+
}
299+
209300
func TestMigrateContextBudgetConfigFileCreatesBackup(t *testing.T) {
210301
t.Parallel()
211302

internal/config/runtime.go

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -8,14 +8,12 @@ import (
88
)
99

1010
const (
11-
DefaultMaxNoProgressStreak = 5
1211
DefaultMaxRepeatCycleStreak = 3
1312
DefaultMaxTurns = 90
1413
)
1514

1615
// RuntimeConfig 定义 runtime 层的可调参数。
1716
type RuntimeConfig struct {
18-
MaxNoProgressStreak int `yaml:"max_no_progress_streak,omitempty"`
1917
MaxRepeatCycleStreak int `yaml:"max_repeat_cycle_streak,omitempty"`
2018
MaxTurns int `yaml:"max_turns,omitempty"`
2119
Verification VerificationConfig `yaml:"verification,omitempty"`
@@ -32,7 +30,6 @@ type RuntimeAssetsConfig struct {
3230
// defaultRuntimeConfig 返回 runtime 配置的静态默认值。
3331
func defaultRuntimeConfig() RuntimeConfig {
3432
return RuntimeConfig{
35-
MaxNoProgressStreak: DefaultMaxNoProgressStreak,
3633
MaxRepeatCycleStreak: DefaultMaxRepeatCycleStreak,
3734
MaxTurns: DefaultMaxTurns,
3835
Verification: defaultVerificationConfig(),
@@ -52,7 +49,6 @@ func defaultRuntimeAssetsConfig() RuntimeAssetsConfig {
5249
// Clone 复制 runtime 配置,避免调用方共享可变状态。
5350
func (c RuntimeConfig) Clone() RuntimeConfig {
5451
return RuntimeConfig{
55-
MaxNoProgressStreak: c.MaxNoProgressStreak,
5652
MaxRepeatCycleStreak: c.MaxRepeatCycleStreak,
5753
MaxTurns: c.MaxTurns,
5854
Verification: c.Verification.Clone(),
@@ -66,9 +62,6 @@ func (c *RuntimeConfig) ApplyDefaults(defaults RuntimeConfig) {
6662
if c == nil {
6763
return
6864
}
69-
if c.MaxNoProgressStreak <= 0 {
70-
c.MaxNoProgressStreak = defaults.MaxNoProgressStreak
71-
}
7265
if c.MaxRepeatCycleStreak <= 0 {
7366
c.MaxRepeatCycleStreak = defaults.MaxRepeatCycleStreak
7467
}
@@ -82,9 +75,6 @@ func (c *RuntimeConfig) ApplyDefaults(defaults RuntimeConfig) {
8275

8376
// Validate 校验 runtime 配置是否满足最小约束。
8477
func (c RuntimeConfig) Validate() error {
85-
if c.MaxNoProgressStreak <= 0 {
86-
return errors.New("max_no_progress_streak must be greater than 0")
87-
}
8878
if c.MaxRepeatCycleStreak <= 0 {
8979
return errors.New("max_repeat_cycle_streak must be greater than 0")
9080
}

0 commit comments

Comments
 (0)