Guide · IA & agents

Construire un mini agent-as-harness sur une tâche synthétique

Un tutoriel local et déterministe pour observer un student, un reviewer et un outil cible sans entraîner de modèle ni toucher à un système réel.

Des composants abstraits passent une suite d’inspections avant d’atteindre un résultat vérifié.

Un exercice sans système réel

Ce tutoriel reproduit uniquement la forme d’un agent-as-harness. Le student propose des opérations sur une petite liste en mémoire ; un reviewer décide si la proposition peut être exécutée ; un outil cible applique l’opération et renvoie l’état visible. Il n’y a ni appel réseau, ni fichier utilisateur, ni modèle à entraîner.

L’objectif est de rendre trois frontières observables : la proposition du student, la décision de revue et la réponse réellement exécutée. Le reviewer ne fournit pas de raisonnement privé au student. Il renvoie seulement une décision et, si nécessaire, une opération de remplacement complète.

Le contrat d’action

Notre environnement accepte deux actions : set pour remplacer la valeur d’une clé existante et read pour relire une clé. Une action inconnue est refusée. Le student n’a pas le droit de déclarer une tâche terminée directement : il doit d’abord relire la valeur qu’il vient de modifier.

type Action =
  | { kind: "set"; key: "status" | "label"; value: string }
  | { kind: "read"; key: "status" | "label" };

type State = { status: string; label: string };

function execute(state: State, action: Action): State | string {
  if (action.kind === "read") return `${action.key}=${state[action.key]}`;
  return { ...state, [action.key]: action.value };
}

Le code est volontairement fermé : les clés possibles sont connues, l’état est local et l’opération ne peut pas atteindre un autre système. Dans un vrai projet, le contrat devrait aussi préciser les permissions, le chemin autorisé, les limites de durée et la manière de traiter une erreur d’outil.

Simuler le student

Pour garder l’expérience reproductible, le student reçoit une instruction et renvoie une liste fixe d’actions. Dans le premier scénario, il propose une modification correcte mais oublie la relecture. Dans le second, il propose une modification puis la relecture attendue.

function student(task: "forget-read" | "read-after-set"): Action[] {
  const setStatus: Action = { kind: "set", key: "status", value: "ready" };
  const readStatus: Action = { kind: "read", key: "status" };
  return task === "forget-read" ? [setStatus] : [setStatus, readStatus];
}

Cette fonction ne prétend pas modéliser le raisonnement d’un modèle. Elle donne simplement deux trajectoires comparables pour tester le harness. C’est une propriété utile d’un exercice pédagogique : l’échec et la réussite doivent être déterministes avant d’ajouter de la variabilité.

Ajouter le reviewer

Le reviewer applique une règle unique : après une action set, la prochaine action doit être une lecture de la même clé. S’il reçoit cette séquence, il laisse passer. Sinon, il remplace la proposition manquante par une lecture complète dans l’espace d’action cible.

type Review =
  | { decision: "PASS"; action: Action }
  | { decision: "REPLACE"; action: Action };

function review(previous: Action | undefined, proposed: Action): Review {
  const needsRead = previous?.kind === "set";
  const isMatchingRead =
    proposed.kind === "read" &&
    previous?.kind === "set" &&
    proposed.key === previous.key;

  if (needsRead && !isMatchingRead) {
    return { decision: "REPLACE", action: { kind: "read", key: previous.key } };
  }
  return { decision: "PASS", action: proposed };
}

La correction est complète et exécutable : elle ne dit pas seulement « vérifie ». Elle fournit l’action read autorisée par le contrat. Dans une implémentation de production, cette règle devrait être complétée par un guard indépendant qui refuse les clés inconnues et les écritures hors périmètre. Le reviewer ne doit jamais élargir les permissions pour réparer sa propre proposition.

Collecter la trajectoire visible

La boucle ci-dessous conserve uniquement les actions acceptées et les observations retournées par l’outil. La décision de revue peut être enregistrée dans un journal d’audit séparé, mais le détail d’une discussion privée n’est jamais ajouté au contexte du student.

function rollout(task: "forget-read" | "read-after-set") {
  let state: State = { status: "draft", label: "demo" };
  let previous: Action | undefined;
  const visible: Array<{ proposed: Action; accepted: Action; observation: string | State }> = [];

  for (const proposed of student(task)) {
    const decision = review(previous, proposed);
    const observation = execute(state, decision.action);
    if (typeof observation === "string") {
      visible.push({ proposed, accepted: decision.action, observation });
    } else {
      state = observation;
      visible.push({ proposed, accepted: decision.action, observation: state });
    }
    previous = decision.action;
  }

  return { state, visible };
}

Dans le scénario forget-read, la boucle termine avec une relecture injectée par le reviewer. Dans le scénario read-after-set, les actions du student passent sans remplacement. Le même outil cible exécute les deux trajectoires, ce qui rend la comparaison plus nette qu’une démonstration où le reviewer disposerait d’une interface différente.

Tester avant et après la revue

Ajoutez deux assertions simples : la valeur finale doit être ready et chaque mutation doit être suivie d’une lecture de la même clé dans la trajectoire acceptée.

const corrected = rollout("forget-read");
const native = rollout("read-after-set");

console.assert(corrected.state.status === "ready");
console.assert(native.state.status === "ready");
console.assert(corrected.visible.at(-1)?.accepted.kind === "read");
console.assert(native.visible.at(-1)?.accepted.kind === "read");

Ces assertions ne prouvent pas qu’un agent est fiable. Elles vérifient seulement le contrat de ce mini environnement. Pour aller plus loin, ajoutez des cas négatifs : clé inconnue, lecture d’une autre clé, outil indisponible et reviewer absent. Le comportement attendu doit être un arrêt explicite, jamais une exécution silencieuse.

Mesurer le coût sans promettre de gain

Comptez le nombre de propositions, de revues et d’actions réellement exécutées. Ici, la correction du premier scénario ajoute une lecture ; dans un système de modèle, elle ajouterait aussi au moins un appel de revue pendant la collecte. Notez ce coût avec le taux de remplacement et les erreurs résiduelles. Une trajectoire plus sûre n’est pas automatiquement meilleure si son coût ou sa latence deviennent incompatibles avec la tâche.

Le preprint Harness-Zero rapporte justement des résultats expérimentaux qui dépendent du modèle, des domaines, du harness et de la collecte. Son idée d’agent-as-harness peut inspirer ce test de frontière, mais cet exercice ne reproduit ni ses benchmarks, ni ses prompts, ni son entraînement, ni ses chiffres. Il sert uniquement à comprendre le contrat « proposer, revoir, remplacer ou laisser passer, exécuter, observer ».

Limites et prochaines étapes

Un système réel doit ajouter une sandbox, des permissions minimales, une validation humaine pour les actions sensibles, un journal protégé et des tests de régression. Il doit également vérifier que le reviewer ne voit pas une information interdite et que l’action de remplacement reste dans l’interface effectivement disponible au déploiement.

Ne transformez pas ce mini agent en mécanisme d’auto-modification. Pour expérimenter, gardez l’état synthétique, les règles versionnées et les sorties inspectables. La valeur de l’exercice est de rendre l’erreur et la correction mesurables, non de supprimer les contrôles autour d’un agent.

Source et attribution

Le contrat de traduction présenté ici est une adaptation originale inspirée de Haoran Ye, Yuxing Lu, Haonan Dong, Zhaochen Su et Guojie Song, « Harness-Zero: Harness Distillation via Agent-as-Harness », arXiv:2609.24974v1 : résumé et preprint, PDF. L’exemple est synthétique, local et sans entraînement ; il ne reprend aucun prompt, code, benchmark, résultat ou visuel du papier.