Skip to content

0.2.14 dapi followups: Host Events Long-Poll & ML-Node Capacity Fallback#1443

Open
a-kuprin wants to merge 9 commits into
devshard-0.2.14-v4from
ak/0.2.14-dapi-followups
Open

0.2.14 dapi followups: Host Events Long-Poll & ML-Node Capacity Fallback#1443
a-kuprin wants to merge 9 commits into
devshard-0.2.14-v4from
ak/0.2.14-dapi-followups

Conversation

@a-kuprin

@a-kuprin a-kuprin commented Jul 11, 2026

Copy link
Copy Markdown
Collaborator

Summary

Standalone devshardd no longer depends on continuous DAPI and inference node reachability for two critical paths: warming escrow sessions and bounding direct-HTTP fallback when AcquireMLNode fails. DAPI listens for chain escrow and maintenance events (scheduled / canceled), publishes them plus per-escrow acquire load over a long-poll RPC (GetHostEvents); devshardd consumes them and gates fallback concurrency with observed node capacity.

This branch builds on upgrade-0.2.14 follow-ups: the long-poll + capacity feature commits, plus hardening for correctness under outages, high load, and unrelated event churn.

Motivation

When inference node or DAPI is slow or unreachable:

  1. Cold escrows — sessions that only exist after a chain query / lazy create
    miss traffic until something warms them.
  2. Unbounded fallbackdoWithFallbackNodes can hammer ML nodes with no
    awareness of broker locks or how many active escrows share a node.

When the inference node (DAPI path) is not available, there is also no
request-time lazy-init of escrow sessions: the client cannot round-trip through
DAPI to create/warm a session on demand. Instead devshardd actively
creates sessions from polled escrow-created host events, so traffic can proceed
on the direct-HTTP fallback path without waiting for a lazy create that cannot
happen.

The design observes capacity and load while DAPI is healthy, then uses that
observation to bound fallback during an outage — without inventing a second
control plane.

Architecture (conceptual)

  chain events ──► DAPI HostEventRing ──► GetHostEvents (long-poll)
                         │                      │
                         │                      ▼
                  EscrowLoadTracker      standalone hostevents.Run
                  (EMA rates)                   │
                         │              ┌───────┴────────┐
                         └──────────────► LoadMap        │
                                        │                ▼
                         ListNodeCapacity ──► NodeCapacityCache
                                        │                │
                                        └──────► doWithFallbackNodes
                                                 (TryAcquire / Unknown)

Host events. DAPI’s event listener appends escrow created/settled and
maintenance scheduled/canceled into a bounded HostEventRing. Clients
long-poll GetHostEvents with a subscribe set and cursor — so maintenance
updates are available the same way as escrow updates (clients that only care
about escrows subscribe to escrow kinds and are not woken by maintenance).
devshardd warms / tears down sessions from escrow events and refreshes its
load map from every reply (including unchanged timeouts).

Capacity fallback. While DAPI is up, devshardd polls ListNodeCapacity
and consumes escrow_load. Fallback slots are
effectiveMax − chargedLocks − localInFlight, with
effectiveMax = maxConcurrent / max(activeEscrows, 4). When DAPI is down,
last-seen capacity is retained (stale) and the local semaphore still bounds
concurrency.

Behavior changes

Area Behavior
Warm path Escrow events warm sessions without a request-time chain round-trip
Maintenance DAPI listens for maintenance scheduled/canceled and serves them via the same long-poll
Long-poll wake Waiters wake only on subscribed kinds — maintenance churn cannot reset an escrow client's deadline or stall escrow_load delivery
Fallback bound Enforced when capacity has been observed; old DAPI (Unimplemented) stays unbounded
Unknown nodes Nodes returned by PickNode but never listed in capacity get a synthetic budget (UnknownMaxConcurrent / Divisor()), not an unbounded bypass
Chain blips Transient chain query failures map to HTTP 503 (chain_unavailable); true not-found stays a hard error
Load tracking Per-escrow rates use a decaying EMA (O(escrows)), not a 30-minute timestamp list (O(RPS × window))
Event ring Fixed-capacity ring buffer — overwrite in place on wrap (no sliding-slice growth / GC churn)

@a-kuprin a-kuprin changed the title Ak/0.2.14 dapi followups 0.2.14 dapi followups: Host Events Long-Poll & ML-Node Capacity Fallback Jul 11, 2026
@a-kuprin a-kuprin mentioned this pull request Jul 11, 2026
@a-kuprin a-kuprin changed the base branch from upgrade-v0.2.14 to devshard-0.2.14-v4 July 12, 2026 11:16
akup and others added 9 commits July 12, 2026 18:59
…and testenv

Squash the pixelplex refactoring branch onto upgrade-0.2.14. Prior history
replayed already-merged 0.2.13/observability commits on top of upgrade,
producing 136 duplicate SHAs; keep only the net tree vs upgrade-0.2.14.

Co-authored-by: Cursor <cursoragent@cursor.com>
Fail closed on shared Postgres with boot migration, tighten leases/paths/
gateway FS checks, optional chain gRPC TLS, and share one chain client;
also private-by-default edge verify/debug routes and related testenv/proxy
wiring.
…lback

Cache chain escrow metadata on GetHostEvents without CreateSession so
versiond children share Postgres safely; bind lazily on the requested
runtime version. Add ListNodeCapacity/escrow_load to bound passive fallback.
@a-kuprin a-kuprin force-pushed the ak/0.2.14-dapi-followups branch from f1d6c4d to 16a4ced Compare July 14, 2026 11:50
@tcharchian tcharchian added this to the v0.2.14 milestone Jul 14, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants