Skip to content

feat(scheduler): land GPU arbiter on one VRAM authority + eviction/heartbeat fixes (#894/#185, continues #1718)#1859

Merged
jaylfc merged 19 commits into
devfrom
feat/gpu-arbiter-takeover
Jul 16, 2026
Merged

feat(scheduler): land GPU arbiter on one VRAM authority + eviction/heartbeat fixes (#894/#185, continues #1718)#1859
jaylfc merged 19 commits into
devfrom
feat/gpu-arbiter-takeover

Conversation

@jaylfc

@jaylfc jaylfc commented Jul 16, 2026

Copy link
Copy Markdown
Owner

Maintainer takeover of the GPU arbiter epic (#894), continuing @hognek's work on #1718 (his commits are preserved here). Rebased onto current dev and closes the #185 gate.

The #185 gate-closer: one VRAM authority

The arbiter shipped inert beside #1725's now-merged VramReservationManager, so there were two reservation ledgers that would double-count once submit_gpu got a caller. This makes the manager the single ledger:

  • The arbiter no longer keeps its own _reserved_vram_mb accounting. It reserves against the shared VramReservationManager that the model-load path (routes/models.py) already uses. app.py builds one manager and passes it into the arbiter, so GPU scheduler tasks and model loads draw from one authority and never double-count.
  • GpuArbiter.reserve_vram() / release_vram() is the arbiter front door for non-task callers.
  • Folding the manager in also retires the arbiter's own probe. The manager probes nvidia-smi in a worker thread (no event-loop stall while holding the reservation lock) and distinguishes no-probe (fail-open, cluster claim_lease parity) from known-full, which is the behaviour feat(models): atomic VRAM check-and-reserve before model load (TOCTOU fix, #1706) #1725 already reviewed and shipped.

Model-loads-as-evictable-submit_gpu-tasks (full queue/eviction semantics for pulls, so a pull that can't fit waits or evicts instead of 503) is intentionally a follow-up, not this PR: eviction of a user's running model is a product/policy decision, and this PR keeps model-pull behaviour identical to today. The one-ledger foundation here is the prerequisite for that upgrade.

Correctness fixes

  • Eviction ordering (Xid-62). _evict_task now cancels AND awaits the evicted task before releasing its reservation + lease, so an evict-to-make-room re-admission cannot pass against not-yet-reclaimed VRAM and start a second concurrent load. Regression test added.
  • Equal-priority eviction. evict_lowest_priority no longer preempts an equal-priority running task (strictly-lower priority only), avoiding eviction thrash between peers.
  • Draining worker heartbeat (Worker/cluster-node service: auto-update with graceful pause, install, restart #890). heartbeat() no longer flips a draining worker back to online; that had re-entered it into routing before its leases finished and defeated graceful drain. Regression test added.
  • /update deploy result. routes/cluster.py now raise_for_status() on the worker deploy response before reading it, so a failed deploy is not reported as status: "updating".

Tests

VramReservationManager gains an injectable probe for tests. The TOCTOU tests now assert against the shared ledger. Full local run green: arbiter (894 + toctou), cluster, leases, routes/cluster, routes/models, vram_reservation — 167 passed, plus the two new regression tests.

Supersedes #1718 (folds in #1690's worker-auto-update + lock fix via the same branch ancestry). #1725 and #1726 are already merged.

Summary by CodeRabbit

  • New Features
    • Added graceful and forced worker draining with drain cancellation and coordinated worker updates.
    • Introduced GPU-aware admission control using shared VRAM reservations, prioritization, and eviction to prevent overcommit.
    • Added GPU resource discovery and extended task resource estimates to include VRAM.
  • Bug Fixes
    • Ensured draining workers stay draining across heartbeats and are excluded from routing, catalog, and lease claims.
    • Improved GPU eviction/termination to reliably cancel work and release reservations without race conditions.
  • Tests
    • Added coverage for worker drain behavior and GPU admission/eviction, including reservation TOCTOU and async ordering.

hognek and others added 18 commits July 7, 2026 12:37
…ction

Add GpuArbiter module that wraps the resource Scheduler with GPU-specific
admission control to prevent concurrent-load driver crashes (Xid 62).

Key features:
- VRAM-aware admission: checks local VRAM probes and cluster worker
  leases before admitting GPU tasks
- Priority queue: pending GPU tasks wait when VRAM is insufficient,
  dequeued in priority order when resources free up
- Eviction: lower-priority running tasks can be evicted to make room
  for higher-priority work
- Lease integration: claims/releases GPU leases via ClusterManager
  for distributed coordination
- GPU resource registration: discovery.py now registers gpu-cuda-N
  resources when GPU backends are healthy
- GPU_POTENTIAL_CAPABILITIES constant for UI latent-capability display

19 unit tests pass. Builds on Slice 1 (VRAM endpoint, #893 leases).

Task: t_d7208884. Fixes #894.
… futures

_evict_task previously only cancelled the _arbiter_future on the Task
object. Directly-admitted tasks never have one set, and for queued-then-
admitted tasks the future is resolved by _drain_queue before _run_gpu_task
completes. In both cases the running GPU work keeps executing and VRAM is
never freed until natural completion.

Now _run_gpu_task registers its asyncio.Task in _running_tasks, and
_evict_task cancels that Task directly. CancelledError propagates to
the payload, the finally block cleans up VRAM/leases, and eviction
actually stops running GPU work.

- Added _running_tasks dict to track asyncio.Tasks
- _run_gpu_task registers current task and handles CancelledError
- _evict_task cancels the asyncio.Task (in addition to _arbiter_future)
- _drain_queue catches CancelledError from evicted tasks to keep
  queue processor alive
- Finally block checks if task was already evicted (entry popped)
  before releasing lease — prevents double-release

Fixes #894.
_evict_task now uses pop(task_id, None) atomically instead of the
check-then-pop pattern, eliminating the TOCTOU KeyError that could
occur when _run_gpu_task's finally block pops the entry between
the existence check and the pop.

Ownership is now explicit: whoever pops self._running first releases
the lease.  _evict_task's pop is the sole lease releaser for evicted
tasks; _run_gpu_task's finally only releases on normal completion
(when it still finds the entry).  Comments document the invariant.

Refs #894.  Task: t_0c86d08e.
…tible with stack

The file was incorrectly brought in from the #1689 version and references
VramAllocation, total_vram_mb, headroom_mb, and evict_callback that don't
exist in the stack's GpuArbiter.  test_gpu_arbiter_894.py covers the same
territory with the correct API.
…M reservation

_addmission_check read live nvidia-smi free VRAM with no reservation,
so two concurrent submit_gpu calls could both pass before either model
loaded — the exact concurrent-load crash (NVIDIA Xid 62) the arbiter
exists to prevent.

Add _reserved_vram_mb counter and _pending_reservations dict, guarded
by _reservation_lock.  _reserve_and_check() atomically checks admission
against (probe - _reserved_vram_mb) and reserves on success.  Reservations
are released in _run_gpu_task's finally block and in _evict_task.

_stats() now includes reserved_vram_mb and pending_reservations for
observability.

Fixes: PR #1683 review feedback #2
Tests: 168 pass (25 arbiter + 143 cluster/worker)
…ed VRAM in cluster path

taOS #1705 — reservation leak fix:
- Move lease claim and _running registration inside try block so the
  finally always releases the VRAM reservation, even on claim failure.
- Await claim_lease / release_lease (both are async on ClusterManager).
- Make _evict_task and evict_lowest_priority async (they await release_lease).
- Subtract _reserved_vram_mb in the cluster-mode admission path, matching
  the local-path behaviour so in-flight reservations are accounted for.
- Update tests: FakeClusterManager methods made async, all _evict_task and
  evict_lowest_priority calls now awaited.

Tests: 16/16 arbiter + 111/111 cluster pass.
…iter

_drain_queue no longer blocks on _run_gpu_task.  The admitted task is
spawned as a background asyncio Task with a done-callback that propagates
the result/exception to the submitter's _arbiter_future.  This keeps the
drain loop responsive on subsequent ticks so eviction-to-make-room can
kick in when higher-priority tasks arrive while VRAM is full.

Also adds eviction-to-make-room: when admission fails for a queued task
the drain now calls evict_lowest_priority with the task's priority as
the floor, then re-checks admission.  Lower-priority running tasks are
evicted to make room; higher-priority runners are left alone.

Queue processing remains intentionally serial — only one task is admitted
per drain cycle to avoid flooding the GPU with concurrent loads.

PR #1683 review feedback #4.
taOS #1706 — TOCTOU fix:
- After eviction-to-make-room frees VRAM, the re-admit in _drain_queue
  used _check_admission (no reservation) instead of _reserve_and_check
  (atomic reservation).  This spawned tasks with no reservation entry,
  re-opening the TOCTOU window and causing _reserved_vram_mb drift.
- Changed to _reserve_and_check so post-eviction admission is atomic
  and properly tracked.
- Also fixes the missing await on evict_lowest_priority (made async
  by the base #1705 fix) and sync _evict_task calls in new tests.

Tests: 22/22 arbiter pass.
…biter

Make evict_lowest_priority, _evict_task, release_tasks_for_worker,
stats, and running_tasks async and guard _running dict access with
_running_lock. Previously _running was mutated under the lock in
_run_gpu_task but read/popped without it in eviction paths — safe
under single-thread-no-await but fragile. Lease release and task
cancellation happen outside the lock to avoid deadlock.

Update all callers: drain_worker → async, route handlers, and tests.
69 targeted tests pass.
- Added vram_probe=_probe_nvidia_vram so the arbiter uses actual GPU
  VRAM instead of the default (0,0) probe that makes all checks pass.
- Wired cluster_manager._gpu_arbiter so eviction paths that access
  the cluster manager can reach the arbiter.
- Added gpu_arbiter.stop() call in shutdown sequence.
- Added _gpu_arbiter attribute to ClusterManager.__init__.
taOS #1707 — _running_lock coverage fix:
- _evict_task now holds _running_lock during the pop, preventing races
  with concurrent drain operations.
- stats() made async (needed for _running_lock consistency).
- All async call sites now properly awaited (evict_lowest_priority,
  _evict_task, stats, claim_lease, release_lease).

Tests: 22/22 arbiter pass.
…, restart

Adds drain_worker/cancel_drain to ClusterManager for graceful worker
detach without dropping inflight GPU tasks (taOS #890).

ClusterManager:
- drain_worker(name, graceful=True): worker enters 'draining' status,
  excluded from routing/catalog/lease claims. When graceful=False,
  all leases are force-released and worker is marked offline.
- cancel_drain(name): returns draining worker to 'online'.
- _monitor_loop: auto-completes drain when all leases released;
  force-finishes stale drains on heartbeat timeout.
- _worker_for_resource, get_workers_for_capability, aggregate_catalog:
  all exclude draining workers.

Routes:
- POST /api/cluster/workers/{name}/drain — begin graceful/force drain
- POST /api/cluster/workers/{name}/cancel-drain — cancel in-progress drain
- POST /api/cluster/workers/{name}/update — full auto-update orchestration:
  drain → deploy update-worker → restart → re-register

Tests: 9 new tests (25 total in test_cluster.py), 191 cluster tests pass.
Replace startswith(name + ':') with _parse_resource_id exact match
to prevent draining worker 'foo' from releasing leases belonging to
worker 'foo-bar' (same prefix-collision class as TOCTOU fixes).
taOS #796 features extracted from #1689 and rebased onto the arbiter stack:

1. pause/resume queue control: GpuArbiter.pause()/resume() halt/restart
   queue processing. Running tasks finish; queued tasks wait. Paused
   queue skips _drain_queue in _process_queue loop.

2. Hardware-aware LLM admission: submit_gpu() accepts required_gpu_arch
   (e.g. 'sm_86'). _check_gpu_arch_compatibility() checks cluster workers
   for compatible GPU hardware via model name and compute_cap field
   before admitting tasks.
…r_894

_evict_task was made async in commit 1789ad0 (running-lock fix).
Three cleanup calls were missing await.
…on force-release, wire arbiter task cancellation

#1690 lock fix: drain_worker force-release and _monitor_loop stale-drain
path now hold _lease_lock during self._leases mutation, serialized with
claim/release/sweep. drain_worker and cancel_drain are now async
(consistent with claim_lease/release_lease — all callers were already
in async handlers).

Cross-cutting wiring: added GpuArbiter.cancel_running_for_leases() and
wired it into drain_worker(graceful=False) and _monitor_loop stale-drain
path. When leases are force-released, any running GPU arbiter tasks
for those leases are cancelled via _evict_task — eviction is no longer
a prod no-op when the arbiter is running.

Added TYPE_CHECKING import for GpuArbiter type annotation on
ClusterManager._gpu_arbiter.
… (cancelled, already_completed) from cancel_running_for_leases

Addresses Kilo review findings on PR #1718:

CRITICAL: Race in stale-drain path — worker.status='offline' was set
after releasing _lease_lock, allowing duplicate worker.leave on next
monitor tick. Now set inside the lock (both drain_worker force-release
and _monitor_loop stale-drain paths).

WARNING: Same pattern in drain_worker(graceful=False) — fixed.

SUGGESTION: cancel_running_for_leases now returns
(cancelled, already_completed) so operators can distinguish force-kills
from natural completions. Callers log both counts.
Maintainer takeover of the arbiter epic (continues hognek's #1718). Merges
current dev and closes the #185 gate so there is ONE VRAM ledger:

- The arbiter no longer keeps its own reservation accounting. It reserves
  against the shared VramReservationManager (#1725) that the model-load path
  in routes/models.py already uses, so GPU scheduler tasks and model loads
  never double-count. app.py builds one manager and passes it into the
  arbiter; reserve_vram/release_vram is the arbiter front door for non-task
  callers. Folding the manager in also removes the arbiter's own probe: the
  manager probes via a worker thread (no event-loop stall) and distinguishes
  no-probe (fail-open, cluster-parity) from known-full.
- Eviction ordering (Xid-62): _evict_task now cancels AND awaits the task
  before releasing its reservation + lease, so a re-admission cannot pass
  against not-yet-reclaimed VRAM. Regression test added.
- evict_lowest_priority no longer preempts an equal-priority running task
  (strictly-lower only) to avoid eviction thrash.
- heartbeat() no longer flips a draining worker back to online (#890),
  which had defeated graceful drain. Regression test added.
- routes/cluster.py /update raises_for_status before reading the worker
  response so a failed deploy is not reported as status:"updating".

VramReservationManager gains an injectable probe for tests. TOCTOU tests
updated to assert against the shared ledger. Model-loads-as-evictable-
submit_gpu-tasks (fuller queue/eviction semantics) is deferred to a follow-up.

Docs-Reviewed: internal GPU-scheduler/cluster admission plumbing; no agent-facing API surface or route module added or removed.
@qodo-code-review

Copy link
Copy Markdown

Qodo reviews are paused for this user.

Troubleshooting steps vary by plan Learn more →

On a Teams plan?
Reviews resume once this user has a paid seat and their Git account is linked in Qodo.
Link Git account →

Using GitHub Enterprise Server, GitLab Self-Managed, or Bitbucket Data Center?
These require an Enterprise plan - Contact us
Contact us →

@coderabbitai

coderabbitai Bot commented Jul 16, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: fb5bddac-8a28-46a7-9f33-3914061ad401

📥 Commits

Reviewing files that changed from the base of the PR and between 6bf501a and 4839501.

📒 Files selected for processing (1)
  • tinyagentos/scheduler/gpu_arbiter.py
🚧 Files skipped from review as they are similar to previous changes (1)
  • tinyagentos/scheduler/gpu_arbiter.py

📝 Walkthrough

Walkthrough

Adds a VRAM-aware GPU arbiter with atomic reservation accounting, queued admission, priority eviction, and lease coordination. Integrates GPU discovery and application lifecycle wiring, and adds graceful worker draining, routing exclusion, lease cleanup, and admin update orchestration.

Changes

GPU arbitration and scheduler integration

Layer / File(s) Summary
VRAM admission and GPU eviction
tinyagentos/scheduler/gpu_arbiter.py, tinyagentos/scheduler/types.py, tinyagentos/vram_reservation.py, tests/test_gpu_arbiter_894.py, tests/test_gpu_arbiter_toctou.py
Adds VRAM-aware task admission, queued processing, priority eviction, lease handling, introspection, and tests for cancellation ordering and reservation races.
GPU discovery and application wiring
tinyagentos/scheduler/discovery.py, tinyagentos/scheduler/__init__.py, tinyagentos/app.py
Registers GPU resources, exports GpuArbiter, and manages shared arbiter startup and shutdown.

Worker draining and updates

Layer / File(s) Summary
Worker drain lifecycle and routing
tinyagentos/cluster/manager.py, tests/test_cluster.py
Adds graceful and force drain transitions, cancellation, monitor completion and timeout handling, heartbeat preservation, lease cleanup, and routing/catalog exclusion.
Drain and worker update endpoints
tinyagentos/routes/cluster.py
Adds admin endpoints for drain, cancel-drain, and update-worker orchestration with rollback on deployment failure.

Estimated code review effort: 5 (Critical) | ~120 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Admin
  participant ClusterRoutes
  participant ClusterManager
  participant Worker
  participant GpuArbiter
  Admin->>ClusterRoutes: POST drain or update request
  ClusterRoutes->>ClusterManager: drain_worker(name, graceful)
  ClusterManager->>Worker: set draining or offline status
  Worker-->>ClusterManager: heartbeat preserves draining status
  ClusterManager->>GpuArbiter: cancel tasks for released leases
  ClusterRoutes->>Worker: POST update-worker
  Worker-->>ClusterRoutes: deployment result
  ClusterRoutes->>ClusterManager: cancel_drain on deployment failure
Loading

Possibly related PRs

  • jaylfc/taOS#1687: Extends ClusterManager.heartbeat with GPU VRAM telemetry and lease handling.
  • jaylfc/taOS#1711: Changes cluster lease-claim status and locking behavior.
  • jaylfc/taOS#1725: Extends the VRAM reservation logic used by GPU arbitration.
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 66.67% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately reflects the main change: GPU arbiter VRAM authority with eviction and heartbeat fixes.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/gpu-arbiter-takeover

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@gitar-bot

gitar-bot Bot commented Jul 16, 2026

Copy link
Copy Markdown

Gitar is working

Gitar

Comment thread tinyagentos/scheduler/gpu_arbiter.py Outdated
gpu_info = worker.hardware.get("gpu", {}) if isinstance(worker.hardware, dict) else {}
gpu_model = gpu_info.get("model", "") or ""
cc = gpu_info.get("compute_cap", "") or ""
if required_gpu_arch in gpu_model or required_gpu_arch in cc:

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

WARNING: required_gpu_arch in gpu_model (and in cc) is a substring match that accepts the wrong architecture.

gpu_model/compute_cap are strings like "NVIDIA A100" or "8.0,8.6". A caller asking for sm_8 would match sm_80, sm_86, and sm_89 all at once, while sm_86 as a substring would also spuriously match a model containing sm_860. More importantly, this check can never actually succeed for the documented input: a compute capability like sm_86 is not a substring of a real compute_cap value such as 8.6 or a model string, so the feature as written will reject hardware that genuinely satisfies the requirement. Use exact/normalized comparison (parse compute_cap to a float/set, or require the caller to pass a comparable token) rather than in.

Comment thread tinyagentos/scheduler/gpu_arbiter.py Outdated
gpu_model = gpu_info.get("model", "") or ""
cc = gpu_info.get("compute_cap", "") or ""
if required_gpu_arch in gpu_model or required_gpu_arch in cc:
if resource_id is None or resource_id.startswith(worker.name + ":"):

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

WARNING: resource_id.startswith(worker.name + ":") reintroduces the worker-name prefix-collision bug this PR explicitly fixed elsewhere (commits 13/16 switched startswith(name + ":") to an exact _parse_resource_id compare to stop foo from matching foo-bar). A resource like foo-bar:gpu-cuda-0 would be matched against worker foo. Use _parse_resource_id(resource_id)[0] == worker.name for an exact-name comparison, consistent with the drain paths in manager.py.

victim_ids.append(task_id)
cancelled = 0
already_completed = 0
for task_id in victim_ids:

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SUGGESTION: cancel_running_for_leases conflates two distinct cases in already_completed: _evict_task returns 0 both when the task is not in _running (already finished or was never tracked, e.g. a lease whose task was not a GPU-arbiter task) and when eviction did nothing. So already_completed can over-count and the operator-facing log is misleading. Consider distinguishing "task found and evicted" vs "task not found in _running".

Suggested change
for task_id in victim_ids:
cancelled = 0
not_found = 0
for task_id in victim_ids:
if await self._evict_task(task_id):
cancelled += 1
else:
not_found += 1
if cancelled or not_found:
logger.info("gpu-arbiter: drain cancelled %d, not-in-running %d (leases=%d)",
cancelled, not_found, len(lease_ids))
return cancelled, not_found

@kilo-code-bot

kilo-code-bot Bot commented Jul 16, 2026

Copy link
Copy Markdown

Code Review Summary

Status: 1 New Issue Found | Recommendation: Address suggestion before merge

Overview

Severity Count
CRITICAL 0
WARNING 0
SUGGESTION 1
Issue Details (click to expand)

SUGGESTION

File Line Issue
tinyagentos/scheduler/gpu_arbiter.py 227 New _resource_on_worker couples to ClusterManager._parse_resource_id (private API); promote to public helper to avoid silent breakage on refactor
Previously reported, still open (unchanged-code, carried forward)
  • tinyagentos/scheduler/gpu_arbiter.py (SUGGESTION, line 469): cancel_running_for_leases over-counts already_completed_evict_task returning 0 means the task was already absent from _running, not naturally completed; the counter conflates the two.
Resolved since previous review (commit 4839501)
  • tinyagentos/scheduler/gpu_arbiter.py (WAS WARNING, line 211): required_gpu_arch in cc substring match fixed to exact required_gpu_arch == cc — no longer prefix-matches wrong architectures.
  • tinyagentos/scheduler/gpu_arbiter.py (WAS WARNING, line 212): resource_id.startswith(worker.name + ":") prefix-collision reintroduced elsewhere fixed via new _resource_on_worker helper using the cluster manager's exact parse.
Files Reviewed (incremental)
  • tinyagentos/scheduler/gpu_arbiter.py - 1 new issue (single fixup commit 4839501; 2 prior warnings resolved)

Fix these issues in Kilo Cloud

Previous Review Summary (commit 6bf501a)

Current summary above is authoritative. Previous snapshots are kept for context only.

Previous review (commit 6bf501a)

Status: 3 Issues Found | Recommendation: Address before merge

Overview

Severity Count
CRITICAL 0
WARNING 2
SUGGESTION 1
Issue Details (click to expand)

WARNING

File Line Issue
tinyagentos/scheduler/gpu_arbiter.py 211 _check_gpu_arch_compatibility uses substring in match for GPU arch — matches wrong architectures and cannot match the documented sm_XX token against real compute_cap values
tinyagentos/scheduler/gpu_arbiter.py 212 resource_id.startswith(worker.name + ":") reintroduces the worker-name prefix-collision bug this PR fixed elsewhere

SUGGESTION

File Line Issue
tinyagentos/scheduler/gpu_arbiter.py 453 cancel_running_for_leases over-counts already_completed (conflates "not in _running" with "evicted")
Files Reviewed (11 files)
  • tinyagentos/app.py
  • tinyagentos/cluster/manager.py
  • tinyagentos/routes/cluster.py
  • tinyagentos/scheduler/__init__.py
  • tinyagentos/scheduler/discovery.py
  • tinyagentos/scheduler/gpu_arbiter.py
  • tinyagentos/scheduler/types.py
  • tinyagentos/vram_reservation.py
  • tests/test_cluster.py
  • tests/test_gpu_arbiter_894.py
  • tests/test_gpu_arbiter_toctou.py

Fix these issues in Kilo Cloud


Reviewed by hy3:free · Input: 83.9K · Output: 7.3K · Cached: 886.4K

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 16

🧹 Nitpick comments (2)
tests/test_cluster.py (1)

75-90: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Cover heartbeats after a drain reaches "offline".

Add force-drain and graceful-completion cases that heartbeat afterward and assert the worker remains excluded. The current test only covers the intermediate "draining" state.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/test_cluster.py` around lines 75 - 90, Add coverage alongside
test_heartbeat_does_not_reonline_draining_worker for workers whose drain
completes as "offline": create force-drain and graceful-completion cases,
heartbeat each worker afterward, and assert heartbeat succeeds without changing
the offline status or re-enabling routing. Reuse the existing ClusterManager and
worker-drain APIs and preserve the current intermediate "draining" test.
tests/test_gpu_arbiter_894.py (1)

360-414: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add an equal-priority non-eviction regression test.

The PR’s strict-priority boundary is important, but the suite only covers higher-versus-lower priority tasks.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/test_gpu_arbiter_894.py` around lines 360 - 414, Extend
TestEvictLowestPriority with an async regression test that starts two running
GPU tasks having equal Priority values, invokes evict_lowest_priority, and
verifies that no task is evicted and both remain in arbiter._running. Reuse the
existing blocking-task setup and ensure both tasks are cleaned up afterward.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@tests/test_gpu_arbiter_894.py`:
- Around line 541-575: Update the _QueuedGpuTask entry in
test_eviction_triggered_when_vram_full so its evictable value matches the test’s
expected eviction behavior; set evictable to True and preserve the existing
eviction assertions.

In `@tests/test_gpu_arbiter_toctou.py`:
- Around line 29-58: Extend test_concurrent_reserve_and_check_only_one_admitted
to exercise the no-local-probe ClusterManager admission path, configuring a
worker with 8192 MiB capacity and two concurrent 5000 MiB requests. Assert
exactly one request is admitted and the cluster worker’s reserved capacity
reflects only that single reservation.

In `@tinyagentos/app.py`:
- Around line 1183-1196: Update the GPU arbiter startup flow around GpuArbiter
and await gpu_arbiter.start() so it never starts when resource_scheduler is
unavailable. Skip initialization and retain the existing fallback behavior, or
provide the actual vanilla scheduler instance instead of passing None; ensure
_run_gpu_task always receives a valid scheduler.

In `@tinyagentos/cluster/manager.py`:
- Around line 212-218: Update the worker heartbeat logic around the status
assignment to preserve administrative drain intent independently of transient
status, keeping workers offline after both forced drains and completed graceful
drains rather than re-onlining them. In tinyagentos/cluster/manager.py lines
212-218, use the existing drain state or an equivalent persistent marker when
deciding whether to set status to "online"; add heartbeat-after-force-drain and
heartbeat-after-graceful-completion regression coverage in tests/test_cluster.py
lines 75-90.

In `@tinyagentos/routes/cluster.py`:
- Around line 1218-1229: Update the worker update flow around
cluster.drain_worker to wait for graceful drain completion, using an appropriate
timeout, before creating the deploy request to /api/worker/deploy. Preserve the
existing error response handling and only trigger deployment after active leases
have finished draining.
- Around line 1212-1219: Update the worker deployment rollback flow around the
status check and cluster.drain_worker call to record whether the worker was
already draining before this request. On deployment failure, call cancel_drain()
only when this request initiated the drain, and derive drain_cancelled from the
actual cancellation result rather than setting it unconditionally; preserve
pre-existing drains.

In `@tinyagentos/scheduler/discovery.py`:
- Line 18: Remove the direct _probe_nvidia_vram dependency from scheduler
discovery and obtain capacity from an injected shared-manager snapshot instead.
Ensure scheduler admission uses the arbiter ledger’s reservation-aware values
across NVIDIA, ROCm, and Metal, and represents unknown capacity explicitly
rather than converting probe failures into fictitious free VRAM.
- Around line 190-202: Update the GPU discovery logic around gpu_backend_types
and the gpu_backends comprehension to use an explicit backend acceleration
signal from each catalog backend, rather than treating backend type alone as GPU
evidence. Ensure CPU-only Ollama is excluded while CUDA-enabled llama-cpp is
included, and reuse this acceleration-based result for GPU tier registration and
capability lookup.

In `@tinyagentos/scheduler/gpu_arbiter.py`:
- Around line 252-254: Update the reservation lifecycle in the task execution
flow around _vram.reserve and the corresponding cleanup block so capacity holds
remain valid for the entire task lifetime. Use a renewable hold or a
non-expiring runtime reservation, and ensure the reservation is released in
finally for both normal completion and failure paths.
- Around line 338-342: Update the queue-draining logic in _drain_queue to detect
and remove entries whose awaited future was cancelled before admitting or
starting GPU work. Apply the same guard to the corresponding queue-entry
handling near the other referenced block, while preserving the existing _evicted
increment and CancelledError propagation in the submitter path.
- Around line 48-57: Preserve the task’s requested resource_id throughout
queueing and dispatch: add it to _QueuedGpuTask, populate it when creating
queued entries, and pass it when the queued task is later executed instead of
using None. Update the related queue handling paths noted in the comment while
keeping existing lease behavior unchanged.
- Around line 262-264: The cluster admission path in
GpuArbiter._check_cluster_admission must atomically reserve capacity or claim
the worker lease instead of returning a snapshot-based decision; update
tinyagentos/scheduler/gpu_arbiter.py lines 262-264 accordingly. Add concurrent
no-local-probe coverage in tests/test_gpu_arbiter_toctou.py lines 29-58
verifying that only one request acquires the worker’s available VRAM.
- Around line 548-555: The queue’s eviction-to-make-room path ignores each
entry’s eviction policy. In tinyagentos/scheduler/gpu_arbiter.py lines 548-555,
gate the evict_lowest_priority call and subsequent admission retry on
entry.evictable. In tests/test_gpu_arbiter_894.py lines 541-575, set
evictable=True for the eviction scenario and add an assertion covering
evictable=False to verify eviction is skipped.
- Around line 246-252: Update the GPU admission flow around _vram.available_vram
and reserve so ledger access remains on the event-loop thread. Add an async
manager operation that acquires the manager lock, runs only the blocking
hardware probe in a worker thread, and performs the _pending sweep and
reservation atomically under that lock; replace the direct
asyncio.to_thread(self._vram.available_vram) call with this operation while
preserving the no-local-GPU behavior.
- Around line 205-207: Update the worker eligibility check in the scheduler
admission logic to skip workers whose status is "draining", alongside other
non-online workers. Ensure only workers with status "online" can satisfy
architecture admission, while preserving the existing iteration and filtering
behavior.

In `@tinyagentos/vram_reservation.py`:
- Line 77: Import Callable from typing so the probe annotation in the relevant
reservation API resolves correctly for Ruff and typing.get_type_hints().

---

Nitpick comments:
In `@tests/test_cluster.py`:
- Around line 75-90: Add coverage alongside
test_heartbeat_does_not_reonline_draining_worker for workers whose drain
completes as "offline": create force-drain and graceful-completion cases,
heartbeat each worker afterward, and assert heartbeat succeeds without changing
the offline status or re-enabling routing. Reuse the existing ClusterManager and
worker-drain APIs and preserve the current intermediate "draining" test.

In `@tests/test_gpu_arbiter_894.py`:
- Around line 360-414: Extend TestEvictLowestPriority with an async regression
test that starts two running GPU tasks having equal Priority values, invokes
evict_lowest_priority, and verifies that no task is evicted and both remain in
arbiter._running. Reuse the existing blocking-task setup and ensure both tasks
are cleaned up afterward.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: dc4c1935-808c-40ec-98d4-ffe7613d7fee

📥 Commits

Reviewing files that changed from the base of the PR and between 6353277 and 6bf501a.

📒 Files selected for processing (11)
  • tests/test_cluster.py
  • tests/test_gpu_arbiter_894.py
  • tests/test_gpu_arbiter_toctou.py
  • tinyagentos/app.py
  • tinyagentos/cluster/manager.py
  • tinyagentos/routes/cluster.py
  • tinyagentos/scheduler/__init__.py
  • tinyagentos/scheduler/discovery.py
  • tinyagentos/scheduler/gpu_arbiter.py
  • tinyagentos/scheduler/types.py
  • tinyagentos/vram_reservation.py

Comment on lines +541 to +575
async def test_eviction_triggered_when_vram_full(self):
"""If VRAM is full, drain should evict a lower-priority task to make room."""
arbiter = GpuArbiter(
vram_probe=lambda: (
(1024, 8192) if len(arbiter_ctx["ref"]._running) > 0 else (8192, 8192)
),
max_queue_size=10,
)
arbiter_ctx: dict[str, object] = {"ref": arbiter}

# Put a low-priority task in _running (simulating an already-running task)
low_task = _make_task("low-running", vram_mb=4096, priority=Priority.BATCH)
low_task.payload = lambda r: asyncio.sleep(60)
arbiter._running["low-running"] = (low_task, None, int(Priority.BATCH), 4096)

# Now queue a higher-priority task that needs VRAM
hi_task = _make_task("hi-queued", vram_mb=4096, priority=Priority.INTERACTIVE_USER)
hi_started = asyncio.Event()
hi_release = asyncio.Event()

async def hi_payload(_resource):
hi_started.set()
await hi_release.wait()
return {"ok": True}

hi_task.payload = hi_payload

loop = asyncio.get_running_loop()
arb_future: asyncio.Future = loop.create_future()
hi_task._arbiter_future = arb_future # type: ignore[attr-defined]

entry = _QueuedGpuTask(
priority=int(hi_task.priority), seq=1, task=hi_task,
required_vram_mb=4096, evictable=False,
)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Align this test with the evictable contract.

It sets evictable=False but expects the queued task to trigger eviction. Set it to True, or change the assertion to require no eviction.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/test_gpu_arbiter_894.py` around lines 541 - 575, Update the
_QueuedGpuTask entry in test_eviction_triggered_when_vram_full so its evictable
value matches the test’s expected eviction behavior; set evictable to True and
preserve the existing eviction assertions.

Comment on lines +29 to +58
async def test_concurrent_reserve_and_check_only_one_admitted(self):
"""Two concurrent _reserve_and_check calls — only first admitted.

With 8192 MiB free and each needing 5000 MiB, the second
_reserve_and_check must see the first's reservation in the shared
ledger and be rejected. The manager's atomic reserve closes the
TOCTOU gap.
"""
arbiter = GpuArbiter(
vram_probe=lambda: (8192, 8192),
max_queue_size=10,
)

results: list[bool] = []

async def try_reserve(task_id):
admission = await arbiter._reserve_and_check(task_id, 5000)
results.append(admission.admitted)

# Launch both concurrently — this is the TOCTOU window.
await asyncio.gather(
try_reserve("t-a"),
try_reserve("t-b"),
)

admitted_count = sum(results)
assert admitted_count == 1, (
f"Expected exactly 1 admission, got {admitted_count}: {results}"
)
assert arbiter._vram.reserved_vram_mb == 5000

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

Exercise the cluster admission branch concurrently.

This test only covers the local manager’s atomic reserve path. Add a no-local-probe ClusterManager case proving two concurrent admissions cannot both claim the same worker capacity.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/test_gpu_arbiter_toctou.py` around lines 29 - 58, Extend
test_concurrent_reserve_and_check_only_one_admitted to exercise the
no-local-probe ClusterManager admission path, configuring a worker with 8192 MiB
capacity and two concurrent 5000 MiB requests. Assert exactly one request is
admitted and the cluster worker’s reserved capacity reflects only that single
reservation.

Comment thread tinyagentos/app.py
Comment on lines +1183 to +1196
gpu_arbiter = GpuArbiter(
scheduler=resource_scheduler if resource_scheduler is not None else None,
cluster_manager=cluster_manager,
vram_reservation=vram_reservation,
max_queue_size=100,
eviction_enabled=True,
)
await gpu_arbiter.start()
app.state.gpu_arbiter = gpu_arbiter
cluster_manager._gpu_arbiter = gpu_arbiter
logger.info("GPU arbiter ready (queue size=100, eviction=enabled, shared VRAM ledger)")
except Exception:
logger.exception("GPU arbiter failed to start — GPU tasks will use vanilla scheduler")
app.state.gpu_arbiter = None

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Do not start the production arbiter without a resource scheduler.

When scheduler construction fails, this passes None; _run_gpu_task then calls task.payload(None) rather than using the claimed “vanilla scheduler” fallback. Resource-dependent payloads can fail or bypass routing.

Skip arbiter startup in this case or inject the actual fallback scheduler.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tinyagentos/app.py` around lines 1183 - 1196, Update the GPU arbiter startup
flow around GpuArbiter and await gpu_arbiter.start() so it never starts when
resource_scheduler is unavailable. Skip initialization and retain the existing
fallback behavior, or provide the actual vanilla scheduler instance instead of
passing None; ensure _run_gpu_task always receives a valid scheduler.

Comment on lines +212 to +218
# A worker mid graceful-drain keeps heartbeating while it finishes
# inflight work. Do NOT flip it back to "online" — that would re-enter
# it into routing/catalog/lease-claims before its leases drain and
# defeat the drain (taOS #890). Leave the drain to complete or be
# cancelled explicitly; every other status re-onlines as before.
if worker.status != "draining":
worker.status = "online"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Keep administratively drained workers offline across heartbeats. Completed and forced drains currently lose their intent once status becomes "offline".

  • tinyagentos/cluster/manager.py#L212-L218: preserve drain intent independently of the transient worker status.
  • tests/test_cluster.py#L75-L90: add heartbeat-after-force-drain and heartbeat-after-graceful-completion regressions.
📍 Affects 2 files
  • tinyagentos/cluster/manager.py#L212-L218 (this comment)
  • tests/test_cluster.py#L75-L90
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tinyagentos/cluster/manager.py` around lines 212 - 218, Update the worker
heartbeat logic around the status assignment to preserve administrative drain
intent independently of transient status, keeping workers offline after both
forced drains and completed graceful drains rather than re-onlining them. In
tinyagentos/cluster/manager.py lines 212-218, use the existing drain state or an
equivalent persistent marker when deciding whether to set status to "online";
add heartbeat-after-force-drain and heartbeat-after-graceful-completion
regression coverage in tests/test_cluster.py lines 75-90.

Comment on lines +1212 to +1219
if worker.status not in ("online", "draining"):
return JSONResponse(
{"error": f"Worker '{name}' is not online (status={worker.status})"},
status_code=400,
)

# Step 1: Begin draining
drain_result = await cluster.drain_worker(name, graceful=True)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Do not undo a pre-existing drain on deployment failure.

The endpoint accepts an already-draining worker, then unconditionally calls cancel_drain(), re-admitting it after failure. Only roll back drains initiated by this request, and derive drain_cancelled from the actual rollback result.

Also applies to: 1236-1241

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tinyagentos/routes/cluster.py` around lines 1212 - 1219, Update the worker
deployment rollback flow around the status check and cluster.drain_worker call
to record whether the worker was already draining before this request. On
deployment failure, call cancel_drain() only when this request initiated the
drain, and derive drain_cancelled from the actual cancellation result rather
than setting it unconditionally; preserve pre-existing drains.

Comment on lines +252 to +254
reservation = await self._vram.reserve(required_vram_mb, caller=f"gpu-task:{task_id}")
if reservation is not None:
self._pending_reservations[task_id] = reservation.reservation_id

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟠 Major | 🏗️ Heavy lift

Renew capacity holds for the full task lifetime.

VRAM reservations expire through the manager TTL, while remote leases expire after 300 seconds. Neither is renewed, so a longer task can remain GPU-resident after its accounting has been reclaimed, allowing over-admission.

Use renewable holds or non-expiring runtime reservations that are released in finally.

Also applies to: 394-397

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tinyagentos/scheduler/gpu_arbiter.py` around lines 252 - 254, Update the
reservation lifecycle in the task execution flow around _vram.reserve and the
corresponding cleanup block so capacity holds remain valid for the entire task
lifetime. Use a renewable hold or a non-expiring runtime reservation, and ensure
the reservation is released in finally for both normal completion and failure
paths.

Comment on lines +262 to +264
# No local GPU probe: try the cluster.
if self._cluster_manager is not None:
return self._check_cluster_admission(required_vram_mb)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift

Cluster admission is not atomic with capacity ownership.

  • tinyagentos/scheduler/gpu_arbiter.py#L262-L264: reserve target capacity or claim its lease within the admission operation instead of returning a snapshot result.
  • tests/test_gpu_arbiter_toctou.py#L29-L58: add concurrent no-local-probe coverage proving only one request acquires the worker’s available VRAM.
📍 Affects 2 files
  • tinyagentos/scheduler/gpu_arbiter.py#L262-L264 (this comment)
  • tests/test_gpu_arbiter_toctou.py#L29-L58
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tinyagentos/scheduler/gpu_arbiter.py` around lines 262 - 264, The cluster
admission path in GpuArbiter._check_cluster_admission must atomically reserve
capacity or claim the worker lease instead of returning a snapshot-based
decision; update tinyagentos/scheduler/gpu_arbiter.py lines 262-264 accordingly.
Add concurrent no-local-probe coverage in tests/test_gpu_arbiter_toctou.py lines
29-58 verifying that only one request acquires the worker’s available VRAM.

Comment on lines +338 to +342
try:
return await done
except asyncio.CancelledError:
self._evicted += 1
raise

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Discard cancelled queue entries before starting GPU work.

Cancelling the submitter cancels its awaited future but leaves the entry queued. _drain_queue later admits and starts that task even though nobody can receive its result.

Proposed guard
         while not self._queue.empty() and not drained:
             entry = self._queue.get_nowait()
+            future = getattr(entry.task, "_arbiter_future", None)
+            if future is not None and future.done():
+                continue
             admission = await self._reserve_and_check(
                 entry.task.id, entry.required_vram_mb
             )

Also applies to: 545-557

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tinyagentos/scheduler/gpu_arbiter.py` around lines 338 - 342, Update the
queue-draining logic in _drain_queue to detect and remove entries whose awaited
future was cancelled before admitting or starting GPU work. Apply the same guard
to the corresponding queue-entry handling near the other referenced block, while
preserving the existing _evicted increment and CancelledError propagation in the
submitter path.

Comment on lines +548 to +555
# Try eviction-to-make-room for higher-priority queued tasks.
# evict_lowest_priority(min_priority=N) skips running tasks
# whose priority value is *lower* than N (i.e. tasks that are
# actually higher priority), so only lower-or-equal priority
# running tasks are candidates for eviction.
evicted = await self.evict_lowest_priority(min_priority=int(entry.task.priority))
if evicted > 0:
admission = await self._reserve_and_check(entry.task.id, entry.required_vram_mb)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

The queue ignores the caller’s eviction policy.

  • tinyagentos/scheduler/gpu_arbiter.py#L548-L555: gate eviction-to-make-room on entry.evictable.
  • tests/test_gpu_arbiter_894.py#L541-L575: use evictable=True when expecting eviction, and add a false-case assertion.
📍 Affects 2 files
  • tinyagentos/scheduler/gpu_arbiter.py#L548-L555 (this comment)
  • tests/test_gpu_arbiter_894.py#L541-L575
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tinyagentos/scheduler/gpu_arbiter.py` around lines 548 - 555, The queue’s
eviction-to-make-room path ignores each entry’s eviction policy. In
tinyagentos/scheduler/gpu_arbiter.py lines 548-555, gate the
evict_lowest_priority call and subsequent admission retry on entry.evictable. In
tests/test_gpu_arbiter_894.py lines 541-575, set evictable=True for the eviction
scenario and add an assertion covering evictable=False to verify eviction is
skipped.

def __init__(
self,
ttl_seconds: float = DEFAULT_RESERVATION_TTL_SECONDS,
probe: "Callable[[], tuple[int, int] | None] | None" = None,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

file="tinyagentos/vram_reservation.py"

echo "== outline =="
ast-grep outline "$file" --view expanded || true

echo
echo "== top of file =="
sed -n '1,120p' "$file" | cat -n

echo
echo "== typing imports/usages =="
rg -n "from typing|Callable|get_type_hints|__future__|annotations" "$file" || true

Repository: jaylfc/taOS

Length of output: 6351


🏁 Script executed:

#!/bin/bash
set -euo pipefail

sed -n '1,140p' tinyagentos/vram_reservation.py | cat -n

Repository: jaylfc/taOS

Length of output: 6623


Import Callable for the probe annotation. Ruff will flag the unresolved name, and typing.get_type_hints() will fail to resolve it.

🧰 Tools
🪛 Ruff (0.15.21)

[error] 77-77: Undefined name Callable

(F821)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tinyagentos/vram_reservation.py` at line 77, Import Callable from typing so
the probe annotation in the relevant reservation API resolves correctly for Ruff
and typing.get_type_hints().

Source: Linters/SAST tools

- _check_gpu_arch_compatibility matches compute_cap exactly so sm_8 cannot
  prefix-match sm_86 (keeps substring match on the freeform model string).
- Add _resource_on_worker using the cluster manager's exact _parse_resource_id
  so gpu-node does not collide with gpu-node-2 (same guard #1726 applied to the
  lease paths); used by the arch check and cluster admission.
@jaylfc

jaylfc commented Jul 16, 2026

Copy link
Copy Markdown
Owner Author

Addressed the Kilo review in 4839501:

  • WARNING (arch substring): compute_cap is now matched exactly (sm_8 can no longer prefix-match sm_86); the freeform model string keeps a substring match.
  • WARNING (resource prefix collision): added _resource_on_worker using the cluster manager's exact _parse_resource_id so gpu-node cannot match gpu-node-2, the same guard fix(cluster): release GPU leases on worker unregister (fixes #1705) #1726 applied on the lease paths; used by both the arch check and cluster admission.
  • SUGGESTION (cancel_running_for_leases already_completed count): left as-is; it is a telemetry counter and conflating not-running with evicted there does not affect behavior.
    Full suite green locally (arbiter + cluster + leases + routes/cluster + routes/models + vram_reservation).

#1726 fixed on the lease paths); falls back to a colon-delimited match.
"""
if self._cluster_manager is not None:
parsed = self._cluster_manager._parse_resource_id(resource_id)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SUGGESTION: Coupling to a private cluster-manager API

_resource_on_worker calls self._cluster_manager._parse_resource_id(...) — a name-mangled-private method on ClusterManager. This works today, but reaching into another component's private API means a refactor of _parse_resource_id (or its return shape) will silently break arch/resource matching here with no static signal.

Consider promoting parse_resource_id to a public method on ClusterManager (it is already used internally by _worker_for_resource), or exposing a dedicated resource_on_worker(resource_id, worker_name) helper on the manager so the arbiter does not depend on its internals.


Reply with @kilocode-bot fix it to have Kilo Code address this issue.

@jaylfc jaylfc merged commit 35ddc1e into dev Jul 16, 2026
9 checks passed
@jaylfc jaylfc deleted the feat/gpu-arbiter-takeover branch July 16, 2026 23:00
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.

2 participants