fix(orchestrator): consolidate stale-heartbeat config + drop dead _task_svc slot

I1: claim_heartbeat_ttl_seconds (300s) overlapped semantically with the
pre-existing claim_stale_seconds (180s). Between 180-300s of silence,
trigger_filter queued duplicate spawns while the reaper hadn't yet
released the claim — exactly the dispatcher churn the reaper was
supposed to close. Collapse to one field (claim_stale_seconds, 180s);
reaper now consumes the same setting trigger_filter uses, so both
agree on 'stale' on the same tick and the reaper runs first.

I2: _task_svc injection slot on AgentOrchestrator.__init__ was
production-dead (always None) and only used by __new__-based test
instances. Drop the __init__ slot + the production branch in
_reap_stale_claims that read it. Tests still pre-bind on __new__
instances; the attribute exists per-instance, not per-class.
This commit is contained in:
Renn F
2026-05-03 04:57:25 +02:00
parent b301020398
commit c12aad3005
3 changed files with 37 additions and 38 deletions
+8 -8
View File
@@ -300,19 +300,19 @@ class Settings(BaseSettings):
)
# Gateway coordination thresholds
# Single source of truth for "claim heartbeat is stale": consumed both by
# `trigger_filter` (deciding whether to QUEUE a fresh spawn) and by
# `_reap_stale_claims` (deciding whether to RELEASE the claim back to
# pending). Keeping them on one field guarantees both layers agree on
# the same tick — the reaper runs first, releases the row, and the
# queued spawn finds an unclaimed task. Splitting them into two fields
# opens a window where trigger_filter queues duplicate spawns against a
# claim the reaper hasn't yet released — pure dispatcher churn.
claim_stale_seconds: int = Field(
default=180,
ge=60,
description="Claim heartbeat staleness threshold (seconds)",
)
claim_heartbeat_ttl_seconds: int = Field(
default=300,
ge=60,
description=(
"If an agent's last_heartbeat_at is older than this, the dispatcher"
" considers the claim dead and releases the task back to pending."
),
)
spawn_cooldown_seconds: int = Field(
default=60,
ge=1,