Aller au contenu
English

Faire une pause quand un quota est atteint

Enregistrez un workflow après une erreur de quota définitive et reprenez quand l’accès est disponible.

Définissez onQuota dans la méthode start() du workflow pour conserver la progression après une erreur de quota définitive. Avec un checkpoint configuré, la tâche concernée se met en pause afin de reprendre plus tard.

import { defineTask, OutpostError } from "@elie-laloum/outpost";

export let calls = 0;
export const review = defineTask({
  key: "review",
  perform: () => {
    if (++calls === 1)
      throw new OutpostError("quota", "You've hit your session limit", {
        resetAt: new Date(Date.now() + 1_000).toISOString(),
      });
    return "reviewed";
  },
});
export function reviewCount() {
  return calls;
}
import {
  createWorkflowCheckpointStore,
  createLocalTransport,
} from "@elie-laloum/outpost";

export const checkpoint = {
  store: createWorkflowCheckpointStore({
    transporter: createLocalTransport({ directory: ".outpost/storage" }),
  }),
  runId: "nightly-2026-09-28",
  version: "1",
};
import { reportValue } from "./reporter.ts";
import { defineWorkflow } from "@elie-laloum/outpost";
import { review } from "./quota-review.ts";
import { checkpoint } from "./quota-checkpoint.ts";

export const result = await defineWorkflow("nightly", [review]).start({
  checkpoint,
  onQuota: { action: "pause", maxWaitMs: 6 * 60 * 60_000 },
});
result.unwrap();
reportValue(result.value(review));
// Example output: reviewed

Le script affiche reviewed : la limite simulée se réinitialise après une seconde, dans la fenêtre maxWaitMs, donc le workflow attend puis relance la tâche.

onQuota exige un checkpoint pour conserver la pause. Avec la valeur par défaut de maxWaitMs, 0, rien n’attend dans le processus : chaque pause est durable.

Les agents et les fournisseurs de modèles rejettent avec une OutpostError de code quota :

SourceSignalRéinitialisation
Claude Coderate_limit_event refusé, rate_limit ou billing_error, texte de limiteDepuis resetsAt
CodexusageLimitExceeded ou rateLimitExceeded, texte de limite d’usageInconnue
Copilot CLIsession.error de type quota ou rate_limit, texte de limiteInconnue
Kimi CodeTexte de quota, de solde ou de limite de débitInconnue
AntigravityRESOURCE_EXHAUSTED ou texte de quotaInconnue
Fournisseurs de modèlesHTTP 429, erreur de flux de limite de débit ou insufficient_quotaDepuis Retry-After

Un signal d’agent CLI ne compte que si le processus de l’agent échoue. Les avis de nouvelle tentative ne sont pas des quotas. quotaFault(error) lit le message et le resetAt d’une erreur de quota interceptée, même imbriquée.

Un agent de secours change d’agent au lieu d’attendre : la tâche ne se met en pause que lorsque tous les candidats ont atteint une limite.

Glissez pour vous déplacer · Ctrl + molette pour zoomer
100 %
  • PauseLa tentative qui a atteint la limite s’arrête.
    1. Garder les retriesL’erreur ne consomme pas les tentatives de retry.
    2. Enregistrer la pauseLa tâche passe en paused avec un enregistrement quota, puis le checkpoint est sauvegardé.
    (Étapes)
    • → Attente : puis
  • AttenteSeulement si l’heure de réinitialisation est connue.
    1. Attendre dans le processusUne réinitialisation comprise dans maxWaitMs émet un événement quota de status: "waiting", puis relance la tâche.
    2. Pause durableSinon, la tâche reste en pause. Les tâches indépendantes continuent, les tâches dépendantes attendent et start() renvoie paused.
    (Étapes)
    • → Reprise : puis
  • RepriseUn start() ultérieur avec le même checkpoint.
    1. RelancerUne réinitialisation inconnue ou passée relance la tâche aussitôt.
    2. Attendre d’abordUne réinitialisation comprise dans maxWaitMs est attendue, puis la tâche s’exécute.
    3. Rester en pauseUne réinitialisation plus lointaine laisse la tâche en pause sans appeler l’agent.
    (Étapes)

L’enregistrement en pause dans result.tasks contient quota.resetAt : planifiez le start() suivant à partir de cette heure. onQuota autorise la relance, sans resume: "retry-incomplete". Une tâche en boucle reprend la phase du tour qui a atteint la limite.

La première tentative après une pause reçoit context.quota : la conversation capturée et la branche de travail conservée.

Tâche ou appelTentative suivante
defineAgentTask()Poursuit la conversation dans votre sandbox et votre workspace.
defineIsolatedTask()La poursuit dans une nouvelle sandbox, sur la même branche ; un workspace intégré part de la branche interrompue.
defineInteractiveAgentTask()Poursuit la conversation du tour interrompu.
defineQueuedTask()Publie un nouveau job, <key>:quota:<attempt>, avec l’idempotencyKey d’origine.
speculate()Dans une course durable, relance les candidats arrêtés par une limite, dans de nouvelles conversations.

Un tour poursuivi envoie une courte consigne de reprise au lieu du brief. Passez quotaResume: "restart" à une tâche d’agent ou isolée pour renvoyer la requête d’origine.

Claude Code, Codex, Copilot CLI et Kimi Code peuvent poursuivre. Un agent de secours repart de son premier candidat avec le brief d’origine.

Transmettre la conversation à un traitement en file

Section intitulée « Transmettre la conversation à un traitement en file »

Un worker enregistre l’erreur de quota d’un traitement dans QueueResult.quota, et defineQueuedTask() rejette avec le code quota. Transmettez la conversation par l’entrée de la tâche :

import { defineQueuedTask } from "@elie-laloum/outpost";
import type { TaskQueue } from "@elie-laloum/outpost";

function implement(queue: TaskQueue) {
  return defineQueuedTask({
    key: "implement",
    queue,
    handler: "implement",
    input: (context) => ({ continueFrom: context.quota?.conversation ?? null }),
    decode: String,
  });
}

Le traitement lance ensuite son dispatch avec continuation: { id: input.continueFrom }. Files de jobs présente les workers et les clés d’idempotence.

Quand des limites arrêtent des candidats et qu’aucun ne gagne, speculate() renvoie le statut quota avec la réinitialisation connue la plus proche. Levez-la depuis une tâche pour mettre le workflow en pause :

import { OutpostError, speculate, defineTask } from "@elie-laloum/outpost";
import type { SpeculationOptions } from "@elie-laloum/outpost";

function race(options: SpeculationOptions) {
  return defineTask({
    key: "race",
    async perform() {
      const result = await speculate(options);
      if (result.status === "quota" && result.quota)
        throw new OutpostError("quota", result.quota.message, {
          ...(result.quota.resetAt ? { resetAt: result.quota.resetAt } : {}),
        });
      return result.winner?.branch ?? null;
    },
  });
}

Une course durable relance ensuite seulement ces candidats, avec des budgets cumulés. Sans durabilité, tous les candidats sont relancés.

  • start() refuse onQuota sans checkpoint.
  • Chaque relance compte dans budget.attempts. Le timeoutMs du workflow interrompt aussi les attentes, et une attente annulée laisse la tâche en pause.
  • Les heures de réinitialisation écrites dans le texte de l’agent ne sont pas analysées ; elles restent dans le message.
  • Les autres agents, la capture désactivée, une requête avec sa propre continuation ou plusieurs passes repartent du brief.
  • Les changements non commités d’une tentative intégrée interrompue restent dans son worktree conservé.
  • Les workers ne transmettent que les conversations capturées par le dispatch du traitement.

API : WorkflowQuotaPolicy · WorkflowQuotaPause · QuotaResumePolicy · quotaFault · TaskContext · QueueResult · WorkflowOptions