From 7199bbf9b5bd203338ed67888f6ca1a5d8f36794 Mon Sep 17 00:00:00 2001 From: openhands Date: Fri, 20 Mar 2026 20:12:45 +0000 Subject: [PATCH 1/4] fix: feat: agents flush context to scratch file before compaction (#262) Co-Authored-By: Claude Opus 4.6 (1M context) --- action/action-agent.sh | 31 ++++++++++++++++++++++++++----- dev/dev-agent.sh | 35 +++++++++++++++++++++++++++++++---- dev/phase-handler.sh | 8 ++++---- gardener/gardener-agent.sh | 15 ++++++++++++++- lib/formula-session.sh | 31 +++++++++++++++++++++++++++++++ planner/planner-run.sh | 17 ++++++++++++++++- predictor/predictor-run.sh | 17 ++++++++++++++++- 7 files changed, 138 insertions(+), 16 deletions(-) diff --git a/action/action-agent.sh b/action/action-agent.sh index a838d30..5a39856 100644 --- a/action/action-agent.sh +++ b/action/action-agent.sh @@ -41,6 +41,7 @@ WORKTREE="${PROJECT_REPO_ROOT}" PHASE_FILE="/tmp/action-session-${PROJECT_NAME:-harb}-${ISSUE}.phase" IMPL_SUMMARY_FILE="/tmp/action-impl-summary-${PROJECT_NAME:-harb}-${ISSUE}.txt" PREFLIGHT_RESULT="/tmp/action-preflight-${ISSUE}.json" +SCRATCH_FILE="/tmp/action-${ISSUE}-scratch.md" log() { printf '[%s] action#%s %s\n' "$(date -u '+%Y-%m-%d %H:%M:%S UTC')" "$ISSUE" "$*" >> "$LOGFILE" @@ -86,7 +87,7 @@ cleanup() { agent_kill_session "$SESSION_NAME" # Best-effort docker cleanup for containers started during this action (cd "${PROJECT_REPO_ROOT}" 2>/dev/null && docker compose down 2>/dev/null) || true - rm -f "$PHASE_FILE" "$IMPL_SUMMARY_FILE" "$PREFLIGHT_RESULT" + rm -f "$PHASE_FILE" "$IMPL_SUMMARY_FILE" "$PREFLIGHT_RESULT" "$SCRATCH_FILE" } trap cleanup EXIT @@ -166,6 +167,24 @@ if [ -n "${_thread_id:-}" ]; then >> "${MATRIX_THREAD_MAP:-/tmp/matrix-thread-map}" 2>/dev/null || true fi +# --- Read scratch file (compaction survival) --- +SCRATCH_CONTEXT="" +if [ -f "$SCRATCH_FILE" ]; then + SCRATCH_CONTEXT="## Previous context (from scratch file) +$(cat "$SCRATCH_FILE") +" +fi +SCRATCH_INSTRUCTION="## Context scratch file (compaction survival) + +Periodically (every 10-15 tool calls), write a summary of: +- What you have discovered so far +- Decisions made and why +- What remains to do +to: ${SCRATCH_FILE} + +If you find this file exists when you start, read it first — it is your previous context. +This file is ephemeral — not evidence or permanent memory, just a compaction survival mechanism." + # --- Build initial prompt --- PRIOR_SECTION="" if [ -n "$PRIOR_COMMENTS" ]; then @@ -193,7 +212,7 @@ in the issue below. ## Issue #${ISSUE}: ${ISSUE_TITLE} ${ISSUE_BODY} - +${SCRATCH_CONTEXT} ${PRIOR_SECTION}## Instructions 1. Read the action formula steps in the issue body carefully. @@ -235,6 +254,8 @@ ${PRIOR_SECTION}## Instructions If the prior comments above show work already completed, resume from where it left off. +${SCRATCH_INSTRUCTION} + ${PHASE_PROTOCOL_INSTRUCTIONS}" # --- Create tmux session --- @@ -264,16 +285,16 @@ case "${_MONITOR_LOOP_EXIT:-}" in # Escalate to supervisor (idle_prompt already escalated via _on_phase_change callback) echo "{\"issue\":${ISSUE},\"pr\":${PR_NUMBER:-0},\"reason\":\"idle_timeout\",\"ts\":\"$(date -u +%Y-%m-%dT%H:%M:%SZ)\"}" \ >> "${FACTORY_ROOT}/supervisor/escalations-${PROJECT_NAME}.jsonl" - rm -f "$PHASE_FILE" "$IMPL_SUMMARY_FILE" "$THREAD_FILE" + rm -f "$PHASE_FILE" "$IMPL_SUMMARY_FILE" "$THREAD_FILE" "$SCRATCH_FILE" ;; idle_prompt) # Notification + escalation already handled by _on_phase_change(PHASE:failed) callback - rm -f "$PHASE_FILE" "$IMPL_SUMMARY_FILE" "$THREAD_FILE" + rm -f "$PHASE_FILE" "$IMPL_SUMMARY_FILE" "$THREAD_FILE" "$SCRATCH_FILE" ;; done) # Belt-and-suspenders: callback handles primary cleanup, # but ensure sentinel files are removed if callback was interrupted - rm -f "$PHASE_FILE" "$IMPL_SUMMARY_FILE" "$THREAD_FILE" + rm -f "$PHASE_FILE" "$IMPL_SUMMARY_FILE" "$THREAD_FILE" "$SCRATCH_FILE" ;; esac diff --git a/dev/dev-agent.sh b/dev/dev-agent.sh index 63f58c7..059fde5 100755 --- a/dev/dev-agent.sh +++ b/dev/dev-agent.sh @@ -88,6 +88,9 @@ IMPL_SUMMARY_FILE="/tmp/dev-impl-summary-${PROJECT_NAME}-${ISSUE}.txt" # Matrix thread tracking — one thread per issue for conversational notifications THREAD_FILE="/tmp/dev-thread-${PROJECT_NAME}-${ISSUE}" +# Scratch file for context compaction survival +SCRATCH_FILE="/tmp/dev-${PROJECT_NAME}-${ISSUE}-scratch.md" + # Timing export PHASE_POLL_INTERVAL=30 # seconds between phase checks (read by agent-session.sh) IDLE_TIMEOUT=7200 # 2h: kill session if phase stale this long @@ -493,6 +496,26 @@ else done fi +# ============================================================================= +# READ SCRATCH FILE (compaction survival) +# ============================================================================= +SCRATCH_CONTEXT="" +if [ -f "$SCRATCH_FILE" ]; then + SCRATCH_CONTEXT="## Previous context (from scratch file) +$(cat "$SCRATCH_FILE") +" +fi +SCRATCH_INSTRUCTION="## Context scratch file (compaction survival) + +Periodically (every 10-15 tool calls), write a summary of: +- What you have discovered so far +- Decisions made and why +- What remains to do +to: ${SCRATCH_FILE} + +If you find this file exists when you start, read it first — it is your previous context. +This file is ephemeral — not evidence or permanent memory, just a compaction survival mechanism." + # ============================================================================= # BUILD PROMPT # ============================================================================= @@ -593,7 +616,7 @@ This is issue #${ISSUE} for the ${CODEBERG_REPO} project. ## Issue: ${ISSUE_TITLE} ${ISSUE_BODY} - +${SCRATCH_CONTEXT} ## CRASH RECOVERY Your previous session for this issue was interrupted. Resume from where you left off. @@ -617,6 +640,8 @@ $(if [ -n "$CI_RESULT" ]; then printf '\n### Last CI result:\n%s\n' "$CI_RESULT" 2. Resume from the last known phase. 3. Follow the phase protocol below. +${SCRATCH_INSTRUCTION} + ${PHASE_PROTOCOL_INSTRUCTIONS}" else # Normal mode: initial implementation prompt @@ -626,7 +651,7 @@ You have been assigned issue #${ISSUE} for the ${CODEBERG_REPO} project. ## Issue: ${ISSUE_TITLE} ${ISSUE_BODY} - +${SCRATCH_CONTEXT} ## Other open issues labeled 'backlog' (for context if you need to suggest alternatives): ${OPEN_ISSUES_SUMMARY} @@ -678,6 +703,8 @@ printf 'PHASE:failed\nReason: refused\n' > \"${PHASE_FILE}\" **Do NOT invent dependencies that aren't real.** If the code compiles and tests pass, that's ready. +${SCRATCH_INSTRUCTION} + ${PHASE_PROTOCOL_INSTRUCTIONS}" fi @@ -745,7 +772,7 @@ case "${_MONITOR_LOOP_EXIT:-}" in else cleanup_worktree fi - rm -f "$PHASE_FILE" "$IMPL_SUMMARY_FILE" "$THREAD_FILE" \ + rm -f "$PHASE_FILE" "$IMPL_SUMMARY_FILE" "$THREAD_FILE" "$SCRATCH_FILE" \ "/tmp/ci-result-${PROJECT_NAME}-${ISSUE}.txt" [ -n "${PR_NUMBER:-}" ] && rm -f "/tmp/review-injected-${PROJECT_NAME}-${PR_NUMBER}" ;; @@ -755,7 +782,7 @@ case "${_MONITOR_LOOP_EXIT:-}" in done) # Belt-and-suspenders: callback in phase-handler.sh handles primary cleanup, # but ensure sentinel files are removed if callback was interrupted - rm -f "$PHASE_FILE" "$IMPL_SUMMARY_FILE" "$THREAD_FILE" \ + rm -f "$PHASE_FILE" "$IMPL_SUMMARY_FILE" "$THREAD_FILE" "$SCRATCH_FILE" \ "/tmp/ci-result-${PROJECT_NAME}-${ISSUE}.txt" [ -n "${PR_NUMBER:-}" ] && rm -f "/tmp/review-injected-${PROJECT_NAME}-${PR_NUMBER}" CLAIMED=false diff --git a/dev/phase-handler.sh b/dev/phase-handler.sh index ae47e97..7248aeb 100644 --- a/dev/phase-handler.sh +++ b/dev/phase-handler.sh @@ -525,7 +525,7 @@ Instructions: cleanup_labels agent_kill_session "$SESSION_NAME" cleanup_worktree - rm -f "$PHASE_FILE" "$IMPL_SUMMARY_FILE" "$THREAD_FILE" + rm -f "$PHASE_FILE" "$IMPL_SUMMARY_FILE" "$THREAD_FILE" "${SCRATCH_FILE:-}" exit 0 else log "PR #${PR_NUMBER} was closed WITHOUT merge — NOT closing issue" @@ -580,7 +580,7 @@ Instructions: # Local cleanup agent_kill_session "$SESSION_NAME" cleanup_worktree - rm -f "$PHASE_FILE" "$IMPL_SUMMARY_FILE" "$THREAD_FILE" \ + rm -f "$PHASE_FILE" "$IMPL_SUMMARY_FILE" "$THREAD_FILE" "${SCRATCH_FILE:-}" \ "/tmp/ci-result-${PROJECT_NAME}-${ISSUE}.txt" [ -n "${PR_NUMBER:-}" ] && rm -f "/tmp/review-injected-${PROJECT_NAME}-${PR_NUMBER}" CLAIMED=false # Don't unclaim again in cleanup() @@ -679,7 +679,7 @@ $(printf '%s' "$REFUSAL_JSON" | head -c 2000) CLAIMED=false # Don't unclaim again in cleanup() agent_kill_session "$SESSION_NAME" cleanup_worktree - rm -f "$PHASE_FILE" "$IMPL_SUMMARY_FILE" "$THREAD_FILE" \ + rm -f "$PHASE_FILE" "$IMPL_SUMMARY_FILE" "$THREAD_FILE" "${SCRATCH_FILE:-}" \ "/tmp/ci-result-${PROJECT_NAME}-${ISSUE}.txt" [ -n "${PR_NUMBER:-}" ] && rm -f "/tmp/review-injected-${PROJECT_NAME}-${PR_NUMBER}" return 1 @@ -708,7 +708,7 @@ $(printf '%s' "$REFUSAL_JSON" | head -c 2000) else cleanup_worktree fi - rm -f "$PHASE_FILE" "$IMPL_SUMMARY_FILE" "$THREAD_FILE" \ + rm -f "$PHASE_FILE" "$IMPL_SUMMARY_FILE" "$THREAD_FILE" "${SCRATCH_FILE:-}" \ "/tmp/ci-result-${PROJECT_NAME}-${ISSUE}.txt" [ -n "${PR_NUMBER:-}" ] && rm -f "/tmp/review-injected-${PROJECT_NAME}-${PR_NUMBER}" return 1 diff --git a/gardener/gardener-agent.sh b/gardener/gardener-agent.sh index 699e8be..48e0091 100644 --- a/gardener/gardener-agent.sh +++ b/gardener/gardener-agent.sh @@ -37,6 +37,7 @@ SESSION_NAME="gardener-${PROJECT_NAME}" PHASE_FILE="/tmp/gardener-session-${PROJECT_NAME}.phase" RESULT_FILE="/tmp/gardener-result-${PROJECT_NAME}.txt" DUST_FILE="$SCRIPT_DIR/dust.jsonl" +SCRATCH_FILE="/tmp/gardener-${PROJECT_NAME}-scratch.md" # shellcheck disable=SC2034 # read by monitor_phase_loop in lib/agent-session.sh PHASE_POLL_INTERVAL=15 @@ -221,13 +222,18 @@ If a choice is unclear, re-escalate that single item with a clarifying question. ${ESCALATION_REPLY}" fi +# ── Read scratch file (compaction survival) ─────────────────────────────── +SCRATCH_CONTEXT=$(read_scratch_context "$SCRATCH_FILE") +SCRATCH_INSTRUCTION=$(build_scratch_instruction "$SCRATCH_FILE") + # ── Build prompt from formula + dynamic context ──────────────────────────── log "Building gardener prompt from formula" PROMPT="You are the issue gardener for ${CODEBERG_REPO}. Work through the formula below. You MUST write PHASE:done to '${PHASE_FILE}' when finished — the orchestrator will time you out if you return to the prompt without signalling. ${CONTEXT_SECTION} -## Formula +${SCRATCH_CONTEXT:+${SCRATCH_CONTEXT} +}## Formula ${FORMULA_CONTENT} ## Runtime context (bash pre-analysis) @@ -253,6 +259,8 @@ NEVER echo or include the actual token value in output — always reference \$CO printf 'ESCALATE\n1. #NNN \"title\" — reason (a) option1 (b) option2\n' >> '${RESULT_FILE}' echo 'CLEAN' >> '${RESULT_FILE}' # only if truly nothing to do +${SCRATCH_INSTRUCTION} + ## Phase protocol (REQUIRED) When all work is done and verify confirms zero tech-debt: echo 'PHASE:done' > '${PHASE_FILE}' @@ -462,4 +470,9 @@ Fix all items above in a single PR. Each is a small change (rename, comment, sty done <<< "$DUST_GROUPS" fi +# ── Cleanup scratch file on normal exit ────────────────────────────────── +if [ "$FINAL_PHASE" = "PHASE:done" ]; then + rm -f "$SCRATCH_FILE" +fi + log "--- gardener-agent done ---" diff --git a/lib/formula-session.sh b/lib/formula-session.sh index a2529de..58665e6 100644 --- a/lib/formula-session.sh +++ b/lib/formula-session.sh @@ -126,6 +126,37 @@ formula_phase_callback() { esac } +# ── Scratch file helpers (compaction survival) ──────────────────────────── + +# build_scratch_instruction SCRATCH_FILE +# Returns a prompt block instructing Claude to periodically flush context +# to a scratch file so understanding survives context compaction. +build_scratch_instruction() { + local scratch_file="$1" + cat <<_SCRATCH_EOF_ +## Context scratch file (compaction survival) + +Periodically (every 10-15 tool calls), write a summary of: +- What you have discovered so far +- Decisions made and why +- What remains to do +to: ${scratch_file} + +If you find this file exists when you start, read it first — it is your previous context. +This file is ephemeral — not evidence or permanent memory, just a compaction survival mechanism. +_SCRATCH_EOF_ +} + +# read_scratch_context SCRATCH_FILE +# If the scratch file exists, returns a context block for prompt injection. +# Returns empty string if the file does not exist. +read_scratch_context() { + local scratch_file="$1" + if [ -f "$scratch_file" ]; then + printf '## Previous context (from scratch file)\n%s\n' "$(cat "$scratch_file")" + fi +} + # ── Prompt + monitor helpers ────────────────────────────────────────────── # build_prompt_footer [EXTRA_API_LINES] diff --git a/planner/planner-run.sh b/planner/planner-run.sh index fd75451..65f6da9 100755 --- a/planner/planner-run.sh +++ b/planner/planner-run.sh @@ -31,6 +31,8 @@ PHASE_FILE="/tmp/planner-session-${PROJECT_NAME}.phase" # shellcheck disable=SC2034 # read by monitor_phase_loop in lib/agent-session.sh PHASE_POLL_INTERVAL=15 +SCRATCH_FILE="/tmp/planner-${PROJECT_NAME}-scratch.md" + log() { echo "[$(date -u +%Y-%m-%dT%H:%M:%S)Z] $*" >> "$LOG_FILE"; } # ── Guards ──────────────────────────────────────────────────────────────── @@ -53,6 +55,10 @@ $(cat "$MEMORY_FILE") " fi +# ── Read scratch file (compaction survival) ─────────────────────────────── +SCRATCH_CONTEXT=$(read_scratch_context "$SCRATCH_FILE") +SCRATCH_INSTRUCTION=$(build_scratch_instruction "$SCRATCH_FILE") + # ── Build prompt ───────────────────────────────────────────────────────── build_prompt_footer " Relabel: curl -sf -H \"Authorization: token \$CODEBERG_TOKEN\" -X PUT -H 'Content-Type: application/json' '${CODEBERG_API}/issues/{number}/labels' -d '{\"labels\":[LABEL_ID]}' @@ -65,12 +71,21 @@ PROMPT="You are the strategic planner for ${CODEBERG_REPO}. Work through the for ## Project context ${CONTEXT_BLOCK}${MEMORY_BLOCK} - +${SCRATCH_CONTEXT:+${SCRATCH_CONTEXT} +} ## Formula ${FORMULA_CONTENT} +${SCRATCH_INSTRUCTION} + ${PROMPT_FOOTER}" # ── Run session ────────────────────────────────────────────────────────── export CLAUDE_MODEL="opus" run_formula_and_monitor "planner" + +# ── Cleanup scratch file on normal exit ────────────────────────────────── +FINAL_PHASE=$(read_phase "$PHASE_FILE") +if [ "$FINAL_PHASE" = "PHASE:done" ]; then + rm -f "$SCRATCH_FILE" +fi diff --git a/predictor/predictor-run.sh b/predictor/predictor-run.sh index dfc2249..253953a 100755 --- a/predictor/predictor-run.sh +++ b/predictor/predictor-run.sh @@ -33,6 +33,8 @@ PHASE_FILE="/tmp/predictor-session-${PROJECT_NAME}.phase" # shellcheck disable=SC2034 # read by monitor_phase_loop in lib/agent-session.sh PHASE_POLL_INTERVAL=15 +SCRATCH_FILE="/tmp/predictor-${PROJECT_NAME}-scratch.md" + log() { echo "[$(date -u +%Y-%m-%dT%H:%M:%S)Z] $*" >> "$LOG_FILE"; } # ── Guards ──────────────────────────────────────────────────────────────── @@ -45,6 +47,10 @@ log "--- Predictor run start ---" load_formula "$FACTORY_ROOT/formulas/run-predictor.toml" build_context_block AGENTS.md RESOURCES.md +# ── Read scratch file (compaction survival) ─────────────────────────────── +SCRATCH_CONTEXT=$(read_scratch_context "$SCRATCH_FILE") +SCRATCH_INSTRUCTION=$(build_scratch_instruction "$SCRATCH_FILE") + # ── Build prompt ───────────────────────────────────────────────────────── build_prompt_footer @@ -58,12 +64,21 @@ about CI health, issue staleness, agent status, and system conditions. ## Project context ${CONTEXT_BLOCK} - +${SCRATCH_CONTEXT:+${SCRATCH_CONTEXT} +} ## Formula ${FORMULA_CONTENT} +${SCRATCH_INSTRUCTION} + ${PROMPT_FOOTER}" # ── Run session ────────────────────────────────────────────────────────── export CLAUDE_MODEL="sonnet" run_formula_and_monitor "predictor" + +# ── Cleanup scratch file on normal exit ────────────────────────────────── +FINAL_PHASE=$(read_phase "$PHASE_FILE") +if [ "$FINAL_PHASE" = "PHASE:done" ]; then + rm -f "$SCRATCH_FILE" +fi From 8d9e216e33cbadb71fdecae7ff016ad6f96e2a36 Mon Sep 17 00:00:00 2001 From: openhands Date: Fri, 20 Mar 2026 20:22:56 +0000 Subject: [PATCH 2/4] ci: retrigger after stale status (#262) From 6405ac98375035e679ca298da56c80160e7b690d Mon Sep 17 00:00:00 2001 From: openhands Date: Fri, 20 Mar 2026 20:47:22 +0000 Subject: [PATCH 3/4] fix: use shared scratch helpers in dev-agent and action-agent to eliminate duplicates (#262) Co-Authored-By: Claude Opus 4.6 (1M context) --- action/action-agent.sh | 19 +++---------------- dev/dev-agent.sh | 19 +++---------------- predictor/predictor-run.sh | 5 ++--- 3 files changed, 8 insertions(+), 35 deletions(-) diff --git a/action/action-agent.sh b/action/action-agent.sh index 5a39856..cf51dd6 100644 --- a/action/action-agent.sh +++ b/action/action-agent.sh @@ -24,6 +24,7 @@ export PROJECT_TOML="${2:-${PROJECT_TOML:-}}" source "$(dirname "$0")/../lib/env.sh" source "$(dirname "$0")/../lib/agent-session.sh" +source "$(dirname "$0")/../lib/formula-session.sh" # shellcheck source=../dev/phase-handler.sh source "$(dirname "$0")/../dev/phase-handler.sh" SESSION_NAME="action-${ISSUE}" @@ -168,22 +169,8 @@ if [ -n "${_thread_id:-}" ]; then fi # --- Read scratch file (compaction survival) --- -SCRATCH_CONTEXT="" -if [ -f "$SCRATCH_FILE" ]; then - SCRATCH_CONTEXT="## Previous context (from scratch file) -$(cat "$SCRATCH_FILE") -" -fi -SCRATCH_INSTRUCTION="## Context scratch file (compaction survival) - -Periodically (every 10-15 tool calls), write a summary of: -- What you have discovered so far -- Decisions made and why -- What remains to do -to: ${SCRATCH_FILE} - -If you find this file exists when you start, read it first — it is your previous context. -This file is ephemeral — not evidence or permanent memory, just a compaction survival mechanism." +SCRATCH_CONTEXT=$(read_scratch_context "$SCRATCH_FILE") +SCRATCH_INSTRUCTION=$(build_scratch_instruction "$SCRATCH_FILE") # --- Build initial prompt --- PRIOR_SECTION="" diff --git a/dev/dev-agent.sh b/dev/dev-agent.sh index 059fde5..6d3e463 100755 --- a/dev/dev-agent.sh +++ b/dev/dev-agent.sh @@ -23,6 +23,7 @@ set -euo pipefail # Load shared environment source "$(dirname "$0")/../lib/env.sh" source "$(dirname "$0")/../lib/agent-session.sh" +source "$(dirname "$0")/../lib/formula-session.sh" # shellcheck source=./phase-handler.sh source "$(dirname "$0")/phase-handler.sh" @@ -499,22 +500,8 @@ fi # ============================================================================= # READ SCRATCH FILE (compaction survival) # ============================================================================= -SCRATCH_CONTEXT="" -if [ -f "$SCRATCH_FILE" ]; then - SCRATCH_CONTEXT="## Previous context (from scratch file) -$(cat "$SCRATCH_FILE") -" -fi -SCRATCH_INSTRUCTION="## Context scratch file (compaction survival) - -Periodically (every 10-15 tool calls), write a summary of: -- What you have discovered so far -- Decisions made and why -- What remains to do -to: ${SCRATCH_FILE} - -If you find this file exists when you start, read it first — it is your previous context. -This file is ephemeral — not evidence or permanent memory, just a compaction survival mechanism." +SCRATCH_CONTEXT=$(read_scratch_context "$SCRATCH_FILE") +SCRATCH_INSTRUCTION=$(build_scratch_instruction "$SCRATCH_FILE") # ============================================================================= # BUILD PROMPT diff --git a/predictor/predictor-run.sh b/predictor/predictor-run.sh index 253953a..cdb217d 100755 --- a/predictor/predictor-run.sh +++ b/predictor/predictor-run.sh @@ -65,12 +65,11 @@ about CI health, issue staleness, agent status, and system conditions. ## Project context ${CONTEXT_BLOCK} ${SCRATCH_CONTEXT:+${SCRATCH_CONTEXT} -} +}${SCRATCH_INSTRUCTION} + ## Formula ${FORMULA_CONTENT} -${SCRATCH_INSTRUCTION} - ${PROMPT_FOOTER}" # ── Run session ────────────────────────────────────────────────────────── From 26d20af48c1b0734c4b6074105a30db7c2188708 Mon Sep 17 00:00:00 2001 From: openhands Date: Fri, 20 Mar 2026 20:58:32 +0000 Subject: [PATCH 4/4] =?UTF-8?q?fix:=20address=20review=20=E2=80=94=20scrat?= =?UTF-8?q?ch=20file=20survives=20crash,=20cap=20read=20size,=20fix=20inst?= =?UTF-8?q?ruction=20(#262)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Remove SCRATCH_FILE from action-agent cleanup() trap so it survives crashes - Change instruction to note contents already injected (avoid wasted tool call) - Cap scratch file read at 8KB via head -c 8192 - Move predictor scratch instruction after formula (consistent placement) - Remove redundant FINAL_PHASE re-reads in planner/predictor Co-Authored-By: Claude Opus 4.6 (1M context) --- action/action-agent.sh | 2 +- lib/formula-session.sh | 4 ++-- planner/planner-run.sh | 4 ++-- predictor/predictor-run.sh | 9 ++++----- 4 files changed, 9 insertions(+), 10 deletions(-) diff --git a/action/action-agent.sh b/action/action-agent.sh index cf51dd6..a1fd470 100644 --- a/action/action-agent.sh +++ b/action/action-agent.sh @@ -88,7 +88,7 @@ cleanup() { agent_kill_session "$SESSION_NAME" # Best-effort docker cleanup for containers started during this action (cd "${PROJECT_REPO_ROOT}" 2>/dev/null && docker compose down 2>/dev/null) || true - rm -f "$PHASE_FILE" "$IMPL_SUMMARY_FILE" "$PREFLIGHT_RESULT" "$SCRATCH_FILE" + rm -f "$PHASE_FILE" "$IMPL_SUMMARY_FILE" "$PREFLIGHT_RESULT" } trap cleanup EXIT diff --git a/lib/formula-session.sh b/lib/formula-session.sh index 58665e6..f09eec1 100644 --- a/lib/formula-session.sh +++ b/lib/formula-session.sh @@ -142,7 +142,7 @@ Periodically (every 10-15 tool calls), write a summary of: - What remains to do to: ${scratch_file} -If you find this file exists when you start, read it first — it is your previous context. +If this file existed at session start, its contents have already been injected into your prompt above. This file is ephemeral — not evidence or permanent memory, just a compaction survival mechanism. _SCRATCH_EOF_ } @@ -153,7 +153,7 @@ _SCRATCH_EOF_ read_scratch_context() { local scratch_file="$1" if [ -f "$scratch_file" ]; then - printf '## Previous context (from scratch file)\n%s\n' "$(cat "$scratch_file")" + printf '## Previous context (from scratch file)\n%s\n' "$(head -c 8192 "$scratch_file")" fi } diff --git a/planner/planner-run.sh b/planner/planner-run.sh index 65f6da9..3ddb6c7 100755 --- a/planner/planner-run.sh +++ b/planner/planner-run.sh @@ -85,7 +85,7 @@ export CLAUDE_MODEL="opus" run_formula_and_monitor "planner" # ── Cleanup scratch file on normal exit ────────────────────────────────── -FINAL_PHASE=$(read_phase "$PHASE_FILE") -if [ "$FINAL_PHASE" = "PHASE:done" ]; then +# FINAL_PHASE already set by run_formula_and_monitor +if [ "${FINAL_PHASE:-}" = "PHASE:done" ]; then rm -f "$SCRATCH_FILE" fi diff --git a/predictor/predictor-run.sh b/predictor/predictor-run.sh index cdb217d..2c43a4f 100755 --- a/predictor/predictor-run.sh +++ b/predictor/predictor-run.sh @@ -64,12 +64,11 @@ about CI health, issue staleness, agent status, and system conditions. ## Project context ${CONTEXT_BLOCK} -${SCRATCH_CONTEXT:+${SCRATCH_CONTEXT} -}${SCRATCH_INSTRUCTION} - +${SCRATCH_CONTEXT} ## Formula ${FORMULA_CONTENT} +${SCRATCH_INSTRUCTION} ${PROMPT_FOOTER}" # ── Run session ────────────────────────────────────────────────────────── @@ -77,7 +76,7 @@ export CLAUDE_MODEL="sonnet" run_formula_and_monitor "predictor" # ── Cleanup scratch file on normal exit ────────────────────────────────── -FINAL_PHASE=$(read_phase "$PHASE_FILE") -if [ "$FINAL_PHASE" = "PHASE:done" ]; then +# FINAL_PHASE already set by run_formula_and_monitor +if [ "${FINAL_PHASE:-}" = "PHASE:done" ]; then rm -f "$SCRATCH_FILE" fi