feat(capacity): make the concurrency policy a pluggable strategy - #64
Merged
Conversation
Publish only dist (minus tests and dist/e2e) plus README/LICENSE via the files field, instead of the whole repo. Also add the missing #!/usr/bin/env node shebang to cli/main.ts, without which the installed bin was not directly executable. Claude-Session: https://claude.ai/code/session_01DFg6QoHAJq5sLXm2vDwcxW
How many devices may exist and run at once was decided in one place that hardcoded a single policy: device limits plus a RAM budget, with the limits themselves derived from CPU and RAM. Pinning a plain number was possible only by setting four coordinated keys, and the RAM gate still sat underneath and could refuse below the pinned number. Capacity is now a `CapacityStrategy` behind one interface. Each strategy lives in its own directory with a single entry point and is registered in one map, from which the config type, its validation, and its defaults are all derived -- so adding a policy touches neither `CapacityCoordinator` nor its callers. Two ship: - `resource` -- today's behaviour, unchanged, still the default. - `fixed` -- a pinned number with no machine inspection at all. `maxRunning` alone is a complete configuration. Config gains a `capacity` namespace discriminated on `strategy`, with the strategy's own options under `capacity.config`. The pre-existing top-level `limits` and `ramBudget` keys are normalized into it per layer, before merging, so layer precedence is unaffected by which spelling each layer uses. Existing config files keep working silently and unchanged; the e2e lane deliberately stays on the old spelling to cover that path end to end. Closes #63 Claude-Session: https://claude.ai/code/session_015M1UR25DZcAxQeYUuEhLZ3
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.
Closes #63
Problem
How many devices may exist and run at once was decided in one place --
src/core/capacity.ts-- which hardcoded a single policy: device-count limitsplus a RAM budget, with the limits themselves derived from CPU and RAM.
Pinning a plain number was almost possible: you could set
limits.maxRunning,limits.ios.*andlimits.android.*. But that is four coordinated keys, and theRAM gate still sat underneath and could refuse provisioning below the pinned
number on a small machine. The pin was not authoritative, and there was no seam
for a third policy.
What changed
Capacity is now a
CapacityStrategybehind one interface. Each strategy lives inits own directory with a single entry point and is registered in one map, from
which the config type, its validation, and its defaults are all derived -- so
adding a policy touches neither
CapacityCoordinatornor its callers.Two strategies ship:
resource-- today's behaviour, unchanged, still the default.fixed-- a pinned number with no RAM budget and no CPU/RAM-deriveddefaults.
maxRunningalone is a complete configuration; the per-platformblocks only carve that budget up.
CapacityCoordinatorkeeps all the reservation accounting and gains no knowledgeof which strategy it holds.
daemon/server.tsstops reaching intoconfig.limitsfor its displayed device ceiling and asks the strategy, so thereported number matches whichever policy is live.
Config
capacityis a discriminated union onstrategy, with the strategy's ownoptions under
capacity.config:Validation is "hand
capacity.configto the selected strategy's validator", soadding a strategy touches zero lines of the config type.
loadConfiggains one step: resolvecapacity.strategyacross the layers first(last layer that names one wins, else
resource), pull that strategy'sdefaults from the registry, then merge. Everything outside
capacitykeeps usingthe existing generic deep merge, untouched.
Backward compatibility
The pre-existing top-level
limitsandramBudgetkeys are resource-shaped bydefinition, so they fold into
capacity.configwhenever the resolved strategy isresource-- which includes every config file written to date, since none ofthem set
capacity.strategy. Existing files keep working silently andunchanged, with identical behaviour and identical defaults. Nothing is
deprecated and no warning is emitted.
holds regardless of which spelling each layer uses.
capacity.configwins over the legacy spelling.resourcestrategy and setting legacy keys is theone case that warns, since those settings would have no effect.
ConfigOverridesaccepts both spellings, sodaemon/main.tsand otherprogrammatic callers are unaffected.
simlock config get/setare generic dotted-path accessors and needed no change.Tests
contract.test.ts-- behaviour every strategy owes its callers, driven off theregistry, so a new strategy is enrolled automatically.
resource(the formercapacity.test.ts, adapted) andfixed.config.test.tscoverage for strategy selection, per-strategy defaults andvalidation, and every backward-compatibility rule above.
doubles as end-to-end coverage that an older config file configures the
resourcestrategy correctly. Noted ine2e/helpers/env.ts.680 unit tests pass. e2e: 35 passed, 1 failure (
slow-android-smoke, a straydaemon process on teardown in the real-emulator lane) which reproduces on
mainand is unrelated to this change.
Docs
docs/CONFIGURATION.mdgains a "Capacity strategies" section with per-strategyoption tables and an "Older config files" note;
docs/ARCHITECTURE.mddescribesthe strategy seam;
docs/CLI.mdpointssimlock configat the new section.https://claude.ai/code/session_015M1UR25DZcAxQeYUuEhLZ3