Skip to content

feat: cap the jobs one token may hold - #98

Merged
cristiangirlea merged 3 commits into
mainfrom
feat/owner-capacity
Oct 26, 2026
Merged

cristiangirlea merged 3 commits into
mainfrom
feat/owner-capacity

Conversation

@cristiangirlea

Copy link
Copy Markdown
Owner

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_CAPACITY caps the queued, running and retained jobs one token may hold.

  • Range 1 to 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.
  • A submit over the owner's cap gets the same 429 capacity_exceeded body a full worker gives, so a capped token learns nothing about other tokens' load. Its Retry-After comes 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.
  • Order in JobStore._accept: draining, shutdown, expiry, the idempotency repeat (answered at either limit, as at full capacity today), the owner's cap, then the total.
  • The existing owner label on bridge_submissions_rejected_total counts these 429s. The start log line gains owner_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-worker runs with BRIDGE_CAPACITY=12 and BRIDGE_OWNER_CAPACITY=8).
  • docs/protocol.md, docs/security.md, docs/testing.md, CHANGELOG.md, .claude/rules/worker.md.

Tests

The store tests cover:

  • two owners, one refused at its cap while the other still submits;
  • the cap's Retry-After from the owner's own jobs;
  • Retry-After precedence 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;
  • a repeated key answered at the cap;
  • the total still enforced when the caps add up to more than it;
  • jobs restored from SQLite, service_restarted included, counted for their owner until they expire, after which the owner submits again and the expired key creates a new job;
  • the default unchanged;
  • invalid values (0, capacity + 1, negative, a bool, a non-integer).

At the HTTP layer: the 429 body equals a full worker's, Retry-After is in range, the counter moves for the right owner, every start refusal names the variable, and the start line carries owner_capacity with 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 fresh tokens-worker per 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 with main at dd2cd9d) in a worktree, PHP 8.5:

  • --profile test config: ok
  • python-tests: Ran 406 tests, OK
  • php-tests: PASS: 148 PHP contract checks; php -l: no syntax errors
  • php-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: ok
  • php-analysis: [OK] No errors; php-psr18-tests: PASS: 178
  • laravel-tests: 94 checks; symfony-tests: 96 checks; tls-check: 7 + 3 checks

PHP 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-After precedence 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.

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.
@cristiangirlea
cristiangirlea merged commit 61f9251 into main Oct 26, 2026
13 checks passed
@cristiangirlea
cristiangirlea deleted the feat/owner-capacity branch October 26, 2026 11:12
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.

feat: a per-owner capacity cap so one client cannot starve the others

1 participant