L'agent que personne ne peut exécuter
La plupart des agents naissent dans un terminal. Vous tapez un prompt, le modèle appelle quelques outils, des fichiers changent, et ça fonctionne. Puis quelqu'un pose la question évidente : peut-il tourner selon une planification, sans vous ?
C'est généralement là que tout s'effondre. La boucle a été écrite autour d'un humain — une personne pour approuver l'étape suivante, lire la sortie, relancer quand un outil échoue, et se souvenir de ce qui s'est passé la fois précédente. Retirez l'humain et il vous reste à reconstruire les parties ennuyeuses : l'état de session, un système de fichiers que le modèle peut manipuler sans danger, des reprises qui survivent à un crash, et un endroit où déployer tout cela.
Flue est un framework TypeScript bâti précisément autour de cette couche manquante. Son argumentaire, signé Fred K. Schott, cofondateur d'Astro, est direct : l'expérience ressemble à Claude Code, mais l'outil est 100% headless et entièrement programmable. Pas d'interface texte, pas d'interface graphique, aucune hypothèse selon laquelle un opérateur surveille. L'équation du framework résume le mieux sa conception :
Agent = Modèle + Harnais
Le modèle est une commodité que vous choisissez avec une chaîne de caractères. Le harnais — sessions, outils, compétences, bac à sable, système de fichiers durable, cible de déploiement — voilà ce que Flue vous apporte réellement.
Ce tutoriel en construit un véritable exemplaire : un agent autonome de revue éditoriale qui lit un article en Markdown, le confronte à une liste de contrôle, appelle un outil typé pour les données qu'il ne peut pas deviner, puis écrit ses conclusions dans un fichier. Vous l'exécuterez en local, le déclencherez via HTTP, puis le déploierez sur Cloudflare Workers où chaque instance d'agent devient son propre Durable Object.
Prérequis
Avant de commencer, assurez-vous de disposer de :
- Node.js 22 ou plus récent et un gestionnaire de paquets (npm, pnpm ou bun)
- Une clé d'API Anthropic (ou celle d'un autre fournisseur pris en charge) exportée comme variable d'environnement
- Une bonne pratique de TypeScript — modules ES, async/await et génériques
- Un compte Cloudflare ainsi que
wrangler, uniquement pour la section déploiement - Une familiarité avec la validation de schémas aide ; Flue utilise Valibot et non Zod pour les schémas d'outils
Une remarque sur les versions : Flue est devenu public le 1er mai 2026 et a livré sa version 1.0 Beta le 16 juin 2026, après une réécriture de 335 commits. Le projet évolue vite, et la bêta a déjà cassé sa propre API d'outils une fois (voir la section dépannage). Épinglez vos versions.
Ce que vous allez construire
Un seul projet avec quatre pièces mobiles :
| Pièce | Fichier | Rôle |
|---|---|---|
| Agent | agents/reviewer.ts | Politique d'exécution : modèle, instructions, outils, compétences, bac à sable |
| Compétence | skills/review-checklist/SKILL.md | La norme éditoriale, en Markdown |
| Outil | shared/tools.ts | Une consultation typée que le modèle ne peut pas halluciner |
| Workflow | workflows/review-article.ts | Orchestration finie, déclenchée par HTTP |
L'agent final accepte un document Markdown, le relit et renvoie des conclusions structurées. Le même code source se déploie sur un serveur Node de longue durée ou sur le réseau edge de Cloudflare, sans réécriture.
Étape 1 : Initialiser le projet
Installez le runtime comme dépendance et la CLI comme dépendance de développement :
mkdir article-reviewer && cd article-reviewer
npm init -y
npm install @flue/runtime
npm install --save-dev @flue/cliGénérez la configuration. L'option --target détermine le runtime pour lequel vous compilez :
npx flue init --target nodeCela produit un fichier flue.config.ts :
// flue.config.ts
import { defineConfig } from '@flue/cli/config';
export default defineConfig({
target: 'node',
});La signature complète mérite d'être connue, car vous utiliserez --root dans un monorepo :
flue init --target <node|cloudflare> [--root <path>] [--force]Créez maintenant l'arborescence que Flue découvre par convention :
mkdir -p agents workflows skills/review-checklist sharedFlue traite ces répertoires comme porteurs de sens :
agents/— un agent par fichier. Le nom du fichier devient l'identifiant de l'agent, doncagents/reviewer.tsest l'agentreviewer. La découverte ici est requise pour les routes d'agents persistants et pourdispatch().workflows/— un workflow par fichier, exporté par défaut.skills/— des ensembles d'instructions en Markdown.shared/— des modules ordinaires, sans traitement particulier.
Étape 2 : Définir votre premier agent
Un agent dans Flue n'est pas un objet à longue durée de vie. defineAgent reçoit un initialiseur — une fonction exécutée chaque fois qu'un exécuteur construit un harnais racine à partir de la définition. La distinction compte : ne le prenez pas pour le constructeur d'une instance persistante unique.
// agents/reviewer.ts
import { defineAgent } from '@flue/runtime';
export default defineAgent(() => ({
model: 'anthropic/claude-sonnet-4-6',
instructions: [
'You are an editorial reviewer for a technical publication.',
'Review the requested document and report only findings supported by evidence from the text.',
'Never invent a quotation, a statistic, or a source.',
].join('\n'),
}));Deux champs et vous avez un agent fonctionnel. L'initialiseur reçoit un objet de contexte, par lequel arrivent l'identité de chaque exécution et les liaisons de plateforme :
export default defineAgent(({ id, env }) => ({
model: 'anthropic/claude-sonnet-4-6',
instructions: `Reviewing under run ${id}.`,
}));id est l'identifiant de l'instance d'agent ou de l'exécution du workflow. env contient les liaisons d'environnement fournies par le runtime — sur Cloudflare, c'est ainsi que vous atteignez les liaisons de votre Worker.
La surface complète de AgentRuntimeConfig est assez réduite pour être mémorisée :
| Champ | Utilité |
|---|---|
model | Spécificateur de modèle par défaut, sous la forme provider/model |
instructions | Ajoutées avant le contexte de l'espace de travail découvert |
tools | Outils personnalisés appelables par le modèle |
skills | Compétences Markdown enregistrées |
actions | Actions réutilisables exposées comme outils gérés par le framework |
subagents | Profils nommés disponibles pour la délégation via session.task() |
sandbox | Où s'exécutent le code et les opérations sur fichiers |
cwd | Répertoire de travail de l'agent |
thinkingLevel | Effort de raisonnement par défaut ; chaque opération peut le surcharger |
profile | Une base réutilisable ; les champs de l'agent la remplacent ou l'étendent |
description | Métadonnées organisationnelles pour cette initialisation |
Remarquez ce qui manque : pas de graphe, pas de registre de nœuds, pas de machine à états. Le pari de Flue est qu'un modèle compétent doté d'un bon harnais surpasse un graphe d'orchestration explicite. Pour la philosophie inverse, comparez avec les graphes à état de LangGraph.
Étape 3 : Transmettre vos normes via une compétence
Les instructions vivent dans la définition de l'agent et s'appliquent à tout. Les normes qui ne concernent qu'un type de tâche relèvent d'une compétence — un simple fichier Markdown que le runtime enregistre et que le modèle peut charger.
<!-- skills/review-checklist/SKILL.md -->
# Editorial review checklist
Apply every rule below. For each finding, quote the offending text.
## Accuracy
- Every version number, benchmark figure, and price must be attributable to the document itself.
- Flag any claim phrased as fact without a source.
## Structure
- The opening must state the problem before naming any product.
- Headings must be scannable and describe outcomes, not features.
- Code blocks must declare a language for syntax highlighting.
## Language
- Prefer the active voice.
- Cut hedging: "arguably", "it could be said", "quite possibly".
- Expand every acronym on first use.
## Output contract
Write findings to `review.md` as a Markdown list, most severe first.
Each entry: severity, the quoted text, and the concrete fix.
If the document passes a section cleanly, say so in one line.Importez-la avec un attribut d'import — c'est ainsi que Flue distingue une compétence d'une simple ressource Markdown :
// agents/reviewer.ts
import { defineAgent } from '@flue/runtime';
import reviewChecklist from '../skills/review-checklist/SKILL.md' with { type: 'skill' };
export default defineAgent(() => ({
model: 'anthropic/claude-sonnet-4-6',
instructions: 'Review the requested document and report only evidence-backed findings.',
skills: [reviewChecklist],
}));Garder la liste de contrôle en Markdown est un vrai gain opérationnel : votre responsable éditorial peut réviser la norme dans une merge request sans toucher au TypeScript, et le diff reste lisible par quelqu'un qui n'écrit pas de code.
Étape 4 : Ajouter un outil typé avec Valibot
Les compétences façonnent le jugement. Les outils, eux, servent aux faits que le modèle n'a aucune raison de deviner — un budget de mots issu du CMS, une liste terminologique approuvée, une date de publication.
defineTool valide et renvoie une définition d'outil figée. Les schémas sont en Valibot, et le schéma input doit être un schéma d'objet de premier niveau :
// shared/tools.ts
import { defineTool } from '@flue/runtime';
import * as v from 'valibot';
import { cms } from './cms.ts';
export const lookupStyleBudget = defineTool({
name: 'lookup_style_budget',
description:
'Read the publication style budget for a content type. Use before commenting on article length or heading depth.',
input: v.object({
contentType: v.picklist(['tutorial', 'news', 'blog']),
}),
output: v.object({
minWords: v.number(),
maxWords: v.number(),
maxHeadingDepth: v.number(),
}),
async run({ input, signal }) {
return cms.getStyleBudget(input.contentType, { signal });
},
});Le contrat de validation est précis, et c'est lui qui rend l'exposition d'outils sûre :
- Les entrées fournies par le modèle sont validées et analysées avant que
runne les reçoive. - Un échec de validation devient une erreur d'outil : le modèle peut la lire et réessayer, sans interrompre l'exécution.
- Quand
outputest présent, la valeur de retour est également validée et analysée, puis capturée en données compatibles JSON et sérialisée pour le modèle. - Sans schéma
output, retournerundefinedenvoienullau modèle. - Les noms d'outils doivent être uniques parmi les outils intégrés et personnalisés actifs ; les collisions sont vérifiées quand une session assemble sa liste d'outils.
Les outils bornés valent mieux que les prompts astucieux
Le motif le plus précieux ici n'a rien à voir avec le typage. C'est votre application qui définit la frontière ; le modèle ne choisit que des valeurs à l'intérieur. Capturez l'identité du locataire par fermeture au lieu de l'accepter en paramètre :
// agents/support.ts
import { defineAgent, defineTool } from '@flue/runtime';
import * as v from 'valibot';
import { orders } from '../shared/orders.ts';
export default defineAgent(({ id: customerId }) => ({
model: 'anthropic/claude-haiku-4-5',
tools: [
defineTool({
name: 'lookup_customer_order',
description: 'Look up one order belonging to this customer.',
input: v.object({
orderId: v.string(),
}),
async run({ input }) {
const status = await orders.getStatus(customerId, input.orderId);
return status ?? 'No accessible order was found.';
},
}),
],
}));customerId provient du contexte de l'initialiseur, pas du modèle. Aucun argument ne permet au modèle de lire les commandes d'un autre client : aucune injection de prompt n'atteint ces données. Comparez avec les défenses au niveau du prompt décrites dans notre guide des garde-fous pour agents IA — les frontières structurelles sont strictement plus solides, car un contournement ne peut pas argumenter face à une fermeture.
Branchez l'outil sur le relecteur :
// agents/reviewer.ts
import { defineAgent } from '@flue/runtime';
import { local } from '@flue/runtime/node';
import reviewChecklist from '../skills/review-checklist/SKILL.md' with { type: 'skill' };
import { lookupStyleBudget } from '../shared/tools.ts';
export default defineAgent(() => ({
model: 'anthropic/claude-sonnet-4-6',
instructions: 'Review the requested document and report only evidence-backed findings.',
skills: [reviewChecklist],
tools: [lookupStyleBudget],
sandbox: local(),
}));Étape 5 : Orchestrer avec un workflow
Un agent est une politique. Un workflow est une tâche finie qui emprunte cette politique et se termine. Exportez-le par défaut depuis workflows/<name>.ts :
// workflows/review-article.ts
import { defineWorkflow } from '@flue/runtime';
import * as v from 'valibot';
import reviewer from '../agents/reviewer.ts';
export default defineWorkflow({
agent: reviewer,
input: v.object({
document: v.string(),
contentType: v.picklist(['tutorial', 'news', 'blog']),
}),
output: v.object({
review: v.string(),
}),
async run({ harness, input }) {
await harness.fs.writeFile('document.md', input.document);
const session = await harness.session();
await session.prompt(
`Review document.md as a ${input.contentType}. Apply the editorial checklist and write your findings to review.md.`,
);
return { review: await harness.fs.readFile('review.md') };
},
});C'est dans cette courte fonction que la conception de Flue porte ses fruits. Trois points méritent l'attention :
harness.fs est le point de passage. Vous écrivez l'entrée sous forme de fichier, l'agent lit et modifie des fichiers, vous relisez le résultat. Pas d'analyse fragile d'une réponse en prose, pas de modèle sommé de produire du JSON propre en espérant que ça tienne. Le système de fichiers est le contrat — exactement la façon dont travaillerait un ingénieur humain.
harness.session() est à état. La session accumule le contexte d'un tour à l'autre. Appelez prompt plusieurs fois et l'agent se souvient des précédents :
async run({ harness, input }) {
await harness.fs.writeFile('document.md', input.document);
const session = await harness.session();
await session.prompt('Read document.md and list every factual claim to review.md.');
await session.prompt('Now verify each listed claim against the document. Delete any you cannot support.');
await session.prompt('Rewrite review.md sorted by severity, most severe first.');
return { review: await harness.fs.readFile('review.md') };
}Chaque passe est facile à raisonner et l'état intermédiaire est inspectable sur disque — bien meilleur pour le débogage qu'un unique prompt gigantesque.
L'agent peut être privé. L'agent d'un workflow n'a pas besoin de vivre sous agents/. Déclarez-le sur place quand rien d'autre ne l'utilise :
const workflow = defineWorkflow({
agent,
async run({ harness }) {
return await (await harness.session()).prompt('Triage this incoming issue.');
},
});La découverte sous agents/ n'est requise que pour les routes d'agents persistants et pour dispatch().
defineWorkflow possède une seconde forme qui accepte une action au lieu de run, lorsque vous voulez que le contrat d'entrée et de sortie vive dans une Action réutilisable :
function defineWorkflow<TAction extends ActionDefinition>(options: {
agent: AgentDefinition;
action: TAction;
}): WorkflowDefinition<TAction>;La forme extraite n'accepte ni input ni output — ces contrats appartiennent à l'Action elle-même.
Étape 6 : Exécuter en local et déclencher via HTTP
Itérez avec le serveur de développement :
npx flue devPour attacher une session interactive à un agent pendant le développement :
npx flue connect reviewer local-sessionPour un artefact Node déployable, compilez et démarrez :
npx flue build --target node
PORT=8080 node dist/server.mjsLa cible Node expose une API de travaux asynchrones — la forme adaptée au travail d'agent, qui prend des secondes à des minutes plutôt que des millisecondes :
POST /workflows/review-articledémarre une exécution et renvoie un identifiantGET /runs/[runId]interroge le résultat
curl -X POST http://localhost:8080/workflows/review-article \
-H 'Content-Type: application/json' \
-d '{"document":"# Draft\n\nOur framework is arguably the fastest.","contentType":"tutorial"}'
# => {"runId":"run_01J..."}
curl http://localhost:8080/runs/run_01J...Pour ajouter un intergiciel — authentification, limitation de débit, journalisation — exportez un gestionnaire de route depuis le workflow :
import type { WorkflowRouteHandler } from '@flue/runtime/node';
export const route: WorkflowRouteHandler = async (c, next) => {
if (c.req.header('authorization') !== `Bearer ${process.env.TRIGGER_TOKEN}`) {
return new Response('Unauthorized', { status: 401 });
}
return next();
};Ne négligez pas cette vérification. Un point d'entrée de workflow dépense de l'argent à chaque appel, ce qui fait d'un endpoint non authentifié une faille de facturation autant que de sécurité. Associez-le à la limitation de débit avec Upstash avant toute exposition publique.
Étape 7 : Choisir un bac à sable
Le bac à sable détermine ce que « écrire un fichier » et « exécuter une commande » signifient réellement. Flue propose deux options aux compromis très différents.
just-bash est le choix par défaut. Il fonctionne en mémoire et n'entraîne aucun coût d'infrastructure. L'agent obtient un système de fichiers cohérent et une sémantique de shell sans rien à provisionner. Pour du travail documentaire comme le nôtre, cela suffit vraiment.
local() donne un accès réel au système de fichiers. Importez-le depuis le point d'entrée Node :
import { local } from '@flue/runtime/node';
export default defineAgent(() => ({
model: 'anthropic/claude-sonnet-4-6',
cwd: '/srv/repositories/catalog-service',
sandbox: local(),
}));Utilisez local() quand l'agent doit opérer sur une copie de travail réelle — relire un dépôt, lancer une suite de tests, compiler un projet. Délimitez-le avec cwd et considérez ce répertoire comme entièrement modifiable par le modèle. Le compromis est réel : vous venez de confier votre disque à un modèle de langage, alors exécutez-le sous un utilisateur non privilégié, dans un conteneur, sur une machine que vous accepteriez de reconstruire.
Voici l'agent plus complet que ce motif produit :
import { defineAgent } from '@flue/runtime';
import { local } from '@flue/runtime/node';
import reviewChecklist from '../skills/review-checklist/SKILL.md' with { type: 'skill' };
import { reviewChange } from '../actions/review-change.ts';
import { repositoryTools } from '../shared/repository-tools.ts';
export default defineAgent(() => ({
model: 'anthropic/claude-sonnet-4-6',
instructions: 'Review the requested change and report only findings supported by evidence.',
cwd: '/srv/repositories/catalog-service',
actions: [reviewChange],
tools: repositoryTools,
skills: [reviewChecklist],
sandbox: local(),
}));Étape 8 : Déployer sur Cloudflare Workers
Flue est le premier framework bâti sur le SDK Agents de Cloudflare, ce qui est moins fortuit qu'il n'y paraît : Cloudflare a acquis l'équipe Astro en janvier 2026.
Changez la cible et le modèle de déploiement se transforme sous vos pieds, sans toucher au code de l'agent :
// flue.config.ts
import { defineConfig } from '@flue/cli/config';
export default defineConfig({
target: 'cloudflare',
});Chaque agent devient un Durable Object. Cette seule phrase règle l'essentiel des difficultés liées à l'hébergement d'agents. Un Durable Object est une unité de calcul mono-thread, adressable et à état, dotée de son propre stockage : l'état de session y trouve naturellement sa place. Aucune session persistante à configurer, aucun serveur à provisionner, et la mise à l'échelle découle de l'architecture au lieu de constituer un chantier.
La durabilité provient des primitives du SDK Agents — runFiber(), stash() et onFiberRecovered() — de sorte qu'une exécution interrompue en plein vol peut reprendre au lieu de repartir de zéro. Si vous avez déjà écrit de la logique de compensation pour des exécutions d'agents à moitié terminées sur une file classique, c'est le point à retenir.
Pour l'exécution de code isolée, la cible Cloudflare s'appuie sur @cloudflare/codemode, qui encapsule les Dynamic Workers : Code Mode crée un Dynamic Worker neuf pour chaque fragment de code, l'exécute, puis le jette. Les isolats démarrent en moins de 10 ms, pour environ 0,002 $ par chargement. Chaque fragment obtient un environnement propre et jetable — une isolation plus solide que la réutilisation d'un conteneur unique de longue durée, et assez économique pour être employée à chaque appel d'outil.
Développez localement contre le runtime Cloudflare, en lisant les variables depuis .dev.vars ou .env :
npx flue dev --target cloudflarePuis compilez et livrez :
# One-off build for Cloudflare
npx flue build --target cloudflare
# Configure a deployed secret interactively, then deploy
npx wrangler secret put ANTHROPIC_API_KEY
npx wrangler deploy --config dist/my-agent/wrangler.jsonNotez que la compilation émet son propre wrangler.json sous dist/ ; pointez wrangler deploy vers ce fichier plutôt que d'en rédiger un à la main.
Comme vous êtes à l'intérieur de la plateforme Cloudflare, les liaisons environnantes sont accessibles via env dans votre initialiseur — AI Gateway, Browser Run, Email Service, Agent Memory et AI Search. Router les appels de modèle par AI Gateway est le gain facile : vous obtenez mise en cache, reprises et visibilité des dépenses par modèle sans toucher au code de l'agent. Notre tutoriel sur l'AI Gateway aborde la même idée sous l'angle du routage de fournisseurs.
Étape 9 : Déléguer à des sous-agents
Une session unique qui porte toutes les préoccupations produit une fenêtre de contexte gonflée et un jugement médiocre. Le champ subagents déclare des profils nommés, et session.task() leur délègue le travail — chacun avec son propre contexte :
export default defineAgent(() => ({
model: 'anthropic/claude-sonnet-4-6',
instructions: 'Coordinate a multi-pass editorial review.',
subagents: [
{
description: 'Verifies factual claims against the document only.',
model: 'anthropic/claude-sonnet-4-6',
instructions: 'Check each claim. Report unsupported ones. Never speculate.',
},
{
description: 'Checks structure, headings, and code-block languages.',
model: 'anthropic/claude-haiku-4-5',
instructions: 'Report structural problems only. Ignore factual content.',
},
],
}));Deux bénéfices. Chaque sous-agent ne lit que ce que sa tâche exige, ce qui maintient la précision et réduit la dépense en jetons — un modèle économique traite la passe structurelle tandis que le modèle coûteux vérifie les faits. Et thinkingLevel se règle par profil, si bien que l'effort de raisonnement va là où il se justifie.
Tester votre implémentation
Les tests d'agents qui appellent un modèle réel sont lents, coûteux et non déterministes. Les utilitaires de test de Flue permettent de scripter les réponses du modèle, pour n'évaluer que votre propre logique :
const provider = createProvider();
provider.setResponses([
fauxAssistantMessage(fauxToolCall('lookup', { limit: '2' }), { stopReason: 'toolUse' }),
fauxAssistantMessage('Done.'),
]);
const harness = await context.initializeRootHarness(
defineAgent(() => ({
model: `${provider.getModel().provider}/${provider.getModel().id}`,
tools: [
defineTool({
name: 'lookup',
description: 'Look up values.',
input: v.object({ limit: v.pipe(v.string(), v.transform(Number)) }),
run: async ({ input }) => input.limit,
}),
],
})),
);
await (await harness.session()).prompt('Look up values.');Notez v.pipe(v.string(), v.transform(Number)) — les modèles renvoient des chaînes pour des arguments numériques beaucoup plus souvent qu'ils ne le devraient, et une transformation absorbe cela à la frontière plutôt que de le laisser dans le corps de run.
Testez sur trois niveaux :
- Les outils isolément. Ce sont des fonctions asynchrones ordinaires. Appelez
rundirectement avec des entrées valides et invalides, et vérifiez qu'une mauvaise entrée produit une erreur d'outil plutôt qu'une exception. - La logique du workflow avec des réponses scriptées. Vérifiez le passage par le système de fichiers, les branchements et le schéma de sortie, sans aucun appel réseau.
- Une petite suite de bout en bout réelle. Quelques exécutions véritables sur des documents connus, en contrôlant que les conclusions ne sont pas vides et citent de véritables extraits. Gardez-la hors du chemin de pre-commit.
Vérifiez que le parcours complet fonctionne avant de continuer :
npx flue build --target node
PORT=8080 node dist/server.mjs &
curl -X POST http://localhost:8080/workflows/review-article \
-H 'Content-Type: application/json' \
-d '{"document":"# Draft\n\nOur framework is arguably the fastest available.","contentType":"tutorial"}'Interrogez l'identifiant d'exécution renvoyé. Une implémentation correcte signale « arguably the fastest » comme une affirmation non étayée, car la liste de contrôle exige une attribution que le document ne fournit pas. Si les conclusions reviennent vides, votre compétence n'atteint pas le modèle — vérifiez la présence de l'attribut d'import.
Dépannage
parameters ou execute lève une exception à la définition d'un outil. C'est le changement cassant de la 1.0 Beta. Les anciens marqueurs lèvent désormais une exception par conception. Renommez parameters en input, renommez execute(args, signal) en run({ input, signal }), et retournez directement des données structurées compatibles JSON au lieu d'appeler JSON.stringify(...). Ajoutez un schéma output là où la forme doit être validée.
Erreurs de collision de noms d'outils. Les noms doivent être uniques parmi les outils intégrés et personnalisés, et la vérification a lieu quand une session assemble sa liste d'outils — la collision peut donc apparaître à l'exécution plutôt qu'à la compilation. Préfixez vos outils personnalisés, par exemple cms_lookup_budget.
La compétence est ignorée. Un import Markdown sans with { type: 'skill' } n'est qu'une chaîne de caractères. C'est l'attribut d'import qui l'enregistre.
Les attributs d'import ne sont pas analysés. Ils requièrent Node 22 ou plus récent et un bundler qui les comprend. Si votre chaîne d'outils rejette la syntaxe, mettez-la à jour au lieu de la contourner.
L'agent ne voit pas vos fichiers. Vérifiez sandbox et cwd. Le bac à sable just-bash par défaut fonctionne en mémoire et ne peut pas lire votre disque du tout — c'est précisément l'objectif. Passez à local() quand de vrais fichiers sont nécessaires.
Variables d'environnement manquantes sur Cloudflare. flue dev --target cloudflare lit .dev.vars ou .env, mais les Workers déployés non. Chaque secret exige wrangler secret put, et une clé qui fonctionne en local ne prouve rien sur la production.
Une exécution s'arrête en cours de route sans erreur. Vérifiez si vous avez atteint une limite de contexte du modèle au sein d'une seule session trop longue. Découpez le travail en plusieurs appels à prompt, ou déléguez à des sous-agents pour que chaque contexte reste réduit.
Notes de production
Trois points comptent plus qu'il n'y paraît :
Le coût est une décision de conception, pas une ligne de facture. Une seule exécution autonome peut passer des dizaines d'appels au modèle. Réglez thinkingLevel délibérément, dirigez les passes économiques vers un modèle économique via subagents, et placez la mise en cache des prompts devant les préfixes d'instructions stables — la compétence de liste de contrôle est identique à chaque exécution, ce qui en fait un préfixe de cache idéal.
Vous ne pouvez pas déboguer ce que vous ne voyez pas. Un agent headless qui échoue silencieusement à 3 heures du matin est pire que pas d'agent du tout. Émettez des traces dès le départ ; notre guide d'observabilité Langfuse couvre l'outillage.
Bornez chaque outil. Relisez l'étape 4. Capturez l'identité du locataire par fermeture et ne passez que le sélecteur restreint en entrée d'outil. C'est la décision de sécurité au meilleur rapport bénéfice/effort dans une base de code d'agents, et elle ne coûte rien.
Pour aller plus loin
- Les blueprints
flue add. Des commandes commeflue add channel slackgénèrent un blueprint en Markdown que les agents peuvent ensuite modifier et intégrer — le framework traite sa propre surface d'extension comme modifiable par les agents. - Les Actions. Extrayez des opérations réutilisables et validées par schéma avec
defineAction(), puis partagez-les entre workflows. - La planification. Pointez un déclencheur cron vers l'endpoint du workflow pour des exécutions réellement sans surveillance, ou comparez avec les tâches de fond durables de Trigger.dev.
- D'autres cibles. Flue se déploie également sur GitHub Actions, ce qui fait d'un agent de revue de merge requests un excellent deuxième projet.
- Comparez les harnais. Le modèle de harnais et de bac à sable de Vercel AI SDK 7 résout des problèmes voisins avec d'autres primitives ; lire les deux clarifie à quoi sert vraiment un harnais.
- Explorez la CLI.
flue add,flue build,flue dev,flue docs,flue init,flue runetflue updateconstituent toute la surface.flue docss'avère réellement utile en pleine tâche.
Conclusion
L'apport de Flue n'est pas une nouvelle manière d'appeler un modèle. C'est l'affirmation que l'ingénierie intéressante des agents réside dans le harnais — et que si vous construisez correctement cette couche, le code de l'agent se réduit à quelque chose qui se lit d'une traite.
Le relecteur que vous venez de construire tient en une quarantaine de lignes de TypeScript plus une liste de contrôle en Markdown. Il dispose de sessions à état, d'outils validés, d'un bac à sable, d'un système de fichiers durable, d'un déclencheur HTTP et d'un chemin vers l'edge où chaque instance devient son propre Durable Object. Rien de tout cela ne vient d'un cérémonial de framework : cela vient du choix d'une cible, en laissant le harnais faire le reste.
Le motif à retenir, quel que soit le framework que vous adopterez : laissez l'application définir la frontière et le modèle choisir à l'intérieur. Les outils bornés, le passage par le système de fichiers plutôt que l'analyse de prose, et les petits contextes délégués ne sont pas des idées propres à Flue. Ce sont elles qui séparent un agent que vous pouvez laisser tourner seul d'une démonstration qui exige un spectateur.
Commencez avec just-bash et un seul workflow. Ajoutez un bac à sable quand l'agent a réellement besoin de vrais fichiers, et des sous-agents quand un contexte unique ne suffit plus. Livrez d'abord la version ennuyeuse — un agent qui tourne chaque nuit sans vous vaut bien plus qu'un agent brillant incapable de quitter votre terminal.