Vercel a publié durable-harness, un paquet open source qui maintient en vie les sessions d'agents de codage à travers les plantages et les dépassements de délai. Vercel durable-harness enveloppe les harness existants, notamment Pi, OpenCode, Claude Code et Codex, sans les modifier. Chaque session vit dans un journal durable. Une fonction morte ne signifie plus une perte de travail. Pour les équipes qui déplacent leurs agents des ordinateurs portables vers le cloud, cela comble un vrai manque.
TL;DR
- Taille : paquet npm, version 0.0.x, Apache-2.0. Les adaptateurs couvrent 5 harness : Pi, OpenCode, fx, Claude Code, Codex.
- Fonctionne sur : Vercel Functions plus Vercel Queues, Node.js 22+ avec Postgres, ou des fichiers sur ordinateur portable. Claude Code et Codex nécessitent une Vercel Sandbox.
- Performance : Aucun benchmark de latence ou de débit publié. Toutes les affirmations de récupération sont auto-déclarées par Vercel.
- Points forts : Les appels de modèle et d'outils terminés sont relus depuis le journal, jamais réexécutés ni facturés deux fois.
- Points faibles : API expérimentale en 0.0.x. Claude Code et Codex ne fonctionnent que dans une Vercel Sandbox, même en auto-hébergé.
Qu'est-ce que durable-harness ?
durable-harness est un ensemble d'adaptateurs de Vercel qui stockent une session de harness dans un Workflow SDK World. Un World regroupe un stockage, une file d'attente et un flux derrière une seule interface. Le paquet utilise la même machinerie qui alimente le Workflow SDK.
Pourquoi les agents de codage perdent-ils leur travail dans le cloud ?
Des harness comme Pi et Claude Code ont été conçus comme des processus locaux de longue durée. Ils conservent la session en mémoire. Localement, l'espace de travail et l'historique résident sur le même disque, donc la reprise ne prend qu'1 prompt. Dans le cloud, une VM avec stockage persistant ne sait pas quels appels ont été complétés. L'équipe Vercel note qu'un tour échoué ou une erreur réseau signifie alors une perte de travail.
Comment fonctionne durable-harness ?
Il écrit chaque étape d'un tour dans un journal durable. Avant qu'un appel de modèle ou d'outil ne s'exécute, il enregistre une intention. Lorsque l'appel se termine, il enregistre le résultat. Chaque tour est adossé à un message sur Vercel Queues. Si la fonction meurt, la file d'attente transmet le tour à une nouvelle fonction. Cette fonction lit le journal et poursuit.
Après un plantage, le journal montre ce qui s'est terminé, ce qui était en cours et ce qui n'a jamais commencé :
- Appels terminés sont lus depuis le journal, de sorte que les tokens de modèle déjà terminés ne sont pas facturés à nouveau.
replay: 'safe'outils, comme les recherches, peuvent s'exécuter à nouveau.replay: 'never'outils, comme les remboursements, ne s'exécutent jamais deux fois. Le modèle est informé que l'appel a peut-être eu lieu.
Les fichiers de session du harness sont également reflétés dans le journal. Cela couvre la transcription de Claude Code, le rollout de Codex et le magasin d'événements d'OpenCode.
Où s'exécutent les fichiers et les commandes shell ?
Les commandes choisies par le modèle ne s'exécutent jamais sur le disque propre de la fonction. La documentation des outils liste 3 options d'espace de travail :
virtual()conserve les fichiers dans le journal de session, avec un shell compatible bash et sans réseau. Il contient environ 500 fichiers.virtual({ durable: false })conserve les fichiers temporaires en mémoire et les efface à chaque tour.sandbox()donne à chaque session une Vercel Sandbox, avec/workspacesur un Drive.
Les documents JSON typés, comme une liste de tâches, vivent également dans le journal. Chacun contient au plus 64 KB.
Run turn</button> <button class="b red" id="kill" disabled>Tuer la fonction</button> <button class="b ghost" id="rst">Réinitialiser</button> </div> <div class="log" id="lg"><div>Appuyez sur Run turn, puis tuez la fonction pendant que la CI s'exécute.</div></div> <p class="note">Scénario tiré de l'article de Vercel : un agent ajoute une page de tarification, ouvre une PR et attend la CI. Simulation illustrative, pas un benchmark.</p> </div> <div class="pane" id="p2"> <p class="sub" style="margin-bottom:12px">Cliquez sur un outil, puis faites-le planter en plein appel.</p> <div class="rules"> <div class="box tool sel" data-r="safe"><h4><code>get_order</code><span class="pill">replay: 'safe'</span></h4><span class="note">Une consultation. Sans danger de la répéter après un plantage.</span></div> <div class="box tool" data-r="never"><h4><code>refund_order</code><span class="pill">replay: 'never'</span></h4><span class="note">Un remboursement. Ne doit jamais s'exécuter deux fois.</span></div> </div> <div class="btns"><button class="b red" id="crash">Planter en plein appel</button></div> <div class="verdict" id="vd">Choisissez un outil et appuyez sur Planter en plein appel.</div> <pre id="code">{ name: 'get_order', replay: 'safe', ... }</pre> </div> <div class="pane" id="p3"> <div class="ws"> <div class="box sel" data-w="0"><h4><code>virtual()</code></h4><span class="note">Fichiers dans le journal de session. Shell compatible Bash, pas de réseau.</span></div> <div class="box" data-w="1"><h4><code>virtual({durable:false})</code></h4><span class="note">Brouillon en mémoire. Effacé à chaque tour.</span></div> <div class="box" data-w="2"><h4><code>sandbox()</code></h4><span class="note">Vercel Sandbox par session, <code>/workspace</code> sur un Drive.</span></div> </div> <div class="box" style="margin-top:14px" id="wsd"></div> </div> <div class="pane" id="p4"> <div class="worlds"> <div class="box world sel" data-g="0"><h4>Vercel</h4><span class="note">Vercel World + traces des Agent Runs</span></div> <div class="box world" data-g="1"><h4>Laptop</h4><span class="note">Fichiers locaux, zéro configuration</span></div> <div class="box world" data-g="2"><h4>Self-host</h4><span class="note">Postgres World, Node.js 22+</span></div> </div> <pre id="wc"></pre> <p class="note" id="wn"></p> </div> <div class="pane" id="p5"> <table> <tr><th>Harness</th><th>Paquet</th><th>S'exécute dans</th><th>Statut</th></tr> <tr><td>Pi</td><td><code>durable-harness/pi-durable</code></td><td>Votre fonction + espace de travail virtuel ou Sandbox</td><td class="y">Pris en charge</td></tr> <tr><td>OpenCode</td><td><code>durable-harness/opencode</code></td><td>Votre fonction + espace de travail virtuel ou Sandbox</td><td class="y">Pris en charge</td></tr> <tr><td>fx</td><td><code>libfx</code></td><td>Durable à lui seul</td><td class="y">Pris en charge</td></tr> <tr><td>Claude Code</td><td><code>durable-harness/claude-code</code></td><td>Vercel Sandbox uniquement</td><td class="n">Expérimental</td></tr> <tr><td>Codex</td><td><code>durable-harness/codex</code></td><td>Vercel Sandbox uniquement</td><td class="n">Expérimental</td></tr> </table> <p class="note">Claude Code et Codex sont des CLI mono-utilisateur qui conservent leur état sur disque, de sorte que chaque session obtient son propre Sandbox.</p> </div> <div class="ft"><span>Source: vercel.com/blog/making-every-harness-durable · Vercel docs · Oct 2026</span><b>© Marktechpost</b></div> </div> <script> (function(){ var R=document.getElementById('mtp-dh'); function $(s){return R.querySelector(s)}function $$(s){return R.querySelectorAll(s)} function rz(){try{parent.postMessage({mtpEmbedHeight:R.offsetHeight+40,id:'mtp-dh'},'*')}catch(e){}} $$('.tab').forEach(function(t){t.onclick=function(){$$('.tab').forEach(function(x){x.classList.remove('on')});$$('.pane').forEach(function(x){x.classList.remove('on')});t.classList.add('on');$('#'+t.dataset.t).classList.add('on');if(t.dataset.t==='p3')ws(cur);setTimeout(rz,60)}}); var S=[['model','Plan the change',1],['read','app/page.tsx',0],['write','app/pricing/page.tsx',0],['run','gh pr create',0],['wait','CI checks on the PR',0],['model','Reply with the result',1]]; function draw(id){var o=$(id);o.innerHTML='';S.forEach(function(s){var li=document.createElement('li');li.innerHTML='<span class="k">'+s[0]+'</span>'+s[1];o.appendChild(li)})} var i=0,tm=null,killed=false,ma=0,mb=0; function log(t,c){var l=$('#lg'),d=document.createElement('div');d.className=c||'';d.textContent=t;l.appendChild(d);while(l.children.length>6)l.removeChild(l.firstChild)} function reset(){clearTimeout(tm);i=0;killed=false;ma=mb=0;draw('#sA');draw('#sB');$('#aRe').textContent=0;$('#bRe').textContent=0;$('#aMc').textContent=0;$('#bMc').textContent=0;$('#go').disabled=false;$('#kill').disabled=true;$('#lg').innerHTML='<div>Appuyez sur Run turn, puis tuez la fonction pendant que la CI en cours.</div>'} function li(id,n){return $(id).children[n]} function step(){if(i>=S.length){$('#kill').disabled=true;log('tour terminé','o');return} ['#sA','#sB'].forEach(function(id){var e=li(id,i);e.className='run'}); log('intention '+S[i][0]+' '+S[i][1],'i'); tm=setTimeout(function(){['#sA','#sB'].forEach(function(id){li(id,i).className='done'});if(S[i][2]){ma++;mb++;$('#aMc').textContent=ma;$('#bMc').textContent=mb}log('résultat '+S[i][0]+' ok','o');i++;step()},i===4?1800:700)} $('#go').onclick=function(){reset();$('#go').disabled=true;$('#kill').disabled=false;$('#lg').innerHTML='';step()}; $('#rst').onclick=reset; $('#kill').onclick=function(){if(i>=S.length)return;clearTimeout(tm);killed=true;$('#kill').disabled=true;var k=i; li('#sA',k).className='dead';li('#sB',k).className='dead';log('la fonction 1 est morte à l\'étape '+(k+1),'x'); setTimeout(function(){log('sans durabilité : redémarrage manuel, le tour recommence','x'); draw('#sA');var re=0,j=0;(function a(){if(j>=S.length){$('#aRe').textContent=re;return}var e=li('#sA',j);e.className='run';setTimeout(function(){e.className=j<k?'rerun':'done';if(j<k){re++;$('#aRe').textContent=re}if(S[j][2]){ma++;$('#aMc').textContent=ma}j++;a()},260)})(); log('avec durable-harness : la file confie le tour à la fonction 2','o'); for(var q=0;q<k;q++)li('#sB',q).className='fromlog'; i=k;(function b(){if(i>=S.length){log('tour terminé, 0 étapes répétées','o');return}var e=li('#sB',i);e.className='run';setTimeout(function(){e.className='done';if(S[i][2]){mb++;$('#bMc').textContent=mb}i++;b()},450)})(); },600)}; reset(); var rule='safe'; $$('.tool').forEach(function(t){t.onclick=function(){$$('.tool').forEach(function(x){x.classList.remove('sel')});t.classList.add('sel');rule=t.dataset.r;$('#vd').textContent='Choisissez un outil et appuyez sur Crash en plein appel.';$('#code').textContent=rule==='safe'?"{ name: 'get_order', replay: 'safe', ... }":"{ name: 'refund_order', replay: 'never', ... }"}}); $('#crash'). onclick=function(){var v=$('#vd');v.style.opacity=0;setTimeout(function(){v.innerHTML=rule==='safe'?'<b style="color:#50e3c2">Repris :</b> la recherche interrompue peut être relancée. Pi la relance ; Claude Code, Codex et OpenCode indiquent au modèle qu\'il peut la répéter sans risque.':'<b style="color:#f5a623">Ne jamais relancer :</b> le modèle apprend que le remboursement a peut-être eu lieu ou non, il peut donc vérifier d\'abord. Chaque harness attend que le journal enregistre un appel \'never\' avant de démarrer.';v.style.transition='opacity .3s';v.style.opacity=1;rz()},150)}; var W=[{t:'Tâches courtes et petits espaces de travail',f:['Fichiers conservés','Dans le journal de session'],m:[['Taille de l\'espace de travail','environ 500 fichiers',55]],x:'Un crash en plein appel ne laisse rien derrière lui, et l\'appel est relancé. Coût : CPU de la fonction, plus événements de journal et stockage.'}, {t:'Travail temporaire au sein d\'un tour',f:['Fichiers conservés','Effacé à chaque tour'],m:[['Persistance','aucune entre les tours',8]],x:'Rien ne va dans le journal. Le modèle apprend que les fichiers sont effacés. Coût : CPU de la fonction uniquement.'}, {t:'Projets réels et travaux de longue durée',f:['Fichiers conservés','Sur un Drive qui survit à chaque Sandbox'],m:[['Isolation','1 Sandbox par session',100]],x:'Une commande interrompue par un crash a pu s\'exécuter en partie, et le modèle en est informé. Coût : utilisation du Sandbox et du Drive, plus un démarrage à la première utilisation.'}]; var cur=0;function ws(n){cur=n;var w=W[n];$('#wsd').innerHTML='<h4>Idéal pour : '+w.t+'</h4><div class="note">'+w.f[0]+': <b style="color:#fff">'+w.f[1]+'</b></div>'+w.m.map(function(m){return '<div class="meter"><i></i></div><div class="ml"><span>'+m[0]+'</span><span>'+m[1]+'</span></div>'}).join('')+'<p class="note">'+w.x+' Les documents JSON typés vivent aussi dans le journal, jusqu\'à 64 Ko chacun.</p>';setTimeout(function(){$$('#wsd .meter i').forEach(function(e){e.style.width=w.m[0][2]+'%'});rz()},40)} $$('.ws .box').forEach(function(b){b.onclick=function(){$$('.ws .box').forEach(function(x){x.classList. remove('sel')});b.classList.add('sel');ws(+b.dataset.w)}});ws(0); var G=[["// On Vercel: @workflow/world-vercel is picked automatically\nimport { claudeCode } from 'durable-harness/claude-code'\nexport const agent = claudeCode({ model: 'anthropic/claude-sonnet-5.5' })",'Les sessions apparaissent dans Agent Runs avec chaque appel de modèle et d\'outil. Les tours repris figurent dans la même trace.'], ["# Local dev: @workflow/world-local, no configuration\n# sessions live in .workflow-data by default\nnpm install durable-harness",'Le World local convient au développement et à un serveur unique avec un disque persistant.'], ["# Self-host: 2 variables move every agent to Postgres\nWORKFLOW_TARGET_WORLD=\"@workflow/world-postgres\"\nWORKFLOW_POSTGRES_URL=\"postgres://user:password@host:5432/database\"",'Nécessite @workflow/world-postgres 5.0.4+ et Node.js 22+. C\'est une implémentation de référence : pas d\'authentification des routes, pas de chiffrement, pas optimisée pour la montée en charge.']]; function wd(n){$('#wc').textContent=G[n][0];$('#wn').textContent=G[n][1];rz()} $$('.world').forEach(function(b){b.onclick=function(){$$('.world').forEach(function(x){x.classList.remove('sel')});b.classList.add('sel');wd(+b.dataset.g)}});wd(0); window.addEventListener('load',rz);window.addEventListener('resize',rz);setTimeout(rz,300); })(); </script> </body></html> ">Où pouvez-vous exécuter durable-harness ?
Le code de l'agent reste le même d'un World à l'autre :
- Vercel : le Vercel World, avec chaque session tracée dans Agent Runs.
- En local : des fichiers sur votre machine, sans aucune configuration.
- Auto-hébergé : le Postgres World, version 5.0.4 ou ultérieure, sur Node.js 22+. Le blog de Vercel mentionne aussi MongoDB, Redis et Turso.
2 variables d'environnement, WORKFLOW_TARGET_WORLD et WORKFLOW_POSTGRES_URL, déplacez un agent vers Postgres.
Comment durable-harness se compare-t-il ?
| Fonctionnalité | durable-harness | Temporal + OpenAI Agents SDK | Inngest AgentKit | Cloudflare Agents SDK |
|---|---|---|---|---|
| Ce que c'est | Adaptateurs de durabilité pour les harnesses de codage existantes | Wrapper d'exécution durable pour l'OpenAI Agents SDK | Framework multi-agents en TypeScript | Agents avec état sur Durable Objects |
| Encapsule Claude Code / Codex | Oui, expérimental, Sandbox uniquement | Non divulgué | Non divulgué | Non divulgué |
| Backend de durabilité | Workflow SDK World (Vercel, local, Postgres) | Serveur Temporal ou Temporal Cloud | Moteur d'orchestration Inngest | Durable Objects, Cloudflare Workflows |
| Langage | TypeScript | Python | TypeScript | TypeScript |
| Licence | Apache-2.0 | MIT (serveur) | Apache-2.0 | MIT |
| Statut | 0.0.x, expérimental | GA depuis le 23 mars 2026 | Non communiqué | Non communiqué |
Points clés
- durable-harness journalise chaque appel de modèle et d'outil, puis reprend les tours interrompus par un crash sur une nouvelle fonction.
- Les appels terminés ne sont jamais répétés, donc les tokens ne sont pas facturés deux fois.
- Pi et OpenCode s'exécutent dans votre fonction ; Claude Code et Codex nécessitent une Vercel Sandbox.
- L'auto-hébergement fonctionne avec 2 variables d'environnement et un Postgres World.
- C'est en 0.0.x et expérimental, sans benchmarks publiés.
Découvrez le dépôt GitHub, la documentation et l'annonce de Vercel. Tout le mérite revient au chercheur de ce projet. Par ailleurs, n'hésitez pas à nous suivre sur Twitter et n'oubliez pas de rejoindre notre SubReddit ML de plus de 150k et de vous abonner à notre Newsletter. Attendez ! êtes-vous sur telegram ? vous pouvez maintenant nous rejoindre également sur telegram.
L'article Vercel Open-Sources durable-harness for Crash-Proof Coding Agents est apparu en premier sur MarkTechPost.