You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Claudify currently routes every Claude model request through the same global BOT_MODEL and BOT_EFFORT values. That couples latency, quality, and cost for unrelated workloads.
A user-facing answer may deserve Sonnet with higher effort, while deterministic background maintenance such as profile extraction, server-memory compaction, or daily summaries can often use Haiku or a lower effort level. Each workload should be independently configurable without duplicating spawn logic or breaking existing deployments.
Current state
All model calls ultimately use runClaude(args, input, model, effort), but every caller passes the same two configuration values:
Workload
Call site
Current model/effort
Interactive Discord response, mention, reply, or reaction
src/askClaude.ts
BOT_MODEL / BOT_EFFORT
Background user-profile extraction
backgroundProfileUpdate() in src/storage/profiles.ts
BOT_MODEL / BOT_EFFORT
Background server-memory update
backgroundServerMemoryUpdate() in src/storage/profiles.ts
BOT_MODEL / BOT_EFFORT
Daily channel summary
generateDailySummary() in src/storage/summaries.ts
BOT_MODEL / BOT_EFFORT
Claude authentication and ccusage do not make model requests and are out of scope.
The current global spawn log also does not identify which workload requested the model, making cost/latency troubleshooting harder:
[Claude CLI] Spawning with model=..., effort=...
Goals
Make model and effort independently configurable for every current Claude workload.
Keep one centralized, typed source of truth for workload configuration.
Preserve BOT_MODEL and BOT_EFFORT as backward-compatible global fallbacks.
Make the resolved workload visible in logs without logging prompt contents or secrets.
Make future Claude call sites declare their workload explicitly.
Preserve the existing shared concurrency/rate limiter and background-processing behavior.
Document a cost-optimized configuration where user responses use a stronger model and maintenance tasks use Haiku/lower effort.
Non-goals
Dynamically selecting a model by inspecting prompt content.
Runtime configuration through Discord commands or writing configuration to storage.
Separate API keys/accounts per workload.
Changing prompts, storage formats, context limits, or the shared Claude queue.
Changing authentication or usage-reporting behavior.
Proposed workload taxonomy
Introduce a closed union or enum rather than passing arbitrary strings:
typeClaudeWorkload=|response|profile-update|server-memory-update|daily-summary;typeClaudeEffort=low|medium|high|xhigh|max;interfaceClaudeWorkloadConfig{workload: ClaudeWorkload;model?: string;// undefined means Claude CLI defaulteffort?: ClaudeEffort;// undefined means omit --effort}
Mapping:
response: all user-facing calls through askClaude().
The type should force a deliberate update when a new workload is added.
Configuration contract
Keep the existing global variables and add explicit per-workload overrides:
Workload
Model variable
Effort variable
Global fallback
BOT_MODEL
BOT_EFFORT
User response
CLAUDE_RESPONSE_MODEL
CLAUDE_RESPONSE_EFFORT
Profile update
CLAUDE_PROFILE_MODEL
CLAUDE_PROFILE_EFFORT
Server memory
CLAUDE_SERVER_MEMORY_MODEL
CLAUDE_SERVER_MEMORY_EFFORT
Daily summary
CLAUDE_SUMMARY_MODEL
CLAUDE_SUMMARY_EFFORT
Resolution precedence for each property:
A non-empty workload-specific override.
The corresponding legacy global BOT_MODEL or BOT_EFFORT value.
The existing built-in fallback (claude-haiku-4-5 for model and no explicit effort flag).
Recommended special-value behavior:
Unset, blank, or inherit: inherit the global value.
default: deliberately bypass the global value and let the Claude CLI choose its default by omitting --model or --effort for that workload.
Effort values remain case-insensitive and normalize to lowercase.
Validate efforts against low, medium, high, xhigh, and max.
Do not hardcode a model allowlist because aliases and model IDs evolve; trim the value and reject control characters/invalid whitespace instead.
An invalid explicit override must produce a clear startup warning and use a deterministic documented fallback. It must not fail silently.
If special values feel too complex for the first implementation, inheritance is required and explicit CLI-default override can be split into a follow-up. The resolution function should still be designed so it can support that state without changing every caller.
Backward compatibility
When none of the new variables are configured, every workload must resolve exactly as it does today:
BOT_MODEL -> all workload models
BOT_EFFORT -> all workload efforts
This ensures existing deployments do not unexpectedly change model, quality, latency, or cost after upgrading.
A cost-optimized deployment can then opt in explicitly:
Alternatively, accept only workload and let runClaude() resolve the settings centrally. The important property is that callers cannot accidentally swap model/effort or omit workload attribution.
Avoid importing higher-level storage or Discord modules into claude.ts; preserve the current dependency direction and no-circular-import rule.
Do not log prompts, OAuth codes, tokens, API keys, or other secrets.
Preserve enough workload context in errors to identify which background task failed.
The queue log should still show active/queued counts.
6. Update user-visible model reporting
!help currently says the bot is powered by BOT_MODEL. It should display the resolved response model because background models are an implementation detail and may differ.
The bot system prompt's botModel template value should likewise use the resolved response model, not the global fallback when a response-specific override is configured.
Validation and failure behavior
Normalize whitespace/casing before resolving effort.
Detect unsupported effort values at startup rather than on the first background job.
Do not silently substitute a more expensive model due to a typo without a warning.
Do not validate model names against a frozen list; Claude aliases change.
Treat model and effort independently: overriding a model must not require overriding effort, and vice versa.
A failure in a profile, server-memory, or summary request must remain non-blocking for the user-facing response.
Error messages should include workload identity but should not expose the prompt or environment.
Tests
Configuration resolution
Use subprocess-based config tests because config.ts is module-cached:
No new variables: all workloads inherit BOT_MODEL and BOT_EFFORT.
Only a workload model is set: effort still inherits globally.
Only a workload effort is set: model still inherits globally.
All workload overrides set: each resolves independently.
Case normalization for effort.
Blank/unset/inherit behavior.
Explicit default behavior if implemented.
Invalid effort behavior and warning/fallback contract.
Model values are trimmed and control characters rejected.
Invocation routing
Using a fake Claude executable or injected runner, assert:
Summary
Claudify currently routes every Claude model request through the same global
BOT_MODELandBOT_EFFORTvalues. That couples latency, quality, and cost for unrelated workloads.A user-facing answer may deserve Sonnet with higher effort, while deterministic background maintenance such as profile extraction, server-memory compaction, or daily summaries can often use Haiku or a lower effort level. Each workload should be independently configurable without duplicating spawn logic or breaking existing deployments.
Current state
All model calls ultimately use
runClaude(args, input, model, effort), but every caller passes the same two configuration values:src/askClaude.tsBOT_MODEL/BOT_EFFORTbackgroundProfileUpdate()insrc/storage/profiles.tsBOT_MODEL/BOT_EFFORTbackgroundServerMemoryUpdate()insrc/storage/profiles.tsBOT_MODEL/BOT_EFFORTgenerateDailySummary()insrc/storage/summaries.tsBOT_MODEL/BOT_EFFORTClaude authentication and
ccusagedo not make model requests and are out of scope.The current global spawn log also does not identify which workload requested the model, making cost/latency troubleshooting harder:
Goals
BOT_MODELandBOT_EFFORTas backward-compatible global fallbacks.Non-goals
Proposed workload taxonomy
Introduce a closed union or enum rather than passing arbitrary strings:
Mapping:
response: all user-facing calls throughaskClaude().profile-update:backgroundProfileUpdate().server-memory-update:backgroundServerMemoryUpdate().daily-summary:generateDailySummary().The type should force a deliberate update when a new workload is added.
Configuration contract
Keep the existing global variables and add explicit per-workload overrides:
BOT_MODELBOT_EFFORTCLAUDE_RESPONSE_MODELCLAUDE_RESPONSE_EFFORTCLAUDE_PROFILE_MODELCLAUDE_PROFILE_EFFORTCLAUDE_SERVER_MEMORY_MODELCLAUDE_SERVER_MEMORY_EFFORTCLAUDE_SUMMARY_MODELCLAUDE_SUMMARY_EFFORTResolution precedence for each property:
BOT_MODELorBOT_EFFORTvalue.claude-haiku-4-5for model and no explicit effort flag).Recommended special-value behavior:
inherit: inherit the global value.default: deliberately bypass the global value and let the Claude CLI choose its default by omitting--modelor--effortfor that workload.low,medium,high,xhigh, andmax.If special values feel too complex for the first implementation, inheritance is required and explicit CLI-default override can be split into a follow-up. The resolution function should still be designed so it can support that state without changing every caller.
Backward compatibility
When none of the new variables are configured, every workload must resolve exactly as it does today:
This ensures existing deployments do not unexpectedly change model, quality, latency, or cost after upgrading.
A cost-optimized deployment can then opt in explicitly:
The exact model IDs remain operator-controlled; this example is guidance, not a hardcoded routing table.
Suggested implementation design
1. Centralize parsing and resolution
Add a typed configuration map in
src/config.ts(or a focused lower-level module if it keepsconfig.tsmanageable):Resolve once at startup. Do not re-read
process.envat each request. Keep parsing, normalization, fallback behavior, and warnings in one place.2. Replace positional spawn configuration
The current positional signature is easy to misuse:
Prefer an options object carrying workload identity and resolved execution settings:
Alternatively, accept only
workloadand letrunClaude()resolve the settings centrally. The important property is that callers cannot accidentally swap model/effort or omit workload attribution.Avoid importing higher-level storage or Discord modules into
claude.ts; preserve the current dependency direction and no-circular-import rule.3. Update every call site
askClaude()usesresponse.backgroundProfileUpdate()usesprofile-update.backgroundServerMemoryUpdate()usesserver-memory-update.generateDailySummary()usesdaily-summary.ClaudeRunnertest types inprofiles.tsso tests can assert workload/model/effort.4. Keep process configuration isolated
For each spawn:
--modelandANTHROPIC_MODELonly from that invocation's resolved model.--effortonly from that invocation's resolved effort.process.envglobally.5. Improve observability
Include the workload in lifecycle logs:
At startup, print a compact resolved routing table once:
Requirements:
6. Update user-visible model reporting
!helpcurrently says the bot is powered byBOT_MODEL. It should display the resolvedresponsemodel because background models are an implementation detail and may differ.The bot system prompt's
botModeltemplate value should likewise use the resolved response model, not the global fallback when a response-specific override is configured.Validation and failure behavior
Tests
Configuration resolution
Use subprocess-based config tests because
config.tsis module-cached:BOT_MODELandBOT_EFFORT.inheritbehavior.defaultbehavior if implemented.Invocation routing
Using a fake Claude executable or injected runner, assert:
--model,ANTHROPIC_MODEL, and--effortagree for each invocation.Regression coverage
!helpand the system prompt report the resolved response model.npm run buildand the complete test suite pass.Documentation and deployment
Update:
README.mdenvironment-variable table and add a complete mixed-model example.AGENTS.mdconfiguration table and model-selection guidance.docker-compose.ymlwith commented or blank opt-in overrides, while ensuring blank values do not accidentally disable inheritance.CHANGELOG.md.Document:
Acceptance criteria
BOT_MODELandBOT_EFFORTremain backward-compatible global fallbacks.runClaude()call declares a typed workload.--model,ANTHROPIC_MODEL, and--effortare scoped per subprocess with no global environment mutation.!helpand the bot system prompt use the resolved response model.npm run buildand the full test suite pass.Follow-up opportunities (not required here)
!config modelscommand.