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

Abierto
#1,746 0 comentarios 0 reacciones 0 asignados Ver en GitHub
Lenguaje dominante
Python
Estrellas
3.9k
Forks
853
Merge medio
1 d 18 h
PR fusionados (30 d)
11

Descripción

`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.

Guía de contribución

Abrir la guía de contribución

Evaluación

Este issue todavía no se ha evaluado.

Recibe los nuevos issues en tu correo

Un resumen breve de issues de GitHub para principiantes.