GUIDE TECHNIQUE — FIABILITÉ & VALIDATION

Harnesses IA en Finance : Contraindre les Modèles à Produire des Résultats Fiables

Comment empêcher un LLM de publier des données financières inventées. Schemas JSON, war rooms multi-persona, workflows dynamiques de validation, et la règle MCP Hard Stop — née d'une rétrospective H1 2026 dont 100% des chiffres étaient hallucinés.

Fiabilité
20 min
Post-mortem réel
Patterns concrets

Pourquoi un LLM nu ne suffit jamais en finance

Le 21 juin 2026, un agent IA autonome a généré une rétrospective du premier semestre pour DailyTickers. L'article de 85 000 caractères était impeccable sur la forme : HTML valide, sections structurées, narratifs fluides, graphiques ECharts cohérents. Il n'y avait qu'un problème : 100% des données financières étaient inventées.

Le palmarès des meilleures actions du semestre affichait META à +32%. En réalité, META faisait -15%. Le palmarès des pires actions listait DAL à -22%. En réalité, DAL faisait +13%. L'article entier était construit autour d'un narratif « l'énergie écrase le marché avec +36% YTD ». La réalité : le secteur énergie faisait -1%. Chaque chiffre, chaque classement, chaque conclusion — tout était faux, mais vraisemblable.

Post-mortem : la rétrospective H1 2026

Donnée Ce que l'agent a écrit Réalité (MCP vérifié)
META YTD +32% -15%
XOM YTD +42% -9%
DAL YTD -22% +13%
MRVL YTD +27% +276%
Secteur Énergie +36% -1%
Russell 2000 YTD +13,1% +29,0%
Nikkei 225 38 500 (-3,3%) 71 250 (+10,0%)
DXY (Dollar Index) au-dessus de 107 autour de 100-101

La cause ? L'agent travaillait en fork — une copie autonome lancée en arrière-plan. Le MCP DailyTickers (la source de données marché en temps réel) était déconnecté (token d'authentification expiré). Sans données fraîches, le modèle a fait ce que les LLM font toujours quand on leur demande des chiffres qu'ils n'ont pas : il a inventé des nombres plausibles. Et il les a inventés avec une assurance absolue, dans un HTML parfaitement formaté.

Les données financières ont une propriété unique : elles sont instantanément vérifiables. Un prix spot, un YTD, un ratio — il suffit d'ouvrir un terminal. Ce qui signifie que les erreurs sont aussi instantanément détectables par n'importe quel lecteur. Dans un contexte éditorial financier, une seule donnée fausse détruit la crédibilité de l'ensemble. Dix données fausses détruisent la crédibilité du site.

100%
Des données du palmarès H1 étaient inventées — pas une approximation, pas une erreur d'arrondi : des chiffres complètement faux
0
Vérifications exécutées avant publication. Aucun cross-check MCP, aucun schema, aucune war room
3 heures
Temps passé à corriger manuellement chaque chiffre, narratif, graphique et tableau après détection
1 règle
MCP Hard Stop — la règle immuable née de cet incident, désormais câblée dans tous les pipelines

Source : Anthropic — Tool Use documentation

Cet article est le guide technique que nous aurions aimé avoir avant l'incident. Il couvre cinq couches de protection — du schema JSON qui force la structure, à la war room multi-persona qui traque les incohérences logiques, en passant par le pattern MCP Hard Stop qui coupe tout si la source de données tombe. L'objectif : qu'aucune donnée financière ne soit jamais publiée sans avoir été vérifiée par au moins deux mécanismes indépendants.

Ce que cet article n'est pas

Ce n'est pas un guide sur le prompt engineering, ni sur le fine-tuning, ni sur le choix du bon modèle. C'est un guide sur l'infrastructure de validation qui entoure le modèle — le harness — pour que même un modèle imparfait produise des résultats fiables. Le modèle est un muscle. Le harness est le squelette.

Forcer la Structure : Schemas JSON et Validation

Le premier problème à résoudre est le plus basique : quand un LLM génère du texte libre, il dérive. « Le S&P 500 est environ à 7 400 points » versus « 7 420,10 ». « Le titre fait grosso modo +15% sur l'année » versus « +14,73% YTD au 18 juin ». En finance, l'approximation n'est pas acceptable. Un investisseur qui lit « environ 7 400 » ne sait pas si c'est 7 350 ou 7 450 — et la différence peut changer une décision.

La solution : ne jamais laisser le modèle répondre en texte libre pour les données structurées. À la place, on définit un JSON Schema qui décrit exactement la forme de la réponse attendue, et on force le modèle à appeler un outil StructuredOutput dont les paramètres sont validés côté SDK. Si le modèle renvoie un champ manquant, un type incorrect, ou une valeur hors des bornes autorisées, le SDK rejette automatiquement et relance.

Le schema comme contrat

Prenons l'exemple d'une analyse de ticker. Sans schema, le modèle peut répondre :

// Sortie libre typique — aucune garantie de structure
"AAPL est noté A-, actuellement à environ $230. Le stop est quelque part
autour de $215. Le YTD est positif, dans les 8-10%. Le R/R est correct,
je dirais 1.8x. Les raisons principales sont les résultats solides,
l'expansion des services, et le buyback massif."

Avec un schema, le modèle est obligé de produire :

// Schema JSON — contrat strict
{
  "type": "object",
  "required": ["ticker", "grade", "price", "ytd_pct", "stop_loss", "risk_reward", "reasons"],
  "properties": {
    "ticker":      { "type": "string", "pattern": "^[A-Z]{1,5}$" },
    "grade":       { "type": "string", "enum": ["A+", "A", "A-", "B+", "B", "B-", "C+", "C", "D"] },
    "price":       { "type": "number", "minimum": 0.01 },
    "ytd_pct":     { "type": "number" },
    "stop_loss":   { "type": "number", "minimum": 0.01 },
    "risk_reward": { "type": "number", "minimum": 0.1 },
    "reasons":     { "type": "array", "items": { "type": "string" }, "minItems": 2 }
  },
  "additionalProperties": false
}

Et la sortie validée :

// Sortie structurée — chaque champ typé et vérifiable
{
  "ticker": "AAPL",
  "grade": "A-",
  "price": 231.42,
  "ytd_pct": 8.17,
  "stop_loss": 214.80,
  "risk_reward": 1.83,
  "reasons": [
    "4 beats consécutifs, guidance relevée Q2",
    "Services revenue +18% YoY, marge 75%",
    "Buyback $110B autorisé, yield implicite 3.8%"
  ]
}

Source : JSON Schema Specification

Le mécanisme de forçage

Le forçage fonctionne via le pattern tool_use des API Claude. Au lieu de demander au modèle de « répondre en JSON » (ce qui échoue souvent — le modèle ajoute du texte autour, oublie des virgules, invente des champs), on lui donne un outil StructuredOutput dont les paramètres sont le schema. Le modèle est contraint d'appeler cet outil — il ne peut pas répondre en texte libre. Et le SDK valide les paramètres avant de les accepter.

// Implémentation — forcer le modèle à utiliser un outil structuré
const response = await client.messages.create({
  model: "claude-sonnet-4-6",
  max_tokens: 4096,
  tools: [{
    name: "StructuredOutput",
    description: "Return the analysis result",
    input_schema: ANALYSIS_SCHEMA   // le schema ci-dessus
  }],
  tool_choice: { type: "tool", name: "StructuredOutput" },  // force l'appel
  messages: [{ role: "user", content: prompt }]
});

// response.content[0].input est déjà validé par le SDK
const result = response.content[0].input;
console.log(result.ticker);     // "AAPL" — garanti string
console.log(result.price);      // 231.42 — garanti number > 0.01
console.log(result.reasons);    // array — garanti >= 2 éléments

Ce que le schema attrape (et ce qu'il ne voit pas)

Type d'erreur Schema l'attrape ? Exemple
Champ manquant OUI stop_loss absent → rejet automatique
Type incorrect OUI price: "deux cent trente" → rejet, number attendu
Valeur hors enum OUI grade: "AAA" → rejet, pas dans la liste
Array vide OUI reasons: [] → rejet, minItems: 2
Prix halluciné NON price: 385.20 quand AAPL est à 231 — structurellement valide
YTD inversé NON ytd_pct: -15 quand le titre fait +8% — c'est un number
Raisons inventées NON "Contrat DoD $50B signé" quand aucun contrat n'existe

Schema = contrat structurel, pas contrat factuel

Un schema garantit que la forme de la réponse est correcte. Il ne dit rien sur le fond. La rétrospective H1 avait un HTML parfaitement structuré avec des nombres parfaitement typés — et 100% faux. Le schema est la première couche de protection, pas la dernière. Il attrape les erreurs de structure ; pour les erreurs de fait, il faut les couches suivantes.

Architecture d'un Harness de Validation

Un harness, c'est un cadre rigide autour d'un processus flexible. Le modèle reste libre de raisonner, de rédiger, de décider — mais chaque sortie passe par une série de filtres avant de toucher le monde réel. L'analogie la plus juste est celle du test automatisé en développement logiciel : le code passe, mais seulement si les tests passent. Ici, le contenu passe, mais seulement si les validations passent.

Les cinq couches

Couche 0 — SourceMCP DailyTickers, API marché, bases de données internes. La source de vérité.
données fraîches
Couche 1 — GénérationLe LLM produit une sortie structurée (JSON via schema) à partir des données source.
schema validation
Couche 2 — Cross-référenceChaque donnée chiffrée est comparée à la source. Prix, YTD, ratios, dates.
fact-check
Couche 3 — Vérification adverseDes agents indépendants tentent de réfuter le contenu. War room multi-persona.
vote PASS/BLOCK
Couche 4 — Gate humainRevue finale par un opérateur. Dernier filet avant publication.
publication
ProductionL'article est publié, indexé, distribué via RSS/Telegram.

Ce que chaque couche attrape

L'idée n'est pas que chaque couche attrape tout — c'est que chaque couche attrape une catégorie d'erreurs différente. Un bug qui échappe au schema sera pris par le cross-check. Un cross-check satisfait peut masquer une incohérence logique que la war room repérera. Et la war room peut rater un problème de ton que l'humain verra.

Couche Type d'erreur ciblé Exemple concret Coût relatif
Schema Erreurs structurelles Champ grade absent, price en string ~0 (validation locale)
Cross-référence Erreurs factuelles Prix $385 quand le spot est $231, YTD inversé 1 appel API par donnée
War room Erreurs logiques et narratives « Énergie leader à +36% » quand le secteur fait -1% N × appel LLM (3-5 agents)
Gate humain Erreurs de jugement Ton trop agressif, recommandation inadaptée au profil Temps humain

Source : Anthropic — Forcing tool use

Sans harness vs. avec harness

Sans harness Avec harness
Temps de publication 2 minutes (génération directe) 5-10 minutes (validation incluse)
Taux d'erreur factuelle 15-40% des données (variable, catastrophique si MCP down) <1% (erreurs restantes = jugement, pas faits)
Coût par article ~$0.50 (un seul appel LLM) ~$2-5 (schema + cross-ref + war room)
Coût d'une erreur publiée 3+ heures de correction + perte de crédibilité Quasi-nul (erreur bloquée avant publication)
Comportement si MCP down Continue avec données inventées (silencieux) STOP IMMÉDIAT + alerte opérateur

La couche la plus importante : le cross-check MCP

Un schema JSON parfaitement validé peut contenir 100% de données fausses — c'est exactement ce qui s'est passé avec la rétrospective H1. Le HTML était valide, les nombres étaient bien des nombres, les pourcentages avaient le bon format. Tout était structurellement correct et factuellement catastrophique. Sans la couche de cross-référence MCP, le harness est une passoire élégante.

Implémentation : le pipeline de validation

// Pipeline de validation — chaque couche peut rejeter
async function validateAndPublish(article) {
  // Couche 1 — Schema validation
  const parsed = validateSchema(article.data, ARTICLE_SCHEMA);
  if (!parsed.valid) throw new Error(`Schema: ${parsed.errors.join(', ')}`);

  // Couche 2 — Cross-référence MCP
  const mcpCheck = await crossReferenceMCP(parsed.data);
  if (mcpCheck.errors.length > 0) {
    throw new Error(`Fact-check: ${mcpCheck.errors.join('; ')}`);
  }
  if (mcpCheck.warnings.length > 0) {
    log(`⚠️ ${mcpCheck.warnings.length} warning(s) — revue manuelle requise`);
  }

  // Couche 3 — War room (si article publié)
  if (article.publish) {
    const verdicts = await warRoom(article, PERSONAS);
    const blocks = verdicts.filter(v => v.verdict === 'BLOCK');
    if (blocks.length > 0) {
      throw new Error(`War room BLOCK: ${blocks.map(b => b.reason).join('; ')}`);
    }
  }

  // Couche 4 — Gate humain (si configuré)
  if (config.requireHumanApproval) {
    await requestApproval(article);
  }

  // Publication
  await publish(article);
}

Défense en profondeur

L'analogie militaire est exacte : aucune ligne de défense n'est parfaite, mais l'attaquant (l'erreur) doit franchir toutes les lignes pour atteindre la cible (la publication). Chaque couche réduit le risque de façon multiplicative : si chaque couche attrape 90% des erreurs de son type, quatre couches en série laissent passer 0,01% — une erreur pour 10 000.

War Rooms : Panels Multi-Persona Adversariaux

Un seul relecteur, même excellent, a des angles morts. Il sait ce qu'il sait — et il ne sait pas ce qu'il ne sait pas. Un analyste quant ne pensera pas à vérifier la conformité réglementaire. Un éditeur ne repérera pas une erreur de méthodologie statistique. Un trader ne verra pas un risque de corrélation macro.

La war room résout ce problème par la diversité des perspectives, pas par la redondance. Au lieu de lancer 5 agents identiques (qui feront les mêmes erreurs), on lance 5 agents avec des personas distinctes — chacun spécialisé dans un type de faille. Chacun reçoit exactement le même input, mais des instructions différentes. C'est la version IA d'un comité de lecture pluridisciplinaire.

Les 5 personas du harness DailyTickers

QA Analyst

Exactitude factuelle. Chaque prix, chaque date, chaque ratio est-il correct ? Les sources sont-elles citées ? Les données du tableau correspondent-elles au narratif ? Cross-check systématique avec MCP.

Quant

Rigueur méthodologique. Les backtests sont-ils réalistes ? Les R/R sont-ils calculés correctement ? Les claims statistiques (PF, Sharpe, max DD) sont-ils cohérents entre eux ? Le sizing respecte-t-il les contraintes de risque ?

Trader

Réalisme de marché. Le setup est-il exécutable ? Le spread est-il tenable sur ce ticker ? Le stop est-il sous un niveau technique réel ? La liquidité permet-elle la taille annoncée ? L'horizon est-il cohérent avec le catalyseur ?

Risk Manager

Scénarios adverses. Quel est le pire cas ? La corrélation entre positions est-elle contrôlée ? Un événement macro (FOMC, NFP, earnings) tombe-t-il dans la fenêtre ? Le drawdown du mode est-il dans les limites ?

Editor

Clarté et conformité. Le ton est-il approprié pour un site financier professionnel ? Les disclaimers sont-ils présents ? Le texte ne fait-il pas de promesse de rendement ? L'article est-il lisible pour le public cible ?

Le mécanisme de vote

Chaque persona examine l'article et rend un verdict parmi trois :

Verdict Signification Action
PASS Aucun problème bloquant détecté dans mon domaine L'article avance
FIXED Problème détecté, mais j'ai la correction exacte La correction est appliquée, puis re-validation
BLOCK Problème grave — publication impossible en l'état L'article est renvoyé pour réécriture avec les findings

La règle de gate : il faut une majorité de PASS (ou FIXED après application des corrections) pour publier. Un seul BLOCK suffit à bloquer la publication. C'est volontairement conservateur — en finance, le coût d'un faux positif (bloquer un article correct) est négligeable comparé au coût d'un faux négatif (publier un article faux).

Implémentation : le workflow war room

// Workflow war room — 5 juges parallèles, vote majoritaire
export const meta = {
  name: 'senior-review',
  description: 'Panel multi-persona QA gate avant publication',
  phases: [
    { title: 'Review', detail: '5 personas évaluent en parallèle' },
    { title: 'Fix', detail: 'Appliquer les corrections FIXED' },
    { title: 'Gate', detail: 'Décision PASS / BLOCK' }
  ]
};

const PERSONAS = [
  { key: 'qa',     icon: '🔬', prompt: `Tu es QA Analyst. Vérifie chaque donnée
    chiffrée contre MCP. Cross-check prix, YTD, ratios. Signale toute
    incohérence entre tableau et narratif.` },
  { key: 'quant',  icon: '📊', prompt: `Tu es Quant. Vérifie la méthodologie :
    R/R calculé correctement ? Backtest réaliste ? PF et Sharpe
    cohérents ? Sizing dans les limites du mode ?` },
  { key: 'trader', icon: '💹', prompt: `Tu es Trader senior. Ce setup est-il
    exécutable ? Stop sous un vrai niveau ? Liquidité suffisante ?
    Catalyseur dans le bon horizon ?` },
  { key: 'risk',   icon: '🛡️', prompt: `Tu es Risk Manager. Quel est le pire cas ?
    Corrélation entre positions ? Événement macro dans la fenêtre ?
    DD du mode dans les limites ?` },
  { key: 'editor', icon: '✏️', prompt: `Tu es Editor. Ton approprié ? Disclaimers
    présents ? Pas de promesse de rendement ? Lisible pour le
    public cible ?` }
];

const VERDICT_SCHEMA = {
  type: 'object',
  required: ['verdict', 'findings'],
  properties: {
    verdict: { type: 'string', enum: ['PASS', 'FIXED', 'BLOCK'] },
    findings: { type: 'array', items: {
      type: 'object',
      properties: {
        severity: { type: 'string', enum: ['info', 'warn', 'error'] },
        description: { type: 'string' },
        fix: { type: 'string' }
      }
    }}
  }
};

phase('Review');
const reviews = await parallel(
  PERSONAS.map(p => () =>
    agent(`${p.prompt}\n\nArticle à évaluer :\n${args.article}`, {
      label: `${p.icon} ${p.key}`,
      schema: VERDICT_SCHEMA,
      phase: 'Review'
    })
  )
);

phase('Gate');
const blocks = reviews.filter(Boolean).filter(r => r.verdict === 'BLOCK');
const passes = reviews.filter(Boolean).filter(r => r.verdict !== 'BLOCK');

return {
  decision: blocks.length > 0 ? 'BLOCK' : 'PASS',
  score: `${passes.length}/${reviews.filter(Boolean).length}`,
  blocks: blocks.map(b => ({ persona: b.key, findings: b.findings })),
  fixes: reviews.filter(Boolean)
    .filter(r => r.verdict === 'FIXED')
    .flatMap(r => r.findings.filter(f => f.fix))
};

Source : Anthropic — Agentic tool use

Cas réel : le screening A+ de juin 2026

En juin 2026, le pipeline a sélectionné 10 tickers pour le label « A+ du mois » : GE, DELL, HPE, PANW, ROST, AMKR, CRDO, ALAB, TGT, DAL. Chaque analyse avait un ratio risque/rendement (R/R) impressionnant de 2× ou plus, des notes A+ justifiées par des fondamentaux solides, et des setups techniques impeccables.

La war room a tout recalé. 0 sur 10 méritaient le A+.

Les findings : les R/R étaient calculés sur des prix « étendus » (en projetant le mouvement au-delà du niveau actuel), pas au spot. C'est le piège classique du « plus gros mouvement » — l'agent choisissait les tickers avec le plus gros potentiel théorique, pas ceux dont le setup était actionnable maintenant. PANW et HPE avaient des risques de dilution (ATM offerings) non vérifiés. CRDO et ALAB avaient des programmes ATM actifs de plusieurs centaines de millions de dollars.

Le second batch — 10 tickers sélectionnés avec le harness complet (R/R au spot, vérification dilution SEC, cross-check MCP) — a passé la war room : 8 vrais A+ (CSCO, IBKR, ALLY, BK, COLB, FLEX, TER, RPRX) et 2 honnêtes A que le panel a refusé de gonfler (D avait un risque merger-arb + ATM $1,8 Md active ; MDLZ était fair-not-cheap). Le harness n'a pas gonflé les notes — il a refusé de mentir.

Ce que la war room apporte

  • Diversité > redondance — 5 perspectives différentes battent 5 fois la même
  • Contrainte positive — le modèle « sait » qu'il sera vérifié, donc il est plus prudent
  • Traçabilité — chaque verdict est loggé avec ses findings, auditable a posteriori
  • Honnêteté forcée — si les données ne justifient pas un A+, le panel dit A. Pas de complaisance

Workflows Dynamiques de Validation

Un harness statique — schema puis cross-check puis war room — fonctionne pour un article unique. Mais quand le pipeline traite 100 candidats par nuit (comme le scanner DailyTickers), il faut un workflow dynamique : qui s'adapte au nombre d'items, qui élimine tôt pour économiser des tokens, et qui parallélise l'enrichissement sans attendre que chaque ticker soit traité séquentiellement.

Le pipeline scanner : 5 étapes, 92% d'attrition

Voici le pipeline réel du scanner nocturne DailyTickers, qui tourne chaque soir entre 22h30 et 23h30 :

1. DiscoverRunScreener — filtre l'univers sur momentum, volume, et régime de marché. ~100 candidats.
100 → 60
2. EnrichMCP QueryData en parallèle : quote, bars_daily, fondamentaux. Rejet si données stale >48h.
60 → 40
3. ValidateAnti-dilution (SEC filings S-3, warrants, ATM, fonds toxiques). Earnings proximity. Risk gating.
40 → 25
4. VerifyCross-ref prix au spot. Scoring multi-axes. Évaluation regime-aware. Corrélation cluster check.
25 → 8
5. PublishGénération HTML. add_card.js. Commit + push. Notification Telegram.

Le pattern pipeline() vs parallel()

La distinction est critique pour la performance. Un pipeline() traite chaque item indépendamment à travers toutes les étapes — l'item A peut être en étape 4 pendant que l'item B est encore en étape 1. Un parallel() avec barrière attend que tous les items finissent une étape avant de passer à la suivante.

Pattern Latence Quand l'utiliser
pipeline() = plus lent item individuel Par défaut. Chaque item est indépendant. L'étape 2 d'un item n'a pas besoin des résultats de l'étape 1 des autres items.
parallel() + barrière = plus lent item × nombre d'étapes Quand l'étape N a besoin de TOUS les résultats de l'étape N-1. Ex : déduplication cross-item avant vérification coûteuse.
// pipeline() — chaque ticker traverse toutes les étapes indépendamment
// Ticker A peut être en verify pendant que Ticker B est encore en enrich
const results = await pipeline(
  candidates,
  // Étape 1 : enrichir via MCP
  ticker => agent(`Enrichis ${ticker} via QueryData`, {
    label: `enrich:${ticker}`, phase: 'Enrich', schema: ENRICH_SCHEMA
  }),
  // Étape 2 : valider (dilution, earnings, risk)
  (enriched, ticker) => agent(`Valide ${ticker}: anti-dilution, earnings, risk`, {
    label: `validate:${ticker}`, phase: 'Validate', schema: VALIDATE_SCHEMA
  }),
  // Étape 3 : scorer et vérifier
  (validated, ticker) => agent(`Score ${ticker} et cross-ref prix au spot`, {
    label: `verify:${ticker}`, phase: 'Verify', schema: SCORE_SCHEMA
  })
);

// Seuls les résultats avec score >= seuil passent à la publication
const winners = results.filter(Boolean).filter(r => r.score >= 85);

Source : Anthropic — Prompt caching (optimisation tokens pour pipelines multi-agents)

Loop-until-dry : ne rien laisser passer

Pour les tâches de découverte (trouver des bugs, identifier des risques, repérer des incohérences), le nombre d'items à trouver est inconnu à l'avance. Le pattern loop-until-dry lance des agents de recherche en rounds successifs jusqu'à ce que K rounds consécutifs ne trouvent rien de nouveau.

// Loop-until-dry — continue tant qu'on trouve du nouveau
const seen = new Set();
let dryRounds = 0;

while (dryRounds < 2) {
  const found = await agent(
    `Cherche des erreurs factuelles dans cet article.
    Déjà trouvés : ${[...seen].join(', ') || 'aucun'}`,
    { schema: FINDINGS_SCHEMA }
  );

  const fresh = found.issues.filter(i => !seen.has(i.key));

  if (fresh.length === 0) {
    dryRounds++;
    log(`Round sec ${dryRounds}/2 — rien de nouveau`);
  } else {
    dryRounds = 0;
    fresh.forEach(i => seen.add(i.key));
    log(`${fresh.length} nouveaux problèmes trouvés (total: ${seen.size})`);
  }
}

Budget-aware : adapter la profondeur au budget

Quand l'opérateur alloue un budget tokens explicite (« +500k »), le workflow adapte dynamiquement sa profondeur de vérification. Plus de budget = plus de rounds de loop-until-dry, plus de personas dans la war room, plus de cross-checks.

// Le budget contrôle la profondeur, pas la structure
const depth = budget.total
  ? Math.floor(budget.remaining() / 100_000)
  : 3;  // défaut : 3 passes de vérification

for (let i = 0; i < depth; i++) {
  const issues = await agent(
    `Pass ${i + 1}/${depth} — vérifie sous un angle différent`,
    { schema: ISSUES_SCHEMA }
  );
  if (issues.length === 0) break;  // early exit si clean
  log(`Pass ${i + 1}: ${issues.length} problèmes — ${Math.round(budget.remaining()/1000)}k restants`);
}

Fan-out + early exit = efficacité maximale

Le pipeline n'est pas juste un filtre — c'est un entonnoir économique. À chaque étape, les candidats éliminés ne consomment plus de tokens aux étapes suivantes. Si l'anti-dilution rejette 20 tickers sur 60, c'est 20 × (verify + score + publish) en tokens économisés. Le design optimal place les filtres les moins coûteux en premier et les plus coûteux en dernier.

Le Pattern MCP Hard Stop

De toutes les règles du harness DailyTickers, une seule est marquée « immuable » dans le CLAUDE.md du projet — c'est-à-dire qu'aucun agent, aucun workflow, aucun opérateur ne peut la contourner sans la réécrire explicitement :

MCP HARD STOP (IMMUABLE)

Si le MCP bloque Auth expirée, timeout, erreur réseau, données incohérentes
→ STOP IMMÉDIAT Ne pas continuer la génération ou la correction d'article
→ Ne JAMAIS substituer Par des données inventées, estimées, ou issues de mémoire
→ Signaler « MCP indisponible, tâche suspendue »
→ Reprendre Uniquement quand MCP reconnecté ET qu'un test de contrôle renvoie des données fraîches

Source : Model Context Protocol — Introduction

Pourquoi cette règle existe

Parce qu'un LLM ne sait pas qu'il ne sait pas. Quand le MCP est down et que le modèle reçoit un prompt « génère la rétrospective H1 avec les performances de chaque secteur », il ne répond pas « je n'ai pas les données ». Il répond avec des données plausibles — parce que c'est ce que les LLM font. Ils complètent la séquence la plus probable. Et la séquence la plus probable après « le secteur énergie en H1 2026 » est un nombre entre +10% et +40%, parce que l'énergie a historiquement bien performé dans des environnements similaires.

Le problème n'est pas que le modèle « ment ». Le problème est qu'il n'a pas de mécanisme interne pour distinguer « je sais » de « je complète ». La seule façon de garantir des données fraîches est de les obtenir d'une source externe fiable — et si cette source est indisponible, la seule option sûre est de ne rien publier.

Implémentation : le pre-flight check

// Pre-flight check — exécuté AVANT toute génération data-dependent
async function mcpPreFlight() {
  try {
    // 1. Test de connectivité
    const health = await mcp.call('GetHealth');
    if (!health || health.status !== 'ok') {
      throw new Error('MCP health check failed');
    }

    // 2. Test de fraîcheur — requête un ticker de référence
    const spy = await mcp.call('QueryData', {
      tickers: 'SPY',
      types: 'quote'
    });

    const quote = spy?.SPY?.quote;
    if (!quote || !quote.price || quote.price <= 0) {
      throw new Error('MCP returned empty or zero price for SPY');
    }

    // 3. Test de cohérence — le prix doit être dans une fourchette raisonnable
    if (quote.price < 3000 || quote.price > 15000) {
      throw new Error(`SPY price ${quote.price} is outside sane range`);
    }

    // 4. Test de timestamp — données de moins de 48h
    const age = Date.now() - new Date(quote.timestamp).getTime();
    if (age > 48 * 3600 * 1000) {
      throw new Error(`SPY data is ${Math.round(age/3600000)}h old (max 48h)`);
    }

    return { ok: true, spyPrice: quote.price };

  } catch (err) {
    // HARD STOP — ne PAS continuer
    console.error(`⛔ MCP HARD STOP: ${err.message}`);
    console.error(`Tâche suspendue. Reprendre uniquement après reconnexion MCP.`);
    return { ok: false, error: err.message };
  }
}

Qu'est-ce que des « données bizarres » ?

Le pre-flight check ne suffit pas. Le MCP peut répondre avec un statut « ok » mais renvoyer des données incohérentes — un prix stale, un NaN, ou une valeur absurde. Voici les signaux d'alerte que le harness surveille en continu :

Signal Détection Action
NaN / null / undefined isNaN(price) || price == null HARD STOP — données corrompues
Prix > 2× attendu Comparer au dernier prix connu ou à la moyenne mobile HARD STOP — possible erreur de source
Timestamp stale > 48h Date.now() - timestamp > 48h HARD STOP — données pas fraîches
Quote ≠ bars Dernier close des bars_daily vs quote actuel > 10% d'écart WARNING — vérifier manuellement
Volume = 0 Volume du jour à zéro un jour de trading WARNING — ticker peut-être delisted ou suspendu

La hiérarchie de fallback

Contrairement à beaucoup de systèmes qui dégradent gracieusement (« si la source A tombe, essayer la source B, puis C »), le harness DailyTickers a une hiérarchie volontairement brutale :

Source 1MCP DailyTickers — données marché temps réel
si MCP down
RienSTOP IMMÉDIAT. Pas de source B. Pas de WebSearch. Pas de cache. Pas d'estimation. Rien.

Pourquoi pas de fallback vers WebSearch ? Parce que WebSearch pour des prix financiers est dangereusement incohérent. Un search peut retourner un prix d'il y a 3 jours mélangé avec un titre d'aujourd'hui. Il peut confondre le pre-market avec le close. Il peut retourner un prix en devise différente sans le signaler. En finance, une donnée de mauvaise qualité qu'on croit bonne est pire qu'aucune donnée.

Fail loud, fail fast

Une page blanche est meilleure qu'une page fausse. En finance, le pire résultat n'est pas « pas de donnée » — c'est « une donnée fausse qui a l'air vraie ». Le lecteur qui voit une page blanche sait qu'il n'a pas l'info. Le lecteur qui voit META à +32% (quand c'est -15%) prend peut-être une décision d'investissement basée sur un mensonge. C'est pourquoi le harness préfère couper plutôt que dégrader.

Arbitrages Coût / Qualité / Latence

Un harness a un coût. Chaque couche de validation consomme des tokens, et en multi-agents les tokens se multiplient. La question n'est pas « faut-il un harness ? » (la réponse est toujours oui pour des données publiées), mais « combien investir dans chaque couche pour maximiser la fiabilité par dollar ? »

Routage par modèle : le bon cerveau pour chaque tâche

Tous les agents n'ont pas besoin du même modèle. Le scanner DailyTickers utilise trois tiers de modèles, chacun choisi pour sa force :

Tâche Modèle recommandé Pourquoi Coût relatif
Extraction mécanique (parse HTML, extraire dates, formatter) Haiku 3.5 Tâche cadrée, pas de jugement — vitesse et coût priment 1×
Génération (rédiger une analyse, formater des données en HTML) Sonnet 4 Bon équilibre entre qualité rédactionnelle et coût 5×
Jugement (verdicts war room, décisions de grade, évaluation stratégique) Opus 4 Raisonnement en profondeur, nuance, résistance aux biais 25×
Enrichissement MCP (appels d'outils structurés, cross-check) Sonnet 4 Fiable sur les appels d'outils, bon suivi de schéma 5×
Anti-dilution (analyse SEC filings, détection ATM/warrants) Opus 4 Requiert une lecture attentive de documents complexes 25×

Budget type d'un run scanner complet

Étape Agents Modèle Tokens (approx.) Coût (approx.)
Screening initial 1 Sonnet ~20K ~$0.10
Enrichissement MCP (×15 tickers) 15 Sonnet ~150K ~$0.75
Anti-dilution (×15) 15 Opus ~120K ~$1.50
Scoring + verify (×10 survivors) 10 Sonnet ~80K ~$0.40
War room (×8 finalists, 5 personas) 40 Opus ~200K ~$2.50
Génération HTML (×8) 8 Sonnet ~100K ~$0.50
Total ~89 ~670K ~$5.75

La « taxe adversariale »

La war room (couche 3) représente environ 40-45% du coût total d'un run. C'est tentant de la supprimer pour « optimiser ». Mais il faut comparer avec le coût de ce qu'elle évite :

Scénario Coût
War room (5 agents × 8 articles) ~$2.50 / run
Correction manuelle d'un article faux ~3 heures humaines + coût de réputation
Article faux publié non détecté Incalculable — perte de confiance lecteurs

La war room est une assurance, pas un coût. Elle représente environ $75/mois pour un scanner nocturne quotidien. Le coût de NE PAS l'avoir a été mesuré précisément le 21 juin 2026 : 3 heures de correction, une rétrospective à réécrire, et une règle HARD STOP immuable ajoutée au code du projet.

Source : Anthropic — Model overview & pricing

Quand sauter la vérification

Contexte Vérification ? Pourquoi
Article publié (scanner, daily, weekly, analyses) TOUJOURS Données visibles par le public — aucune exception
Draft interne (brouillon, exploration) Schema + cross-ref Pas de war room, mais les faits doivent être justes
Brainstorming (idées, hypothèses) Schema seul La structure suffit, les faits seront vérifiés plus tard
Code / refactoring Tests unitaires Le harness n'est pas pertinent — les tests le sont

Ne jamais optimiser en supprimant la vérification

Si le harness coûte trop cher, optimisez le modèle (Haiku pour l'extraction, Sonnet pour la génération), pas la vérification. Réduire le nombre de personas de 5 à 3 est acceptable. Supprimer la war room ne l'est pas. Réduire le cross-check MCP est interdit.

Construire la Confiance par la Contrainte

Il y a un paradoxe au cœur de ce guide : plus on contraint un LLM, plus il devient utile. Un modèle libre produit du texte fluide et plausible — mais en finance, plausible et vrai sont deux choses différentes. Le harness ne limite pas le modèle : il le force à opérer dans la zone où il est fiable, et il délègue le reste à des mécanismes qui ne hallucinent pas (API, schemas, votes).

L'incident de la rétrospective H1 2026 n'était pas un échec du modèle. Le modèle a fait exactement ce qu'on lui a demandé : générer un article plausible sur le premier semestre. Le vrai échec était l'absence de harness — pas de schema pour contraindre la structure, pas de cross-check pour vérifier les faits, pas de war room pour repérer les incohérences narratives, et surtout pas de HARD STOP pour couper quand la source de données est tombée.

Le coût du harness est réel : +30-50% de tokens, +5-8 minutes de latence par article. Mais ce coût est dérisoire comparé au coût d'une seule publication fausse. Et il diminue avec l'apprentissage : chaque incident identifié par le harness devient une nouvelle règle, un nouveau filtre, un nouveau persona dans la war room. Le système s'améliore à chaque erreur qu'il attrape.

Les 5 principes du harness financier

  • Ne jamais publier de sortie LLM non vérifiée — même si l'HTML est parfait, même si le narratif est fluide, même si les chiffres « ont l'air bons »
  • Schema = contrat structurel, MCP = vérité terrain — le schema attrape la forme, le MCP attrape le fond. Les deux sont nécessaires, aucun n'est suffisant
  • War rooms > single reviewer — la diversité des perspectives (QA, Quant, Trader, Risk, Editor) bat la redondance. 5 vues différentes > 5 fois la même
  • Pipeline > monolithe — fan-out parallèle + early exit = efficacité maximale. Éliminer tôt pour économiser les couches coûteuses
  • Fail loud > fail silent (MCP HARD STOP) — si la source de données tombe, arrêter tout est la seule option sûre. Une page blanche > une page fausse

Source : Anthropic — Structured JSON output

La finance est un domaine où les erreurs ont des conséquences réelles. Un prix faux peut déclencher un trade. Un grade artificiel peut attirer un investisseur vers un titre dangereux. Une performance inventée peut fausser une allocation. Le harness n'est pas un luxe — c'est la condition sine qua non pour que l'IA soit plus fiable qu'un analyste humain fatigué, plutôt que moins fiable qu'un simple tableur.

Pour aller plus loin

Article Ce qu'il couvre
Workflows Multi-Agents Les 5 patterns d'orchestration (fan-out, pipeline, war room, panel de juges, boucles) avec code et schémas
MCP Données Marché Comment connecter Claude à des données de marché temps réel via le protocole MCP
Inside the Scanner Pipeline Le pipeline complet du scanner nocturne : du screening initial à la publication automatique
Le problème Schemas JSON Architecture War Rooms Workflows Hard Stop Coût/Qualité Conclusion