SMOODEV-2306: Add limits — the fourth @smooai/config kind#114
Merged
Conversation
Limits are numeric, segment-resolved, clamp-aware config values. They flow
through the SAME server segment evaluator as feature flags (resolved live,
never baked) and tune a soft target within a client-applied [min, max] clamp,
replacing env stopgaps like SMOOTH_AGENT_MAX_ITERATIONS. Motivating consumer:
smooth-operator reads `agentMaxIterations` by {orgId, agentId}.
Why a new kind: none of public/secret/featureFlag fit a soft numeric target
that resolves per-segment and carries its own clamp metadata so a stale/bad
server value can never push a consumer out of a safe range.
- Schema (TS): defineConfig({ limitsSchema }) + defineLimit({default,min,max,step}),
exposing LimitKeys + _limitsMeta, serialized as bounded JSON-Schema number
nodes in the push wire format. Limits stay OUT of the bake/resolution chain.
- TS client: ConfigClient.evaluateLimit, createLimitEvaluator, getClientLimit,
clampLimit, and a `limit` tier (getLimit sync + evaluateLimit async, clamped)
on buildClientConfig / buildConfig. Typed Limit* error classes.
- Polyglot parity mirroring the feature-flag evaluator: Rust evaluate_limit +
clamp_limit, Python evaluate_limit + clamp_limit, Go EvaluateLimit + ClampLimit,
.NET EvaluateLimitAsync + LimitSpec.Clamp — each with typed Limit* errors.
- Endpoint contract: POST /organizations/{org_id}/config/limits/{key}/evaluate.
- Tests pass in all five languages (TS/Rust/Python/Go/.NET). Design + rollout
gaps in DESIGN-limits.md; usage in SCHEMA_USAGE.md; changeset added.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
🦋 Changeset detectedLatest commit: 99d918a The changes in this PR will be included in the next version bump. This PR includes changesets to release 1 package
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
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.
Problem
@smooai/confighas three kinds:publicConfigSchema,secretConfigSchema,featureFlagSchema. None fit a soft, tunable numeric target — a value that resolves differently per{ orgId, agentId, plan, … }, flips without a redeploy, and carries its own clamp so a stale/bad server value can't push a consumer out of a safe range. Concretely, smooth-operator needsagentMaxIterationsper{orgId, agentId}to replace theSMOOTH_AGENT_MAX_ITERATIONSenv stopgap.Solution — a numeric sibling of feature flags
Limits are the fourth kind: numeric, segment-resolved, clamp-aware, resolved live (never baked) through the SAME server segment evaluator as feature flags. A limit is a soft target — consuming code still hard-clamps; the config tunes within a client-applied
[min, max]. No new resolution engine: the client code paths parallel the feature-flag evaluator exactly.defineConfig({ limitsSchema })+defineLimit({ default, min, max, step }), exposingLimitKeys+_limitsMeta, serialized as bounded JSON-Schema number nodes in thesmooai-config pushwire format. Limits stay out of the bake/env/file resolution chain (they never bake).clampLimit, pure + identical in every language): non-finite/non-numeric →default; snap tostep; clamp into[min, max]. Lives on the client so a stale server value can't escape the declared range.Per-language surface
client.evaluateLimit(key, ctx?, env?)→EvaluateLimitResponseclampLimit(raw, def)config.limit.getLimit(key)/getClientLimitclient.evaluate_limit(key, ctx, env).awaitclamp_limit(raw, &spec)client.evaluate_limit(key, ctx?, env?)clamp_limit(raw, spec)client.EvaluateLimit(ctx, key, evalCtx, env)ClampLimit(raw, spec)client.EvaluateLimitAsync(key, ctx?, env?, ct)spec.Clamp(raw)Each with typed
Limit*errors mirroring the feature-flag hierarchy (404/400/5xx). AlsocreateLimitEvaluatorfactory + alimittier (getLimitsync +evaluateLimitasync, clamped) onbuildClientConfig/buildConfig.Endpoint contract:
POST /organizations/{org_id}/config/limits/{key}/evaluate.Test notes
Tests pass in all five languages:
-D warningscleanpnpm typecheck+oxfmt --check+oxlint(0 errors) clean.Known gaps (see
DESIGN-limits.md).smooai-configand pushed; polyglot runtimes consume (evaluate) them, so nativedefine_configsignatures were left at three tiers./config/limits/{key}/evaluateis documented + called here; wiring the route (alias to the numeric segment evaluator) is the smooai-monorepo follow-up.useLimitEvaluationdeferred (the latter waits onuseFeatureFlagEvaluation, which never landed).Docs:
DESIGN-limits.md(full design + rollout),SCHEMA_USAGE.md(usage), changeset added. Links SMOODEV-2306.🤖 Generated with Claude Code