fix: count a run completed at the DONE transition, not after it (researchx#28)
ci / backend (uv, Python 3.12: ruff/mypy/pytest) (push) Successful in 31s
ci / frontend (Next.js on pnpm: typecheck/lint/test/build) (push) Successful in 1m35s
ci / backend (uv, Python 3.12: ruff/mypy/pytest) (pull_request) Successful in 31s
ci / frontend (Next.js on pnpm: typecheck/lint/test/build) (pull_request) Successful in 1m35s

The cost counter was incremented by a watcher that awaited the run task, so it
lagged the status flip: the run read as done while /api/config still counted
zero completed runs. The increment now happens in the same step as the
transition to DONE, so any client that observes the run as done also observes
the counter.

Refs researchx#28
This commit is contained in:
2026-10-27 05:18:51 +00:00
parent c7ce780edd
commit 70243eeb8b
2 changed files with 7 additions and 3 deletions
+2 -3
View File
@@ -226,6 +226,7 @@ def create_app(
storage=storage,
registry=registry,
event_bus=bus,
on_complete=runs.note_completed,
)
probe_data: dict[str, Any] | None = None
if supports_probe(model):
@@ -240,9 +241,7 @@ def create_app(
runs.add(run_id, actor, task, bus)
async def _watch() -> None:
state = await task
if state.state == RunStateName.DONE:
runs.note_completed()
await task
asyncio.create_task(_watch())
return _summary(run_id)
+5
View File
@@ -1,4 +1,5 @@
import asyncio
from collections.abc import Callable
from datetime import UTC, datetime
from typing import Any
@@ -34,6 +35,7 @@ class RunActor:
storage: LocalStorageAdapter,
registry: MethodologyRegistry,
event_bus: EventBus,
on_complete: Callable[[], None] | None = None,
) -> None:
self._config = run_config
self._model = model
@@ -41,6 +43,7 @@ class RunActor:
self._storage = storage
self._registry = registry
self._bus = event_bus
self._on_complete = on_complete
self._store = EvidenceStore()
self.state = RunState(
run_id=run_config.run_id,
@@ -83,6 +86,8 @@ class RunActor:
async def _checkpoint(self, target: RunStateName) -> None:
if self.state.state != target:
self.state.transition(target)
if target is RunStateName.DONE and self._on_complete is not None:
self._on_complete()
await self._persist()
async def _wait_if_paused(self) -> None: