# Prompt-Lock Fix Implementation Plan (Planner Claude — Final) - **Planner**: Planner Claude (`canary-projects-multi-agent-mux-planner-claude`) - **Date**: 2026-07-11 - **Brief**: `.mam/reports/brief-planner-prompt-lock-plan.md` - **Inputs consolidated**: 1. Reviewer Cline — `.agents/reports/canary-projects-multi-agent-mux-reviewer-cline/report-prompt-lock-analysis.md` 2. Creator Claude — `.agents/reports/canary-projects-multi-agent-mux-creator-claude/report-prompt-lock-analysis.md` - **Executor**: Antigravity - **Roadmap linkage**: closes **FW-W2** (`FUTURE_WORKS.md:25` / `FUTURE_WORKS.ko.md:24`) --- ## 0. Consolidation Verdict Both analyses agree on the root causes (keys sent on **timers, not evidence**; no dialog detection; `wait_for_tui_ready` misclassifies dialogs as ready) and on the remedy shape (evidence-based `send_keys_safe` in `lib.sh`). I adopt **Creator Claude's helper design** as the base with four planner amendments (A1–A4 below). **Discrepancy resolved — the delegate-job duplicate.** Cline's Site B is **real and Creator Claude missed it**: `.agents/skills/multi-agent-mux-delegate-job/multi-agent-mux-delegate-job:365-369` is a raw copy-paste of the `inject_instructions` body (verified in code; Creator's "sole caller is `create_session.sh:384`" is technically true of the *function* but ignores the duplicated *logic*). This is the highest-traffic delegation path and **must** be in the mod-sites list (MS-7). I verified `lib.sh` is side-effect-free at source time (only variable defaults + function definitions), so the delegate-job wrapper can safely `source` it — this also retires the copy-paste that violates lib.sh's single-source-of-truth mandate (header §4.1). **Planner amendments to Creator's helper:** - **A1 — marker derivation**: Creator's `head -c 200 | tail -c 24` can straddle a newline in the multi-line instructions built at `create_session.sh:374-381`, producing a marker that can never match a single captured line. Amended: last 24 chars of the **last non-empty line**. - **A2 — submit verification**: Creator's "marker left `tail -n 5`" alone risks a false *failure* (Claude's TUI echoes the submitted prompt into the transcript just above the input box, so the marker can linger in the bottom 5 lines after a successful submit → spurious retries → exit 4 → spurious rollback). Amended: submitted = marker left the **bottom 3 lines** *AND* the pane visibly changed relative to a pre-Enter snapshot. Line count is DoD-tunable (DoD-6). - **A3 — dialog detection scope**: grep the **bottom 20 lines** of the pane, not the full viewport — dialog signatures appearing in agent *conversation output* higher up must not block delivery forever. - **A4 — resume needs an accept-policy helper, not `send_keys_safe`**: `send_keys_safe` deliberately *refuses* to type into dialogs; the resume flow must *accept* the trust/bypass dialogs. That is a separate policy helper, `handle_startup_dialogs` (conditional, signature-gated — replaces the blind `Enter/Down/Enter` both reports condemned). Non-goal (out of scope, per surgical principle): adding dialog auto-accept to the **create** path — it has never had dialog handling; with MS-2/MS-5 a dialog during create now fails loudly with rollback instead of silently prompt-locking. If field data shows trust dialogs during create, a follow-up can reuse `handle_startup_dialogs`. --- ## 1. Deliverable 1 — Concrete Helper Implementation **Insert into `.agents/skills/lib.sh` immediately after `inject_instructions` (after current line 1100).** Plain bash, no new dependencies. ```bash # --------------------------------------------------------------------------- # Prompt-lock safe delivery (FW-W2). Keys are sent on evidence, not timers. # send_keys_safe returns 0 only if the text was verifiably submitted. # Exit codes: 1=pane never quiesced 2=dialog blocking input # 3=paste not visible 4=Enter not accepted # Callers MUST handle non-zero. # --------------------------------------------------------------------------- # Server-aware tmux (same isolation rule as inject_instructions). _sks_tmux() { if [ -n "${TMUX_SERVER_NAME:-}" ] && [ "$TMUX_SERVER_NAME" != "default" ]; then tmux -L "$TMUX_SERVER_NAME" "$@" else tmux "$@" fi } _pane_capture() { _sks_tmux capture-pane -p -t "$1" 2>/dev/null || echo ""; } # _pane_quiescent [tries=20] [interval=0.5] # Renderer settled = two consecutive identical non-empty captures. # Defeats RC-A (Blessed/Ink renderer bottleneck) without a magic fixed sleep. _pane_quiescent() { local sess="$1" tries="${2:-20}" interval="${3:-0.5}" prev="__none__" cur i for ((i = 0; i < tries; i++)); do cur=$(_pane_capture "$sess") [ -n "$cur" ] && [ "$cur" = "$prev" ] && return 0 prev="$cur" sleep "$interval" done return 1 } # _pane_dialog_open — focus-stealing modal signatures (trust / # permission / OAuth / list-selection), checked in the bottom 20 pane lines # only (dialogs render near the input area; conversation text above must not # trigger this). Tokens must NOT appear on normal idle prompt screens — # validate against real captures per agent TUI release (DoD-5). _pane_dialog_open() { _pane_capture "$1" | tail -n 20 | grep -Eq \ 'Do you trust the files|Yes, proceed|No, exit|Allow this|Press Enter to continue|browser to authenticate|Use arrow keys|Esc to cancel' } # send_keys_safe [job_id] # 1. Wait for renderer quiescence (RC-A). # 2. Refuse to paste while a dialog is open (RC-B/RC-C): wait up to # SKS_DIALOG_TIMEOUT (default 30 s); if SKS_DIALOG_ESCAPE=1, send a single # Escape per poll and re-check. NEVER a blind Enter — accepting an unknown # dialog is a policy decision, not a delivery detail. # 3. Paste via unique buffer; verify the text landed (marker visible). # 4. Submit C-m; verify submission (marker left the input area AND the pane # changed); retry up to 3 times — re-Enter on unsubmitted text is idempotent. send_keys_safe() { local sess="$1" text="$2" job_id="${3:-adhoc}" local marker pre_submit deadline try # Verification token: last 24 chars of the last non-empty line (multi-line safe). marker=$(printf '%s' "$text" | tr -d '\r' | awk 'NF {line=$0} END {print line}' | tail -c 24) _pane_quiescent "$sess" || { echo "send_keys_safe: pane never quiesced ($sess)" >&2; return 1; } deadline=$(( $(date +%s) + ${SKS_DIALOG_TIMEOUT:-30} )) while _pane_dialog_open "$sess"; do if [ "${SKS_DIALOG_ESCAPE:-0}" = "1" ]; then _sks_tmux send-keys -t "$sess" Escape sleep 1 fi if [ "$(date +%s)" -ge "$deadline" ]; then echo "send_keys_safe: dialog blocking input ($sess)" >&2 return 2 fi sleep 2 done _sks_tmux set-buffer -b "sks_$job_id" "$text" _sks_tmux paste-buffer -b "sks_$job_id" -t "$sess" _sks_tmux delete-buffer -b "sks_$job_id" 2>/dev/null || true sleep 0.5 _pane_capture "$sess" | grep -Fq "$marker" || { echo "send_keys_safe: paste not visible ($sess)" >&2; return 3; } for try in 1 2 3; do pre_submit=$(_pane_capture "$sess") _sks_tmux send-keys -t "$sess" C-m sleep "$try" # Submitted = input area released the text AND rendering changed after Enter. if ! _pane_capture "$sess" | tail -n 3 | grep -Fq "$marker" \ && [ "$(_pane_capture "$sess")" != "$pre_submit" ]; then return 0 fi done echo "send_keys_safe: Enter not accepted after 3 tries ($sess)" >&2 return 4 } # handle_startup_dialogs [timeout_sec=20] # Post-start/resume dialog policy for claude: accept the trust / bypass # dialogs ONLY when their signature is positively on screen; return as soon # as the TUI banner is ready. Replaces the blind Enter/Down/Enter sequence. # Non-fatal by design: on timeout the caller proceeds (attach shows leftovers). handle_startup_dialogs() { local sess="$1" timeout="${2:-20}" waited=0 pane while [ "$waited" -lt "$timeout" ]; do pane=$(_pane_capture "$sess" | tail -n 20) if printf '%s\n' "$pane" | grep -q 'Do you trust the files'; then _sks_tmux send-keys -t "$sess" Enter # accept trust prompt elif printf '%s\n' "$pane" | grep -q 'Yes, proceed'; then _sks_tmux send-keys -t "$sess" Down # select "Yes, proceed" sleep 0.3 _sks_tmux send-keys -t "$sess" Enter elif printf '%s\n' "$pane" | grep -Eq 'Anthropic|Assistant|Chat|Welcome|projects'; then return 0 # ready, no dialog fi sleep 2 waited=$((waited + 2)) done return 0 } ``` > ⚠️ **Signature-token caveat (both analyses inherited this)**: neither input report captured the *actual* dialog text of current claude/agy/hermes/cline TUI builds. The token lists above are the best available hypotheses. **DoD-5 makes validating them against real `capture-pane` output a merge blocker** — the executor must adjust tokens to observed text before committing. --- ## 2. Deliverable 2 — Surgical Mod-Sites List Every change traces to a verified defect site. No other lines are touched. | # | File : lines (current) | Change | |---|---|---| | **MS-1** | `.agents/skills/lib.sh` (insert after 1100) | Add the §1 helper block verbatim. | | **MS-2** | `.agents/skills/lib.sh:1056` | `wait_for_tui_ready` claude regex: `"Anthropic\|Assistant\|Chat\|Dangerously\|dangerously\|Enter\|Welcome\|projects"` → `"Anthropic\|Assistant\|Chat\|Welcome\|projects"` (drop the three tokens that also match trust/bypass dialogs — RC-B fix). | | **MS-3** | `.agents/skills/lib.sh:1050-1053` | Inside the retry loop, before the `case`: skip the ready-check while a dialog is up — insert `if _pane_dialog_open "$sess"; then sleep 1; continue; fi` after the capture (requires MS-1 helpers; they are defined later in the file but resolved at call time — bash allows this). | | **MS-4** | `.agents/skills/lib.sh:1083` | `echo "⚠️ Warning: ... Proceeding anyway..."` → `echo "⚠️ TUI readiness check timed out for '$sess'." >&2; return 1` — the gate must be allowed to fail. | | **MS-5** | `.agents/skills/lib.sh:1088-1100` | Rewrite `inject_instructions` body as a thin wrapper: `inject_instructions() { send_keys_safe "$1" "$2" "${3:-onboard}"; }` (same signature; sole caller inherits the fix; keep the function comment, note the delegation). | | **MS-6** | `.agents/skills/multi-agent-mux-create/scripts/create_session.sh:199` | `wait_for_tui_ready "$SESSION_NAME" "$AGENT"` → explicit guard: `if ! wait_for_tui_ready "$SESSION_NAME" "$AGENT"; then echo "ERROR: agent TUI never became ready — aborting (rollback via trap)" >&2; exit 1; fi` (the `trap cleanup_tmux_on_error EXIT` armed at line 196 kills the session and removes the isolation home). | | **MS-7** | `.agents/skills/multi-agent-mux-create/scripts/create_session.sh:384` | Guard the injection: `if ! inject_instructions "$SESSION_NAME" "$instructions" "$DELEGATE_JOB_ID"; then delegate_publish_event "$DELEGATE_JOB_ID" error "instruction injection failed (prompt-lock, rc=$?)"; exit 1; fi` — the job gets a terminal `error` event (no more zombie jobs waiting for watchdog timeout), then the EXIT trap rolls the session back. `started` (line 386) now only publishes on verified delivery. | | **MS-8** | `.agents/skills/multi-agent-mux-delegate-job/multi-agent-mux-delegate-job:364-369` | Replace the duplicated raw block (`set-buffer`/`paste-buffer`/`sleep 0.5`/`C-m`/`delete-buffer`) with: `source "$SCRIPT_DIR/../lib.sh"` (immediately before use; lib.sh is load-side-effect-free — verified) then `if ! send_keys_safe "$sess" "$instructions" "$job_id"; then echo "ERROR: 프롬프트 주입 실패 — 세션 '$sess' (프롬프트 잠금 의심)" >&2; return 1; fi`. Keep the local `_tmux` definition (lines 347-350) — still used by `has-session` (352) and the attach hint (371). The `return 1` propagates under `set -euo pipefail` and fires the EXIT trap at 358-362, which publishes the `error` event. | | **MS-9** | `.agents/skills/multi-agent-mux-resume/SKILL.md:150-156` | Replace the blind block (`sleep 5; Enter; sleep 3; Down; sleep 0.3; Enter`) with: `handle_startup_dialogs "$SESSION_NAME" 20` (the embedded script already sources `lib.sh` at line 68). Fixes both failure directions: no-dialog → no stray keys into the prompt/history; late dialog → polled, not raced. | | **MS-10** | `.agents/skills/multi-agent-mux-stop/scripts/stop_session.sh:192` | `tmux send-keys -t "$SESSION_NAME" "$exitkey" Enter 2>/dev/null \|\| true` → `send_keys_safe "$SESSION_NAME" "$exitkey" "stop$$" \|\| echo "graceful: safe delivery failed (rc=$?) — falling back to kill chain"` (script already sources lib.sh at line 32; the SIGTERM→SIGKILL fallback chain at 193-207 stays byte-identical). | | **MS-11** | `.agents/skills/multi-agent-mux-create/SKILL.md:214-215` | Replace the stray-Enter probe example with a passive one: `tmux capture-pane -t "$SESSION_NAME" -p -S -20 # TUI ready = agent banner visible, no dialog text` (never fire keys as a liveness probe). | | **MS-12** | `FUTURE_WORKS.md:25`, `FUTURE_WORKS.ko.md:24` | Mark **FW-W2** resolved using the existing FW-D1 strikethrough convention (`~~...~~` + resolution date 2026-07-11 + commit ref), both languages. | **Explicitly NOT touched** (verified non-vulnerable, matching Creator §2-F): `update_yaml_resumed.sh`, `scripts/install_mam.sh`, `reconcile.sh`, all Python backplane scripts. --- ## 3. Deliverable 3 — Execution Instructions (for Antigravity) Run from the repo root, in order. Halt and report back on any non-zero step that isn't explicitly tolerated. ### T0 — Preflight ```bash cd /home/godopu16/PuKi/laa/canary_projects/multi-agent-mux git diff --quiet && git diff --cached --quiet || { echo "ABORT: dirty tree"; exit 1; } ``` (Untracked files are expected: the two analysis reports and this plan.) ### T1 — Apply MS-1…MS-5 to `lib.sh` Insert the §1 helper block after line 1100; apply the four edits to `wait_for_tui_ready` / `inject_instructions` exactly as specified in §2. Then: ```bash bash -n .agents/skills/lib.sh ``` ### T2 — Apply MS-6/MS-7 to `create_session.sh`, MS-8 to the delegate-job wrapper, MS-9 to `resume/SKILL.md`, MS-10 to `stop_session.sh`, MS-11 to `create/SKILL.md` ```bash bash -n .agents/skills/multi-agent-mux-create/scripts/create_session.sh \ .agents/skills/multi-agent-mux-stop/scripts/stop_session.sh \ .agents/skills/multi-agent-mux-delegate-job/multi-agent-mux-delegate-job command -v shellcheck >/dev/null && shellcheck -S warning \ .agents/skills/lib.sh \ .agents/skills/multi-agent-mux-create/scripts/create_session.sh \ .agents/skills/multi-agent-mux-stop/scripts/stop_session.sh || true ``` Zero *new* findings allowed (pre-existing findings are out of scope). ### T3 — Verification gate (Definition of Done) — scratch server only, never real sessions All tmux activity on `tmux -L sks-test`; `tmux -L sks-test kill-server` afterwards. 1. **DoD-1 RC-A (renderer stall)**: mock TUI that floods stdout for 10 s before reading stdin (`while :; do echo spam; done & sleep 10; kill %1; cat`) → `send_keys_safe` must wait out quiescence and deliver (rc 0); confirm by capturing the mock's received line. 2. **DoD-2 RC-B/C (dialog block)**: mock that prints `Do you trust the files in this folder?` and swallows input → `send_keys_safe` must refuse to paste and return **2** after `SKS_DIALOG_TIMEOUT=6`; with `SKS_DIALOG_ESCAPE=1` verify a single Escape per poll, still no blind Enter. 3. **DoD-3 E2E create**: real `create_session.sh --submit-job` on a scratch workspace → instructions verifiably submitted, `started` event observed, normal stop afterwards. 4. **DoD-4 E2E delegate + resume + stop**: delegate-job `submit` to a running scratch session (delivery via the new path); resume a stopped claude scratch session **twice** — once where the trust dialog appears, once where it doesn't — confirm no stray keys land in the prompt in the second case; `--graceful` stop delivers `/exit` via the helper and the fallback chain still engages when the pane is blocked. 5. **DoD-5 signature validation (merge blocker)**: `capture-pane` the real trust/bypass/permission dialogs of the current claude build; confirm every `_pane_dialog_open` and `handle_startup_dialogs` token matches observed text and none appears on the idle prompt screen; adjust tokens to reality before committing. 6. **DoD-6 A2 tuning**: during DoD-3, confirm the submit check (marker leaves bottom-3-lines + pane change) doesn't false-fail on the TUI's transcript echo; tune the `tail -n 3` count if needed and record the final value in the commit message. ### T4 — Commit (single atomic commit) ```bash git add .agents/skills/lib.sh \ .agents/skills/multi-agent-mux-create/scripts/create_session.sh \ .agents/skills/multi-agent-mux-create/SKILL.md \ .agents/skills/multi-agent-mux-delegate-job/multi-agent-mux-delegate-job \ .agents/skills/multi-agent-mux-resume/SKILL.md \ .agents/skills/multi-agent-mux-stop/scripts/stop_session.sh \ FUTURE_WORKS.md FUTURE_WORKS.ko.md \ .agents/reports/canary-projects-multi-agent-mux-reviewer-cline/report-prompt-lock-analysis.md \ .agents/reports/canary-projects-multi-agent-mux-creator-claude/report-prompt-lock-analysis.md \ .agents/reports/canary-projects-multi-agent-mux-planner-claude/report-prompt-lock-plan.md git commit -m "fix(skills): evidence-based prompt delivery (send_keys_safe) to end prompt-lock (FW-W2) - Add send_keys_safe + quiescence/dialog-detection helpers to lib.sh: keys are sent on pane evidence, never fixed timers; distinct exit codes 1-4; dialogs are never blindly Enter-ed - inject_instructions delegates to send_keys_safe; create --submit-job publishes a terminal error event on delivery failure (no zombie jobs) - wait_for_tui_ready: drop dialog-ambiguous tokens, treat open dialogs as not-ready, return 1 on timeout instead of proceeding - delegate-job wrapper: replace copy-pasted raw paste/C-m block with lib.sh send_keys_safe (restores single source of truth) - resume: conditional signature-gated dialog handling replaces blind Enter/Down/Enter; stop --graceful delivers exitkey safely, fallback chain unchanged - create SKILL: passive capture-pane probe instead of stray Enter - Mark FW-W2 resolved; add 3-agent analysis/plan reports" ``` ### T5 — Post-commit sanity ```bash git status --short # expected: empty grep -n "Proceeding anyway" .agents/skills/lib.sh # expected: no match grep -rn "sleep 0.5$" .agents/skills/multi-agent-mux-delegate-job/multi-agent-mux-delegate-job # expected: no match ``` **Halt conditions**: any DoD failure, any `bash -n` error, any new shellcheck warning, or dialog tokens that cannot be validated (DoD-5) → stop, do not commit, report back to Planner with the failing capture. --- ## 4. Risk Register | Risk | Severity | Mitigation | |---|---|---| | Dialog signature tokens don't match real TUI text | High (silently defeats RC-B fix) | DoD-5 is a merge blocker; tokens curated per agent release | | A2 submit-check false-fails on transcript echo | Medium (spurious rollback) | Pane-change AND-condition + DoD-6 tuning | | `wait_for_tui_ready` now failing hard changes create-path behavior on slow hosts | Medium | 15×1s budget unchanged; failure now rolls back loudly instead of injecting blind — strictly better; monitor first real runs | | Longer stop latency (`--graceful` quiescence wait) | Low | Fallback chain untouched; worst case ≈ +10 s before kill-session | | delegate-job sourcing lib.sh in copied-out installs | Low | Installers ship `.agents/skills/` as a tree incl. lib.sh; verified side-effect-free load | --- ## Final Authorization **Plan status: APPROVED for execution** by Antigravity exactly as written in §3. DoD-5 (real-capture validation of dialog signatures) is a hard merge blocker. Any deviation halts execution and returns to the Planner.