Concurrency guards: single-flight, semaphore and timeouts #61

4 Commits
Author SHA1 Message Date
dev_agentandClaude Opus 5.5 603db32a41 Assert the late-save row is ok to prove the post-save timeout path
CI / ci (push) Successful in 1m33s
CI / ci (pull_request) Successful in 1m34s
The row-saved-late test only checked that one ai_runs row existed, so it
would also pass if the work itself had timed out before the save. It now
requires the row to be ok with no error, which holds only when the work
ended in time and the save alone ran past the deadline.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 10:30:09 -04:00
dev_agentandClaude Opus 5.5 7b81d48150 Fail a run whose ai_runs row is saved after the skill timeout
CI / ci (pull_request) Successful in 1m36s
CI / ci (push) Successful in 1m35s
runOnce checked the deadline only before saving the row. The save can
wait for the store's single writer, so a run whose work ended in time
could return success after its timeout. Check the deadline again once
the row is saved and return llm.ErrTimeout when it has passed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 10:21:10 -04:00
dev_agentandClaude Opus 5.5 a6f750ea04 Report work that ends after the skill timeout as llm.ErrTimeout
CI / ci (push) Successful in 1m36s
CI / ci (pull_request) Successful in 1m33s
runOnce only mapped an expired context to llm.ErrTimeout when execute
returned an error, so a PostProcess that succeeded after the deadline was
reported as a success. Check the deadline after execution (context cause
or the clock, since the timer may not have fired yet) and fail such a run
with llm.ErrTimeout and a provider_error ai_runs row.

Adds a TestRunTimeout case with a skill whose PostProcess succeeds only
after its timeout.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 10:12:25 -04:00
dev_agentandClaude Opus 5.5 d03068ace5 Add concurrency guards to harness.Run: single-flight, semaphore and timeouts
CI / ci (push) Successful in 1m34s
CI / ci (pull_request) Successful in 1m37s
harness.Run now takes a scope key. Concurrent runs of one skill with one
scope share a single in-flight run and its result, errors included, so
only one Generate call is made; different scopes run independently. A
process-wide Guard (NewGuard, DefaultMaxCalls = 2) bounds concurrent LLM
calls with a context-aware semaphore. Each skill declares Timeout()
(DefaultTimeout = 60 s), enforced via context over the whole run; a run
past it fails with an error matching llm.ErrTimeout. A caller whose
context ends gets the context error; the shared run is cancelled, and
its slot freed, once no caller waits for it.

llm.Fake results can now block (Release) and signal entry (Started), so
tests hold calls in flight.

Maestro task: 8f533721-32c9-4f73-af9f-9a9f0e94abf8

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 10:00:20 -04:00