feat(events): a failed step may say when to retry (MODULE_API 1.15.0)
A module's perform() may answer { ok: false, retry: true, retryAfterMs }
and the runner waits that long instead of the flat EVENT_STEP_RETRY_MS
(Rust PLAN_REDESIGNS §14.3.13, D371). Dangerous Treasures' warm-up is the
first user.
- dispatch.classify reads it on the retry shape only, clamped to 60 000 to
1 800 000 ms; a value that is not a number is dropped and logged, and the
step retries on the flat delay.
- It is one attempt of EVENT_STEP_MAX_ATTEMPTS and degrades the run like any
retry. A run paused or cancelled in the wait is not picked up.
- Every step.retry log line now carries retryAt (and retryAfterMs when
hinted); the run console reads "retrying at 20:10".
- MODULE_API 1.15.0, additions only; the engagement manifest is regenerated.
Module-uo (^1.10.0) answers none of it and is unaffected.
- Test harness: resourcesDb.finishedRunsWithNothingLeft was never stubbed in
eventRunner.test.js, so a tick that reached the cleanup sweep with a
terminal run waited on the dead pool.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E14m6SuuY6i1vASFeGDBeY
This commit is contained in:
@@ -428,13 +428,28 @@ async function drainStep(run, step, now, carry = {}) {
|
||||
}
|
||||
|
||||
if (result.outcome === 'retry' && step.attempts + 1 < MAX_ATTEMPTS) {
|
||||
await stepsDb.reschedule(step.id, new Date(now.getTime() + RETRY_MS), result.error)
|
||||
// The module's `retryAfterMs` when it gave one (already clamped by the
|
||||
// dispatcher), else the flat delay. **It is still one attempt** of
|
||||
// MAX_ATTEMPTS: a hint changes when, never how many, so a module that answers
|
||||
// "not yet" for ever fails the step on schedule like any other. A run paused
|
||||
// or cancelled in the wait is not picked up — `findDue` reads only running
|
||||
// runs — and the staff Retry on a paused step is unaffected.
|
||||
const delayMs = result.retryAfterMs || RETRY_MS
|
||||
const retryAt = new Date(now.getTime() + delayMs)
|
||||
await stepsDb.reschedule(step.id, retryAt, result.error)
|
||||
await logDb.write({
|
||||
runId: run.id,
|
||||
stepId: step.id,
|
||||
kind: 'step.retry',
|
||||
phase: step.phase,
|
||||
detail: { action: step.action_id, attempt: step.attempts + 1, of: MAX_ATTEMPTS, error: result.error },
|
||||
detail: {
|
||||
action: step.action_id,
|
||||
attempt: step.attempts + 1,
|
||||
of: MAX_ATTEMPTS,
|
||||
error: result.error,
|
||||
retryAt: retryAt.toISOString(),
|
||||
...(result.retryAfterMs ? { retryAfterMs: result.retryAfterMs } : {}),
|
||||
},
|
||||
})
|
||||
// Degraded from the FIRST retry, not from the eventual failure. An event
|
||||
// whose announcements are landing on the second attempt is having trouble
|
||||
|
||||
Reference in New Issue
Block a user