anthropics / anthropics/anthropic-sdk-python
environments work poller: ack-before-yield makes any consumer failure permanently strand the work item — request deferred ack or a nack/fail API
- Linguagem predominante
- Python
- Estrelas
- 3.9k
- Forks
- 853
- Merge médio
- 1d 18h
- PRs com merge (30d)
- 11
Descrição
`anthropic.lib.environments._poller` acknowledges every work item **before** yielding it to the consumer (`_poller.py:234`; docstring at `:66`: "Each yielded `BetaSelfHostedWork` has already been ack'd"). There is no deferred-ack option and no nack/fail API. Verified identical from 0.109.1 through 0.116.0.
For self-hosted managed-agents environments this makes any consumer-side exception after the yield permanently strand that session:
- the acked item freezes in `state='starting'` with `latest_heartbeat_at=null`;
- `reclaim_older_than_ms` cannot reach it (it only reaches unacknowledged items), and lease-TTL re-queue never fires (requires a first heartbeat);
- the platform keeps one work item per session, so all subsequent `user.message` events park in `requires_action` with **no new work enqueued** — the session is wedged until an operator runs `work.stop(work_id, force=True)` and sends a `user.interrupt`.
We hit this in production twice in one day (different consumer exceptions, same terminal state) and separately counted 29 items stuck in `starting` with null heartbeats.
## Ask
Either of:
1. **Deferred ack**: an opt-in poller mode that acks after the consumer callback returns successfully (at-least-once semantics), or
2. **nack/fail API**: a way for a consumer to return a failed item to a re-pendable state.
Related, happy to file separately: documenting which work-item states re-enqueue on which user events (empirically: `stopped` re-pends on message/interrupt; a dead-heartbeat `started` item only after a server-side stop ~5 min later; an acked-but-never-heartbeated `starting` item never), and a server-side janitor for `starting` + null-heartbeat items older than a TTL.
## Workaround we ship today
Consumer retry + `work.stop(force=True)` on final failure, plus a scheduled sweep that force-stops stale `starting` items and interrupts sessions parked in `requires_action` with no live worker. Effective, but every self-hosted-environment consumer will independently rediscover this failure class — ack-after-success semantics in the SDK would eliminate it.
Guia de contribuição
Avaliação
Esta issue ainda não foi avaliada.