[モデルフォールバック再試行時に元のユーザープロンプトが破棄される] - resolveFallbackRetryPrompt Discards Original User Prompt on Model Fallback Retry
モデルの呼び出しが失敗し、フォールバック再試行が発生すると、resolveFallbackRetryPrompt()関数は元のユーザープロンプト全体を汎用メッセージに置き換え、エージェントがタスクコンテキストを失う原因となります。
🔍 症状
観察可能な動作
モデルの呼び出しが失敗またはタイムアウトし、OpenClawがフォールバックリトライを実行すると、エージェントは元のタスク指示の代わりに汎用メッセージのみを受け取ります。これは2つの異なる方法で現れます:
1. セッションログの証拠
セッションログには、元のタスクを置き換えるsenderメタデータのないユーザーメッセージが表示されます:
// Session log excerpt (session ec19b29b)
Line 25: { customType: "model-snapshot", provider: "xiaomi", model: "mimo-v2-pro" }
Line 26: { role: "user", content: "Continue where you left off. The previous model attempt failed or timed out.", sender: null }
Line 27: { role: "assistant", model: "mimo-v2-pro" } // fallback retry response2. サブエージェントシナリオでのコンテキスト損失
サブエージェントが特定のタスク指示(例:[Subagent Task]: RECORD時系列整列)で生成されると、元のタスクは完全に破棄されます:
// Original prompt sent to subagent
"[Subagent Task]: RECORD時系列整列"
// Prompt received after fallback retry
"Continue where you left off. The previous model attempt failed or timed out."エージェントはセッション履歴のみからタスクを推測する必要があり、以下のリスクがあります:
- 不完全なコンテキストに基づく不正確なアクション選択
- 意図したタスクの完了失敗
- 予期しない出力の生成の可能性
3. 診断指標
問題はresolveFallbackRetryPrompt()関数の動作を調べることで特定できます:
// Current implementation (dist/agent-command-*.js)
function resolveFallbackRetryPrompt(params) {
if (!params.isFallbackRetry) return params.body;
if (!params.sessionHasHistory) return params.body;
return "Continue where you left off. The previous model attempt failed or timed out.";
}🧠 原因
技術的分析
根本原因は、resolveFallbackRetryPrompt()の条件ロジックにあり、これは連結戦略ではなく早期リターンの置換戦略を使用しています。
失敗シーケンス:
- エージェントが元のユーザープロンプトを含む`params.body`をモデルに送信します
- モデルの呼び出しが失敗またはタイムアウトします(例:xiaomi/mimo-v2-pro)
- OpenClawが`params.isFallbackRetry = true`を設定します
- OpenClawが`params.sessionHasHistory = true`を確認します(セッションには以前のメッセージが含まれています)
- 関数が置換パスを実行します:
return "Continue where you left off. The previous model attempt failed or timed out."; - 元の`params.body`が完全に破棄されます
アーキテクチャ上の不整合:
この関数は2つの異なる要件を混同しています:
- 要件A:リトライが発生したことをモデルに通知する
- 要件B:元のタスク指示を保持する
現在の実装は要件Aを満たしますが、要件Bは完全に違反しています。
コードパス分析:
javascript // In runAgentAttempt(): const effectivePrompt = resolveFallbackRetryPrompt({ body: params.body, isFallbackRetry: params.isFallbackRetry, sessionHasHistory: params.sessionHasHistory });
isFallbackRetryがtrueでsessionHasHistoryがtrueの場合、関数はparams.bodyと組み合わせる代わりに固定文字列を返します。
影響チェーン:
Original Prompt (params.body)
↓
Model Call Fails
↓
isFallbackRetry = true
↓
sessionHasHistory = true
↓
resolveFallbackRetryPrompt() returns FIXED_STRING
↓
effectivePrompt = FIXED_STRING ← Original task LOST
↓
Fallback model receives no task context🛠️ 解決手順
解決戦略
元のプロンプトを置き換えるのではなく、先頭に追加するようにresolveFallbackRetryPrompt()を変更します。
コード変更
ファイル: dist/agent-command-*.js(バージョンにより異なる)
修正前:
function resolveFallbackRetryPrompt(params) {
if (!params.isFallbackRetry) return params.body;
if (!params.sessionHasHistory) return params.body;
return "Continue where you left off. The previous model attempt failed or timed out.";
}修正後:
function resolveFallbackRetryPrompt(params) {
if (!params.isFallbackRetry) return params.body;
if (!params.sessionHasHistory) return params.body;
return "[System: Previous model attempt failed or timed out. Continuing from where you left off.]\n\n" + params.body;
}代替実装(より詳細)**
明確な分離を必要とする環境向け:
function resolveFallbackRetryPrompt(params) {
if (!params.isFallbackRetry) return params.body;
if (!params.sessionHasHistory) return params.body;
const retryNotice = "[System: Previous model attempt failed or timed out. Continuing from where you left off.]\n\n";
const originalPrompt = params.body;
return retryNotice + originalPrompt;
}展開手順
- 影響を受けるファイルの特定:
find /path/to/openclaw -name "agent-command-*.js" -type f - 元のファイルのバックアップ:
cp /path/to/agent-command-*.js /path/to/agent-command-*.js.bak - sedを使用した修正の適用:
sed -i 's/return "Continue where you left off. The previous model attempt failed or timed out.";/return "[System: Previous model attempt failed or timed out. Continuing from where you left off.]\\n\\n" + params.body;/g' /path/to/agent-command-*.js - 変更の検証:
grep -A3 "function resolveFallbackRetryPrompt" /path/to/agent-command-*.js - OpenClawサービスの再起動:
sudo systemctl restart openclaw
🧪 検証
検証方法論
修正を確認するには、モデルの失敗をシミュレートし、フォールバックリトライプロンプトが元のタスクを含んでいることを検証します。
テスト手順
1. デバッグログの有効化:
export OPENCLAW_LOG_LEVEL=debug
export DEBUG=openclaw:agent:*2. フォールバックシナリオのトリガー:
意図的に失敗するモデルでテストエージェント設定を作成します:
// test-fallback-prompt.json
{
"agent": {
"model": "intentionally-invalid-model-for-testing",
"fallbackModel": "gpt-4o-mini",
"prompt": "[Test Task]: Identify the color of the sky"
}
}3. エージェントの実行:
openclaw run --config test-fallback-prompt.json --session test-fallback-$(date +%s)4. セッションログの確認:
openclaw session log --session-id <session-id> --format json | jq '.messages[] | select(.role == "user") | {content, sender, metadata}'5. 予期される出力の検証:
修正前、出力は以下を示します:
{
"content": "Continue where you left off. The previous model attempt failed or timed out.",
"sender": null,
"metadata": {}
}修正後、出力は以下を示すはずです:
{
"content": "[System: Previous model attempt failed or timed out. Continuing from where you left off.]\n\n[Test Task]: Identify the color of the sky",
"sender": "system",
"metadata": {
"isFallbackRetry": true
}
}自動検証スクリプト
#!/bin/bash
SESSION_ID=$(openclaw session list --limit 1 --format json | jq -r '.[0].id')
FALLBACK_USER_MSG=$(openclaw session log --session-id "$SESSION_ID" --format json | jq -r '.messages[] | select(.role == "user" and .sender == null) | .content')
if echo "$FALLBACK_USER_MSG" | grep -q "\[System: Previous model attempt"; then
if echo "$FALLBACK_USER_MSG" | grep -q "\[Test Task\]"; then
echo "✅ VERIFIED: Original prompt preserved in fallback retry"
exit 0
else
echo "❌ FAILED: System message present but original prompt missing"
exit 1
fi
else
echo "❌ FAILED: Generic message still being used (fix not applied)"
exit 1
fi⚠️ よくある落とし穴
エッジケースと環境固有の落とし穴
1. 空のプロンプトボディ(params.bodyが空文字列の場合)
params.bodyが空文字列の場合、修正後の実装ではシステム通知のみを含むプロンプトが生成されます:
// Result when params.body = ""
"[System: Previous model attempt failed...]\n\n"
// ← Empty original task (may be valid if session history is sufficient)緩和策: 連結前にparams.bodyが空でないことを確認します:
function resolveFallbackRetryPrompt(params) {
if (!params.isFallbackRetry) return params.body;
if (!params.sessionHasHistory) return params.body;
const retryNotice = "[System: Previous model attempt failed or timed out. Continuing from where you left off.]\n\n";
return params.body ? retryNotice + params.body : retryNotice.trim();
}2. 非常に長い元のプロンプト
リトライ通知と連結された長いプロンプトは、モデルのコンテキスト制限を超える可能性があります。
- 元のプロンプトがモデルの制限に近づいたら、トークン使用量を監視します
- `params.body`がしきい値(例:4000トークン)を超えた場合、切り詰めることを検討します
3. 元のプロンプト内の非ASCII文字
日本語文字(報告された問題であるRECORD時系列整列のように)は適切に保持する必要があります:
// Verify encoding is maintained
const testPrompt = "[Subagent Task]: RECORD時系列整列";
const fixed = "[System: ...]\n\n" + testPrompt;
console.log(fixed.includes("RECORD時系列整列")); // Must be true4. Dockerコンテナのキャッシュ
OpenClawがDockerで実行されている場合、キャッシュされたJavaScriptファイルが残っている可能性があります:
# Rebuild container to ensure new code is deployed
docker-compose down
docker-compose build --no-cache openclaw
docker-compose up -d5. 複数回の連続フォールバックリトライ
モデルが連続して複数回失敗した場合、各リトライが追加のシステム通知を先頭に追加する可能性があります:
// After 3 retries, prompt becomes:
"[System: ...]\n\n[System: ...]\n\n[System: ...]\n\n[Test Task]: ..."
// ← Duplicate notices accumulate緩和策: 追加前に、元のプロンプトが既にリトライ通知を含んでいるか確認します:
function resolveFallbackRetryPrompt(params) {
if (!params.isFallbackRetry) return params.body;
if (!params.sessionHasHistory) return params.body;
if (params.body.includes("[System: Previous model attempt")) return params.body;
return "[System: Previous model attempt failed...]\n\n" + params.body;
}6. バージョン互換性
関数シグネチャまたは呼び出し先は、バージョン間で変更される可能性があります:
| バージョン | ファイルパターン | ステータス |
|---|---|---|
| 2026.4.9 | agent-command-8TL7BESJ.js | 影響あり |
| 2026.4.11 | agent-command-BUw17dbz.js | 影響あり |
展開しているバージョンで正確な関数の場所とパラメータ名を常に確認してください。
🔗 関連するエラー
コンテキスト的に関連する問題
以下のエラーと過去の報告は、フォールバックリトライプロンプトの動作に関連しています:
1. モデルタイムアウトエラー
- E_TIMEOUT: モデル呼び出しが時間制限を超過
- E_MODEL_UNAVAILABLE: モデルエンドポイントに到達不可
- E_RATE_LIMIT: フォールバック試行中のAPIレート制限超過
これらのエラーはisFallbackRetryフラグをトリガーし、問題のあるコードパスがアクティブになります。
2. コンテキストウィンドウエラー
- E_CONTEXT_LENGTH: 連結されたプロンプト + 履歴がモデルのコンテキスト制限を超える
- 修正後、リトライ通知 + 元のプロンプト + 履歴が制限を超えると発生する場合がある
3. 過去の報告
| 問題ID | 説明 | ステータス |
|---|---|---|
| GH-XXXX | 初期報告:サブエージェントがリトライ時にタスクコンテキストを失う | オープン |
| GH-YYYY | セッションログにnull senderメタデータを持つユーザーメッセージが表示される | 関連 |
4. 関連する設定パラメータ
// These parameters control the fallback behavior
interface FallbackConfig {
enabled: boolean; // Enable/disable fallback retry
maxRetries: number; // Maximum retry attempts
retryDelay: number; // Delay between retries (ms)
retryModels: string[]; // List of fallback models to try
preserveOriginalPrompt: boolean; // NEW: Flag to preserve original prompt
}5. コードベースの同様のパターン
同様の置換vs連結問題を示す可能性のある他の関数:
- `resolveSystemPrompt()` - システム指示を上書きする可能性がある
- `injectContextSummary()` - コンテキストを追加ではなく置換する可能性がある
- `formatHistoryForModel()` - 履歴を要約ではなく切り詰める可能性がある
6. 監視の推奨事項
フォールバックリトライシナリオのテレメトリを実装します:
// Suggested metrics to track
metrics.increment('openclaw.fallback.retry.count');
metrics.gauge('openclaw.fallback.prompt.length', effectivePrompt.length);
metrics.histogram('openclaw.fallback.prompt.original_length', params.body.length);