Problem
With flows run --local-agent on relayflows 2.0.29, a Promise.all over 9 f.llm calls fails every time. A body with 5 passes. It needs no application code:
// runtime-parallel-llm-repro.flow.ts
const xs = await Promise.all([1,2,3,4,5,6,7,8,9].map((i) =>
f.llm(`Return {"x": ${i}+${input.n}} as JSON only.`, { output: s, model: "claude-haiku-4-5-20251001" })));
FAILED [protocol_error] relayflowd could not complete the run request: Agent lease is already expired for 01M365AS4PJ4NRG4VFG684CEFV/llm-3.
From the failing child's journal (capacity 1), attempt 1 was started with a 30s lease and never heartbeated. The kernel journaled lease_expired at +30s and retried, and attempt 2 succeeded. The worker's withWorkerLease then threw already expired at its first check. In other words, the worker got the dispatch after the lease had run out.
Suspect: WorkerSlots (authored-worker-step.ts) is meant to hold overflow calls before run.start, so the kernel never leases a step the worker can't take. Yet queued calls end up holding kernel leases they don't use. Either admission isn't bounded the way the comment in worker-slots.ts says, or the LlmWorker's dispatch handling is delayed. This regressed or survived #554, which is in 2.0.29.
Scope
Find out why a queued LLM dispatch reaches the worker after its lease deadline, and fix it so N concurrent f.llm calls run at most capacity at a time with no expired leases. The fatal-error half is #560. This issue is about never producing the stale dispatch in the first place.
Acceptance
Problem
With
flows run --local-agenton relayflows 2.0.29, aPromise.allover 9f.llmcalls fails every time. A body with 5 passes. It needs no application code:--agent-capacity4 (the default) and 1: 00-job1-attempt3-capacity1-lease-expired.txt.From the failing child's journal (capacity 1), attempt 1 was started with a 30s lease and never heartbeated. The kernel journaled
lease_expiredat +30s and retried, and attempt 2 succeeded. The worker'swithWorkerLeasethen threwalready expiredat its first check. In other words, the worker got the dispatch after the lease had run out.Suspect:
WorkerSlots(authored-worker-step.ts) is meant to hold overflow calls beforerun.start, so the kernel never leases a step the worker can't take. Yet queued calls end up holding kernel leases they don't use. Either admission isn't bounded the way the comment inworker-slots.tssays, or theLlmWorker's dispatch handling is delayed. This regressed or survived #554, which is in 2.0.29.Scope
Find out why a queued LLM dispatch reaches the worker after its lease deadline, and fix it so N concurrent
f.llmcalls run at mostcapacityat a time with no expired leases. The fatal-error half is #560. This issue is about never producing the stale dispatch in the first place.Acceptance
f.llmcalls against the real kernel with a fake CLI completes with nolease_expiredattempt in any child journal, at capacity 1 and at the default.Promise.allinjobs/shared.tsrunEngineandjobs/new-agency.ts(feat(examples): prompt-lab — the Prompt Lab product brief as one relayflow #559).