Repository navigation
feat: cap the jobs one token may hold - #98
Merged
Merged
Conversation
BRIDGE_OWNER_CAPACITY limits one owner's queued, running and retained jobs, from 1 to BRIDGE_CAPACITY, so one client cannot fill a shared worker and starve the others. A submit over the cap is the same 429 a full worker gives, with Retry-After from that owner's own earliest expiry; a repeated idempotency key is still answered, and the total capacity still applies. Unset, the cap is the whole capacity and nothing changes.
# Conflicts: # docs/testing.md
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.
Fixes #76.
What
Per-client tokens scope who can see a job, but every token shares one capacity, so one client could fill the worker and starve the others.
BRIDGE_OWNER_CAPACITYcaps the queued, running and retained jobs one token may hold.BRIDGE_CAPACITY. A value that doesn't parse or is out of range stops the worker at start with a message naming the variable and the capacity in force. Unset means the whole capacity: the new check is skipped and a single-token deployment behaves exactly as before.429 capacity_exceededbody a full worker gives, so a capped token learns nothing about other tokens' load. ItsRetry-Aftercomes from that owner's own earliest expiry, or 1 when it holds only unfinished jobs, because only its own jobs expiring frees a slot under both limits. An owner below its cap on a full store still gets the store-wide rule.JobStore._accept: draining, shutdown, expiry, the idempotency repeat (answered at either limit, as at full capacity today), the owner's cap, then the total.ownerlabel onbridge_submissions_rejected_totalcounts these 429s. Thestartlog line gainsowner_capacity.Files
worker/ai_bridge/jobs.py(Settings.owner_capacity, its validation, the cap in_accept,_retry_after(owner)),worker/ai_bridge/server.py(reading and checking the variable, the start line),worker/ai_bridge/log.py(the start schema).tests/python/test_jobs.py,tests/python/test_server.py,tests/python/test_log.py,tests/integration.py;docker/compose.yaml(tokens-workerruns withBRIDGE_CAPACITY=12andBRIDGE_OWNER_CAPACITY=8).docs/protocol.md,docs/security.md,docs/testing.md,CHANGELOG.md,.claude/rules/worker.md.Tests
The store tests cover:
Retry-Afterfrom the owner's own jobs;Retry-Afterprecedence with controlled finish times: an owner below its cap on a full store gets the store-wide expiry, and an owner at its cap gets its own, many seconds apart;service_restartedincluded, counted for their owner until they expire, after which the owner submits again and the expired key creates a new job;At the HTTP layer: the 429 body equals a full worker's,
Retry-Afteris in range, the counter moves for the right owner, every start refusal names the variable, and the start line carriesowner_capacitywith and without the cap.Integration on the two-token peer: the app token fills its cap of 8 and gets a 429 with
Retry-After, its repeated key is still answered, and the other token submits until the total of 12. The scenario assumes a freshtokens-workerper run, which CI and the check give it. The two precedence and restore tests were shown to fail against deliberately broken versions: the checks swapped, and restored jobs not counted.Checks
The full command block of
AGENTS.md, run as one command on this head (5b6bce9: the slice commit 9345681 merged withmainat dd2cd9d) in a worktree, PHP 8.5:--profile test config: okpython-tests: Ran 406 tests, OKphp-tests: PASS: 148 PHP contract checks;php -l: no syntax errorsphp-integration: PASS: 192 total PHP checks;integration: PASS (… jobs scoped to their token; a per-token capacity cap)mcp-tests: Ran 125 tests, OK;composer-install: okphp-analysis: [OK] No errors;php-psr18-tests: PASS: 178laravel-tests: 94 checks;symfony-tests: 96 checks;tls-check: 7 + 3 checksPHP 8.2, 8.3 and 8.4 are left to CI.
Review
Independent read-only review by codex (another model family): no implementation defect, and two missing tests (the
Retry-Afterprecedence and owner accounting across restore and expiry), both added in this commit. It also checked the integration arithmetic on the shared peer: the PHP suite leaves app 2 and other 1, and the cap scenario ends at app 8 and other 4, filling 12. Security: nothing open.