-
Notifications
You must be signed in to change notification settings - Fork 1.2k
feat(server): reserve interactive capacity from a worker fan-out (#4546) #4614
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -134,6 +134,7 @@ import { | |||||||||||||||||||||
| type RequestLogEntry, | ||||||||||||||||||||||
| } from "./request-log"; | ||||||||||||||||||||||
| import { sessionLaneIdFromRequest } from "./request-log-conversation"; | ||||||||||||||||||||||
| import { admitWorkflowTurn, type WorkflowLane } from "../lib/workflow-budget"; | ||||||||||||||||||||||
| export { | ||||||||||||||||||||||
| addFinalRequestLog, | ||||||||||||||||||||||
| filterRequestLogs, | ||||||||||||||||||||||
|
|
@@ -1292,13 +1293,36 @@ export function startServer(port?: number, deps: StartServerDeps = {}): Server<W | |||||||||||||||||||||
| ): Promise<Response> { | ||||||||||||||||||||||
| const lease = tryAdmitTurn(sessionLaneIdFromRequest(req.headers)); | ||||||||||||||||||||||
| if (!lease) return serverBusyResponse(req, "active turns", policy); | ||||||||||||||||||||||
| // A fan-out shares the conversation it serves. Without a reserve, a worker burst takes every | ||||||||||||||||||||||
| // slot under its own root and the interactive turn that started it waits behind its own | ||||||||||||||||||||||
| // children. A request that names a parent is treated as that fan-out; a top-level request is | ||||||||||||||||||||||
| // the conversation and may use the reserved slots. | ||||||||||||||||||||||
| const workflowRootId = req.headers.get("x-codex-parent-thread-id")?.trim() || undefined; | ||||||||||||||||||||||
| const workflowThreadId = req.headers.get("thread-id")?.trim() || undefined; | ||||||||||||||||||||||
| const workflowLane: WorkflowLane = workflowRootId !== undefined | ||||||||||||||||||||||
| && workflowThreadId !== undefined | ||||||||||||||||||||||
| && workflowThreadId !== workflowRootId | ||||||||||||||||||||||
| ? "worker" | ||||||||||||||||||||||
| : "interactive"; | ||||||||||||||||||||||
| const workflow = admitWorkflowTurn(workflowRootId, workflowLane, undefined, workflowThreadId); | ||||||||||||||||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
On a remote deployment with multiple configured API keys, Useful? React with 👍 / 👎. |
||||||||||||||||||||||
| if (workflow && !workflow.admitted) { | ||||||||||||||||||||||
| lease.release(); | ||||||||||||||||||||||
| return formatErrorResponse( | ||||||||||||||||||||||
| 429, | ||||||||||||||||||||||
| workflow.reason === "workflow-sends-exhausted" ? "workflow_budget_exhausted" : "queue_capacity_exceeded", | ||||||||||||||||||||||
| "This task has reached its concurrent-work limit, so no further upstream request was made. Work already in flight settles as it finishes.", | ||||||||||||||||||||||
|
Comment on lines
+1312
to
+1313
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
When AGENTS.md reference: src/AGENTS.md:L24-L25 Useful? React with 👍 / 👎. |
||||||||||||||||||||||
| ); | ||||||||||||||||||||||
|
Comment on lines
+1310
to
+1314
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win Apply CORS headers to workflow refusals. This return bypasses Wrap this response with Proposed fix- return formatErrorResponse(
+ return withCors(formatErrorResponse(
429,
workflow.reason === "workflow-sends-exhausted" ? "workflow_budget_exhausted" : "queue_capacity_exceeded",
"This task has reached its concurrent-work limit, so no further upstream request was made. Work already in flight settles as it finishes.",
- );
+ ), req, policy);📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents
Comment on lines
+1310
to
+1314
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
For an allowed browser-origin request that reaches the workflow limit, this return occurs before the route callback applies AGENTS.md reference: src/AGENTS.md:L24-L25 Useful? React with 👍 / 👎. |
||||||||||||||||||||||
| } | ||||||||||||||||||||||
| const releaseWorkflow = (): void => { if (workflow?.admitted) workflow.lease.release(); }; | ||||||||||||||||||||||
| let response: Response; | ||||||||||||||||||||||
| try { | ||||||||||||||||||||||
| response = await work(lease); | ||||||||||||||||||||||
| } catch (error) { | ||||||||||||||||||||||
| releaseWorkflow(); | ||||||||||||||||||||||
| lease.release(); | ||||||||||||||||||||||
| throw error; | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
| releaseWorkflow(); | ||||||||||||||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win 🔎 Supported by static analysis🏁 Script executed: #!/bin/bash
set -euo pipefail
ast-grep outline src/server/index.ts --items all --type function
rg -n -C 5 'isTransferred\(\)|turnAdmissionLease|bindAbortController|\.bind\(|release\(\)' srcRepository: lidge-jun/opencodex Length of output: 50376 🏁 Script executed: #!/bin/bash
set -euo pipefail
printf '%s\n' '--- server flow ---'
sed -n '1240,1350p' src/server/index.ts
printf '%s\n' '--- relevant declarations and bindings ---'
rg -n -C 8 'ActiveTurnLease|isTransferred|releaseWorkflow|runAdmittedHttpTurn|admitWorkflowTurn' src/server src/libRepository: lidge-jun/opencodex Length of output: 33411 🏁 Script executed: #!/bin/bash
set -euo pipefail
printf '%s\n' '--- active turn lease implementation ---'
sed -n '165,315p' src/server/lifecycle.ts
printf '%s\n' '--- workflow admission contract ---'
sed -n '35,145p' src/lib/workflow-budget.ts
printf '%s\n' '--- lease transfer and terminal cleanup call sites ---'
rg -n -C 10 'registerTurn\(|unregisterTurn\(|trackStreamLifetime\(|bindAbortController\(' src/server src/libRepository: lidge-jun/opencodex Length of output: 44965 Release workflow admission when transferred HTTP work terminates.
Later worker requests can therefore pass the workflow concurrency check while the transferred stream is still active. Release the workflow lease from the same terminal callback that calls 🤖 Prompt for AI AgentsThere was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
When AGENTS.md reference: src/AGENTS.md:L24-L25 Useful? React with 👍 / 👎. |
||||||||||||||||||||||
| if (!lease.isTransferred()) { | ||||||||||||||||||||||
| lease.release(); | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
There was a problem hiding this comment.
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
Apply the top-level workflow root consistently.
When a request has
thread-idbut nox-codex-parent-thread-id,src/server/index.ts:1300-1307passesundefinedtoadmitWorkflowTurn.src/lib/workflow-budget.ts:92then returnsundefined, so the request bypasses workflow concurrency and child admission.src/server/responses/core.ts:5043-5053also passesundefinedtochargeWorkflowSendsandworkflowSendCeilingReached, so the 256-send workflow limit is bypassed for Responses requests.src/server/context-history.ts:31-35identifies root model requests withthread-idand no fabricated parent key. Usethread-idas the root fallback. Apply the same fallback to Responses send accounting. Do not count the top-level thread as a child;admitWorkflowTurndocuments thatchildIdis for fan-out members.Proposed fix
🤖 Prompt for AI Agents