Tuning Knobs
vanzik v2.0updated view on github
Every setting that changes Vanzik's runtime behavior, grouped by how you change it. Ordered from safest to most surgical.
Policy YAML#
These live in the policy file (configs/*.yaml) under the proxy: and notifications: top-level blocks, or per-scope rule blocks. The server reloads the file on every change (--watch only adds a log line), but only the rules take effect live: the proxy: values and the notifier timeouts are read at startup, so changing them needs a restart (see CONFIG.md § Interaction with hot reload).
proxy.session#
proxy:
session:
ttl: "1h" # default; dashboard sessions expire this long after login
| Key | Default | Bounds |
|---|---|---|
ttl |
1h |
Any time.ParseDuration value. Operator shift = 8h is common. |
proxy.request#
proxy:
request:
max_body_bytes: 1048576 # 1 MB
| Key | Default | Bounds |
|---|---|---|
max_body_bytes |
1048576 (1 MB) |
0 means the default; negative fails at load. Values over a few MB defeat the DoS guard — keep tight. |
proxy.audit#
proxy:
audit:
default_limit: 50 # example override — used when /v1/audit omits ?limit
max_limit: 200 # example override — ?limit=N above this is silently clamped
| Key | Default |
|---|---|
default_limit |
100 |
max_limit |
1000 |
notifications.dispatch_timeout#
notifications:
dispatch_timeout: "10s" # HTTP client timeout for webhook and Slack targets
Default 10s. It is the http.Client timeout of each webhook and Slack notifier (enqueueing never blocks). Set it short if you have a flaky notifier that stalls workers.
Per-scope rate_limit#
rules:
- scope: network
rate_limit:
max_requests: 60
window: "1m"
Per-instance. See OPERATIONS.md for multi-replica arithmetic.
Per-scope limits (cost)#
rules:
- scope: cost
limits:
max_per_action: "$0.25"
max_per_session: "$15.00"
alert_threshold: "$0.15"
max_per_action: hard DENY above this.max_per_session: hard DENY when the session's running cost (keyed by tenant andsession_id) plusest_costwould exceed.alert_threshold: REQUIRE_APPROVAL (no reservation) when a single action exceeds.max_per_actionis checked first, so the threshold must be below it: here a cost between $0.15 and $0.25 gets REQUIRE_APPROVAL, and above $0.25 a DENY.
CLI flags (restart to apply)#
vanzik server \
--policy /etc/vanzik/policy.yaml \
--port 8080 \
--api-key $KEY \
--base-url https://guardrails.example \
--allowed-origin https://app.example \
--tls-terminated-upstream \
--audit-log /var/lib/vanzik/audit.jsonl \
--session-cost-ttl 24h \
--session-cost-sweep-interval 1h \
--data-dir /var/lib/vanzik \
--audit-backend file \
--watch \
--dashboard
The persistence knobs (--persist, --store-dsn, --data-dir, --audit-backend) are covered in CLI.md.
Multi-node (PostgreSQL only): --reconcile-interval (default 2s) is the one knob that trades convergence for store traffic — it bounds both the distributed rate-limit overshoot (≈ interval × peak rate per extra replica) and the cross-node approval staleness (a resolution or one-shot consumption on another node lands within one interval). Halving it halves both windows and doubles reconcile queries; the /v1/check hot path is untouched either way. --node-id is identity, not tuning — just keep it distinct per replica.
See CLI.md for the full list.
Hardcoded constants (as of v1.0)#
These are not configurable yet. Listed so you know what ceiling you're running against. Changing any of these requires a code change.
| Constant | Value | Source | Notes |
|---|---|---|---|
MaxPendingApprovals |
10000 |
pkg/proxy/server.go |
When full, a new approval evicts the one oldest resolved entry; with none resolved, /v1/check gets 503 with Retry-After: 30. |
SSEChannelBufferSize |
64 |
pkg/proxy/server.go |
Slow SSE consumer drops events. |
MaxSessions |
1024 |
pkg/proxy/auth.go |
Expired sessions are swept at login; live sessions are never evicted, so at the cap /auth/login returns 503 with Retry-After: 5. |
MaxBuckets (rate limiter) |
10000 |
pkg/ratelimit/ratelimit.go |
Stale buckets evicted on insert pressure. |
DefaultWorkers (notifier) |
8 |
pkg/notify/notify.go |
Goroutine pool for dispatch. |
DefaultQueueSize (notifier) |
256 |
pkg/notify/notify.go |
Shared queue depth across notifiers. |
| Policy watch fallback poll interval | 2s |
pkg/policy/watcher.go |
Reloads are fsnotify-driven (immediate); the 2 s os.Stat().ModTime() poll is the fallback when fsnotify is unavailable. |
| Histogram buckets (ms) | 0.25..10000 |
pkg/metrics/metrics.go |
Contract — re-bucketing invalidates historical Prometheus data. |
| Notifier histogram buckets (s) | 0.005..10 |
pkg/metrics/metrics.go |
Same contract note. |
| Audit scanner buffer | 4 MiB per line |
pkg/audit/logger.go |
Raised from stdlib default to tolerate long rows. |
absoluteMaxBufferBytes (LLM proxy) |
64 MiB |
pkg/llmproxy/streaming.go |
Hard safety ceiling on the streaming buffers. A MaxBufferBytes of 0 disables the operator cap (--max-buffer-bytes itself rejects 0; only reachable by embedding the package) but this ceiling still applies — the stream is refused fail-closed past it. |
If you need any of these configurable in YAML, open an issue with the use case.
Environment variables#
None of the knobs on this page has an environment variable. VANZIK_API_KEY, VANZIK_URL and VANZIK_POLICY stand in for flags; a flag always wins. The full list: CLI.md § Environment variables.
What you usually don't need to tune#
- Histogram buckets. Re-bucketing breaks historical Prometheus data; prefer adding new histograms in code.
- Body cap. 1 MB is already 10× a typical
CheckRequest; raising it weakens DoS protection. - Notifier workers. Eight is plenty for most deployments — dropped events usually mean a slow target, not too few workers.
- Rate-limit
MaxBuckets. 10k tracked agents per instance is enough for almost everyone.
Related docs#
docs/CONFIG.md— YAML schema forproxy.*andnotifications.*blocks.docs/OPERATIONS.md— what to do when a ceiling bites.docs/OBSERVABILITY.md— metrics to watch before you tune.