feat(runtime): cap concurrent and runaway agent work - #1197
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
07138b1 to
13dff11
Compare
13dff11 to
203a81f
Compare
203a81f to
b7e6e7a
Compare
b7e6e7a to
dcdbf90
Compare
dcdbf90 to
7f13f8c
Compare
7f13f8c to
72a5168
Compare
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Want reviews to match your repository better? Bugbot Learning can learn team-specific rules from PR activity. A team admin can enable Learning in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit 72a5168. Configure here.
| return await callback(lock); | ||
| } finally { | ||
| await state.releaseLock(lock); | ||
| } |
There was a problem hiding this comment.
Mutation lock expires during admission wait
High Severity
Conversation mutation locks use a 10s TTL, but admission work now waits up to 10s for the global admission lock and can then read every active conversation before writeConversation extends the mutation lock. Under admission contention, the mutation lock can expire mid-operation, so acquire, check-in, release, and complete fail with a fenced-lock error even though the worker still believes it owns the lease.
Additional Locations (2)
Reviewed by Cursor Bugbot for commit 72a5168. Configure here.


Junior currently has no installation-wide cap on how many conversations may execute at once, and no durable cap on how long one agent turn may keep calling the model and tools. A bad automation, repeated resume, or agent loop can therefore consume workers and model budget until someone notices.
This PR adds four configurable safety budgets:
The first two are capacity budgets. When they are full, Junior keeps inbound work in its durable queue and tries again later. Step and runtime budgets stop the runaway turn. Messages are not dropped.
Budgets are declared in one private internal registry instead of four unrelated checks. Each definition owns its environment variable, default, unit, display copy, runtime stage, queue/stop outcome, and
MaybePromisemeasurement function.botConfig.budgetscontains only numeric limits, so Nitro never serializes budget functions. Runtime code calls the same asynccheckBudgets()path for conversation admission and each agent step.The same registry generates the dashboard descriptions, so the System UI maps generic budget rows and does not branch on budget names. Adding a future budget such as tool calls per turn or daily model spend requires one definition plus the usage value needed to measure it.
The telemetry follows current OpenTelemetry GenAI conventions where they exist:
gen_ai.conversation.id,gen_ai.operation.name,gen_ai.agent.name,gen_ai.request.model, token usage, anderror.type. OpenTelemetry does not currently define a durable turn ID, cumulative agent-step count, or cumulative turn runtime, so those useapp.ai.turn.id,app.ai.turn.step_count, andapp.ai.turn.runtime_msrather than newgen_ai.*fields.Turn IDs are inherited by
gen_ai.invoke_agent, childgen_ai.chatspans, budget events, and Sentry issues. When a durable turn becomes completed, failed, or abandoned, the activechat.turnspan receives the exact persisted turn ID, step count, cumulative runtime, slice ID, state, model, token usage, and estimated cost. This lets operators query real p50/p90/p95/p99/max budget usage instead of reconstructing turns from sampled child spans.Suggested review order:
services/budgets.ts— internal registry, config loading, descriptions, andcheckBudgets()task-execution/state.ts— globally serialized active-conversation admissionagent/resume.tsandstate/turn-session.ts— durable step/runtime accounting and terminal telemetrySystemBudgets.tsx— generic rendering of budget descriptionsThe main operational tradeoff is that active-conversation admission uses a small expiring lease list under one short global lock. The list is bounded by global capacity, refreshed by worker check-ins, and cleaned on release, completion, expiry, and deletion.
Focused runtime, telemetry, recovery, registry/config, dashboard API, and generic UI tests pass. The actual built
/systempage was checked at 1440px and 390px widths with no horizontal overflow. Commit hooks passed architecture, file-length, migration, dashboard-style, lint, and formatting checks.