···32323333All string values in config support `${ENV_VAR}` expansion.
34343535+## Runtime Model
3636+3737+There are two different config lifecycles:
3838+3939+- one-shot commands such as `run`, `telegram`, and `slack`
4040+- the long-running `console serve` process
4141+4242+One-shot commands are simple:
4343+4444+```text
4545+process start
4646+ |
4747+ v
4848+load config once
4949+ |
5050+ v
5151+run with that config until process exit
5252+```
5353+5454+`console serve` is different because the process stays alive. It uses runtime snapshots.
5555+5656+Resolved console config path:
5757+5858+- `--config`, if explicitly set
5959+- otherwise the first existing file in `config.yaml`, `~/.morph/config.yaml`
6060+- if neither exists, the default write target is local `config.yaml`
6161+6262+Snapshot build flow:
6363+6464+```text
6565+ startup / config file change
6666+ |
6767+ v
6868+ +-------------------------------------------+
6969+ | loadConsoleRuntimeConfig(configPath, ...) |
7070+ | ----------------------------------------- |
7171+ | 1. shared defaults |
7272+ | 2. MISTER_MORPH_* env |
7373+ | 3. captured runtime flag overrides |
7474+ | current code: inherited --log-* flags |
7575+ | 4. read + ${ENV_VAR} expand config.yaml |
7676+ +-------------------------------------------+
7777+ |
7878+ v
7979+ +---------------------+
8080+ | immutable snapshot |
8181+ | reader: *viper.Viper|
8282+ +---------------------+
8383+ | |
8484+ v v
8585+ +----------------+ +----------------------+
8686+ | Console Local | | Managed Runtimes |
8787+ | in-process rt | | telegram / slack |
8888+ +----------------+ +----------------------+
8989+```
9090+9191+What this means in practice:
9292+9393+- The runtime does not use the global process `viper` as live mutable state.
9494+- A running `console serve` instance works from its current snapshot.
9595+- When `config.yaml` changes, a new snapshot is built and swapped in.
9696+- If rebuilding fails, the old snapshot keeps running.
9797+9898+## Console Update Path
9999+100100+The console Web API and setup repair path do not mutate runtime state directly. They only write YAML to the resolved config path.
101101+102102+```text
103103+browser / repair UI
104104+ |
105105+ v
106106+PUT /api/settings/* or PUT /api/setup/file?key=config
107107+ |
108108+ v
109109+write config.yaml only
110110+ |
111111+ v
112112+no direct global viper mutation
113113+no direct runtime restart call
114114+ |
115115+ v
116116+console config poller notices file fingerprint change
117117+ |
118118+ v
119119+build new snapshot
120120+ | |
121121+ | success | failure
122122+ v v
123123+swap local runtime keep old snapshot
124124+reload managed runtimes log warning
125125+```
126126+127127+This separation is intentional:
128128+129129+- the write path is responsible only for durable config
130130+- the runtime layer is responsible only for consuming snapshots
131131+- concurrency stays inside each runtime instance, not inside the config writer
132132+133133+## Console Startup With Invalid Config
134134+135135+`console serve` tries to build a runtime snapshot from the resolved config path at startup.
136136+137137+If the config file is invalid:
138138+139139+- the HTTP server still starts
140140+- the runtime falls back to a defaults-only snapshot
141141+- the setup repair UI can fix `config.yaml`
142142+- later successful file changes replace the fallback snapshot
143143+144144+This avoids a deadlock where a broken config prevents the repair UI from starting.
145145+35146## Common CLI Flags
3614737148Global flags:
···8899## 1) Goal
10101111-This note explains where configuration defaults come from today, why there are two defaulting entrypoints, where each one is used, and which one should become the shared authority.
1111+This note records the default-config decision for `mistermorph`.
1212+1313+The target is simple:
12141313-This is intentionally documented outside the current PowerShell PR scope.
1515+- there is only one source of truth for shared defaults
1616+- the default process runtime and `integration` see the same defaults
1717+- config snapshot lifecycle is separate from config file editing
1818+1919+This note is intentionally limited to defaults and snapshot boundaries.
2020+It does not define a hot-reload mechanism.
14211515-## 2) Current Defaulting Entrypoints
2222+## 2) Defaulting Shape
16231717-There are currently two functions that apply defaults to a `viper.Viper` instance:
2424+There are still two call paths that can write defaults into a `viper.Viper` instance:
18251926- `internal/configdefaults.Apply`
2027- `integration.ApplyViperDefaults`
21282222-They overlap heavily.
2929+But they no longer define defaults independently.
23302424-Both set defaults for common runtime and tool keys such as:
3131+The shared authority is:
25322626-- `llm.*`
2727-- agent limits like `max_steps`
2828-- file state/cache directories
2929-- `tools.read_file.*`
3030-- `tools.write_file.*`
3131-- `tools.bash.*`
3232-- `tools.powershell.*`
3333-- `tools.url_fetch.*`
3434-- `tools.web_search.*`
3333+- `internal/configdefaults.Apply`
35343636-## 3) `internal/configdefaults.Apply`
3535+The compatibility entrypoint is:
37363838-This is the main program default source for the global CLI/runtime `viper`.
3737+- `integration.ApplyViperDefaults`
39384040-It should be treated as the repo-wide authority for shared defaults.
3939+`integration.ApplyViperDefaults` delegates to `internal/configdefaults.Apply`.
4040+It exists because `integration` is a public package and some callers want a direct helper that writes defaults into their own `viper` instance.
4141+4242+## 3) Shared Authority
4343+4444+`internal/configdefaults.Apply` is the repo-wide authority for shared defaults.
4545+4646+It defines shared runtime defaults such as:
4747+4848+- `llm.*`
4949+- agent limits like `max_steps`
5050+- file state/cache directories
5151+- `logging.*`
5252+- `tasks.*`
5353+- `multimodal.image.sources`
5454+- `console.*`
5555+- `telegram.*`
5656+- `slack.*`
5757+- `line.*`
5858+- `lark.*`
5959+- `tools.*`
41604261Primary uses:
43624463- CLI initialization through `initConfig -> initViperDefaults`
4564- main registry loading from the global `viper`
6565+- isolated readers through delegation from `integration.ApplyViperDefaults`
46664767Relevant code:
48684969- `cmd/mistermorph/defaults.go`
5070- `cmd/mistermorph/root.go`
5171- `cmd/mistermorph/registry.go`
5252-5353-Operationally, this is the defaulting path for the normal application runtime:
5454-5555-- `run`
5656-- `console`
5757-- `telegram`
5858-- `slack`
5959-- `line`
6060-- `lark`
6161-6262-as long as they are reading from the process-wide `viper`.
7272+- `internal/configdefaults/defaults.go`
63736474## 4) `integration.ApplyViperDefaults`
65756666-This is used when code creates a fresh temporary `viper` instance and wants a self-contained config reader.
7676+This remains the public helper for callers that construct a fresh `viper` instance and want the standard default set.
67776868-It is better understood as a construction helper for isolated readers, not as an independent authority.
7878+It is not an independent authority.
7979+It is a compatibility and convenience entrypoint for `integration`.
69807081Primary uses:
7182···8899- config validation against a temporary reader
89100- integration runtime bootstrapping from explicit overrides
901019191-## 5) Why Two Sources Exist
102102+Because it delegates to the shared authority, the default process runtime and `integration` now see the same defaults.
921039393-From a first-principles view, the two functions solve different construction modes:
104104+## 5) Logging Defaults
941059595-- one for the global process config
9696-- one for isolated temporary readers
106106+`logging.*` belongs to the shared default set and should live in `internal/configdefaults.Apply`.
971079898-That distinction is reasonable.
108108+CLI logging flags are an input surface, not an independent default authority.
109109+Their job is to override when the user explicitly passes a flag.
110110+They should not carry a separate runtime truth.
99111100100-What is not ideal is that the defaults themselves are duplicated instead of sharing a single authority.
112112+## 6) Why This Cleanup Was Needed
101113102102-## 6) Current Problem
114114+Before this cleanup, the two call paths had already drifted.
103115104104-The current issue is not that there are two call paths.
105105-The current issue is that both paths each define defaults directly.
116116+Examples that had already diverged:
106117107107-This is already causing real drift, not just theoretical duplication.
108108-109109-Examples:
110110-111111-- `internal/configdefaults.Apply` contains keys that are absent from `integration.ApplyViperDefaults`, such as:
112112- - `tasks.*`
113113- - `multimodal.image.sources`
114114- - `console.*`
115115- - `line.*`
116116- - `lark.*`
117117- - `telegram.serve_listen`
118118- - `slack.serve_listen`
119119-- `integration.ApplyViperDefaults` contains `logging.*`, while `internal/configdefaults.Apply` does not
120120-- at least one shared key already disagrees:
118118+- `multimodal.image.sources` existed in one path but not the other
119119+- `logging.*` existed in a different place from the rest of the shared defaults
120120+- at least one shared key had conflicting values:
121121 - `telegram.addressing_interject_threshold`
122122- - `internal/configdefaults.Apply` sets `0.6`
123123- - `integration.ApplyViperDefaults` sets `0.3`
124122125125-That means whenever a new config key is added, especially under `tools.*`, there is a risk of:
123123+That kind of duplication is enough to make generated config, console views, and runtime behavior disagree.
126124127127-- updating one defaults function but not the other
128128-- drifting platform-specific behavior
129129-- drifting Console Settings behavior from main runtime behavior
125125+## 7) Snapshot Boundary
130126131131-This is the structural reason the PowerShell work had to touch both files.
127127+Defaults authority and snapshot lifecycle are related but not the same concern.
132128133133-## 7) Recommended Direction
129129+The intended runtime model is:
134130135135-The likely cleanup direction is:
131131+- each runtime operates on its own config snapshot
132132+- a snapshot is rebuilt from resolved config input
133133+- each runtime is responsible for its own concurrency safety
136134137137-- keep both call paths
138138-- reduce to one authority for shared defaults
135135+The intended config editing model is:
139136140140-Possible shape:
137137+- Console Web API edits `config.yaml`
138138+- editing `config.yaml` does not itself define snapshot refresh semantics
141139142142-- `internal/configdefaults.Apply` remains the shared source of truth
143143-- `integration.ApplyViperDefaults` calls into it first
144144-- `integration.ApplyViperDefaults` only adds integration-specific defaults if it truly owns any
145145-- shared `logging.*` defaults should also be moved under the same authority instead of living only in the isolated-reader path
146146-147147-This keeps the useful construction split without duplicating the actual default values.
148148-149149-## 8) Recommendation For Scope
150150-151151-This should be treated as a follow-up cleanup, not part of the PowerShell feature delivery itself.
140140+In other words:
152141153153-Reason:
154154-155155-- it changes configuration architecture
156156-- it is broader than shell-tool support
157157-- it deserves a refactor-only change with regression checks around settings loading
142142+- config mutation and snapshot regeneration should not be fused into one hidden responsibility
143143+- reload, restart, file watch, or explicit regenerate flows should be treated as a separate design decision