Skip to content

Commit 0d7ac27

Browse files
committed
pref(runtime): 去掉taskcompletion无用信号
1 parent 06d6471 commit 0d7ac27

16 files changed

Lines changed: 110 additions & 418 deletions

internal/promptasset/assets.go

Lines changed: 0 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -24,10 +24,6 @@ var coreSections = loadCoreSections()
2424

2525
var repeatCycleReminder = mustReadTemplate("templates/runtime/self_healing_repeat_cycle.txt")
2626

27-
var completionProtocolReminder = mustReadTemplate("templates/runtime/completion_protocol_reminder.md")
28-
29-
var completionProtocolFinalReminder = mustReadTemplate("templates/runtime/completion_protocol_final_reminder.md")
30-
3127
var compactSystemPromptTemplate = mustReadTemplate("templates/context/compact_system_prompt.md")
3228

3329
var planModePlanPrompt = mustReadTemplate("templates/context/plan_mode_plan.md")
@@ -54,16 +50,6 @@ func RepeatCycleReminder() string {
5450
return repeatCycleReminder
5551
}
5652

57-
// CompletionProtocolReminder 返回缺少 task_completion 时的普通协议提示。
58-
func CompletionProtocolReminder() string {
59-
return completionProtocolReminder
60-
}
61-
62-
// CompletionProtocolFinalReminder 返回缺少 task_completion 时的最终协议提示。
63-
func CompletionProtocolFinalReminder() string {
64-
return completionProtocolFinalReminder
65-
}
66-
6753
// CompactSystemPrompt 返回 compact 场景使用的静态 system prompt。
6854
func CompactSystemPrompt(taskStateContract string, summaryFormat string) string {
6955
replacer := strings.NewReplacer(

internal/promptasset/assets_test.go

Lines changed: 9 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -61,20 +61,6 @@ func TestRuntimeReminderTemplates(t *testing.T) {
6161
if !strings.Contains(RepeatCycleReminder(), "exact same arguments") {
6262
t.Fatalf("expected repeat-cycle reminder guidance, got %q", RepeatCycleReminder())
6363
}
64-
for name, prompt := range map[string]string{
65-
"completion": CompletionProtocolReminder(),
66-
"final_completion": CompletionProtocolFinalReminder(),
67-
} {
68-
if !strings.Contains(prompt, "Completion retry rule") {
69-
t.Fatalf("%s reminder should contain retry rule, got %q", name, prompt)
70-
}
71-
if !strings.Contains(prompt, "Do not repeat file lists") {
72-
t.Fatalf("%s reminder should prevent repeated summaries, got %q", name, prompt)
73-
}
74-
if !strings.Contains(prompt, "at most one brief final sentence") {
75-
t.Fatalf("%s reminder should keep final prose concise, got %q", name, prompt)
76-
}
77-
}
7864
}
7965

8066
func TestPlanModePromptTemplates(t *testing.T) {
@@ -109,6 +95,15 @@ func TestPlanModePromptTemplates(t *testing.T) {
10995
if !strings.Contains(PlanModePrompt("build_execute"), "create current-run required todos") {
11096
t.Fatalf("expected build prompt to require direct-build todo bootstrap")
11197
}
98+
if !strings.Contains(PlanModePrompt("build_execute"), "simple conversational inputs") {
99+
t.Fatalf("expected build prompt to cover simple conversational completion")
100+
}
101+
if !strings.Contains(PlanModePrompt("build_execute"), "without an explicit actionable request") {
102+
t.Fatalf("expected build prompt to cover non-actionable input completion")
103+
}
104+
if !strings.Contains(PlanModePrompt("build_execute"), "do not inspect or analyze the project") {
105+
t.Fatalf("expected build prompt to prevent needless project analysis for casual chat")
106+
}
112107
if got := PlanModePrompt("unknown"); got != "" {
113108
t.Fatalf("PlanModePrompt(unknown) = %q, want empty", got)
114109
}

internal/promptasset/templates/context/plan_mode_build_execute.md

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,9 @@ You are currently in build execution.
1010
- Do not create or rewrite the current full plan in this stage.
1111
- If the current plan appears outdated, explain the mismatch and continue, or recommend switching back to planning.
1212
- Do not output `plan_spec` or `summary_candidate` in build execution.
13-
- When the task is complete, your final reply MUST start with `{"task_completion":{"completed":true}}` followed by your user-facing message. Without this signal, the runtime will issue up to two protocol reminders and then terminate the run.
14-
- Do NOT output `task_completion` while you still have tool calls to make. Tools always take priority over completion signals.
15-
- Acceptance is terminal: once you signal completion, the runtime performs a final yes/no check against the plan's verify criteria. If it fails, the run ends — there is no retry.
13+
- If your response includes tool calls, the runtime will execute them and give you the results so you can continue working.
14+
- If your response fully satisfies the user's current input and no tool calls remain, reply directly without any tools. The runtime treats a non-tool response as your final answer and runs acceptance checks against it.
15+
- This applies to simple conversational inputs too, including greetings, casual chat, short Q&A, acknowledgements, open-ended offers for help, and inputs without an explicit actionable project request.
16+
- For simple conversational inputs or inputs without an explicit actionable request, answer briefly, do not call tools, and do not inspect or analyze the project just to make progress.
17+
- Do not stop working while you still have necessary tool calls to make. Tools take priority only when they are actually needed.
18+
- Acceptance is terminal: your final answer enters a yes/no check against the plan's verify criteria. If it fails, the run ends — there is no retry.

internal/promptasset/templates/runtime/completion_protocol_final_reminder.md

Lines changed: 0 additions & 17 deletions
This file was deleted.

internal/promptasset/templates/runtime/completion_protocol_reminder.md

Lines changed: 0 additions & 15 deletions
This file was deleted.

internal/runtime/acceptgate_runtime.go

Lines changed: 1 addition & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -5,23 +5,12 @@ import (
55
"strings"
66

77
"neo-code/internal/partsrender"
8-
"neo-code/internal/promptasset"
98
providertypes "neo-code/internal/provider/types"
109
"neo-code/internal/runtime/acceptgate"
1110
runtimefacts "neo-code/internal/runtime/facts"
1211
agentsession "neo-code/internal/session"
1312
)
1413

15-
const missingCompletionSignalLimit = 6
16-
17-
// completionProtocolReminderForStreak 根据连续缺失完成信号的次数返回对应协议提示。
18-
func completionProtocolReminderForStreak(streak int) string {
19-
if streak >= missingCompletionSignalLimit-1 {
20-
return promptasset.CompletionProtocolFinalReminder()
21-
}
22-
return promptasset.CompletionProtocolReminder()
23-
}
24-
2514
// evaluateAcceptGate 从运行态提取事实快照,并执行最终 Accept Gate。
2615
func (s *Service) evaluateAcceptGate(ctx context.Context, state *runState, assistantMessage providertypes.Message) acceptgate.Report {
2716
if state == nil {
@@ -48,7 +37,7 @@ func (s *Service) evaluateAcceptGate(ctx context.Context, state *runState, assis
4837
PlanVerify: planVerify,
4938
Facts: factsSnapshot,
5039
Todos: todos,
51-
LastAssistantText: renderAssistantTextWithoutCompletion(assistantMessage),
40+
LastAssistantText: strings.TrimSpace(partsrender.RenderDisplayParts(assistantMessage.Parts)),
5241
})
5342
}
5443

@@ -107,25 +96,3 @@ func (s *Service) emitAcceptGateReport(state *runState, report acceptgate.Report
10796
Results: append([]acceptgate.CheckResult(nil), report.Results...),
10897
})
10998
}
110-
111-
func renderAssistantTextWithoutCompletion(message providertypes.Message) string {
112-
text := strings.TrimSpace(partsrender.RenderDisplayParts(message.Parts))
113-
if text == "" {
114-
return ""
115-
}
116-
candidate, ok := extractPlanningJSONObjectIfPresent(text, "task_completion")
117-
if !ok {
118-
return text
119-
}
120-
return strings.TrimSpace(stripPlanningJSONObjectText(text, candidate))
121-
}
122-
123-
// stripCompletionSignalFromAssistantMessage 移除仅供 runtime 控制使用的 task_completion JSON,保留用户可见回复。
124-
func stripCompletionSignalFromAssistantMessage(message providertypes.Message) providertypes.Message {
125-
text := renderAssistantTextWithoutCompletion(message)
126-
if strings.TrimSpace(text) == strings.TrimSpace(partsrender.RenderDisplayParts(message.Parts)) {
127-
return message
128-
}
129-
message.Parts = []providertypes.ContentPart{providertypes.NewTextPart(text)}
130-
return message
131-
}

internal/runtime/controlplane/stop_reason.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,8 @@ const (
1616
StopReasonVerificationFailed StopReason = "verification_failed"
1717
// StopReasonAccepted 表示 completion gate 与 verifier gate 均通过并完成收尾。
1818
StopReasonAccepted StopReason = "accepted"
19-
// StopReasonMissingCompletionSignal 表示模型停止调用工具但没有输出结构化完成信号
20-
StopReasonMissingCompletionSignal StopReason = "missing_completion_signal"
19+
// StopReasonEmptyResponse 表示模型连续返回空文本响应
20+
StopReasonEmptyResponse StopReason = "empty_response"
2121
// StopReasonAcceptCheckFailed 表示最终 Accept Gate 的验收项失败。
2222
StopReasonAcceptCheckFailed StopReason = "accept_check_failed"
2323
// StopReasonTodoNotConverged 表示 required todo 尚未收敛。

internal/runtime/planning.go

Lines changed: 2 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -31,14 +31,6 @@ type planTurnOutput struct {
3131
DisplayText string `json:"-"`
3232
}
3333

34-
type taskCompletionSignal struct {
35-
Completed bool `json:"completed"`
36-
}
37-
38-
type completionTurnOutput struct {
39-
TaskCompletion taskCompletionSignal `json:"task_completion"`
40-
}
41-
4234
// resolvePlanningStage 根据当前会话模式映射出活动的 planning stage。
4335
func resolvePlanningStage(session agentsession.Session) string {
4436
if agentsession.NormalizeAgentMode(session.AgentMode) == agentsession.AgentModePlan {
@@ -142,23 +134,6 @@ func maybeParsePlanTurnOutput(message providertypes.Message) (planTurnOutput, bo
142134
return output, true, nil
143135
}
144136

145-
// maybeParseCompletionTurnOutput 仅在 assistant 明确输出结构化完成信号时返回完成标记。
146-
func maybeParseCompletionTurnOutput(message providertypes.Message) (bool, error) {
147-
text := strings.TrimSpace(partsrender.RenderDisplayParts(message.Parts))
148-
if text == "" || !strings.Contains(text, `"task_completion"`) {
149-
return false, nil
150-
}
151-
candidate, ok := extractPlanningJSONObjectIfPresent(text, "task_completion")
152-
if !ok {
153-
return false, nil
154-
}
155-
var output completionTurnOutput
156-
if err := json.Unmarshal([]byte(candidate.Text), &output); err != nil {
157-
return false, nil
158-
}
159-
return output.TaskCompletion.Completed, nil
160-
}
161-
162137
// extractPlanningJSONObjectIfPresent 在文本中提取首个配平的 JSON 对象。
163138
type extractedPlanningJSONObject struct {
164139
Text string
@@ -410,14 +385,11 @@ func approveCurrentPlan(session *agentsession.Session, planID string, revision i
410385
return nil
411386
}
412387

413-
// markCurrentPlanCompleted 在结构化完成信号和验收同时通过后推进计划完成态
414-
func markCurrentPlanCompleted(session *agentsession.Session, completionSignaled bool) bool {
388+
// markCurrentPlanCompleted 在验收通过后推进计划完成态
389+
func markCurrentPlanCompleted(session *agentsession.Session) bool {
415390
if session == nil || session.CurrentPlan == nil {
416391
return false
417392
}
418-
if !completionSignaled {
419-
return false
420-
}
421393
if session.CurrentPlan.Status == agentsession.PlanStatusCompleted {
422394
return false
423395
}

internal/runtime/planning_test.go

Lines changed: 6 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -191,49 +191,6 @@ Explanation still continues.`)},
191191
}
192192
}
193193

194-
func TestMaybeParseCompletionTurnOutput(t *testing.T) {
195-
t.Parallel()
196-
197-
completed, err := maybeParseCompletionTurnOutput(providertypes.Message{
198-
Role: providertypes.RoleAssistant,
199-
Parts: []providertypes.ContentPart{
200-
providertypes.NewTextPart("{\"task_completion\":{\"completed\":true}}\n任务已经完成。"),
201-
},
202-
})
203-
if err != nil {
204-
t.Fatalf("maybeParseCompletionTurnOutput() error = %v", err)
205-
}
206-
if !completed {
207-
t.Fatal("expected structured completion signal to be detected")
208-
}
209-
210-
completed, err = maybeParseCompletionTurnOutput(providertypes.Message{
211-
Role: providertypes.RoleAssistant,
212-
Parts: []providertypes.ContentPart{providertypes.NewTextPart("plain answer only")},
213-
})
214-
if err != nil {
215-
t.Fatalf("maybeParseCompletionTurnOutput() natural language error = %v", err)
216-
}
217-
if completed {
218-
t.Fatal("expected natural language without completion JSON not to signal completion")
219-
}
220-
}
221-
222-
func TestMaybeParseCompletionTurnOutputIgnoresInvalidStructuredReply(t *testing.T) {
223-
t.Parallel()
224-
225-
completed, err := maybeParseCompletionTurnOutput(providertypes.Message{
226-
Role: providertypes.RoleAssistant,
227-
Parts: []providertypes.ContentPart{providertypes.NewTextPart(`{"task_completion":"done"}`)},
228-
})
229-
if err != nil {
230-
t.Fatalf("maybeParseCompletionTurnOutput() error = %v", err)
231-
}
232-
if completed {
233-
t.Fatal("expected invalid completion payload to be ignored")
234-
}
235-
}
236-
237194
func TestExtractPlanningJSONObjectIfPresent(t *testing.T) {
238195
t.Parallel()
239196

@@ -322,31 +279,22 @@ func TestMarkCurrentPlanCompleted(t *testing.T) {
322279
Verify: acceptText("验证一"),
323280
},
324281
}
325-
if !markCurrentPlanCompleted(&session, true) {
326-
t.Fatalf("expected draft plan with completion signal to transition to completed")
282+
if !markCurrentPlanCompleted(&session) {
283+
t.Fatalf("expected draft plan to transition to completed")
327284
}
328285
if session.CurrentPlan.Status != agentsession.PlanStatusCompleted {
329286
t.Fatalf("Status = %q, want completed", session.CurrentPlan.Status)
330287
}
331288
if !session.PlanCompletionPendingFullReview {
332289
t.Fatal("expected completed plan to request one final full-plan review turn")
333290
}
334-
if markCurrentPlanCompleted(&session, true) {
291+
if markCurrentPlanCompleted(&session) {
335292
t.Fatalf("expected completed plan not to transition again")
336293
}
337294

338-
session.CurrentPlan = &agentsession.PlanArtifact{
339-
ID: "plan-2",
340-
Revision: 1,
341-
Status: agentsession.PlanStatusDraft,
342-
Spec: agentsession.PlanSpec{
343-
Goal: "草案计划",
344-
Steps: []string{"步骤一"},
345-
Verify: acceptText("验证一"),
346-
},
347-
}
348-
if markCurrentPlanCompleted(&session, false) {
349-
t.Fatalf("expected missing completion signal to keep plan unfinished")
295+
session2 := agentsession.New("no plan")
296+
if markCurrentPlanCompleted(&session2) {
297+
t.Fatalf("expected no plan to return false")
350298
}
351299
}
352300

0 commit comments

Comments
 (0)