Une exécution nanobot qui ressemble à une boucle infinie est une exécution bornée : dans le code source livré de la v0.3.5, chaque tour d’agent est plafonné par agents.defaults.maxToolIterations, dont la valeur par défaut est 200, et un mainteneur affirme publiquement qu’il ne s’agit pas d’une boucle littéralement infinie. Le véritable problème est la consommation de tokens, car une exécution qui atteint ce plafond journalise 201 tours d’assistant et renvoie un prompt de plus en plus long à chaque fois. Déterminer lequel de ces cas vous concerne est un diagnostic en quatre questions, et la correction étayée par des preuves n’est pas une valeur de configuration.
Commencez par écarter la fausse piste évidente. Le rapport amont est l’issue #5781, dont le titre mentionne dream.maxIterations, et la PR #5782 est intitulée « fix(dream): enforce configured iteration limit ». Cette clé n’existe pas dans v0.3.5, et la PR a été fermée sans fusion le 16 septembre 2026. Un tutoriel fondé dessus ne configure donc rien.
Une précision, car la requête fait remonter le mauvais tracker. Cette page concerne HKUDS/nanobot, sous licence MIT, installé comme paquet PyPI nanobot-ai — 0.3.5, Python 3.11 ou version ultérieure, téléversé le 15 septembre 2026. obot-platform/nanobot est un autre projet Go dont le README indique le mode maintenance avec les issues désactivées ; ses numéros d’issues ne constituent pas des preuves ici. Et pip install nanobot, sans suffixe, installe un paquet indépendant de navigation robotique.
Ce qui a été signalé, et pour quelle version
Dream est la tâche planifiée de consolidation de la mémoire de nanobot, exécutée par la passerelle comme une tâche cron nommée dream. Ce n’est pas une tâche d’embeddings ou d’index vectoriel : Kunavo ne fournit aucun modèle d’embeddings, et Dream n’en a pas besoin, car la mémoire persistante de nanobot est constituée de simples fichiers dans l’espace de travail et une exécution Dream correspond à du trafic de chat ordinaire.
L’issue #5781 a été ouverte le 15 septembre 2026 par BrianMwangi21 contre nanobot v0.3.0, sous Python 3.12 avec un modèle de raisonnement atteint via OpenRouter. Le symptôme : la tâche Dream alternait read_file sur memory/history.jsonl avec read_file sur un seul SKILL.md, des dizaines de fois, tandis que la trace de raisonnement redélibérait sur l’emplacement où déposer un seul petit fait. Sept exécutions consignées sur environ 26 heures allaient d’environ 25 minutes à environ 111 minutes ; celles proches de 200 appels d’outils atteignaient le plafond global, et un point de contrôle de session affichait runtime_checkpoint.iteration: 164, phase tools_completed, lors du même appel read_file.
Trois limites de portée accompagnent ces chiffres. Il s’agit des journaux de passerelle déclarés par un seul utilisateur, sur un seul modèle, en v0.3.0 — aucun mainteneur ne les a reproduits et personne ne les a retestés en v0.3.5. Le problème est marqué enhancement et priority: p2, et non bug, et il est toujours ouvert. Cette situation s’est déjà produite : le problème n° 3073, une boucle read_file presque identique sur history.jsonl datant du 12 avril 2026, a été fermé non prévu. Rien de tout cela ne permet d’affirmer que nanobot est généralement coûteux ; cela justifie de vérifier si votre propre installation se comporte ainsi.
La clé obsolète et la limite réellement active
Toute la confusion vient d’un écart de version, et la source tranche la question. Consultez les deux balises de version du 21 septembre 2026 :
| Clé de configuration | Dans v0.3.0 | Dans v0.3.5 | À faire maintenant |
|---|---|---|---|
dream.maxIterations | Valeur par défaut 15, marquée # Deprecated: no longer used | Supprimée du schéma | Ne l’écrivez pas ; elle ne configure rien |
dream.maxBatchSize | Valeur par défaut 20, même commentaire d’obsolescence | Supprimée | Ne l’écrivez pas |
dream.annotateLineAges | Valeur par défaut true, même commentaire d’obsolescence | Supprimée | Ne l’écrivez pas |
dream.enabled | Valeur par défaut true | Valeur par défaut true | Modifiable dans les paramètres d’exécution de WebUI |
dream.intervalH | Valeur par défaut 2 | Valeur par défaut 2 | Modifiez config.json ; ce n’est pas un champ de WebUI |
dream.cron | Null ; remplacement hérité | Null ; remplacement hérité | Prend le pas sur intervalH lorsqu’il est défini |
dream.modelOverride | Déclarée, commentée en attente d’implémentation | Implémentée | Noms de préréglages uniquement, jamais un identifiant de modèle brut |
agents.defaults.maxToolIterations | 200 | 200 | La seule limite, et elle s’applique à l’ensemble du processus |
Deux éléments rendent ce tableau facile à interpréter incorrectement. Le 200 est vrai deux fois : c’est la valeur de la configuration du rapporteur et la valeur par défaut fournie à la ligne 129 de nanobot/config/schema.py, identique dans la balise v0.3.5 et sur main, de sorte qu’un lecteur qui ne l’a jamais défini a tout de même 200. Et ce nombre n’est pas documenté : une récupération de la référence de configuration 0.3.5 de nanobot lui-même, le 21 septembre 2026, a renvoyé 488,608 octets de HTML avec zéro occurrence de maxToolIterations, et le docs/configuration.md du dépôt à cette balise n’en contient pas non plus. Ce nombre est vérifiable dans le code source et dans un commentaire de mainteneur, pas dans une page de documentation — méfiez-vous donc de tout tutoriel citant un autre chiffre.
modelOverride est le seul remède soumis à une condition de version. En v0.3.0, le champ existait avec le commentaire en attente d’implémentation et aucun code de résolution ; la PR #5107, fusionnée le 27 juillet 2026, l’a implémenté pour v0.3.5. La page mémoire officielle 0.3.5 indique qu’il sélectionne une entrée nommée dans model_presets pour Dream, accepte uniquement les noms de préréglages et que les identifiants de modèle bruts ne sont pas pris en charge. Cette page documente trois clés Dream et ne mentionne nulle part de limite d’itérations. Voici la surface complète actuelle, avec le bloc providers dont elle dépend, couvert sur la page de configuration de nanobot :
{
"modelPresets": {
"dream-cheap": {
"provider": "kunavo",
"model": "claude-haiku-4-5",
"maxTokens": 8192
}
},
"agents": {
"defaults": {
"maxToolIterations": 200,
"dream": {
"enabled": true,
"intervalH": 2,
"modelOverride": "dream-cheap"
}
}
}
}Notez ce qui ne figure pas dans ce fichier : aucun moyen de limiter Dream seul. AgentLoop est construit une fois avec max_iterations à partir des valeurs par défaut du processus, et le chemin cron de Dream comme le chemin manuel /dream appellent process_direct(...) sans argument d’itération, tout comme les sous-agents. Réduire la limite la réduit donc simultanément pour le chat, Dream, le heartbeat et les sous-agents.
Diagnostiquez dans cet ordre
Répondez à ces quatre questions avant de modifier quoi que ce soit, car trois sont gratuites et la quatrième indique si les trois premières sont pertinentes. Les chaînes des journaux sont le texte littéral de v0.3.5 ; les accolades représentent des valeurs d’exécution.
| Question | Où regarder | Ce que signifie la réponse |
|---|---|---|
| 1. L’exécution s’est-elle arrêtée d’elle-même ou a-t-elle atteint la limite ? | Journal de passerelle : Max iterations (200) reached, depuis agent/loop.py | Présent signifie une exécution limitée, avec environ 201 tours d’assistant. Absent signifie que l’exécution s’est terminée avant la limite — soit le modèle a convergé, soit l’exécution a échoué et a journalisé Dream cron job failed |
| 2. Le curseur Dream a-t-il avancé ? | Trois lignes distinctes dans cli/gateway_runtime.py : Dream cron job completed, cursor advanced to … ; … completed with no memory changes; cursor advanced to … ; Dream cron job did not complete (…); cursor remains at … | La troisième ligne correspond au mécanisme de sécurité de v0.3.5 : une exécution incomplète laisse le lot être retenté. Cela signifie également que le même lot revient au tick suivant |
| 3. Le même outil, avec les mêmes arguments, se répète-t-il ? | Les lignes Tool call: entre ces deux marqueurs | Un outil et des arguments identiques répétés indéfiniment indiquent une non-convergence du modèle, ce que l’amont a conclu. La seule protection contre les répétitions de v0.3.5 correspond à web_fetch et web_search ; une répétition de read_file n’est pas détectée |
| 4. Quel a été le coût ? | llm_usage.sqlite3 dans le répertoire de configuration, agrégé par date et par source ; côté passerelle, par clé et par jour dans usage | Dream est marqué dream. Le heartbeat est marqué cron, donc filtrer sur "heartbeat" ne renvoie rien |
Vous n’avez pas besoin d’attendre deux heures le tick cron pour reproduire le problème. La commande /dream exécute le même travail à la demande et signale la même distinction dans le chat : Dream completed in Ns. ou Dream completed in Ns; no memory changes. en cas de succès, contre Dream did not complete after Ns (reason); memory cursor was not advanced. lorsqu’il ne se termine pas. Une autre modification de v0.3.5 compte lorsque vous cherchez des preuves : les fichiers JSONL de session ont été déplacés sous l’arborescence sessions/<workspace-id>/ du répertoire de configuration, de sorte que les chemins v0.3.0 cités dans le fil du problème ne correspondent pas à l’emplacement de votre point de contrôle.
Cinq leviers et ce que chacun permet réellement de justifier
| Levier | Éléments probants | À privilégier lorsque | Ce que cela vous coûte |
|---|---|---|---|
| Mettre à niveau v0.3.0 vers v0.3.5 | Le commit 4e2640f, présent en v0.3.5 mais pas en v0.3.0, ne fait avancer le curseur que lorsque la raison d’arrêt est completed | Votre symptôme est une mémoire ignorée plutôt qu’une dépense | Cela ne raccourcit pas une boucle non convergente, et personne n’a retesté #5781 sur 0.3.5 |
| Changer le modèle de Dream | Le seul remède avec une comparaison avant/après : dans l’audit du rapporteur, un lot auquel un modèle avait consacré 91 minutes sans terminer a été achevé par l’exécution suivante avec un autre modèle en environ une minute, avec 6 appels d’outils, à invite, historique et outils identiques | La qualité du chat doit rester sur votre modèle coûteux | Nécessite v0.3.5 et un préréglage défini ; en v0.3.0, le champ ne résout rien |
Réduire maxToolIterations | Modifiable dans les paramètres d’exécution de WebUI, minimum 1, par webui/settings_runtime.py | Vous voulez une limite maximale dans le pire cas pendant le diagnostic | Un seul réglage pour le chat, Dream, le heartbeat et les sous-agents ; une exécution limitée ne fait pas avancer le curseur, donc le même lot est retenté au tick suivant |
| Ralentir ou désactiver Dream | intervalH, ou dream.enabled dans WebUI ; la PR fusionnée #5407 retire le travail persistant lorsqu’il est désactivé | La consolidation ne vaut pas son coût pour votre charge de travail | Vous perdez la consolidation de la mémoire, qui est précisément la fonctionnalité |
| Router afin que le renvoi coûte moins cher | En v0.3.5, exactement deux spécifications de fournisseur définissent supports_prompt_caching : anthropic et openrouter ; le champ vaut false par défaut | Vous acceptez que les longues exécutions se produisent et voulez qu’elles coûtent moins cher | Cela dépend du chemin de protocole que vous avez configuré et de votre vérification de l’utilisation renvoyée |
Les deux identifiants de modèle du rapporteur étaient deepseek/deepseek-v4-flash-0731 et openai/gpt-5.6-luna tels qu’il les a écrits via OpenRouter ; ces identifiants et leurs prix n’ont pas été vérifiés ici. Considérez donc cette comparaison comme un élément montrant que le modèle détermine la convergence, et non comme une recommandation de l’un ou l’autre. L’amont est parvenu à la même conclusion : en annonçant la clôture de la PR #5782 sur le problème #5781, chengyongru a écrit qu’une limite fixe d’itérations de Dream ne traite pas le problème sous-jacent de convergence dépendante du modèle et peut entraîner le retentissement répété d’exécutions autrement viables.
Coût d’une exécution limitée : calcul illustratif
Il s’agit de calculs illustratifs de jetons, pas du coût mesuré d’une tâche ni d’un plafond de facturation. nanobot ne publie aucun nombre de jetons par exécution pour Dream ; chaque donnée d’entrée est donc une hypothèse à remplacer par vos propres mesures. Supposons une exécution qui atteint la limite à 201 tours d’assistant — le nombre de l’audit du rapporteur — chacun renvoyant une invite maintenue à 25,000 jetons, ce qui décrit son propre espace de travail, et non une valeur publiée. Cela représente 5.03M jetons d’entrée. Les jetons de sortie sont exclus, et une véritable invite s’allonge avec chaque résultat d’outil ; ce calcul sous-estime donc une exécution réelle de deux façons à la fois. Les tarifs sont les prix actuels du catalogue Kunavo.
| Modèle | Entrée par 1M | Lecture du cache par 1M | Une exécution limitée, sans cache | Même exécution, renvois lus depuis le cache |
|---|---|---|---|---|
| Claude Haiku 4.5 | $0.70 | $0.07 | $3.52 | $0.37 |
| GPT-5.6 Terra | $0.70 | $0.07 | $3.52 | $0.37 |
| Claude Sonnet 5 | $1.40 | $0.14 | $7.04 | $0.74 |
La dernière colonne suppose un cas optimal que rien ici n’a testé : le premier envoi au tarif d’entrée ordinaire et tous les 200 renvois servis comme lectures du cache. Une écriture dans le cache est facturée à son propre tarif, qui pour certains modèles est supérieur au tarif d’entrée ordinaire ; une entrée de cache expire ; et une invite qui s’allonge est réécrite plutôt que relue. Considérez donc l’écart entre les deux dernières colonnes comme l’ampleur du gain possible, et non comme un devis. Le seul point est que, lors d’une longue exécution répétitive, c’est le renvoi, et non le modèle, qui représente la dépense.
La question de savoir si les marqueurs de cache atteignent réellement le réseau est déterminée par votre configuration nanobot, et cela est visible dans le code source fourni. Une clé de fournisseur inventée sous providers est traitée comme un fournisseur compatible OpenAI ordinaire et laisse supports_prompt_caching à sa valeur par défaut false ; le client de nanobot n’envoie donc aucun marqueur cache_control sur ce chemin, même pour un identifiant de modèle de type Claude. Conserver le préréglage du fournisseur intégré anthropic et remplacer providers.anthropic.apiBase les maintient. Cela décrit ce qu’envoie le client nanobot, et non ce que fait un quelconque point de terminaison de son côté, et Kunavo n’a pas testé nanobot en conditions d’exécution. Consultez l’utilisation renvoyée par un appel réel avant de budgétiser un travail récurrent comme mis en cache — la documentation du cache montre à quoi ressemble un cache fonctionnel et la référence de l’URL de base fournit les deux conventions de point de terminaison.
Le montant du catalogue Kunavo constitue un plancher de facturation plutôt qu’un plafond : lorsque l’amont communique son coût, la facture correspond au montant le plus élevé entre le coût du catalogue et le coût amont multiplié par la majoration applicable. Le rechargement minimal est de $10 en crédit prépayé — un minimum de financement, et non des frais de tâche ni un abonnement. Consultez les détails de facturation et l’optimisation des coûts pour connaître la méthode de mesure qui remplace les hypothèses ci-dessus.
Vérifier la correction sur une exécution
Modifiez une seule chose, puis exécutez /dream une fois au lieu d’attendre la planification, et vérifiez les trois marqueurs dans l’ordre : aucun avertissement Max iterations (200) reached, une ligne cursor advanced to … plutôt que cursor remains at …, et un nombre plausible d’appels d’outils entre les deux. Lisez ensuite le montant enregistré par votre propre compte pour cette période, par clé et par jour, dans usage ; le stockage de nanobot compte les jetons, pas l’argent. Si vous configurez le point de terminaison pour la première fois, le démarrage rapide et la page de configuration de nanobot couvrent les deux chemins de protocole, et la création d’un compte Kunavo est l’étape préalable au financement d’une clé. Vous comparez plutôt les durées d’exécution ? nanobot contre OpenClaw met les deux cadences d’arrière-plan côte à côte, et le répertoire des API d’agents indexe les clients par protocole réseau.
Questions fréquentes
nanobot est-il réellement bloqué dans une boucle infinie ?
Non, et un mainteneur l’affirme publiquement. En commentant l’issue #5324 le 10 août 2026, chengyongru a écrit que l’agent runner de nanobot est limité par agents.defaults.maxToolIterations, à 200 par défaut ; il ne s’agit donc pas d’une boucle littéralement infinie — tout en ajoutant qu’une longue boucle bornée peut quand même entraîner une très forte consommation cumulée de tokens, de sorte que l’impact pratique est réel. Le code source livré avec la v0.3.5 le confirme : max_tool_iterations vaut 200 par défaut dans nanobot/config/schema.py, et agent/loop.py journalise l’avertissement « Max iterations (200) reached » lorsqu’une exécution l’atteint. Ce que les gens appellent une boucle infinie est une exécution qui atteint ce plafond, ce qui signifiait dans les journaux d’un rapporteur 201 tours d’assistant relisant les deux mêmes fichiers.
Pourquoi définir dream.maxIterations ne fait-il rien ?
Parce que ce champ n’existe plus. Dans nanobot v0.3.0, la configuration Dream contenait max_iterations, max_batch_size et annotate_line_ages, chacun marqué dans le code source par le commentaire « Deprecated: no longer used » ; dans le tag v0.3.5, les trois ont disparu, et la classe ne définit plus que enabled, intervalH, cron et modelOverride. Le mainteneur chengyongru a déclaré le 15 septembre 2026 que dream.maxIterations avait été intentionnellement marqué comme obsolète et inutilisé lorsque Dream a été déplacé vers la boucle d’agent normale, puis supprimé de main. La pull request qui aurait dû le restaurer, #5782, a été fermée sans fusion le lendemain. Écrire cette clé dans une configuration v0.3.5 revient à écrire une clé que le schéma ne définit pas.
Comment limiter les tokens uniquement pour la tâche Dream de nanobot ?
Pas sous la forme d’un budget cumulé, dans nanobot v0.3.5. Rien dans le code livré ne totalise les tokens d’une exécution pour l’arrêter lorsqu’un seuil est atteint, et agents.defaults.maxToolIterations est le seul plafond d’itérations — il s’applique à l’ensemble du processus, simultanément aux tours de chat ordinaires, à Dream, au heartbeat et aux sous-agents. Deux éléments peuvent être limités à Dream seul via agents.defaults.dream.modelOverride : le modèle et les limites par appel de ce préréglage, car ModelPresetConfig contient maxTokens et contextWindowTokens, et dream_runtime() résout le préréglage nommé dans le runtime sous lequel s’exécute Dream. Ces limites s’appliquent à chaque appel individuel, pas au total de l’exécution ; 200 appels avec un maxTokens faible restent donc 200 appels. La planification peut également être limitée via intervalH. Un budget cumulé est ce que l’amont a pour l’instant refusé d’ajouter : dans l’issue #5781, le 16 septembre 2026, le jour où il a fermé la PR #5782, chengyongru a écrit qu’un budget de tokens ou de ressources pour les tâches en arrière-plan nécessitait une conception plus détaillée couvrant l’unité et la portée du budget, la sémantique d’arrêt, le comportement des nouvelles tentatives et du curseur, l’observabilité et l’interaction avec différents modèles, et qu’il ne prévoyait pas de faire avancer ce sujet pour le moment.
Comment arrêter une exécution Dream de nanobot déjà en cours ?
Pas avec /stop, à la lecture du code source de la v0.3.5. /stop est documenté comme annulant le tour actif de l’agent pour ce chat et est implémenté comme une annulation des tâches enregistrées sous la clé de session de ce chat, tandis qu’une exécution Dream est créée sous sa propre clé éphémère de la forme dream:YYYYMMDD-HHMMSS. Il s’agit d’une lecture du chemin de code, pas d’un résultat testé — /stop n’a pas été exécuté ici contre une exécution Dream en direct. Les leviers documentés sont /restart, la désactivation de agents.defaults.dream.enabled ou l’arrêt du processus de passerelle. La PR fusionnée #5407 dans la v0.3.5 garantit que la désactivation retire effectivement la tâche système persistante au lieu de la laisser planifiée.
Comment voir combien de tokens les tâches en arrière-plan de nanobot ont utilisées ?
Lisez le magasin d’utilisation local, pas une commande slash. nanobot v0.3.5 enregistre chaque appel de modèle avec un champ source typé comme user, api, cron, dream ou system, les écrit dans llm_usage.sqlite3 dans le répertoire de configuration — ~/.nanobot par défaut — avec des colonnes de tokens d’entrée, de sortie, de lecture du cache et d’écriture du cache, et les agrège par date et par source. Deux pièges. Il n’existe aucune commande /insights ou /cost : les deux pull requests qui en proposaient une, #3735 et #3921, ont été fermées sans fusion, et la liste intégrée de la v0.3.5 est /new, /compact, /stop, /restart, /status, /model, /history, /goal, /trigger, /dream, /dream-log, /dream-restore, /dream-prompt, /evaluator-prompt, /skill, /help et /pairing. Et les libellés sont asymétriques : les dépenses de Dream sont marquées dream, mais celles du heartbeat sont marquées cron, car la clé de session du heartbeat est littéralement « heartbeat ». Il s’agit de décomptes de tokens, pas d’argent ; rapprochez-les donc du propre registre de votre fournisseur.
La mise à niveau vers nanobot v0.3.5 corrige-t-elle la boucle ?
Personne n’a dit que c’était le cas, et cette page ne le dira pas non plus. L’issue #5781 a été ouverte contre la v0.3.0 et est toujours ouverte, avec les étiquettes enhancement et priority p2 ; le rapporteur n’a pas retesté après la mise à niveau et aucun mainteneur ne l’a reproduite. Ce que corrige la v0.3.5 est plus limité, mais vaut la peine : le commit 4e2640f empêche une exécution incomplète de faire avancer le curseur Dream et de sauter silencieusement l’historique, la PR fusionnée #5442 fait signaler pourquoi une exécution incomplète ne s’est pas terminée, et la PR fusionnée #5325 fait retourner à edit_file « Error: new_text must be different from old_text. » au lieu de signaler une réussite lors d’une modification sans effet. Ce dernier changement traite la boucle lecture-puis-modification de l’issue #5324, pas la boucle en lecture seule de #5781. Aucun garde-fou général contre les appels d’outils identiques répétés n’est livré non plus dans la v0.3.5. Le seul garde-fou de répétition du code livré, repeated_external_lookup_error dans nanobot/utils/runtime.py, bloque un web_fetch ou web_search identique après deux tentatives et ne correspond à aucun autre nom d’outil ; un read_file répété continue donc jusqu’au plafond d’itérations. Les cinq pull requests proposant un garde-fou général — #3077, #4522, #5344, #3701 et #4154 — ne sont toutes pas fusionnées.
Vérifié le 21 septembre 2026 : l’API GitHub pour HKUDS/nanobot, ainsi que pour les problèmes #5781, #5324 et #3073 et les pull requests #5782, #5107, #5325, #5442, #5407, #3077, #4522, #5344, #3701, #4154, #3735, #3921 et #4622 ; l’enregistrement PyPI de nanobot-ai 0.3.5 ; la documentation mémoire et configuration 0.3.5 de nanobot ; et le code source fourni à la balise v0.3.5 pour chaque valeur par défaut, chaîne de journal et chemin de code cités ci-dessus, comparés à v0.3.0 lorsque les deux diffèrent. Kunavo n’a ni installé ni exécuté nanobot, aucun comportement décrit ici n’a été testé par Kunavo, et la boucle du problème #5781 n’a été retestée par personne sur v0.3.5. Les tarifs des jetons proviennent du catalogue actuel et chaque montant en dollars correspond à un calcul illustratif fondé sur les hypothèses indiquées.