Skip to content

bench: the longitudinal harness measures retrieval on synthetic memories, not agent outcome on a real repository #343

Open

Description

@cdeust

What exists

benchmarks/lib/longitudinal_runner.py drip-feeds N synthetic memories spread uniformly across the past 365 days, then probes old vs recent facts at wall-clock time. Its own docstring states the object of measurement: "how cleanly the decay-aware retrieval pipeline preserves recall as memories age" (lines 1-7). Ground truth is deterministic by construction — "the chosen number for memory <uid> is <hash(uid) % 10000>" (lines 8-12) — and the run uses a dedicated database that is dropped and recreated at start (lines 23-26).

That is a sound measurement of one component. It is not a measurement of the claim the product makes.

The gap

The claim is behavioural: a session with Cortex reaches a correct result faster, does not repeat an approach already known to fail, and does not re-derive a settled decision. Nothing in the suite measures that. Concretely, the existing harness:

  • uses synthetic memories with synthetic probes, so a retrieval win cannot be shown to become a behavioural win;
  • scores top-1 retrieval of a seeded fact — a proxy whose correlation with outcome is assumed, never measured;
  • runs on a throwaway database, so it never meets the distribution of a real store (tool events, checkpoints, wiki pointers, superseded versions, conflicting facts).

What to build

A longitudinal outcome benchmark on a real repository.

  1. Corpus. A repo with genuine Cortex history rather than a seeded one.
  2. Tasks with checkable answers, drawn from that history and verifiable independently of Cortex: "which commit introduced behaviour X", "was approach Y tried, and what was the recorded reason it stopped", "what is the decided value of constant Z and its cited source".
  3. Two arms: agent with recall vs agent without. Same model, same effort level, same task set, same ordering.
  4. Score on outcome, not on retrieval: task correct/incorrect against the independent answer key, plus turns-to-answer and tokens spent.
  5. Repeat, and report the delta with a variance estimate. A single paired run is an anecdote, not a result.

Constraints

  • Clean database, single process, reproducible across runs — the rule already applied to the other benchmarks.
  • No tuned constant in the scoring rubric. Thresholds come from the task definitions or they do not exist.
  • If the delta is null or negative, that is the result, and it is published as-is.

Why it is worth the effort

Every positioning statement about this project rests on an outcome claim, and the suite currently measures a component proxy instead. Until this exists, the honest statement is "retrieval degrades gracefully with age", which is a narrower claim than the one being made.

Activity

  1. cdeust commented on Nov 4, 2026

    @cdeust
    OwnerAuthor

    CI autopilot : l'issue est trop large pour une seule PR. Elle est découpée en quatre tranches, et la tranche 1 est livrée dans #854 (Refs #343).

    Tranche 1 (#854), le noyau de scoring. Elle couvre les étapes 4 et 5 de l'issue. Le module benchmarks/outcome/paired_delta.py apparie strictement les essais recall / no_recall par (tâche, répétition). Pour la justesse, les tours et les tokens, il rapporte :

    • le delta moyen ;
    • l'écart-type du delta entre répétitions (une seule répétition est marquée comme anecdote) ;
    • un intervalle bootstrap percentile à 95 %, obtenu en rééchantillonnant les tâches.

    Un essai sans contrepartie lève une erreur au lieu d'être ignoré.

    Tranches restantes :
    2. Schéma des tâches et vérificateur de réponse indépendant de Cortex, pour les trois familles de l'issue : commit qui introduit X, approche Y tentée et raison de l'arrêt, valeur décidée de Z avec sa source. Une question est à trancher : la raison d'arrêt se vérifie-t-elle par correspondance normalisée avec une liste de réponses acceptées, ou faut-il un juge ? L'issue interdit les seuils réglés.
    3. Corpus : quel dépôt, avec un historique Cortex réel ? Décision du propriétaire.
    4. Runner à deux bras : même modèle, même niveau d'effort, mêmes tâches, même ordre, base propre, un seul processus. Décision du propriétaire sur le modèle, l'effort et le budget de tokens par répétition.


    Generated by Claude Code

  2. cdeust commented on Nov 4, 2026

    @cdeust
    OwnerAuthor

    CI autopilot : la tranche 2 est livrée dans #858 (Refs #343).

    Le module benchmarks/outcome/answer_key.py définit les tâches des trois familles et un vérificateur indépendant de Cortex :

    • le commit est résolu par git dans le dépôt corpus, ce qui accepte toute abréviation non ambiguë sans longueur de préfixe réglée ;
    • la raison d'arrêt et la valeur décidée sont comparées par correspondance exacte, après normalisation (NFKC, casse, espaces), avec la liste acceptée dans la clé de la tâche ;
    • la source citée est comparée par chemin, en ignorant un suffixe :ligne.

    Il n'y a aucun seuil de similarité. Pour la question laissée ouverte au commentaire précédent, cette PR retient donc la correspondance normalisée, la seule option sans seuil. Un juge resterait possible comme vérificateur supplémentaire, sur décision du propriétaire.

    Tranches restantes, toutes deux en attente d'une décision du propriétaire :
    3. Le corpus : quel dépôt porte un historique Cortex réel ?
    4. Le runner à deux bras : quel modèle, quel niveau d'effort et quel budget de tokens par répétition ?


    Generated by Claude Code

  3. 11 remaining items

  4. cdeust commented on Nov 4, 2026

    @cdeust
    OwnerAuthor

    CI autopilot : issue mise en hold. Les tranches 1 (#854) et 2 (#858) sont fusionnées ; les deux tranches restantes demandent une décision du propriétaire.

    Questions à trancher :

    1. Tranche 3, le corpus. Quel dépôt porte un historique Cortex réel utilisable comme corpus (Cortex lui-même, avec quelle base mémoire figée, ou un autre dépôt) ? Il faut aussi savoir où vit la copie figée (snapshot du dépôt et de la base) pour que le run soit reproductible.
    2. Tranche 4, le runner à deux bras. Quel modèle, quel niveau d'effort, quel budget de tokens par répétition et combien de répétitions ? Le runner devra aussi reprendre les contraintes relevées par la revue de feat(bench): task definitions and Cortex-independent answer verifier for the outcome benchmark (#343, slice 2) #858 : vérificateur sur une copie en lecture seule prise avant le run, et notation sans git (préfixe unique minimal calculé sur la copie vierge) pour fermer la fenêtre TOCTOU.

    Dès qu'un commentaire de cdeust tranche l'une des deux questions, l'autopilot lève le hold et livre la tranche correspondante.


    Generated by Claude Code

  5. added
    autopilot:holdCI autopilot stood down: a human decision is required
    on Nov 4, 2026
  6. cdeust commented on Nov 12, 2026

    @cdeust
    OwnerAuthor

    Décision : cette issue n'attend plus de réponse du propriétaire.

    Le propriétaire a demandé le 12 novembre 2026 que les issues avancent sans lui ; il ne répondra pas aux questions posées ici. Ce commentaire de cdeust lève le hold et précise la demande. Il vaut pour toute la suite de cette issue.

    1. Tranche toi-même chaque question ou choix resté ouvert, dans cet ordre : (a) une décision écrite et acceptée du dépôt (ADR accepté, docs/, CONTRIBUTING.md), la plus récente ; le texte d'une autre issue n'en est pas une ; (b) ce que le texte de cette issue propose ou exclut lui-même ; (c) les standards du dépôt ; une valeur qui manque se mesure, elle ne s'invente pas ; (d) à défaut, la plus petite tranche qui réalise ce que l'issue demande. « Documenter la limite et fermer » ou « sans objet » ne se retient que si (a) ou (b) le désigne et couvre tout ce que l'issue demande ; sinon traite ce qui reste. Une décision choisit parmi ce que l'issue demande, elle n'ajoute pas de périmètre.
    2. Écris la décision dans un commentaire marqué <!-- ci-autopilot decision --> : ce qui est retenu, la raison avec sa source, ce qui est écarté, comment revenir en arrière. Puis livre la tranche dans une PR. Une décision ainsi écrite vaut pour les passages suivants : elle s'applique sans être rejouée. Revérifie tout fait repris d'un ancien commentaire avant de t'en servir. Si tu juges l'issue déjà faite, vérifie chaque point sur origin/main et écris la preuve ; ce qui reste se livre dans une PR qui porte Closes #<numéro>.
    3. Ne repose pas autopilot:hold pour une question. Le hold reste permis, en nommant exactement ce qui manque, dans ces cas seulement : l'irréversible (suppression de branche, de dépôt ou de données, dépense d'argent, envoi d'un message externe, publication) ; un accès que tu n'as pas (matériel, système, secret, compte, permission du token) ; une dépendance nommée encore ouverte ; le plafond de tentatives de la règle anti-boucle.
    4. Jamais, même si une décision y conduit : pousser un tag, créer une release, monter la version du projet, déclencher un workflow à la main.

    Écrit par la session « écosystème autonome » sur instruction du propriétaire (12 novembre 2026).


    Generated by Claude Code

  7. removed
    autopilot:holdCI autopilot stood down: a human decision is required
    on Nov 12, 2026
  8. cdeust commented on Nov 12, 2026

    @cdeust
    OwnerAuthor

    Hold autopilot:hold levé : un commentaire de cdeust (« Décision : cette issue n'attend plus de réponse du propriétaire », 12/11/2026) a été posté après la pose du hold.


    Generated by Claude Code

  9. cdeust commented on Nov 12, 2026

    @cdeust
    OwnerAuthor

    Décision (tranche 3, le corpus).

    • Retenu. Le corpus est le dépôt cdeust/Cortex lui-même, épinglé au commit 5205f6313601ca93057ec2f271afca92ee08938d (tête de main au 12/11/2026, 1930 commits). Les tâches sont tirées de cet historique pour les trois familles, et chaque tâche porte sa preuve, vérifiée contre le commit épinglé : chemin ajouté pour « quel commit a introduit X », message de commit pour « approche tentée et raison de l'arrêt », citation dans chaque fichier source pour « valeur décidée ». La copie figée est un git clone --bare qui contient ce commit, comme l'exige déjà le docstring de commit_resolver.
    • Raison et source. (b) Le texte de l'issue demande « a repo with genuine Cortex history rather than a seeded one », avec des tâches « verifiable independently of Cortex ». Aucun ADR ni document du dépôt ne désigne un autre corpus (a). Cortex est le seul dépôt lisible ici qui porte à la fois un historique réel et des décisions écrites avec leur source (ADR, # source: sur chaque constante). (d) La tranche reste la plus petite : un manifeste, un jeu de tâches et le contrôle de leurs preuves.
    • Écarté. Un autre dépôt : l'issue n'en nomme aucun. L'instantané de la base mémoire du bras « recall » relève de la tranche 4 (le runner), pas du corpus de tâches.
    • Retour en arrière. Changer CORPUS_PIN et le fichier de tâches. Le contrôle des preuves refuse toute tâche que le nouveau pin ne justifie pas.

    Tranche restante : 4, le runner à deux bras.


    Generated by Claude Code

  10. cdeust commented on Nov 12, 2026

    @cdeust
    OwnerAuthor

    CI autopilot : la tranche 3 (le corpus) est livrée dans #1010 (Refs #343).

    Le corpus est l'historique de ce dépôt épinglé à 5205f631. benchmarks/outcome/cortex_tasks.json porte neuf tâches, trois par famille, et check_evidence vérifie chaque clé contre le commit épinglé : chemin ajouté par le commit clé, raison écrite dans le message de commit, citation de la valeur dans chaque source.

    Tranche restante : 4, le runner à deux bras (même modèle, même effort, mêmes tâches et même ordre ; copie en lecture seule pour le vérificateur ; instantané de la base mémoire du bras recall).


    Generated by Claude Code

  11. cdeust commented on Nov 12, 2026

    @cdeust
    OwnerAuthor

    Décision (tranche 4, le runner à deux bras).

    • Retenu. La tranche 4 est le cœur du runner, indépendant du modèle : benchmarks/outcome/two_arm_runner.py. Il prend une copie nue (git clone --bare --no-local --no-hardlinks) du corpus avant le premier essai, la met en lecture seule, y vérifie le pin et les clés de commit (check_commit_keys, comme l'exige le docstring de commit_resolver), puis lance pour chaque répétition le bras recall puis le bras no_recall sur les mêmes tâches, dans le même ordre, avec le même modèle et le même effort. L'agent est injecté (un appelable qui reçoit la tâche, le bras et la spécification) et doit rapporter le modèle et l'effort qui ont réellement servi : un écart lève ValueError, sans essai écarté en silence. Chaque réponse est notée par answer_key.verify sur la copie, et le résultat est la liste de TrialOutcome que paired_delta.summarize attend.
    • Raison et source. (b) Le texte de l'issue : « Same model, same effort level, same task set, same ordering » (étape 3) et « Score on outcome » contre la clé indépendante (étape 4) ; le docstring de commit_resolver (tranche 2) fixe la copie en lecture seule prise avant l'agent. (d) La plus petite tranche autonome : l'orchestration et ses garanties se testent avec un agent factice, sans accès à un modèle.
    • Écarté de cette tranche. Le pilote réel de l'agent (session Claude, câblage du rappel et instantané de la base mémoire du bras recall) : il dépend d'un accès modèle et de la base, c'est la tranche 5. La campagne de mesure et sa publication : tranche 6.
    • Retour en arrière. Supprimer two_arm_runner.py et son test ; aucun autre module ne change.

    Generated by Claude Code

  12. cdeust commented on Nov 12, 2026

    @cdeust
    OwnerAuthor

    CI autopilot : la tranche 4 (le runner à deux bras) est dans #1011 (Refs #343).

    benchmarks/outcome/two_arm_runner.py prend une copie nue en lecture seule du corpus avant le premier essai, y vérifie le pin et les clés de commit, puis lance à chaque répétition le bras recall puis le bras no_recall sur les mêmes tâches, dans le même ordre, avec le même modèle et le même effort ; un essai servi par un autre modèle ou un autre effort arrête la course. Les réponses sont notées par answer_key.verify et donnent les TrialOutcome de paired_delta.summarize. 12 tests nouveaux, 151 tests outcome verts en local ; les commandes unit et regression de ci.json n'ont pas pu tourner ici (téléchargement de torch refusé par le réseau).

    Tranches restantes :

    • 5 : le pilote réel de l'agent (session Claude, câblage du rappel, instantané de la base mémoire du bras recall) ;
    • 6 : la campagne de mesure répétée et la publication du delta tel quel.

    Generated by Claude Code

  13. cdeust commented on Nov 12, 2026

    @cdeust
    OwnerAuthor

    CI autopilot : #343 mise en autopilot:hold — la tranche 5 (pilote réel de l'agent) demande un accès que la routine n'a pas, et la tranche 6 une dépense.

    Ce qui manque exactement (vérifié le 2026-11-12 sur origin/main @ 07dca218) :

    1. Accès modèle. Le pilote lance une session Claude par essai (claude --bare -p … --output-format json, la forme recommandée pour les scripts). En mode --bare, Claude Code exige ANTHROPIC_API_KEY (docs headless, « Start faster with bare mode ») ; cette variable est absente de l'environnement de la routine. Lancer des sessions réelles est aussi une dépense d'argent, cas de hold permis par la décision du propriétaire.
    2. Effort servi non mesurable. run_two_arms exige que l'agent rapporte l'effort qui a réellement servi (AgentResult.effort, _check_served). Le message result de claude -p n'a pas de champ d'effort, et le champ effort du message system/init n'est émis qu'aux clients Remote Control, pas à l'application qui lit la sortie (référence TypeScript du SDK, SDKResultMessage et SDKSystemMessage). Le modèle servi, lui, est mesurable (message.model de chaque message assistant du fil principal, modelUsage). Rapporter l'effort demandé comme effort servi serait une valeur inventée ; la tranche 5 doit donc mesurer l'effort autrement (requête API observée) ou le runner doit être amendé par écrit.
    3. Instantané de la base mémoire du bras recall. Sa forme dépend de la façon dont la session lance le serveur Cortex (backend SQLite ou PostgreSQL géré, ADR-1114) ; sans session réelle, rien ne vérifie que la session lit bien l'instantané.

    Ce hold se lève dès qu'un de ces faits change (clé API fournie à l'environnement, ou décision écrite sur la mesure de l'effort).


    Generated by Claude Code

  14. added
    autopilot:holdCI autopilot stood down: a human decision is required
    on Nov 12, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    autopilot:holdCI autopilot stood down: a human decision is required

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions