# Hard-coded constants audit — operator-tunable vs keep in code

**Generated:** 2026-04-04  
**Scope:** `scripts/*.py` module-level `CONST =` style bindings (plus notable class-level tunables referenced from the same files).  
**Goal:** Classify what could move to **dashboard / JSON** (`memory/*.json`), **env** (`.env`), **schedules** (`memory/schedules.json`), vs **leave in source**.

## Summary

| Tier | Meaning | Count (approx.) |
|------|---------|-----------------|
| **0** | Already tunable via existing JSON + UI or documented env | See §1 |
| **1** | **Strong** candidates for `operator_settings.json` or a new small JSON + dashboard section | ~45 names |
| **2** | Good **env-only** candidates (`FUROSHIKI_*` + `.env.template`) | ~35 names |
| **3** | **Brain / doctor / repair** — tune rarely; env or advanced “operator” JSON | ~25 names |
| **4** | **Semantic / dedup thresholds** — tune for research; risk of silent behavior change | ~20 names |
| **5** | **Do not externalize** — protocol IDs, SQL fragments, template text, directory layout contracts | Many |

---

## 1. Already configurable (no audit action)

| Mechanism | What it covers |
|-----------|----------------|
| `memory/budget_settings.json` | LLM spend caps — dashboard **Settings** (budget section) |
| `memory/timezone_settings.json` | IANA TZ — dashboard **Settings** |
| `memory/operator_settings.json` | DND, backup dir, global token ceilings, feature flags — dashboard **Settings** |
| `memory/listener_advanced_settings.json` | Telegram preamble, context augmentations, trace debug — dashboard **Advanced** |
| `llm-routing.json` / `llm-tier-models.json` / `llm-model-presets.json` | Per-source models, tiers, experiment shortcuts — dashboard **Models** (**LLM routing** / **Model tiers** / **Model presets**) |
| `memory/schedules.json` | Brain task cadence — dashboard **Scheduler** + `furoshiki cron` |
| `.env` / `.env.template` | API keys, `FUROSHIKI_*` overrides already listed in template |

**Env-backed in code today (not duplicate dashboard):**  
`llm.py`: `OPENROUTER_REQUEST_TIMEOUT`, `OPENROUTER_REQUEST_TIMEOUT_SHORT`, `OPENROUTER_RETRY_BACKOFF_S`, `_OPENROUTER_MAX_ATTEMPTS`, `_LLM_LOG_*`, `LLM_ROUTING_CONFIG_PATH`, `FUROSHIKI_OPENROUTER_CONTEXT_COMPRESSION`, `FUROSHIKI_OPENROUTER_APP_*` (OpenRouter app attribution headers)  
`db.py`: `SQLITE_CONNECT_TIMEOUT`  
`api_server.py`: `PORT`, `API_HOST`  
`micro_brain.py`: `DELEGATION_*` (context turns, max chars, thread brief, brief max tokens)  
`telegram_listener.py`: `MODEL` (`FUROSHIKI_LISTENER_MODEL`), `TELEGRAM_OPENROUTER_SESSION_GAP_MIN`  
`log_store.py`: `FUROSHIKI_LOG_QUEUE_MAX`, `FUROSHIKI_LOG_BATCH_SIZE`, `FUROSHIKI_LOG_FLUSH_SEC`  
`operator_tier1.py` (agent runner): `FUROSHIKI_AGENT_RUNNER_MAX_CONCURRENT` (default 3), `FUROSHIKI_AGENT_TASK_DEFAULT_BUDGET` (default 20), `FUROSHIKI_AGENT_RUNNER_ROUNDS_PER_TICK` (default 5)  
`operator_tier1.py` (personality roles): `FUROSHIKI_PERSONALITY_ROLES` (default off), `FUROSHIKI_PERSONALITY_ROLES_SEMANTIC_TIEBREAK` (default off), `FUROSHIKI_PERSONALITY_ROLES_SEMANTIC_THRESHOLD` / `_MAX_USER_CHARS` / `_MAX_USER_LINES`, and `memory/operator_settings.json` → `flags.*` + nested `personality_roles.*` — situational Band-1 mask (strict triggers + optional local-embed tie-break)  
`operator_tier1.py` (topic zones learning): `FUROSHIKI_TOPIC_ZONE_LEARN_PERSIST` (default on), `FUROSHIKI_TOPIC_ZONE_LEARN_MAX` (default 5, max 50) and `memory/operator_settings.json` → `topic_zones` (`persist_learned`, `max_per_zone`) — bounded insert of heuristic `source=learned` rows into `topic_zone_keywords`; consumer: `context_zones.py` / `db.persist_learned_topic_keywords()`

---

## 2. Tier 1 — Strong candidates for dashboard / `operator_settings` (or adjacent JSON)

These are **operator-visible** knobs that currently require editing Python or lack a single place in the UI.

### 2.1 Telegram / listener (chat behavior)

| File | Constant | Default | Suggested home |
|------|----------|---------|----------------|
| `operator_tier1.py` / `telegram_listener.py` | `FUROSHIKI_PERSONALITY_ROLES`, `FUROSHIKI_PERSONALITY_ROLES_SEMANTIC_TIEBREAK`, `FUROSHIKI_PERSONALITY_ROLES_SEMANTIC_THRESHOLD`, `FUROSHIKI_PERSONALITY_ROLES_SEMANTIC_MAX_USER_CHARS`, `FUROSHIKI_PERSONALITY_ROLES_SEMANTIC_MAX_USER_LINES` | roles off; tie-break off; threshold 0.84; 2000 chars; 6 lines | env + `operator_settings` flags + nested `personality_roles` |
| `operator_tier1.py` / `telegram_listener.py` | `FUROSHIKI_CONFABULATION_GATE_ENABLE`, `FUROSHIKI_CONFABULATION_GATE_MODE`, `FUROSHIKI_CONFABULATION_MIN_CLAIM_TOKENS`, `FUROSHIKI_CONFABULATION_MIN_MATCH_RATIO`, `FUROSHIKI_CONFABULATION_INFERENCE_RATIO_FACTOR`, `FUROSHIKI_CONFABULATION_MAX_SENTENCES`, optional `FUROSHIKI_CONFABULATION_QUALIFIER_PREFIX` / `FUROSHIKI_CONFABULATION_QUESTION_REPLACEMENT` | gate off; `log_only`; 2; 0.42; 0.72; 0 (unlimited) | env + nested `confabulation_gate` in `operator_settings.json` |
| `telegram_listener.py` | `POLL_TIMEOUT` | 30 | `operator_settings` or env `FUROSHIKI_TELEGRAM_POLL_TIMEOUT` |
| `telegram_listener.py` | `MAX_HISTORY_TURNS` | 20 | `operator_settings` |
| `telegram_listener.py` | `NEW_SESSION_GAP_MIN` | 30 | Already related to `FUROSHIKI_TELEGRAM_OPENROUTER_SESSION_GAP_MIN` — **merge** into one settings group |
| `telegram_listener.py` | `DELEGATION_ASK_CONFIDENCE` | 0.75 | `operator_settings` nested `delegation_thresholds` or env |
| `telegram_listener.py` | `DELEGATION_MICRO_CONFIRM_THRESHOLD` | 0.85 | same |
| `telegram_listener.py` | `DELEGATION_RESOLVE_MIN_CONFIDENCE` | 0.82 | same |
| `telegram_listener.py` | `DELEGATION_PENDING_TTL` | 600 | same |
| `telegram_listener.py` | `ANSWER_MATCH_CONFIDENCE` / `HIGH_VERIFY_SKIP_THRESHOLD` / `ASSISTANT_Q_LINK_CONFIDENCE` | 0.75 / 0.90 / 0.80 | `operator_settings` (Q/A pipeline) |

### 2.2 Reply queue & nudges

| File | Constant | Default | Suggested home |
|------|----------|---------|----------------|
| `reply_queue.py` | `NUDGE_DELAY_HOURS` | 6 | `operator_settings` |
| `reply_queue.py` | `NUDGE_REPEAT_HOURS` | 12 | `operator_settings` |
| `reply_queue.py` | `MAX_NUDGES` | 2 | `operator_settings` |
| `reply_queue.py` | `STALE_EXPIRE_HOURS` | 48 | `operator_settings` |

### 2.3 Voice / outreach cadence

**Implemented in `operator_settings.json` → `voice_dispatcher`** (via `operator_tier1.get_voice_dispatcher_settings()`): `min_post_conversation_minutes`, `priority_cooldown_minutes`, `give_up_after_sends`, `give_up_min_cooldown_min`, `relax_tier_cap_when_unanswered`, `unanswered_min_minutes`, `mind_queue_outreach_overlap_threshold`, `engagement_drive` sub-keys, **`user_topic_expire_enabled`**, **`user_topic_expire_min_chars`**, **`user_topic_expire_sim`**, **`user_topic_expire_done_sim`** (listener → `expire_mind_queue_for_user_topic_message`), **`outreach_precompose_memory_enabled`**, **`outreach_precompose_memory_n`**, **`outreach_precompose_memory_min_similarity`** (`voice_dispatcher` compose) — each with `FUROSHIKI_*` env fallbacks in `operator_tier1.py`.

**Implemented in `operator_settings.json` → `continuity`** (via `operator_tier1.get_continuity_settings()`): `enabled`, `min_idle_minutes_after_assistant`, `min_reply_chars`, `max_per_session`, `max_per_day`, `min_confidence`, `model` — env fallbacks `FUROSHIKI_CONTINUITY_*`. Consumer: `telegram_listener.py` + `conversation_continuity.py` (in-thread follow-up; not `voice_dispatcher`).

| File | Constant | Default | Suggested home |
|------|----------|---------|----------------|
| `voice_dispatcher.py` | `MIN_POST_CONVERSATION_MINUTES` | 20 | Done — `operator_settings` |
| `voice_dispatcher.py` | `PRIORITY_COOLDOWN` | dict | Done — `operator_settings` |

**User needs (Tier 1):** `operator_settings.json` → `user_needs` via `get_user_needs_settings()` — `enable`, `confidence_half_life_hours`, `min_directive_confidence`, `conflict_level_delta`, `conflict_min_confidence`, `space_veto_level`, `space_veto_min_confidence`, `minimal_lonely_ping_enable`, `loneliness_critical`, `communication_need_critical`, `max_lonely_pings_per_week`, `global_checkin_cooldown_hours`, `inference_max_tokens`; env mirrors `FUROSHIKI_USER_NEEDS_*` in `operator_tier1.py`. Consumers: `user_needs_model.py`, `soul_engine.py`, `run_post_conversation.py`, `telegram_listener.py`, `outreach_pulse.py`.

### 2.4 Post-conversation & reflection gates

| File | Constant | Default | Suggested home |
|------|----------|---------|----------------|
| `run_post_conversation.py` | `QUIET_WINDOW_MINUTES` | 20 | `operator_settings` |
| `run_post_conversation.py` | `MIN_SESSION_BYTES` | 500 | `operator_settings` |
| `run_post_conversation.py` | `MAX_ASK_USER_PER_RUN` | 2 | `operator_settings` |
| `run_post_conversation.py` | `RESOLVED_QUESTION_MIN_CONFIDENCE` | 0.75 | `operator_settings` |
| `run_afternoon_processing.py` | `MIN_SESSION_BYTES` | 2000 | `operator_settings` (or unify with post-conversation) |

### 2.5 Session context / recap sizes

| File | Constant | Default | Suggested home |
|------|----------|---------|----------------|
| `refresh_session_context.py` | `RECAP_SNIPPET_MAX_CHAT` | 600 | `operator_settings` |
| `refresh_session_context.py` | `RECAP_SNIPPET_MAX_OUTREACH` | 200 | `operator_settings` |
| `db.py` | `RECENT_OUTREACH_PROMPT_MAX_JOINED` | 1200 | `operator_settings` |

### 2.6 User facts & retrieval

| File | Constant | Default | Suggested home |
|------|----------|---------|----------------|
| `db.py` | `USER_FACT_MIN_CONFIDENCE_EMBED` | 0.5 | `operator_settings` |
| `db.py` | `USER_FACT_SEMANTIC_DEDUP_SIMILARITY` | 0.88 | `operator_settings` |
| `user_fact_jit.py` | `FUROSHIKI_USER_FACT_JIT_EXTRACT` | 1 (on) | env |

### 2.7 Context trace / dashboard log buffers (disk & UI)

| File | Constant | Default | Suggested home |
|------|----------|---------|----------------|
| `context_trace.py` | `MAX_TRACES` | 100 | `listener_advanced` or `operator_settings` |
| `context_trace.py` | `MAX_OUTREACH_TRACES` | 100 | same |
| `context_trace.py` | `MAX_TELEGRAM_TURNS` | 500 | same |

### 2.8 Plugin hooks

| File | Constant | Default | Suggested home |
|------|----------|---------|----------------|
| `plugin_loader.py` | `DEFAULT_TIMEOUT` | 30 | env or `operator_settings` |
| `plugin_loader.py` | `CRON_TIMEOUT` | 120 | env or `operator_settings` |

### 2.9 Backup

| File | Constant | Default | Suggested home |
|------|----------|---------|----------------|
| `backup.py` | `DEFAULT_KEEP` | 10 | `operator_settings` (`backup_keep_count`) |

### 2.10 API server safety

| File | Constant | Default | Suggested home |
|------|----------|---------|----------------|
| `api_server.py` | `REPAIR_MAX_DISPATCH_SPAWNS` | 3 | env (align with `doctor.py` / `repair_dispatcher`) |

### 2.11 Topic zones (context blend learning)

**Implemented** in `memory/operator_settings.json` → `topic_zones` via `operator_tier1.get_topic_zones_learning_settings()` with env fallbacks `FUROSHIKI_TOPIC_ZONE_LEARN_PERSIST` and `FUROSHIKI_TOPIC_ZONE_LEARN_MAX`. Consumers: `context_zones.build_learned_keywords()` → optional `db.persist_learned_topic_keywords()`. Zone vocabulary itself lives in SQLite `topic_zones` / `topic_zone_keywords` (dashboard **Self & user → Topic zones**; JSON under `/dashboard/api/topic-zones` in `dashboard_routes.py`).

| File | Constant | Default | Suggested home |
|------|----------|---------|----------------|
| `operator_tier1.py` | `FUROSHIKI_TOPIC_ZONE_LEARN_PERSIST` | on | `operator_settings` → `topic_zones.persist_learned` |
| `operator_tier1.py` | `FUROSHIKI_TOPIC_ZONE_LEARN_MAX` | 5 (1–50) | `operator_settings` → `topic_zones.max_per_zone` |

---

## 3. Tier 2 — Env-only candidates (document in `.env.template`; optional dashboard later)

| File | Constant | Default | Notes |
|------|----------|---------|-------|
| `llm.py` | `_OPENROUTER_LOG_REQUEST_BEGIN` | on | `FUROSHIKI_OPENROUTER_LOG_REQUEST_BEGIN` exists inline — ensure template |
| `brain.py` | `TICK_INTERVAL` | 10 | High impact; prefer env `FUROSHIKI_BRAIN_TICK_SEC` |
| `brain.py` | `CHILD_CHECK_S` | 10 | env |
| `brain.py` | `CHILD_RESTART_BACKOFF_MAX` | 120 | env |
| `doctor.py` | `SOUL_ENGINE_WARN_MINUTES` / `CRITICAL_MINUTES` | 20 / 60 | env |
| `doctor.py` | `LOG_ROTATE_BYTES` | 10 MiB | env |
| `doctor.py` | `MIND_QUEUE_STUCK_DAYS` | 7 | env |
| `doctor.py` | `CRON_LOG_PURGE_DAYS` | 30 | env |
| `doctor.py` | `ERROR_RECURRENCE_THRESHOLD` / `ERROR_WINDOW_HOURS` | 3 / 24 | env |
| `doctor.py` | `REPAIR_*` (dedup hours, excerpt lines, pending stuck, retry) | various | env |
| `doctor.py` | `BRAIN_HEARTBEAT_STALE_SEC` | 120 | env |
| `doctor.py` | `DOCTOR_TICK_INTERVAL` | 300 | env |
| `repair_dispatcher.py` | `MAX_ATTEMPTS` / `CLI_TIMEOUT_SECONDS` | 3 / 300 | env (match names with `self_modification_dispatcher` if unified) |
| `self_modification_dispatcher.py` | `MAX_ATTEMPTS` / `CREATION_TIMEOUT` / `REVIEW_TIMEOUT` | 1 / 300 / 120 | env |

---

## 4. Tier 3 — Soul engine, mind queue, curiosity (tune for product; consider JSON)

| File | Constant | Default | Note |
|------|----------|---------|------|
| `soul_engine.py` | `KIND_BASE_SCORE`, `URGENCY_MULTIPLIER`, `SELF_QUESTION_PRIORITY_MULTIPLIER`, `KIND_DEFAULT_PRIORITY` | dicts | Large; **future** `soul_engine_tuning.json` if needed |
| `soul_engine.py` | `RECENCY_HALF_LIFE_DAYS` | 14 | Candidate for env |
| `soul_engine.py` | `MAX_MIND_QUEUE_INSERT` | 10 | env |
| `soul_engine.py` | `WATCHING_AUTO_DISMISS_DAYS` | 7 | env |
| `soul_engine.py` | `ANTICIPATION_EXPIRE_DAYS` | 14 | env |
| `curiosity_triage.py` | `TRIAGE_BATCH_LIMIT` | 5 | env |
| `run_self_questions_contemplation.py` | `DEFAULT_BATCH` / `DEFAULT_CHAT_LINES` / `MIN_RESOLVE_CONFIDENCE` | 14 / 36 / 0.72 | env |

---

## 5. Tier 4 — Semantic dedup & clustering (high regression risk)

| File | Constant | Default | Note |
|------|----------|---------|------|
| `self_question_dedup.py` | `TEXT_SIMILARITY_THRESHOLD` etc. | 0.62–0.72 | Changing affects DB/Chroma dedup; needs tests + migration notes |
| `behavioral_patterns.py` | `CLUSTER_THRESHOLD` | 0.55 | Same |
| `emotion_model.py` / `emotion_signals.py` | floors, drift rates, `MICRO_*` | various | Identity-adjacent; prefer env behind a single “experimental” flag |

---

## 6. Tier 5 — Leave in code (do not move to settings)

- **Model tier sentinels:** `MODEL_FAST`, `MODEL_DEEP`, … — contract with `llm-tier-models.json`.
- **Path layout:** `DB_PATH`, `MEMORY_DIR`, `PLUGINS_DIR`, PID files — changing breaks installers and docs.
- **SQL fragments:** `PRIORITY_TIER_RANK_SQL`, `USER_PROFILE_SEMANTIC_QUERY` structure.
- **Harness / source string IDs:** `CONVERSATION_SOURCE_*`, `context_augmentations` harness IDs.
- **Repair / delegation protected paths:** `PROTECTED_PATHS`, `ALLOWED_WRITE_TARGETS` — security policy belongs in code review, not JSON on disk next to runtime.
- **Templates:** `onboard.py` SOUL/USER/MEMORY templates — versioned in git.
- **Enum-like frozensets:** `ARTIFACT_REPORT_TASK_TYPES`, `OPERATIONAL_OUTREACH_KINDS` — protocol between components.

---

## 7. Recommended implementation order

1. **Extend `operator_settings`** with grouped sections: `telegram` (poll, history turns, session gap), `delegation_thresholds`, `reply_queue`, `voice_context` (e.g. `mind_queue_max_stale_in_prompt`), `post_conversation`, `session_recap`, `user_facts`, `context_trace_limits`, `backup_keep`, `plugin_timeouts`.
2. **Add env mirrors** for every new key (same pattern as today) for CI and headless hosts.
3. **Extend dashboard Settings** with collapsible sections + validation; reuse GET/PUT `/operator-settings` with merged schema.
4. **Tier 3 / 4** only after integration tests and `furoshiki trace --simulate` checks for prompt pipelines.

---

## 8. Files with no `CONST =` grep hit but important tunables

- **`listener_advanced_settings.py`**: All runtime fields are already in JSON + **Advanced** page.
- **`telegram_preamble_status.py`**: Mostly reads from `listener_advanced_settings` / env — few stray literals; spot-review when touching prompts.
- **`outreach_pulse.py`**: Kind sets are semantic; age thresholds inside functions — secondary pass if outreach tuning is requested.

---

*This document is descriptive. Implementing changes should follow `docs/SELF-AWARENESS-DESIGN.md`, `.env.template`, and the dashboard HTML sync rule.*
