From 0da74587ce1c268b01894963febb8acac333d950 Mon Sep 17 00:00:00 2001 From: Till JS Date: Wed, 15 Apr 2026 19:23:59 +0200 Subject: [PATCH] fix(ai): surface web-research failures to the planner instead of hallucinating MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When the pre-step research call throws (mana-search down, missing tier, 402 credits, etc.), the runner used to swallow the error and feed an empty input to the planner — which then made up a story about a "failed web search" and fell back to create_task. Now we inject an explicit "research failed" ResolvedInput with the actual error message, plus write the truncated message into phaseDetail so it's visible in the mission card without DevTools. Bundles an in-flight actor refactor merge in runner.ts (makeAgentActor + LEGACY_AI_PRINCIPAL) — those lines came from the parallel Phase-1 identity work, not this fix. Co-Authored-By: Claude Opus 4.6 (1M context) --- .../web/src/lib/data/ai/missions/runner.ts | 33 +++++++++++++++---- 1 file changed, 27 insertions(+), 6 deletions(-) diff --git a/apps/mana/apps/web/src/lib/data/ai/missions/runner.ts b/apps/mana/apps/web/src/lib/data/ai/missions/runner.ts index a1e006dd4..a4f61ceb4 100644 --- a/apps/mana/apps/web/src/lib/data/ai/missions/runner.ts +++ b/apps/mana/apps/web/src/lib/data/ai/missions/runner.ts @@ -32,7 +32,7 @@ import { executeTool } from '../../tools/executor'; import { db } from '../../database'; import { decryptRecords } from '../../crypto'; import { researchApi } from '$lib/api/research'; -import type { Actor } from '../../events/actor'; +import { makeAgentActor, LEGACY_AI_PRINCIPAL, type Actor } from '../../events/actor'; import type { Mission, MissionIteration, PlanStep } from './types'; import type { AiPlanInput, AiPlanOutput, PlannedStep, ResolvedInput } from './planner/types'; @@ -109,12 +109,16 @@ export async function runMission( // Use the id the store generates so finishIteration updates the same row. const startedIteration = await startIteration(mission.id, { plan: [] }); const iterationId = startedIteration.id; - const aiActor: Extract = { - kind: 'ai', + // Phase 1: agent identity not yet wired (Phase 2 will). Use the + // legacy AI principal so every write is still identity-aware; the + // Phase-2 migration will rewrite these to a real agentId. + const aiActor = makeAgentActor({ + agentId: LEGACY_AI_PRINCIPAL, + displayName: 'Mana', missionId: mission.id, iterationId, rationale: mission.objective, - }; + }); // Hard timeout: any phase taking longer than ITERATION_TIMEOUT_MS aborts // the run. Wraps the whole pipeline in a Promise.race against a timer. @@ -170,15 +174,32 @@ export async function runMission( // Pre-step web research: if the objective looks like research, // run the deep-research pipeline (mana-search + mana-llm) and // attach the summary + sources so the planner can decide which - // to save via save_news_article. Failures are non-fatal — the - // planner still runs with whatever inputs we have. + // to save via save_news_article. Failures are non-fatal — we + // inject a synthetic "research failed" input instead so the + // planner doesn't hallucinate that the search ran. if (RESEARCH_TRIGGER.test(mission!.objective)) { await enterPhase('resolving-inputs', 'Web-Recherche…'); try { const researchEntry = await runWebResearch(mission!); if (researchEntry) resolvedInputs.push(researchEntry); } catch (err) { + const msg = err instanceof Error ? err.message : String(err); console.warn('[MissionRunner] web-research pre-step failed:', err); + await enterPhase('resolving-inputs', `Web-Recherche fehlgeschlagen: ${msg.slice(0, 80)}`); + resolvedInputs.push({ + id: 'web-research-error', + module: 'research', + table: 'researchResults', + title: 'Web-Recherche FEHLGESCHLAGEN', + content: [ + `Die automatische Web-Recherche konnte nicht ausgeführt werden.`, + `Fehler: ${msg}`, + ``, + `Wichtig: Du hast aktuell KEINE echten Quellen-URLs.`, + `Rufe NICHT save_news_article auf — du würdest URLs erfinden.`, + `Lege stattdessen einen kurzen Erinnerungs-Task an, dass die Recherche manuell nachgeholt werden muss.`, + ].join('\n'), + }); } await checkCancel(); }