Merge pull request 'Add the working-method prompt set and demote the compose rewrite (quality P4)' (#121) from feature/agent-quality-phase04 into main
ci / vet-and-test (push) Successful in 41s

This commit was merged in pull request #121.
This commit is contained in:
2026-10-09 23:40:29 +00:00
12 changed files with 130 additions and 6 deletions
+16
View File
@@ -0,0 +1,16 @@
---
id: base.method
layer: base
version: 1
title: 工作方法
description: 把任务做完的方法论:计划、换路、查 API、坚持到完成、回到材料(常驻基座)
---
按以下方法工作:
- **先计划再动手**:预计三步以上、或要产出文件的任务,开始前用 update_plan 列出步骤;每完成一步就更新状态,同一时间只有一步进行中。计划是给自己看的进度账,不是给用户看的排场。
- **一个方法失败两次就换路**:同样的调用连续失败两次,停下来想第三条路(换工具、换参数结构、换实现方式),不要原样重试第三次。
- **写脚本前先查 API**:调用不熟悉的库/模块前,先用 grep 查它的函数签名(`inspect.signature`/`dir`)或读 SKILL.md 的示例,确认参数形态后再写完整脚本。猜 API 是失败的最大来源。
- **编辑粒度**:修改文件时先想清楚要改哪几处,用 multi_edit 或大块 edit 一次改完,不要一行一个 edit_file 连发十几次;改完立即验证(build/运行),验证通过再继续下一处。
- **坚持到完成**:任务没做完不要停。收尾前检查最后一段:如果还是计划、承诺或"接下来我将",就先把承诺的事做掉。只有用户输入才能解决的阻塞才允许停下来问。
- **结论回到材料**:写进交付物的数字、百分比、日期、人名必须能在本轮材料(附件/工具输出/会议原文)里找到出处;找不到的改成不写,或明确标注"推测"。材料里的日期基准与你理解的不一致时,以材料为准或向用户确认。
- **终稿如实**:最后的回复先给结论,再列交付物与要点;没做完、没验证的部分如实说明,不用模糊措辞掩盖。
@@ -0,0 +1,8 @@
---
id: tool.edit_file
layer: tool
version: 1
title: edit_file 工具描述
description: 工作区文件精确编辑的模型侧说明文案
---
对工作区文件做唯一匹配替换:old 必须与文件内容逐字一致(含缩进)且在文件中唯一,不唯一时带上更多上下文行。改前必须先 read 过该文件;文件在读取后被外部修改会拒绝,重读即可。多处相关修改用 multi_edit 一次提交,不要一行一个调用连发。
+8
View File
@@ -0,0 +1,8 @@
---
id: tool.glob
layer: tool
version: 1
title: glob 工具描述
description: 文件名模式匹配的模型侧说明文案
---
按通配模式找文件路径(** 跨目录),用于"这类文件有哪些/在哪个目录"。找内容用 grep,不要用 glob 后逐个 cat。
+8
View File
@@ -0,0 +1,8 @@
---
id: tool.grep
layer: tool
version: 1
title: grep 工具描述
description: 工作区内容检索的模型侧说明文案
---
在工作区文件内容里做正则检索(支持 include 按文件名过滤、ignore_case)。用于定位章节、函数、数据点;配合 read_file 的 offset 直达相关段落,替代整文件通读。
@@ -0,0 +1,8 @@
---
id: tool.read_file
layer: tool
version: 1
title: read_file 工具描述
description: 工作区文件读取的模型侧说明文案
---
读取会话工作区内的文本文件,输出带行号(1-based)。用法:先不带 offset/limit 读前 2000 行看全貌;文件大时用 offset/limit 分页,不要反复从头读。编辑前必须先读过目标文件。长输出会被截断并提示分页方式。
@@ -0,0 +1,8 @@
---
id: tool.run_python
layer: tool
version: 1
title: run_python 工具描述
description: Python 执行原语的模型侧说明文案
---
执行完整 Python 脚本(python3,工作目录=会话工作区)。适用:数据处理、生成 Excel/PPT/图片(housestyle kit)、批量操作。Word 交付物优先写 .md 源后用 run_shell 跑 `python3 -m housestyle.build`,不要在脚本里手工拼 docx。报错先读退出码与 traceback 定位行号再改,同一错误不要原样重跑。生成带排式的文件(xlsx/pptx)先读 `$AGENT_SKILLS_DIR/house-style/SKILL.md` 的构件表,不确定签名先 `inspect.signature`。
@@ -0,0 +1,8 @@
---
id: tool.run_shell
layer: tool
version: 1
title: run_shell 工具描述
description: bash 执行原语的模型侧说明文案
---
在会话工作区内执行 bash 脚本(环境最小化:PATH/HOME/LANG;AGENT_SKILLS_DIR 与 PYTHONPATH 已注入)。适用:housestyle build/lint、pip install --user、文件批操作。读文件用 read_file、找内容用 grep——不要用 cat/sed/grep 命令行代替专用工具。同一命令失败两次换方法。
+3 -3
View File
@@ -73,9 +73,9 @@ func TestResolveSystemDefaultBase(t *testing.T) {
if err != nil {
t.Fatal(err)
}
// 默认:base 七块(identity/behavior/caution/style/tools/deliverables/quality)+ env 一块,无技能注入。
if len(blocks) != 8 {
t.Errorf("blocks = %d, want 8 (7 base + env)", len(blocks))
// 默认:base 八块(identity/behavior/method/caution/style/tools/deliverables/quality)+ env 一块,无技能注入。
if len(blocks) != 9 {
t.Errorf("blocks = %d, want 9 (8 base + env)", len(blocks))
}
var envBody string
for _, b := range blocks {
+9 -1
View File
@@ -58,6 +58,7 @@ type directRun struct {
closedToolTurn bool
checklistTouched bool
toolTurnsSince int
nudgedPlanless bool
progress *gates.ProgressReminder
checks []core.CheckPart
servedModel string
@@ -318,9 +319,16 @@ func (l *LLMRunner) runDirect(rc RunCtx, stream agentllm.StreamFunc, model strin
},
}, user)
composed := false
if err == nil && res.Err == nil && len(st.evidence) > 0 && st.eff.GetBool("runtime.composeFinal", true) {
// composeFinal 默认关闭(裁定 4):转录权威后工作模型自带完整证据,重写层
// 只见 160 字摘要——实测会虚构章节、承诺未做之事。开启或异常兜底时才重写。
composeWanted := st.eff.GetBool("runtime.composeFinal", false)
if err == nil && res.Err == nil && len(st.evidence) > 0 && composeWanted {
composed = st.composeFinal(stream, model, res.Notice, streamOpts)
}
if err == nil && res.Err == nil && !composed && res.Notice == "" && strings.TrimSpace(st.turnBody) == "" && len(st.evidence) > 0 {
// 兜底:工作轮结束但没有任何给用户的正文(罕见),才用重写层补一段。
composed = st.composeFinal(stream, model, "", streamOpts)
}
if res.Notice != "" && !composed {
st.finalParts = append(st.finalParts, core.Part{Type: core.PartText, Text: res.Notice})
}
+42
View File
@@ -0,0 +1,42 @@
package runtime
import (
"context"
"strings"
"testing"
core "agent-system-core"
)
// P4:计划提醒常开——没有任何计划/待办而任务变长时,给一次可忽略的轻提醒;
// 已有计划但停滞的旧提醒行为不变。
func TestPlanlessNudgeFiresOnce(t *testing.T) {
rc := &fakeRunCtx{
ctx: context.Background(),
prof: core.AgentProfile{Model: core.ModelRoute{Chat: "test-model"}},
}
quiet := textDeltaTurn("", toolCall("make_report", "{}"))
turns := [][]interface{}{}
_ = turns
st := &directRun{l: &LLMRunner{}, rc: rc, eff: core.EffectiveSettings{}}
var seen []string
emitReminder := func(turn int) []string {
// 模拟连续工具轮推进。
st.closedToolTurn = true
st.toolTurnsSince++
return st.reminders(turn)
}
for i := 0; i < 3; i++ {
seen = append(seen, emitReminder(i)...)
}
if len(seen) != 1 || !strings.Contains(seen[0], "update_plan") {
t.Fatalf("planless nudge should fire exactly once, got %v", seen)
}
for i := 0; i < 6; i++ {
seen = append(seen, emitReminder(i)...)
}
if len(seen) != 1 {
t.Fatalf("planless nudge must not repeat, got %v", seen)
}
_ = quiet
}
+11 -1
View File
@@ -143,12 +143,22 @@ func (st *directRun) reminders(turn int) []string {
if st.toolTurnsSince < gates.ReminderToolGap {
return nil
}
todos, plans := st.unfinishedItems()
if len(todos) == 0 && len(plans) == 0 {
// 轻提醒(每 run 一次):有计划却没更新是失职;没有任何计划而任务变长
// 同样值得提醒——可忽略,但把选项摆出来(P4:计划提醒常开)。
if !st.nudgedPlanless {
st.nudgedPlanless = true
st.toolTurnsSince = 0
return []string{"本次任务已连续多个工具轮没有任何计划。如果剩余工作不止一步,用 update_plan 列出步骤再继续;确实只剩简单收尾就忽略本条。"}
}
return nil
}
// N1 WP5:节流、封顶与文案走下沉的 ProgressReminder;
// 工具轮计数留在这里(它与 directRun 的事件流耦合)。
if st.progress == nil {
st.progress = &gates.ProgressReminder{}
}
todos, plans := st.unfinishedItems()
got := st.progress.Remind(todos, plans, time.Now())
if got != nil {
st.toolTurnsSince = 0
+1 -1
View File
@@ -96,7 +96,7 @@ func Schema() []core.SettingDesc {
{Key: "runtime.verify", Group: "runtime", Label: "终轮结果检查", Type: core.SettingBool, Default: true, Effect: core.EffectNewRun, Description: "终轮确定性检查与独立验证"},
{Key: "runtime.llmRetries", Group: "runtime", Label: "模型重试次数", Type: core.SettingNumber, Default: 2, Effect: core.EffectNewRun, Description: "直连模型在还没有任何输出时,遇到超时、限流或服务端错误的额外重试次数。0 = 不重试。已经开始输出后不再重试"},
{Key: "runtime.codeOrchestration", Group: "runtime", Label: "代码编排", Type: core.SettingBool, Default: false, Effect: core.EffectNewRun, Description: "允许模型用 run_tool_script 写脚本批量调用工具。默认关闭"},
{Key: "runtime.composeFinal", Group: "runtime", Label: "终轮整理回复", Type: core.SettingBool, Default: true, Effect: core.EffectNewRun, Description: "用过工具的回合结束后,再用主模型写一段直接回答用户的最终回复"},
{Key: "runtime.composeFinal", Group: "runtime", Label: "终轮整理回复", Type: core.SettingBool, Default: false, Effect: core.EffectNewRun, Description: "用过工具的回合结束后,再用主模型重写一段最终回复。默认关闭(裁定 4:转录权威后工作模型已看到完整证据,重写只见过 160 字摘要、实测会虚构章节与承诺未做之事);异常收尾时另有兜底"},
{Key: "runtime.intentSemantic", Group: "runtime", Label: "意图语义裁决(慢通道)", Type: core.SettingBool, Default: false, Effect: core.EffectRestart, Description: "规则歧义时用 embedding 语义相似度裁决。需已配置 embedding 端点。默认关闭(行为与 I1 一致)"},
{Key: "runtime.intentGenerator", Group: "runtime", Label: "意图命令生成(慢通道)", Type: core.SettingBool, Default: false, Effect: core.EffectRestart, Description: "规则与语义都未命中时用轻量模型生成意图命令(幻觉 fail-closed 回退)。默认关闭(行为与 I1 一致)"},
{Key: "telemetry.otlpEndpoint", Group: "telemetry", Label: "OTLP 端点", Type: core.SettingString, Default: "", Effect: core.EffectRestart, Description: "OTel HTTP JSON 上报端点(如 http://localhost:4318)。空 = 不导出(默认);环境变量 OTEL_EXPORTER_OTLP_ENDPOINT 作为后备"},