Tout est un plugin : la conception Cordis derrière DeepSeek Harness
Comment Harness de DeepSeek construit un environnement d’exécution pour des agents à partir de plugins, en s’appuyant sur les concepts de Cordis tels que les effets réversibles, les dépendances réactives et un cycle d’agent remplaçable.
Sources: DeepSeek Quietly Built a Different Kind of AI Agent Framework
La recette habituelle et ses limites
La plupart des frameworks d’agents partent des mêmes éléments : un modèle de langage, quelques outils, une conversation enregistrée et une boucle qui les englobe tous. Le schéma ci-dessous montre leur structure.
User asks something
↓
LLM thinks
↓
LLM calls a tool
↓
Tool returns data
↓
LLM continues
↓
Final answer
Par définition, cela compte déjà comme un agent. Les problèmes commencent lorsque le système se développe. Les exigences s’accumulent : plusieurs fournisseurs de modèles, des outils de navigateur, l’exécution en shell, des serveurs MCP, des flux d’approbation, de la mémoire, de la télémétrie, de l’authentification, des interfaces utilisateur différentes, des agents en arrière-plan, des prompts personnalisés, des permissions et le rechargement en temps réel. Bientôt, chaque composant dépend de tous les autres. Changer le fournisseur de modèles a des répercussions sur le cycle des agents, supprimer un plugin laisse des écouteurs d’événements en place, et recharger un outil peut endommager les agents qui sont en cours d’exécution à ce moment-là. Les plugins dépendent également de services qui peuvent apparaître ou disparaître au cours de leur exécution.
DeepSeek Harness aborde ce problème d’une manière très différente. Son idée centrale semble presque trop simple : tout est un plugin, y compris les éléments que la plupart des frameworks considèrent comme essentiels.
En dessous de tout cela se trouve un cadre appelé Cordis, issu d’un article de recherche intitulé « A Programming Paradigm for Spatiotemporal Composability ». Cet article est intéressant, car le projet n’est pas simplement un autre système pour organiser les modules JavaScript. Il aborde une question d’ingénierie plus profonde : comment les composants peuvent-ils s’associer, se séparer, se remplacer mutuellement et modifier leurs dépendances au sein d’un programme en cours d’exécution, sans que le système ne sombre dans le chaos ?
Qu’est-ce qu’un harness
DeepSeek Harness, abrégé en dsh, est un harness d’agent open source publié par DeepSeek. Un harness constitue l’infrastructure qui entoure le modèle. Que ce soit un modèle DeepSeek, Claude, GPT, Gemini ou un modèle local, le harness s’occupe de tout ce qui l’entoure.
┌───────────────┐
│ LLM │
└───────┬───────┘
│
┌──────────▼──────────┐
│ Agent Harness │
│ │
│ Prompt construction │
│ Tool execution │
│ Session history │
│ Permissions │
│ Streaming │
│ Agent lifecycle │
│ Model adapters │
└─────────────────────┘
Cette différence est très importante. L’attention se porte généralement sur l’intelligence du modèle, mais la création d’outils tels que Claude Code, Codex, Cursor ou un agent autonome fonctionnant sur le long terme montre que les aspects liés au temps d’exécution sont tout aussi cruciaux. DeepSeek Harness gère ce temps d’exécution à l’aide d’un ensemble de plugins composés dynamiquement, et non comme une application monolithique ; cette décision provient directement de Cordis.
Un problème de dépendances
Imaginez créer un agent de codage à partir de trois plugins.
GitPlugin
TerminalPlugin
CodingAgent
L’agent de codage a besoin de Git et du Terminal. Au démarrage, la méthode la plus simple consiste à les charger dans l’ordre.
load Git
load Terminal
load CodingAgent
C’est simple, et la plupart des frameworks d’injection de dépendances gèrent cela sans problème. La question plus difficile est de savoir ce qui se passe si le plugin Git disparaît pendant que le programme s’exécute. Il a pu être mis à jour, sa configuration a pu changer, un utilisateur l’a peut-être désactivé, ou une autre implémentation en a pris le relais. Le temps de exécution doit alors arrêter tout ce qui dépend de ce service.
CodingAgent
↓ -------->depends on
GitPlugin
Si Git disparaît, l’agent de codage ne devrait pas continuer à agir comme s’il existait. Et si un autre fournisseur de Git apparaît plus tard, l’agent devrait peut-être reprendre son fonctionnement. Deux problèmes distincts se cachent ici, que l’article de Cordis nomme composabilité temporelle et composabilité spatiale.
Composabilité temporelle : quitter proprement
Supposons qu’un plugin s’charge et enregistre un écouteur d’événements.
events.on("message", handler)
Plus tard, le plugin est déchargé, et la question est de savoir ce qu’il advient du gestionnaire d’événements. Si rien ne le supprime, le plugin disparaît nommément tandis qu’une partie de lui continue de fonctionner. Ajoutons à cela que le plugin a pu démarrer des compteurs temporels, ouvrir des connexions de base de données, enregistrer des commandes, modifier l’état partagé, ajouter des outils, attacher des gestionnaires d’événements et lancer des processus en arrière-plan. Le déchargement signifie inverser chacune de ces actions.
C’est ce qu’on appelle la composabilité temporelle : un composant qui rejoint le système doit permettre à ce dernier d’annuler complètement ce que le composant a fait. Cordis considère de tels changements comme des effets réversibles.
Effets sans jargon
Un effet n’est rien d’autre que quelque chose que votre code fait pour modifier le monde extérieur ou l’état en temps de exécution. Prenons par exemple l’enregistrement d’un outil.
toolRegistry.add("search", searchTool)
Au préalable de l’appel, le registre a cet aspect.
tools = {}
Par la suite, il contient le nouvel outil.
tools = {
search: searchTool
}
L’opération de décalage vers l’avant est une addition.
add(searchTool)
Cordis souhaite cependant plus que cela. Il veut que l’opération possède sa propre inverse. Conceptuellement, un effet associe une action à son annulation.
Effect:
do: add search tool
undo: remove search tool
Sans notation mathématique, l’idée se formule comme suit.
state --effect--> new state
new state --inverse--> original state
Cette inverse est l’élément clé, et Cordis qualifie l’ensemble de ce mécanisme d’effet réversible.
Chaque modification entraîne une opération de nettoyage
Cordis met cela en œuvre à l’aide d’un schéma similaire au suivant, où une fonction applique une modification et renvoie la fonction qui la annule.
ctx.effect(() => {
registerSomething()
return () => {
unregisterSomething()
}
})
Le temps de exécution applique l’effet, enregistre la fonction de nettoyage et l’appelle plus tard lorsque le plugin disparaît.
apply effect
↓
store cleanup function
↓
plugin runs
↓
plugin unloads
↓
run cleanup
Lorsqu’un plugin contient plusieurs effets, ceux-ci sont appliqués dans un seul ordre.
effect A
effect B
effect C
Les nettoyages s’effectuent ensuite dans le sens inverse, comme une pile qui se déroule.
undo C
undo B
undo A
Pourquoi inverser ? Prenons un plugin qui commence comme ceci.
1. open database
2. start service using database
3. start agent using service
Mettre fin au fonctionnement dans le même ordre serait dangereux, car la base de données se ferme alors que d’autres processus l’utilisent encore.
close database
stop service
stop agent
L’ordre sûr est l’inverse.
stop agent
stop service
close database
C’est le dernier entré qui sort en premier.
Last In
First Out
Cordis intègre ce comportement dans la gestion du cycle de vie des composants.
Le nettoyage inverse seul ne suffit pas
C’est là que l’article devient plus intéressant. Supposons que le plugin A modifie une valeur partagée, puis que le plugin B modifie cette même valeur, et qu’ensuite A tente de restaurer sa version initiale. Cela peut effacer les modifications apportées par B.
Initial: x = 10
Plugin A: x = 20
Plugin B: x = 30
Plugin A unloads: restore x = 10
B reste actif, mais son état a disparu. Ainsi, les effets réversibles ne sont sûrs que sous certaines hypothèses d’indépendance, que l’article formalise sous le nom d’indépendance des effets. De manière informelle, deux effets sont indépendants lorsque l’application ou la réversibilité de l’un n’annule pas le sens de l’autre. Le fait d’utiliser des outils différents suffit généralement à remplir cette condition, puisqu’ils agissent sur des emplacements distincts.
Plugin A registers tool "search"
Plugin B registers tool "calculator"
Remplacer la même configuration ne suffit probablement pas.
Plugin A replaces configuration X
Plugin B also replaces configuration X
Cette distinction revient plus tard lorsque le système de contexte de Cordis est abordé.
Composabilité spatiale : savoir ce dont a besoin un plugin
La composabilité temporelle se demande si un composant peut être supprimé en toute sécurité. La composabilité spatiale se demande si le système comprend sur quoi dépend le composant. Pensez à ce plugin.
CodingAgent
Cela nécessite trois choses.
LLM
ToolRegistry
SessionStore
Le code traditionnel pourrait faire appel à des variables globales.
global.llm
global.tools
global.sessions
Dans ce cas, l’architecture ne dispose d’aucune connaissance réelle des dépendances. Cordis les rend explicites. Conceptuellement, un plugin déclare ce qu’il injecte.
inject: [
"llm",
"tools",
"sessions"
]
Désormais, l’environnement de exécution peut voir les exigences du plugin sous forme d’arbre.
CodingAgent
├── requires LLM
├── requires ToolRegistry
└── requires SessionStore
Cela lui permet de réagir lorsque ces services changent.
Dépendances réactives
Ce pourrait être la meilleure idée dans Cordis. Supposons qu’un composant ait besoin d’un LLM.
LLM
D’abord, il n’y en a pas.
LLM unavailable
Ainsi, le composant ne peut pas devenir actif. Plus tard, un plugin fournisseur s’charge et en fournit un.
OpenAI Plugin loads
↓
provides LLM
↓
dependency becomes satisfied
↓
component activates
Si ce fournisseur est supprimé, cette fonctionnalité disparaît avec lui.
OpenAI Plugin unloads
↓
LLM disappears
↓
dependency becomes unsatisfied
↓
dependent component unloads
Lorsqu’un autre fournisseur est chargé, la capacité revient.
DeepSeek Plugin
↓
provides LLM
↓
component becomes active again
Les dépendances ne sont donc pas vérifiées une seule fois au démarrage. Elles sont réactives, c’est pourquoi le texte parle de coeffets réactifs.
Effets versus coeffets
La terminologie peut sembler complexe, mais la distinction est simple. Un effet décrit ce que composant modifie, en s’écoulant depuis le plugin vers l’environnement.
Plugin → Environment
Les exemples incluent ces actions.
register tool
open database
subscribe to event
start timer
Un coeffet décrit ce que le composant exige de son environnement, en sens inverse.
Environment → Plugin
Des exemples typiques sont ces besoins.
need an LLM
need a logger
need session storage
need a tool registry
En combinant les deux, les exigences s’écoulent dans une seule direction.
requirements
Environment ───────────────────> Plugin
pendant que les effets reviennent en arrière.
Environment <─────────────────── Plugin
effects
C’est l’idée de base derrière Cordis. Un composant indique ce dont il a besoin, ce qu’il fournit et quels effets réversibles il produit.
I NEED these services.
I PROVIDE these services.
I PERFORM these reversible effects.
Le temps de exécution coordonne ensuite le cycle de vie.
D’où vient le terme « spatiotemporel »
Le titre de l’article prend maintenant tout son sens. La composabilité spatiale concerne les relations entre les composants.
A depends on B
B provides X
C depends on X
La composabilité temporelle concerne la manière dont les composants évoluent dans le temps.
load
activate
replace
deactivate
unload
reload
Tous ensemble, ils forment toute la philosophie.
Spatiotemporal composability = dependency-aware composition + reversible lifecycle changes
L’objet de contexte
DeepSeek Harness ne passe pas manuellement des dizaines de services par l’intermédiaire de constructeurs. Les plugins interagissent grâce à un contexte partagé, généralement nommé de cette manière.
ctx
Conceptionnellement, il expose les principaux services.
ctx
├── llm
├── tools
├── sessions
├── agents
├── agentLoop
├── logger
└── ...
Cependant, ctx n’est pas simplement un objet JavaScript rempli de variables globales. Grâce à lui, les plugins trouvent leurs dépendances, proposent des services, enregistrent des effets, écoutent les événements, isolent les ressources, interceptent l’accès aux services et coordonnent leurs cycles de vie ; c’est pourquoi Cordis présente ce contexte comme une abstraction unificatrice.
De la théorie au projet concret
DeepSeek Harness expose ses principaux sous-systèmes via ce contexte partagé.
ctx.sessions
ctx.systemPrompt
ctx.tools
ctx.agents
ctx.agentLoop
ctx.llm
Remarquez quelque chose d’inhabituel : même la boucle des agents n’est qu’un service.
ctx.agentLoop
Ainsi, la boucle peut être remplacée, et il ne s’agit pas de code sacré du framework. Cela mène au principe fondamental du design : il n’existe pas de noyau privilégié que chaque fonctionnalité doive corriger.
Pourquoi la boucle en tant que plugin est importante
La plupart des frameworks d’agents contiennent une boucle centrale similaire à celle-ci.
while not finished:
response = model(history)
if response.tool_call:
result = execute_tool(response.tool_call)
history.append(result)
else:
return response
Ensuite, chaque fonctionnalité avancée commence à la modifier. Validation avant l’exécution des commandes ? Il faut corriger le boucle. Suivi en temps réel ? Il faut corriger le boucle. Tentatives de récupération, délégation à plusieurs agents, un autre moteur d’inférence ? Il faut corriger le boucle. Finalement, cela se transforme en un hub auquel sont rattachés des outils, des modèles et de la mémoire.
Agent Loop
/ | \
/ | \
tools models memory
/ | \
approvals retries tracing
La boucle devient l’objet principal de l’application. DeepSeek Harness évite ce problème en permettant à la boucle de faire partie de la même architecture de plugins que tous les autres éléments.
La structure d’une interaction avec un agent
Au niveau technique, une interaction passe par des étapes plus ou moins similaires à celles-ci.
turn/start
↓
claim incoming user input
↓
assemble prompt + available tools
↓
agent/pre-step
↓
step/start
↓
record user message
↓
reconstruct conversation history
↓
send model request
↓
stream assistant response
↓
tool call?
│
├── yes → execute tool
│ record result
│ continue another step
│
└── no → finish
↓
step/end
↓
turn/end
Ce qui est important, c’est que chaque étape met en évidence les événements et les points de connexion des services, offrant ainsi aux plugins la possibilité de participer sans avoir à réécrire l’ensemble du système en temps de exécution.
Événements en cascade en tant que middleware d’agent
Cordis prend en charge plusieurs modes pour l’exécution des événements. Un mode pratique, celui en cascade, fonctionne comme un middleware. Imaginez une requête qui traverse une chaîne de traitement.
request
↓
Plugin A
↓
Plugin B
↓
Plugin C
↓
LLM
Le plugin B peut envelopper l’exécution dans un gestionnaire qui reçoit l’étape suivante.
async function handler(next) {
console.log("before")
const result = await next()
console.log("after")
return result
}
L’exécution est alors imbriquée, chaque plugin s’exécutant avant et après l’appel interne.
Plugin A before
Plugin B before
Plugin C before
LLM
Plugin C after
Plugin B after
Plugin A after
Cela inclut les permissions, la réécriture des requêtes, la télémétrie, le cache, l’acheminement des modèles, les vérifications de sécurité et l’injection de contexte ; rien de tout cela n’a besoin d’être intégré directement dans l’adaptateur du modèle.
Le modèle se trouve derrière une séparation de capacités
Cet outil abstrait également le LLM, de sorte qu’un agent n’a pas besoin de comprendre chaque SDK des fournisseurs. Il communique avec un seul service.
Agent
↓
ctx.llm
↓
Model Adapter
↓
Provider API
Un fournisseur pourrait l’implémenter pour l’API DeepSeek.
DeepSeek API
Un autre traite d’OpenAI.
OpenAI
Un troisième concerne Anthropic.
Anthropic
Le reste de l’architecture de l’agent prend en charge la même capacité de LLM. La documentation Harness qualifie parfois cela d’« interface de capacité », composée d’une définition de service et d’un fournisseur de service.
Service Definition
↓
Service Provider
↓
Consumer
Pour le LLM, cette chaîne va de l’interface à un fournisseur spécifique.
LLM interface
↓
OpenAI provider
↓
Agent loop
Ainsi, ajouter un fournisseur de modèle ne signifie pas nécessairement réécrire le moteur d’exécution. Vous fournissez simplement une autre implémentation de la même fonctionnalité.
Sessions basées sur des événements
Les conversations ne sont pas conservées par le système sous forme d’un grand tableau mutable qui constitue la source de vérité. Les sessions sont plutôt stockées sous forme d’un flux d’événements à ajout uniquement, de cette manière.
turn/start
user/message
assistant/chunk
assistant/chunk
assistant/message
tool/call
tool/result
assistant/message
turn/end
L’historique du modèle peut être reconstitué à partir de ces événements, ce qui fait du journal de session la source de vérité.
Session log = source of truth
Cela remplace l’alternative courante.
Mutable chat history = source of truth
Pourquoi les agents bénéficient de l’event sourcing
Les agents sont devenus suffisamment complexes pour que le stockage d’une simple liste de messages ne soit souvent pas suffisant.
[
{"role": "user", "content": "..."},
{"role": "assistant", "content": "..."}
]
Le débogage peut nécessiter de savoir quel outil a été exposé et lequel a été invoqué, quel modèle a généré une réponse, quelle instruction système a été appliquée, où une requête a commencé, où une étape de raisonnement s’est terminée, quelle configuration était active, si une appel a été réessayé et quelles sorties en flux sont revenues. Le système d’encadrement enregistre des événements de session structurés afin que tout ce que le modèle pouvait voir puisse être reconstitué. L’un des principes de sa conception stipule que tout ce qui est visible pour le modèle doit pouvoir être récupéré à partir des journaux. Au lieu de spéculer sur les raisons pour lesquelles l’agent a agi de la sorte, on peut reconstituer la requête qui se cache derrière son comportement.
Fibres et état du cycle de vie
Cordis représente les composants en exécution par l’intermédiaire d’une Fibre. Cela n’a rien à voir avec les fibres du système d’exploitation. Une Fibre Cordis est une instance de composant en temps de exécution, et elle passe par des états tels que ceux-ci.
PENDING
↓
LOADING
↓
ACTIVE
↓
UNLOADING
↓
DISPOSED
Il existe également des états d’échec. Plus important encore, une fibre enregistre plusieurs informations la concernant.
what dependencies I need
what providers currently satisfy them
what effects I have created
what cleanup functions belong to me
Lorsque les dépendances changent, la fibre peut mettre à jour son cycle de vie.
Pourquoi l’identité du fournisseur est importante
Supposons qu’un composant dépende d’un service de base de données.
database
Pour l’instant, il est fourni par un seul fournisseur.
DatabaseProvider A
Lors d’un rechargement en temps réel, il est remplacé par un autre.
DatabaseProvider B
Même lorsque les deux retournent des objets JavaScript équivalents, Cordis peut distinguer les identités des fournisseurs. La raison en est que la signification d’une dépendance ne réside pas seulement dans l’existence d’une valeur.
"value exists"
Cela peut également signifier que la valeur provient d’une autre instance de composant.
"this value came from a different component instance"
Ainsi, le changement de fournisseur peut amener les éléments dépendants à se recharger correctement, ce qui évite de nombreux bugs liés à des références obsolètes.
Déchargement dans l’ordre des dépendances
Considérons une chaîne dans laquelle un plugin fournit la base de données.
Plugin A provides database
Un deuxième plugin dépend de la base de données, et un troisième dépend du deuxième.
Plugin B depends on databasePlugin C depends on Plugin B
La chaîne de dépendances est la suivante.
A
↓
B
↓
C
Désormais, le premier est supprimé. Un environnement d’exécution naïf pourrait fermer immédiatement la base de données, même si les deux autres sont encore en cours d’exécution. Cordis coordonne plutôt le graphe de dépendances. Le déchargement commence.
A begins unloading
Ensuite, les dépendants sont libérés un par un avant que l’original ne termine son nettoyage.
↓ mark A unavailableB notices dependency disappeared↓ B unloadsC notices B disappeared↓ C unloadsthenA completes cleanup
L’ordre est extrêmement important, car il empêche les dépendants d’utiliser quelque chose qui est déjà à moitié détruit.
Isolement : un nom, des mondes distincts
Un seul espace de noms de service global peut devenir limitant. Imaginez deux agents ayant besoin de bases de données différentes.
Agent A → Database A
Agent B → Database B
Ils demandent toujours la même chose.
database
Cordis prend en charge l’isolement, ce qui permet de résoudre les problèmes depuis une paire.
service name + isolation realm
et pas seulement à partir d’un nom.
service name
Conceptuellement, chaque domaine résout le même nom vers son propre instance.
Realm A: database → DB_A
Realm B: database → DB_B
La même dépendance logique peut donc se résoudre différemment en fonction du contexte, ce qui est utile pour les systèmes multi-locataires, les multiples agents, les environnements de test, les plugins isolés et les espaces de travail distincts.
Interception
Cordis peut également influencer l’accès aux services à l’aide de métadonnées. Prenons par exemple un registre d’outils que l’agent administrateur pourrait consulter.
shell
browser
filesystem
deploy
alors qu’un agent restreint voit un ensemble plus petit.
browser
filesystem-readonly
Au lieu de créer un registre entièrement distinct, l’accès peut être intercepté en fonction du contexte.
ctx + metadata
↓
service resolution
↓
modified view of capability
C’est un modèle solide pour les systèmes de politiques.
Remplacement en temps réel plus sûr des modules
Le rechargement en temps réel semble simple.
file changed
↓
reload module
Dans un environnement de exécution de plugin, les choses deviennent complexes. Que se passe-t-il si le nouveau module contient une erreur, et que devient la vieille version fonctionnelle ? Le chargeur Cordis prend en charge un comportement de rechargement qui fonctionne comme une transaction.
working plugin
↓
code changes
↓
attempt new import
↓
success?
┌───────┴───────┐
yes no
↓ ↓
replace rollback
plugin old module
Cela s’inscrit naturellement dans le cadre des effets réversibles. Un plugin disposant d’un cycle de vie clair, d’un ensemble de dépendances bien défini et d’une procédure de nettoyage est bien moins risqué à remplacer.
Étude de cas dans le monde réel
Cordis n’a pas été créé uniquement pour DeepSeek Harness. L’article examine Koishi, un framework de chatbot open source construit sur cette base, doté d’un écosystème communautaire comprenant des milliers de plugins. Un véritable écosystème rend la composition dynamique bien plus convaincante que dix modules conçus manuellement. Cependant, l’article reste prudent et ne prétend pas que des tests contrôlés aient montré d’importantes améliorations en termes de performance ou de productivité. D’autres évaluations sont encore nécessaires.
Tout ne peut pas être annulé
Ici, la théorie rencontre la réalité. Certains effets en temps de exécution sont véritablement réversibles, comme l’ouverture et la fermeture d’un fichier.
open file
→ close file
D’autres forment également de bons couples.
allocate memory
→ free memorystart worker
→ terminate workerregister event listener
→ unregister listener
Puis il y a l’envoi d’un e-mail.
send email
Quel en serait l’inverse ?
unsend email?
Généralement, c’est impossible, et il en va de même pour un groupe d’autres actions.
send HTTP request
transfer money
publish message
delete external data
Lorsqu’un événement affecte le monde extérieur, il se peut qu’il n’y ait pas de véritable réparation possible. La meilleure solution disponible est alors la compensation.
payment → refund
message → follow-up correction
database mutation → compensating transaction
La compensation n’est pas identique à l’inversion mathématique. L’article reconnaît explicitement cette distinction, et Cordis fonctionne le mieux pour les effets que le temps d’exécution peut réellement contrôler et corriger.
Cordis compte sur votre nettoyage
Supposons qu’un plugin démarre un serveur à l’intérieur d’un effet et retourne une fonction de nettoyage.
ctx.effect(() => {
startServer()
return () => {
// supposedly cleanup
}
}))
Cordis ne dispose d’aucun moyen de prouver que le nettoyage inverse réellement l’état initial du programme. Le modèle formel stipule que l’opération inverse doit ramener l’environnement à son état antérieur, mais sa mise en œuvre dépend du programmeur pour qu’il écrive un destructeur valide. Associer un effet à un nettoyage constitue donc une discipline à respecter.
effect + cleanup
Ce n’est pas une vérification formelle automatique, et c’est là une différence importante.
Pourquoi cela convient aux agents IA
Les applications traditionnelles restent assez stables après leur démarrage. Les agents vont dans la direction opposée. Les systèmes d’agents futurs pourraient effectuer continuellement des opérations de ce type.
install tool
remove tool
change model
spawn agent
kill agent
load skill
reload code
switch permissions
connect service
disconnect service
modify environment
Imaginez un agent qui décide qu’il a besoin du support de GitHub, charge une capacité GitHub, puis l’abandonne plus tard lorsqu’elle n’est plus utile, ou remplace une implémentation qui a échoué. Un système de ce type exige des sémantiques de composition en temps de exécution bien plus robustes que celles offertes par un injecteur de dépendances conventionnel. Sans elles, un agent en exécution prolongée pourrait accumuler des éléments inutiles.
orphan listeners
stale tools
dangling processes
broken dependencies
zombie services
duplicated handlers
Cordis s’efforce de faire de la composition en temps d’exécution un concept logiciel de premier plan.
Pourquoi la conception semble élégante
Avec Cordis qui gère la composition, DeepSeek Harness peut modéliser presque tout de la même manière.
Model provider → plugin
Tool → plugin
Agent loop → plugin
Session store → plugin
UI → plugin
Telemetry → plugin
Approval system → plugin
Sandbox → plugin
Credential store → plugin
Les nouvelles fonctionnalités ne deviennent pas des cas particuliers au sein du framework. Elles deviennent plutôt de nouveaux acteurs au sein d’un système de composition.
Profils et bundles
Puisque tout est un plugin, personne ne veut les assembler manuellement cinquante fois à chaque fois. Le framework ajoute donc des compositions de niveau supérieur. Un profil décrit une configuration de système nommée, tandis qu’un bundle distribue des groupes de plugins ainsi que leurs configurations. Un bundle de base pourrait ressembler à ceci.
Base Bundle
├── model adapters
├── tools
├── sessions
├── sandbox
├── approvals
├── credentials
├── settings
└── telemetry
Un autre ensemble pourrait inclure une application web.
Web Application
Un autre pourrait générer un agent sans tête.
Headless Agent
L’architecture reste modulaire sans rendre l’utilisation quotidienne difficile.
Cinq idées à retenir
Même si tout le reste disparaît, ces cinq points doivent être mémorisés.
Les effets réversibles en premier : toute modification apportée par un composant doit pouvoir être annulée.
change + inverse
Le deuxième type est constitué des coeffets réactifs. Les composants déclarent ce dont ils ont besoin, et l’environnement de exécution réagit lorsque des dépendances apparaissent, disparaissent ou changent.
dependency available → activate
dependency gone → deactivate
Le troisième élément est le contexte. Les effets, les dépendances et l’accès aux services se trouvent au sein d’un contexte de exécution unifié.
ctx
Quatrièmement, il y a un calcul de composants dynamique, qui décrit un composant en fonction de ses besoins, de ce qu’il fournit et de ce qu’il modifie, fournissant ainsi au système en temps réel suffisamment de structure pour gérer le chargement et le déchargement.
Dependencies + Provisions + Effects
Le cinquième est la composabilité spatio-temporelle, qui combine les aspects spatiaux et temporels.
spatial:
who depends on whom?
+
temporal:
how can components safely enter and leave?
En une phrase, il s’agit d’un changement réversible associé à une dépendance réactive.
Pourquoi c’est important au-delà d’un seul projet
La plupart des frameworks d’agents actuels supposent encore une application plutôt statique : lancer le programme, charger les outils, charger le modèle, exécuter l’agent, arrêter. Les agents autonomes visent des environnements d’exécution capables de se modifier tout en restant actifs. Imaginez un agent capable de suivre une séquence comme celle-ci.
discover a capability
install it
use it
upgrade it
replace it
remove it
continue running
À ce stade, la gestion du cycle de vie des plugins cesse d’être une fonctionnalité architecturale agréable pour devenir une infrastructure fondamentale. C’est ce qui rend le projet intéressant. Le modèle n’est pas la partie la plus novatrice ; c’est l’environnement de exécution qui l’est.
La leçon plus profonde
L’architecture logicielle concerne généralement la composition à un moment donné : on assemble des composants et on exécute le programme.
A + B + C
Cordis pose des questions plus difficiles.
What if A disappears?
What if B is replaced?
What if C suddenly gains a dependency?
What if another implementation of A appears?
What if all of this happens while the system is running?
Cela change complètement la nature du problème. Les composants cessent d’être des blocs statiques pour agir comme des participants vivants : ils arrivent, établissent des relations, modifient leur environnement, dépendent des autres et finalement partent.
Le point clé
DeepSeek Harness peut sembler n’être qu’un autre framework d’agents, et son attrait ne réside pas dans sa capacité à appeler un LLM ou à exécuter des outils, ce que de nombreux frameworks font déjà. L’idée principale est que le système de composition lui-même constitue l’architecture. Chaque capacité est un composant, chaque dépendance est explicite, chaque effet contrôlable sait comment se nettoyer lui-même, les composants réagissent lorsque les dépendances changent, et même la boucle d’agent peut être remplacée sans que ce ne soit du code de framework intouchable. Le principe directeur est de faire de chaque capacité un composant, de rendre chaque dépendance explicite et de s’assurer que chaque effet dispose de sa propre fonction d’annulation.
Dans les applications ordinaires, il s’agit d’une architecture de plugins bien organisée. Dans les agents IA à long terme qui se modifient eux-mêmes, cela peut avoir une importance bien plus grande. Si ces agents changent finalement leur propre environnement de exécution, le logiciel aura besoin d’une méthode fondée sur des principes pour s’adapter sans endommager progressivement l’environnement dans lequel il fonctionne. C’est le problème que DeepSeek Harness, ainsi que la recherche Cordis qui le sous-tend, visent à résoudre.