Repository navigation
feat: add a status view for task polling and honor limit on GET /tasks - #227
Merged
Merged
Conversation
kaiitunnz
force-pushed
the
fix/225-task-status-view
branch
from
October 17, 2026 04:36
7285b7c to
710ae20
Compare
) GET /api/v1/tasks/{id} returns the full task: spec plus its YAML source, ~230 KB each for a Lumilake inference task, so a poller reading one status field pulled ~0.5-1 MB per call. GET /api/v1/tasks ignored `limit` (unknown key -> no-op), so `limit=100` returned every task: 240+ MB on the home cluster on 2026-10-12, the day status polling under load preceded refused connections and 502s. Server: - `?view=status` on GET /tasks/{id} and GET /tasks returns TaskStatusView: status, placement, timing, errors. Default stays `full` (compatible). - GET /tasks honours `limit` (after filtering). view/limit are not treated as model-field filters. SDK: - tasks.status(id) -> TaskStatusView (sync + async). Works against older servers too: they ignore ?view and the slim model ignores extra fields. - tasks.wait() polls status() and fetches the full task once at the end, so its return type is unchanged. - tasks.list(limit=...). Tests: router (status view omits spec/source and matches full on shared keys; limit; bad view -> 422) and SDK/server parity for TaskStatusView. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Signed-off-by: Noppanat Wadlom <noppanat.wad@gmail.com>
Signed-off-by: Noppanat Wadlom <noppanat.wad@gmail.com>
Signed-off-by: Noppanat Wadlom <noppanat.wad@gmail.com>
Signed-off-by: Noppanat Wadlom <noppanat.wad@gmail.com>
Signed-off-by: Noppanat Wadlom <noppanat.wad@gmail.com>
…y name Signed-off-by: Noppanat Wadlom <noppanat.wad@gmail.com>
Signed-off-by: Noppanat Wadlom <noppanat.wad@gmail.com>
Signed-off-by: Noppanat Wadlom <noppanat.wad@gmail.com>
kaiitunnz
force-pushed
the
fix/225-task-status-view
branch
from
October 17, 2026 05:43
710ae20 to
950e6d5
Compare
Signed-off-by: Noppanat Wadlom <noppanat.wad@gmail.com>
Signed-off-by: Noppanat Wadlom <noppanat.wad@gmail.com>
7 of 8 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Purpose
Relates to #225. Status pollers overload the server:
GET /api/v1/tasks/{id}returns the task's spec plus its YAMLsource, ~230 KB each for a Lumilake inference task. A poller that only readsstatuspulls ~0.5–1 MB per call.GET /api/v1/tasksignoredlimit. It was treated as an unknown filter key, solimit=100returned every task: 240+ MB on the home cluster on 2026-10-12. That was the day heavy status polling preceded refused connections and 502s.With this PR, a poller can ask for a task's status view, which holds no spec or source.
limitkeeps the newest matching tasks, and the SDK and CLI pollers use the status view.Changes
?view=statusonGET /tasks/{id}andGET /tasksreturns aTaskStatusView(src/server/schemas/task.py). It holds status, placement, timing, errors and the task's latest update (SSH/serve info, private keys stripped). Each field has the type, description and requiredness of itsTaskInfocounterpart. The default view staysfull.limit.GET /taskslists tasks oldest submission first.limit(1–10000) keeps the newest matching tasks, applied after filtering and permissions. Neitherviewnorlimitis treated as a field filter.tasks.status(id)andtasks.list_statuses(...)return status views, sync and async.tasks.list(limit=...).tasks.wait_status()pollsstatus()until the task is terminal and returns its status view, sync and async.tasks.wait()waits the same way and then fetches the full task once; its return type is unchanged.tasks.ssh_info()andtasks.wait_for_ssh()poll the status view.flowmesh task watch,flowmesh task stopandflowmesh sshpoll the status view.task watchstill prints the full task at the end;task stopandsshnever fetch it.mcp1.28.1 → 1.30.0 (worker CPU runtime) for CVE-2026-59951, which failspip-auditonmaintoo.docs/API.mdanddocs/SDK.mdcoverview,limit,tasks.status(),tasks.list_statuses()andtasks.wait_status(). The cursor-pagination notes indocs/API.md,docs/SDK.mdanddocs/ARCHITECTURE.mdcover log queries only, the routes that takebefore/after, and describe a cursor as the opaqueentries[].cursorstring.Design
The status view is opt-in, because the SDK's
TaskInforequiressourceandtask, and making the slim view the default would break every released SDK. An older server ignoresviewand returns the full task, which validates as a status view, so the new SDK calls work against any server.limitreturns the newest tasks, ordered by submission time and then id. That matches the log routes, wherelimitwithout a cursor returns the newest entries. Without cursors, a lower cap would leave no way to fetch more than the cap, solimitcaps at 10000, as the log routes do.kaiitunnz#64 adds cursor pages for
/tasksand/workflows, but as a breaking{entries, next_cursor, prev_cursor}response. This PR adopts its listing order and its newest-firstlimit. A port of the cursor pages belongs in its own PR.These parts of #225 are left for later:
GET /tasksstill returns full tasks, including bothsourceand its deprecatedraw_yamlcopy.POST /tasks/status).The load drops once Lumilake's
FlowMeshRuntimeManager.fetch_task_status, which callsfm.tasks.retrieve()and reads onlystatus,errorandlast_error, switches tofm.tasks.status()after this SDK is released.Test Plan
Test Result
uv run pre-commit run --all-files: passed (gitleaks, isort, black, ruff, codespell, mypy, and requirements sync).uv run pytest tests/ --ignore=tests/worker/test_mp_executor_cleanup_gpu.py: 3098 passed, 21 skipped.latest_updatekeys stripped;TaskInfocounterpart;viewbeing a 422 on both routes, andlimit=0andlimit=10001being 422s;limitkeeping the newest;status(),list_statuses()andwait_for_ssh()requestingview=status, sync and async;list(limit=)sendinglimit;wait()polling the status view and then fetching the full task once;wait_status()returning the terminal status view with no extra fetch, sync and async;flowmesh task watchpolling the status view and printing the full task once.echo_local.yamltasks and anssh_debug_shell.yamlsession were checked over REST, the SDK and the CLI, and all checks passed:GET /taskslists in submission order,limitkeeps the newest with filters still applied, and badviewandlimitvalues return 422.wait_status(),wait(),status(),list_statuses()andwait_for_ssh()return the right types and values, sync and async. The server's access log shows the waits polling?view=statusand no full fetch of the SSH task. The session'slatest_update.sshcomes through the status view unchanged.flowmesh task watchmade one full fetch at the end.flowmesh task stopstopped the SSH session to DONE using only status-view polls.Pre-submission Checklist
pre-commit run --all-filesand fixed any issues.uv run pytest tests/passes locally.uv sync --all-packages --group ci --frozen).[BREAKING]and described migration steps above.