Skip to content

[Router] fix(looper): lifecycle-own workflow state and make resume semantics real #2522

Open
pranavthakur0-0 wants to merge 3 commits into
vllm-project:mainfrom
pranavthakur0-0:fix/looper-lifecycle-resume-2471
Open

[Router] fix(looper): lifecycle-own workflow state and make resume semantics real #2522
pranavthakur0-0 wants to merge 3 commits into
vllm-project:mainfrom
pranavthakur0-0:fix/looper-lifecycle-resume-2471

Conversation

@pranavthakur0-0

Copy link
Copy Markdown

Closes #2471

Purpose

  • What does this PR change?
    Moves the workflow state store to the router lifecycle so it's shared across requests instead of being created per-request. Also adds a 512KB payload limit, a 10K item memory limit, and background cleanup goroutines for the state stores.
  • Why is this change needed?
    Redis clients were leaking because they were created per-request without being closed. Also, memory states couldn't actually resume across different HTTP turns before this. The caps prevent memory blowouts.
  • Which module(s) does this affect? Router / CLI / Dashboard / Operator / Fleet-Sim / Bindings / Training / E2E / Docs / CI/Build
    Router

Test Plan

  • What commands, checks, or manual steps should reviewers use?
    make agent-ci-gate CHANGED_FILES="..." (runs the new pause/resume and concurrent take tests in workflows_state_store_test.go).
  • Why is this validation sufficient for the affected module(s)?
    The tests spin up 20 goroutines to prove we don't double-consume state, and verify that oversized payloads are actually rejected.

Test Result

  • What were the actual results?
    Tests passed. The CI gate ran cleanly and verified the exact-once extraction.
  • Any follow-up risks, gaps, or blockers?
    We capped the payload size, but we haven't capped the actual breadth_schedule or modelRefs count (mentioned in security: cap Looper fan-out and total upstream-call amplification #1456). That needs a separate config fix later.

Semantic Router PR Checklist
  • PR title uses module-aligned prefixes such as [Router], [CLI], [Dashboard], [Operator], [Fleet-Sim], [Bindings], [Training], [E2E], [Docs], or [CI/Build]
  • If the PR spans multiple modules, the title includes all relevant prefixes
  • Commits in this PR are signed off with git commit -s
  • The Purpose, Test Plan, and Test Result sections reflect the actual scope, commands, and blockers for this change

@netlify

netlify Bot commented Jul 14, 2026

Copy link
Copy Markdown

Deploy Preview for vllm-semantic-router ready!

Name Link
🔨 Latest commit eba63f6
🔍 Latest deploy log https://app.netlify.com/projects/vllm-semantic-router/deploys/6a56ebe886956d00094ae283
😎 Deploy Preview https://deploy-preview-2522--vllm-semantic-router.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.
🤖 Make changes Run an agent on this branch

To edit notification comments on pull requests, go to your Netlify project configuration.

@github-actions

Copy link
Copy Markdown
Contributor

👥 vLLM Semantic Team Notification

The following members have been identified for the changed files in this PR and have been automatically assigned when their GitHub accounts are assignable in this repository:

📁 src/semantic-router

Owners: @FAUST-BENCHOU, @shraderdm, @drivebyer, @ramkrishs, @WUKUNTAI-0211, @AayushSaini101, @siloteemu
Files changed:

  • src/semantic-router/pkg/extproc/req_filter_looper.go
  • src/semantic-router/pkg/extproc/router.go
  • src/semantic-router/pkg/extproc/router_build.go
  • src/semantic-router/pkg/looper/looper.go
  • src/semantic-router/pkg/looper/rl_driven_test.go
  • src/semantic-router/pkg/looper/workflows.go
  • src/semantic-router/pkg/looper/workflows_state_store.go
  • src/semantic-router/pkg/looper/workflows_state_store_test.go

vLLM

🎉 Thanks for your contributions!

This comment was automatically generated based on the OWNER files in the repository.

@pranavthakur0-0 pranavthakur0-0 changed the title [Router] fix(looper): lifecycle-own workflow state and make resume semantics real #2495 [Router] fix(looper): lifecycle-own workflow state and make resume semantics real Jul 14, 2026
@github-actions

github-actions Bot commented Jul 14, 2026

Copy link
Copy Markdown
Contributor

✅ Supply Chain Security Report — All Clear

Scanner Status Findings
AST Codebase Scan (Py, Go, JS/TS, Rust) 29 finding(s) — MEDIUM: 22 · LOW: 7
AST PR Diff Scan No issues detected
Regex Fallback Scan No issues detected

Scanned at 2026-07-15T02:10:44.005Z · View full workflow logs

…nd background cleanup for workflow state stores

Signed-off-by: pranavthakur0-0 <pranavthakurdew@gmail.com>
Signed-off-by: pranavthakur0-0 <pranavthakurdew@gmail.com>
@pranavthakur0-0
pranavthakur0-0 force-pushed the fix/looper-lifecycle-resume-2471 branch from b39c6cd to 41c62eb Compare July 14, 2026 08:41

@Xunzhuo Xunzhuo left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Thanks for moving workflow pause/resume state behind a shared store. The core direction is useful, but the current memory bound and router/store lifecycle are not safe enough to meet #2471. Please address the inline blockers. Two additional ownership points should be handled in the same pass: buildRouterComponents currently starts a file-store sweeper even when no workflow path is enabled, and the public/fallback NewWorkflowsLooper path creates a per-instance sweeper without an owner-visible Close. CI is green and git diff --check is clean; the local race test could not link without the native Candle/ML libraries.

defer s.mu.Unlock()
s.cleanupLocked(time.Now().UTC())
normalizeWorkflowToolStateForStore(state)
if len(s.states) >= maxMemoryStateEntries {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

[P1] Enforce a store-wide byte budget, not only a per-entry/cardinality cap. At 10,000 entries of up to 512 KiB each, this store admits about 4.88 GiB of serialized state before Go object overhead, so a client can still OOM the router while staying within both limits. Please account aggregate serialized bytes on insert/replacement and decrement them on Take, expiry, and Clear, with boundary/concurrency tests.

if r.lookupTableCancel != nil {
r.lookupTableCancel()
}
if r.WorkflowStateService != nil {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

[P1] Closing the store here is not sufficient without draining users of this router generation. RouterService.Process loads the old pointer without a lease, while reload swaps and immediately closes it: an in-flight Redis-backed stream can then use a closed client, and a memory/file pause can publish an ID into a retired store that the next request cannot resume from. Please add generation leasing/drain (or keep compatible state-service ownership stable across reloads), close the current router during Server.Stop, and test in-flight pause/resume across reload and shutdown under -race.

},
},
// Redis is excluded from unit tests because it requires an
// external server. The Redis backend shares the same interface

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

[P1] This E2E coverage claim is currently false: there is no registered workflow-state Redis testcase/profile, and these unit tests exercise one store instance directly rather than two independent router requests. Please add the Redis integration plus router-level pause/resume coverage required by #2471, including Redis TTL/pool behavior and reload/shutdown ownership, before treating the backend contract as covered.

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.

looper: lifecycle-own workflow state and make resume semantics real

8 participants