From fd2d5165a69a26a04b0697c7ae0dc6b9fa3bbb62 Mon Sep 17 00:00:00 2001 From: dj-oyu <68707227+dj-oyu@users.noreply.github.com> Date: Sun, 22 Feb 2026 11:28:56 +0900 Subject: [PATCH] fix: enforce MEMORY.md header preservation and simplify /plan clear Strengthen prompts to prevent LLM from overwriting the # Active Plan header block. Include full header in Target Format template. Simplify /plan clear to check ReadMemory instead of HasActivePlan so orphaned MEMORY.md files can be cleared. Remove tg.expand() for default sizing. Co-Authored-By: Claude Opus 4.6 --- pkg/agent/context.go | 10 ++++++---- pkg/agent/loop.go | 6 +++--- pkg/agent/memory.go | 26 +++++++++++++++++++------- 3 files changed, 28 insertions(+), 14 deletions(-) diff --git a/pkg/agent/context.go b/pkg/agent/context.go index 4e4835ada..dcef0640e 100644 --- a/pkg/agent/context.go +++ b/pkg/agent/context.go @@ -82,20 +82,22 @@ Your workspace is at: %s 3. **Memory & Plans** - Use memory/MEMORY.md for structured plans. + - NEVER remove or overwrite the header block (# Active Plan, > Task:, > Status:, > Phase:). The system parses these lines to track plan state. - If Status is "interviewing": Ask clarifying questions. After each answer, use edit_file to save findings to ## Context in memory/MEMORY.md. - When you have enough information, write ## Phase and ## Commands sections into MEMORY.md, then set Status to "executing". + When you have enough information, add ## Phase sections with "- [ ]" checkbox steps, and ## Commands section below the header. Then change > Status: to "executing". - If Status is "executing": Work through the current Phase's steps. - Mark each [x] via edit_file. The system will auto-advance phases. - - Plan format: + Mark each "- [x]" via edit_file. The system will auto-advance phases. + - Plan format (header is written by the system — do NOT delete it): # Active Plan > Task: > Status: interviewing | executing > Phase: ## Phase 1: - [ ] Step 1 - ## Phase 2: <title> - [ ] Step 2 + ## Phase 2: <title> + - [ ] Step 1 ## Commands build: <build command> test: <test command> diff --git a/pkg/agent/loop.go b/pkg/agent/loop.go index 8989ee020..6a154bbaa 100644 --- a/pkg/agent/loop.go +++ b/pkg/agent/loop.go @@ -772,7 +772,7 @@ func (al *AgentLoop) runAgentLoop(ctx context.Context, agent *AgentInstance, opt if agent.ContextBuilder.GetPlanStatus() == "interviewing" && agent.interviewStaleCount >= interviewStaleThreshold { messages = append(messages, providers.Message{ Role: "user", - Content: "[System] You have been interviewing for several turns without updating memory/MEMORY.md. Please use edit_file now to save your findings to the ## Context section, or organize the plan into Phases if you have enough information.", + Content: "[System] You have been interviewing for several turns without updating memory/MEMORY.md. Please use edit_file now to save your findings to the ## Context section, or organize the plan into ## Phase sections with `- [ ]` checkbox steps if you have enough information.", }) } @@ -968,7 +968,7 @@ func buildPlanReminder(planStatus string) (providers.Message, bool) { case "interviewing": content = "[System] You are interviewing the user to build a plan. " + "Ask clarifying questions and save findings to ## Context in memory/MEMORY.md using edit_file. " + - "When you have enough information, write ## Phases and ## Commands sections." + "When you have enough information, write ## Phase sections with `- [ ]` checkbox steps, and ## Commands section." case "review": content = "[System] The plan is under review. " + "Wait for the user to approve or request changes. Do not proceed with execution." @@ -2469,7 +2469,7 @@ func (al *AgentLoop) handlePlanCommand(args []string) (string, bool) { sub := args[0] switch sub { case "clear": - if !agent.ContextBuilder.HasActivePlan() { + if agent.ContextBuilder.ReadMemory() == "" { return "No active plan to clear.", true } if err := agent.ContextBuilder.ClearMemory(); err != nil { diff --git a/pkg/agent/memory.go b/pkg/agent/memory.go index 2aa51075a..fd6fd7ac0 100644 --- a/pkg/agent/memory.go +++ b/pkg/agent/memory.go @@ -449,23 +449,35 @@ func (ms *MemoryStore) GetInterviewContext() string { sb.WriteString("- Tooling preferences (test framework, linter, formatter, CI)\n") sb.WriteString("- Key commands the user already runs (build, test, deploy)\n") sb.WriteString("\n### Rules\n") + sb.WriteString("- NEVER remove or overwrite the header block (`# Active Plan`, `> Task:`, `> Status:`, `> Phase:` lines). The system parses these to track state.\n") sb.WriteString("- After each answer, use edit_file to append findings to the ## Context section of memory/MEMORY.md.\n") - sb.WriteString("- When you have enough information, use edit_file to write ## Phase, ## Commands, and ## Context sections into memory/MEMORY.md.\n") + sb.WriteString("- When you have enough information, use edit_file to add ## Phase, ## Commands, and ## Context sections BELOW the header block.\n") + sb.WriteString("- Each step MUST use checkbox syntax: `- [ ] description`. The system parses checkboxes to track progress.\n") sb.WriteString("- Organize into 2-5 phases with 3-5 steps each.\n") - sb.WriteString("- After writing Phases, set Status to executing. The system will handle the rest.\n") - sb.WriteString("\n### Target Format\n") - sb.WriteString("```\n") + sb.WriteString("- After writing Phases, change `> Status: interviewing` to `> Status: executing` via edit_file.\n") + sb.WriteString("\n### Target Format (MANDATORY — system parses this exact structure)\n") + sb.WriteString("\n") + sb.WriteString("# Active Plan\n") + sb.WriteString("> Task: <description>\n") + sb.WriteString("> WorkDir: <path>\n") + sb.WriteString("> Status: interviewing\n") + sb.WriteString("> Phase: 1\n") + sb.WriteString("\n") sb.WriteString("## Phase 1: <title>\n") - sb.WriteString("- [ ] Step\n") + sb.WriteString("- [ ] Step description\n") + sb.WriteString("- [ ] Step description\n") + sb.WriteString("\n") sb.WriteString("## Phase 2: <title>\n") - sb.WriteString("- [ ] Step\n") + sb.WriteString("- [ ] Step description\n") + sb.WriteString("- [ ] Step description\n") + sb.WriteString("\n") sb.WriteString("## Commands\n") sb.WriteString("build: <project-specific build command>\n") sb.WriteString("test: <project-specific test command>\n") sb.WriteString("lint: <project-specific lint command>\n") + sb.WriteString("\n") sb.WriteString("## Context\n") sb.WriteString("<collected requirements, decisions, environment>\n") - sb.WriteString("```\n") return sb.String() }