feat(agent): L1 记忆注入——重试任务带上次失败原因喂回 prompt

- store.lastRunErrorOf(taskId):取最近一次失败 run 的 error(任意 kind)
- orchestrator.claimOne:写 job.json 时填充 task.lastRunError 下发 worker
- runner 三处 prompt(executor/planner/conflict)注入「## 上次失败原因」段(截断 2000 字符)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
wangjia
2026-06-24 22:09:06 +08:00
parent 7a6c826610
commit ecea1ae9b6
4 changed files with 97 additions and 2 deletions
+23 -1
View File
@@ -25,7 +25,24 @@ const DEFAULT_MAX_TURNS = 100;
/** 若项目未配置 timeoutMs 时的全局兜底超时(30min */
const DEFAULT_TIMEOUT_MS = 30 * 60_000;
/** 组装任务提示词:标题 + operations/spec 全文 + 执行约束 */
/**
* L1 记忆注入:若任务带「上次失败原因」(重试任务),生成一段提醒;否则空数组。
* 由 daemon 在 claimOne 时把 store.lastRunErrorOf 填进 task.lastRunError 下发(见 orchestrator)。
*/
function lastErrorLines(task: Task): string[] {
const err = task.lastRunError?.trim();
if (!err) return [];
return [
'## 上次失败原因(这是重试任务,请勿重蹈覆辙)',
'上一次执行本任务的 agent 失败,错误信息如下。请先理解失败根因,针对性规避:',
'```',
err.length > 2000 ? err.slice(0, 2000) + '\n…(已截断)' : err,
'```',
'',
];
}
/** 组装任务提示词:标题 + operations/spec 全文 + 上次失败(L1) + 执行约束 */
export function buildPrompt(task: Task): string {
const body = task.operations ?? task.spec ?? task.plan ?? '';
return [
@@ -35,6 +52,7 @@ export function buildPrompt(task: Task): string {
'## 任务内容',
body || '(无详细说明,按标题完成)',
'',
...lastErrorLines(task),
'## 执行约束(必须遵守)',
'- 只在当前工作目录(git worktree)内改动文件,不得读写或修改 worktree 之外的任何文件。',
`- 完成后用 \`git add -A && git commit\` 提交全部改动,commit message 必须包含任务 ID「${task.id}」。`,
@@ -117,10 +135,12 @@ export type PlannerFn = (task: Task, project: Project, kind: PlanKind, runId: st
/** planner 提示词:只读代码库,spec=写改动方案;decompose=拆子任务并在末尾输出 fenced JSON。 */
export function buildPlannerPrompt(task: Task, kind: PlanKind): string {
const head = [`# 任务:${task.title}`, `任务 ID${task.id}`];
const errLines = lastErrorLines(task);
if (kind === 'spec') {
if (task.spec) head.push('', '## 现有方案草稿(可改进/替换)', task.spec);
return [
...head, '',
...errLines,
'## 你的角色:方案作者(只读,不改任何文件)',
'只读这个代码库,为上述任务写一份「具体改动方案」。不要编辑文件、不要执行有副作用的命令。',
'## 输出(markdown 正文)',
@@ -133,6 +153,7 @@ export function buildPlannerPrompt(task: Task, kind: PlanKind): string {
if (task.plan) head.push('', '## 现有分析草稿(可改进/替换)', task.plan);
return [
...head, '',
...errLines,
'## 你的角色:任务拆解者(只读,不改任何文件)',
`当前任务层级 depth=${task.depth ?? 1}(根=1),最多拆到 depth=4(剩余可拆层数 ${Math.max(0, 4 - (task.depth ?? 1))} 层)。`,
'只读这个代码库,分析上述(复杂)任务,拆成 2–6 个更小、可独立交付的子任务。不要编辑文件、不要执行有副作用的命令。',
@@ -189,6 +210,7 @@ export function buildConflictPrompt(task: Task, conflictFiles: string[]): string
'## 冲突文件',
conflictFiles.length ? conflictFiles.map((f) => `- ${f}`).join('\n') : '(以 git status 为准)',
'',
...lastErrorLines(task),
'## 执行约束(必须遵守)',
'- 逐个打开冲突文件,理解 <<<<<<< HEAD 与 >>>>>>> 两侧的意图,保留双方有价值的改动,不得丢弃任一方的实质内容。',
'- 解决完所有冲突后,用 `git add -A` 暂存,然后 `git commit`(直接用默认合并 commit message,不要修改)。',