These are instance-wide startup settings. Change the environment or application YAML and restart
that service; this change does not add runtime configuration mutation or tenant overrides.
The defaults live in the bean binding declarations. The environment reference is generated from
docker/.env.example and docker/learner.env.example; regression tests exercise binding and behavior.
The L1 environment names follow Spring Boot record binding: hyphens in a property name are removed,
so max-weight-bytes is POINTSMAN_CACHE_L1_MAXWEIGHTBYTES. Use the exact spellings below.
L1 weight accounting#
effective budget = min(max-weight-bytes, max-size * min-entry-weight-bytes)
entry weight = max(min-entry-weight-bytes, entry-overhead-bytes + 2 * counted UTF-16 units)
The counted payload includes the key, content, reasoning, tool arguments and retained logprobs. Two bytes per UTF-16 unit is a representation constant; the metadata term is a tunable estimate. Caffeine takes a weight ceiling or an entry ceiling, so the per-entry minimum preserves an entry-equivalent bound with weight eviction. Defaults yield 40,960,000 bytes (39.1 MiB), because 10,000 * 4,096 is smaller than 64 MiB. This is approximate payload accounting, not a JVM RSS promise.
For an effective 128 MiB budget, both the byte ceiling and entry-equivalent ceiling must permit it:
POINTSMAN_CACHE_L1_MAXWEIGHTBYTES=134217728
POINTSMAN_CACHE_L1_MAXSIZE=32768
POINTSMAN_CACHE_L1_MINENTRYWEIGHTBYTES=4096
POINTSMAN_CACHE_L1_ENTRYOVERHEADBYTES=1024
The admin effective-configuration view shows configured values and the derived effective budget. An entry heavier than the budget is not retained. Eviction is asynchronous; this is a steady-state cache budget. The L2 memory backend, active requests, SDKs and other caches have separate allocations.
Audit inventory#
All values in the following table are configurable without a source change. Units are part of the names. Invalid nonpositive values refuse startup; the L1 metadata overhead may be zero, and the minimum entry weight cannot exceed its byte ceiling. Large values still require operator sizing.
| Environment variable | Default | Meaning / consequence |
|---|---|---|
POINTSMAN_CACHE_L1_MAXSIZE |
10000 | Maximum entry-equivalent count; the per-entry weight floor enforces it. |
POINTSMAN_CACHE_L1_TTLMINUTES |
15 | L1 TTL in minutes; refresh-on-hit determines sliding versus fixed expiry. |
POINTSMAN_CACHE_L1_MAXWEIGHTBYTES |
67108864 | Estimated payload ceiling in bytes (64 MiB), not a measured JVM heap limit. |
POINTSMAN_CACHE_L1_MINENTRYWEIGHTBYTES |
4096 | Minimum weight per entry. Effective budget=min(max-weight-bytes, max-size * this). |
POINTSMAN_CACHE_L1_ENTRYOVERHEADBYTES |
1024 | Estimated metadata bytes added to key/text UTF-16 bytes; zero is allowed. |
POINTSMAN_STREAMING_ASSEMBLY_MAX_CHARS |
524288 | Maximum cumulative UTF-16 characters accepted while assembling one provider stream; overflow aborts the stream. |
POINTSMAN_STREAMING_ASSEMBLY_MAX_TOOL_CALLS |
128 | Maximum distinct tool-call indices assembled in one stream; overflow aborts the stream. |
POINTSMAN_TOKENS_MAX_CONCURRENT_TOKENIZATIONS |
2 | Concurrent tokenizer-mode requests per instance. Up to POINTSMAN_TOKENS_MAX_WAITING_TOKENIZATIONS (16) further requests wait at most POINTSMAN_TOKENS_ADMISSION_WAIT_MS (25 ms) for a slot; beyond that, or after the wait, the request receives 503. |
POINTSMAN_USAGE_JOURNAL_REPLAY_BATCH_SIZE |
100 | Maximum terminal journal records delivered per replay pass. |
POINTSMAN_USAGE_JOURNAL_REPLAY_INTERVAL_MS |
1000 | Delay after each journal replay pass and before its first pass, milliseconds. |
POINTSMAN_CACHE_CONTEXT_MEMO_MAX_SIZE |
1000 | Maximum conversation summaries held per instance; eviction only causes recomputation. |
POINTSMAN_CACHE_CONTEXT_MEMO_TTL_MS |
120000 | Fixed conversation-summary memo lifetime, milliseconds. |
POINTSMAN_CACHE_REDIS_SCAN_COUNT |
500 | Redis SCAN work hint for cache clearing; a hint, not a guaranteed batch size. |
POINTSMAN_PROMETHEUS_CONNECT_TIMEOUT_MS |
2000 | Prometheus TCP connection timeout, milliseconds. |
POINTSMAN_PROMETHEUS_READ_TIMEOUT_MS |
3000 | Prometheus response read timeout, milliseconds. |
POINTSMAN_BANDIT_PARAMS_TIMEOUT_MS |
2000 | Background learner parameter-fetch timeout, milliseconds; independent of poll cadence. |
POINTSMAN_BANDIT_REWARD_TIMEOUT_MULTIPLIER |
4 | Reward timeout is existing POINTSMAN_BANDIT_TIMEOUT_MS times this multiplier. |
POINTSMAN_BANDIT_BREAKER_WINDOW_SIZE |
5 | Number of reward calls in the learner circuit-breaker sliding window. |
POINTSMAN_BANDIT_BREAKER_HALF_OPEN_CALLS |
3 | Number of permitted learner reward probes in half-open state. |
POINTSMAN_BANDIT_FEEDBACK_WINDOW_MAX_ENTRIES |
100000 | Local feedback throttle key capacity; eviction can reset a local-only throttle. Use Redis for fleet limits. |
POINTSMAN_AUTH_LOGIN_THROTTLE_MAX_ENTRIES |
100000 | Local failed-login username capacity; eviction forgets that username window. Size for exposed account cardinality. |
POINTSMAN_ROUTING_AFFINITY_MAX_ENTRIES |
32768 | Maximum local conversation pins; at capacity new pins are declined after expired pins are removed. |
POINTSMAN_ROUTING_AFFINITY_TTL_MS |
300000 | Sliding conversation affinity lifetime, milliseconds; this does not extend provider-side prompt cache lifetime. |
POINTSMAN_PROVIDERS_MODEL_CACHE_MAX_ENTRIES |
64 | Maximum dynamic provider/credential client entries; new entries at capacity are refused. |
POINTSMAN_PROVIDERS_CLOSE_QUEUE_CAPACITY |
100 | Queued retired clients awaiting closure; saturation runs the close on the caller, never drops it. |
POINTSMAN_COST_SAMPLER_MAX_SCOPES |
10000 | In-memory cost-learning states (one per tenant and objective: cost group, or per organisation and selection: cost profile). Beyond it the least recently used state is dropped, which returns it to declared prices; counted as pointsman_cost_sampler_evictions_total. Measured 17 October 2026 at the cap with 8 arms each: about 10 MB of heap (about 1 KB per state), a choice in 0.8 us (p99 4.4 us); under continuous churn of 100,000 new states the heap stayed at 12 MB and a new state took p99 1.5 ms (eviction runs in batches of 1%). |
POINTSMAN_COST_SAMPLER_MAX_ARMS_PER_SCOPE |
64 | Members learned per state. A member beyond it is ranked from its declared price and not learned; counted as kind=arm_cap. |
POINTSMAN_COST_SAMPLER_MAX_LABEL_SCOPES |
100000 | Quality-label states (feedback_floor). Never evicted - forgetting a label could re-admit an excluded model - so a NEW organisation and profile beyond it records no labels (counted as kind=label_scope_refused, warned once). Measured 17 October 2026: about 0.7 KB of heap per label state with four labelled models, so the full 100,000 is about 73 MB; on a small heap, lower it to the number of organisations times cost profiles you expect. |
Learner settings: BANDIT_DB_TIMEOUT_SECS=5.0 bounds SQLite lock waiting, and
BANDIT_PENDING_SWEEP_INTERVAL_SECS=60.0 controls cleanup of the dormant decide endpoint's pending
state. They are typed and range-validated in LearnerSettings; the live posterior algorithm is unchanged.
Learning capacity (measured 17 October 2026)#
The learner (policy service, one worker by invariant) applies rewards one at a time under one lock, each committed to
SQLite. On the 16-core validation host an update took about 6 ms (p99 about 10 ms) whether a key had 2 or 64 arms and
whether the store held 100 or 5,000 keys, so it sustains about 150 rewards per second. Above that rate the gateway's
reward queue (POINTSMAN_BANDIT_REWARD_QUEUE_SIZE, 1,000) fills and drops the newest rewards, counted as
gateway_bandit_rewards_dropped_total{reason="overflow"}; routing is not affected. At 5,000 keys the published parameter
document was 1.9 MB (built in 30 ms) and a restart loaded the store in 0.17 s.
Learning has a cumulative horizon. The learner keeps every reward event for idempotent retries and revisions, up to
BANDIT_REWARD_EVENT_LIMIT (1,000,000). At the limit it refuses new rewards (503) rather than evict that history, and
balanced bandit groups stop learning - they keep routing on the last posteriors. At a sustained R rewards per second the
limit is reached after 1,000,000 / R seconds: about 2.3 days at 5 per second, 11.6 days at 1 per second. The limit can be
raised to 100,000,000; the store grew by roughly 600 bytes per event in the measurement. There is no archival or
checkpoint tool yet, so plan the limit against the expected reward rate. objective: cost groups learn in the gateway
and do not use this store.
The gateway's cost sampler is bounded by the three POINTSMAN_COST_SAMPLER_* settings above, and starts again from
declared prices after a restart.
Constants deliberately retained#
The audit covers resource capacities, client timeout budgets, cache lifetimes, batching and worker admission. Existing configured values (request body ceiling, output cap, retry timing, retention, queue depth and posterior parameters) retain their existing configuration paths.
Protocol limits (four stop sequences, JavaScript-safe feedback revisions), encoding widths, hash constants, feature-vector dimensions, schema identifiers and numerical guards are not deployment knobs. The tokenizer's named chat-framing allowance remains an estimate of a provider format, not an operator-controlled admission limit. Redis SCAN count is configurable, but Redis may return more or fewer entries than requested. Executor worker count and one-chunk stream prefetch preserve execution and backpressure semantics; the closer's idle timeout has no effect while its core thread is retained. Admin page/range ceilings and bounded diff rendering remain API complexity limits. Changing those contracts warrants API review rather than silently allowing deployment-specific response shapes.
No claim is made that every numeric literal should become a setting. A future operational limit must have an explicit unit, documented default, validation, saturation behavior and a test proving its configured value reaches the consumer. Preserve immutable protocol and algorithm constants by name.
TCP admission before the request gate#
server.tomcat.accept-count defaults to 1024 queued TCP connections, configurable
through Spring Boot's SERVER_TOMCAT_ACCEPTCOUNT environment variable. Set a positive
integer and restart the gateway. The operating system may cap the effective listen
backlog; inspect it on Linux with ss -lnt and check net.core.somaxconn.
This queue is separate from pointsman.intake.max-in-flight, which bounds admitted
/v1/ requests. Increasing the TCP backlog does not grant more application permits.
It lets short connection bursts reach the request gate instead of waiting for TCP
retransmission after the listen queue overflows. It is still finite and cannot make
unbounded connection floods or exhausted CPU capacity safe.
Tomcat 10.1 closes HTTP/1.1 connections after a 503 response, so repeated overload refusals create connection churn. On the CPX62 diagnostic workload (200 simultaneous requests, 32 application permits), the inherited backlog of 100 produced approximately one-second TCP retries. An otherwise identical 1024-backlog arm removed captured handshake retries and brought refusal p99 below the unchanged 100-ms gate. This is a measured diagnostic profile, not an independent-generator capacity guarantee.
Measure both client latency and server processing time. A fast filter cannot reject a request that has not yet crossed the TCP connection queue. Keep ingress/network latency, HTTP protocol and connection reuse in the report. See the overload investigation.