Skip to content

feat(config): expose hot-reload API for GUI integration#3715

Merged
Hmbown merged 3 commits into
Hmbown:mainfrom
gaord:feat/gui-config-hot-reload
Jun 28, 2026
Merged

feat(config): expose hot-reload API for GUI integration#3715
Hmbown merged 3 commits into
Hmbown:mainfrom
gaord:feat/gui-config-hot-reload

Conversation

@gaord

@gaord gaord commented Jun 28, 2026

Copy link
Copy Markdown
Contributor

Summary

Adds HTTP endpoints and protocol plumbing so GUI clients (CodeWhale VSCode) can read, persist, and hot-reload TUI configuration without restarting the engine. This unblocks the GUI /config panel — saving a setting in the GUI now takes effect on the next prompt turn in the running TUI engine.

New endpoints (runtime API, --port)

  • GET /v1/config returns the full GUI-facing config snapshot (model, provider, approval mode, subagents, MCP path, settings-backed keys, …)
  • POST /v1/config/set persists a single key to config.toml or settings.toml without mutating the in-memory state — the caller POSTs /v1/config/reload to apply
  • POST /v1/config/reload re-reads the on-disk config, swaps the live Arc<RwLock<Config>>, and syncs running engines via Op::SetModel / Op::SetCompaction / Op::SetSubagentRuntimeConfig

New app-server protocol

  • AppRequest::ConfigReload + app/config/reload route — mirrors the HTTP reload over stdio
  • Runtime::update_config / Runtime::reload_config_and_policy push fresh snapshots into the headless Runtime on every set/unset

Fixes folded in

  • --config <path> preserved end-to-end: RuntimeApiState.config_path flows from CLI → Config::load → all config_persistence::persist_* calls. Previously a custom --config path was lost on reload/persist, causing GUI edits to silently target the default discovery path instead of the file the server booted from.
  • subagents_enabled and subagents_max_depth persist correctly via persist_subagents_bool_key / persist_subagents_integer_key. Previously these two keys were rejected as unknown.
  • max_history returns 400 for non-integer input instead of silently falling back to 100 via unwrap_or.
  • RuntimeThreadManager.config moved from Config to Arc<RwLock<Config>> so reloads are visible to active threads. All 3 read sites go through read_config() or a snapshot clone, avoiding held guards across await points.

Testing

  • cargo fmt --all -- --check
  • cargo clippy --workspace --all-targets --all-features (no new warnings; 2 pre-existing &*config deref warnings fixed)
  • cargo test --workspace --all-features (78 runtime_api::tests + 45 app-server tests pass)

New tests (14 in runtime_api::tests)

  • set_config validates input for max_history, subagents_enabled, subagents_max_depth (rejects non-integer / non-bool with 400)
  • set_config rejects unknown keys with 400
  • set_config with persist:false is a true dry run — disk file unchanged
  • set_config with --config <path> writes to the specified file (not default discovery)
  • set_config for subagents_enabled=false and subagents_max_depth below ceiling persist correctly
  • set_config response contains all expected fields (key/value/persisted/requires_reload)
  • reload_config returns success, refreshes mcp_config_path, reads from config_path (not default), returns 500 on malformed TOML
  • reload_config applies multiple accumulated persisted keys in one shot (model + max_depth + enabled)

Checklist

  • Updated docs or comments as needed (doc comments on Runtime::update_config / reload_config_and_policy explain what is and isn't refreshed)
  • Added or updated tests where relevant (14 new tests)
  • Verified TUI behavior manually if UI changes — N/A, no TUI UI changes; verified via GUI side (separate GUI PR)
  • Harvested/co-authored credit uses a GitHub numeric noreply address — N/A, no co-authors

Adds HTTP endpoints and protocol plumbing so GUI clients can read,
persist, and hot-reload TUI configuration without restarting the engine:

- `GET /v1/config` returns the full GUI-facing config snapshot
- `POST /v1/config/set` persists a single key without mutating the
  in-memory state (caller POSTs /v1/config/reload to apply)
- `POST /v1/config/reload` re-reads the on-disk config, updates the
  live Arc<RwLock<Config>>, and syncs running engines via
  Op::SetModel / Op::SetCompaction / Op::SetSubagentRuntimeConfig
- `app/config/reload` mirrors the same flow over the stdio app-server
  protocol; Runtime::update_config / reload_config_and_policy push
  fresh snapshots into the headless Runtime on every set/unset

Notable fixes folded in:
- `--config <path>` is now preserved end-to-end across reload and
  persistence so GUI edits target the same file the server booted
  from instead of falling back to default discovery
- `subagents_enabled` and `subagents_max_depth` now persist correctly
  via persist_subagents_bool_key / persist_subagents_integer_key
- `max_history` rejects non-integer input with 400 instead of silently
  falling back to the default
- RuntimeThreadManager.config moved from Config to
  Arc<RwLock<Config>> so reloads are visible to active threads; all
  read sites go through read_config() or a snapshot clone

Tests: 14 new runtime_api::tests cover the set/get/reload contract,
validation errors, subagent edge cases, persist:false dry-run, and
multi-key accumulated reloads. Existing 64 runtime_api + 45
app-server tests still pass.

Signed-off-by: Ben Gao <bengao168@msn.com>
@gaord gaord requested a review from Hmbown as a code owner June 28, 2026 06:55
gaord and others added 2 commits June 28, 2026 18:02
Resolve 3 conflicts so PR Hmbown#3715 merges cleanly:

- app-server/src/lib.rs (ConfigSet/ConfigUnset): keep BOTH the
  PR's runtime.update_config() hot-reload for the in-process Runtime
  AND main's invalidate_stdio_bridge() for the stdio bridge child.
- runtime_threads.rs: use the PR's cfg.* snapshot style (self.config
  is now Arc<RwLock<Config>>) and add main's new moraine_fallback field.

Verified: cargo check --workspace --all-features, 78 runtime_api tests,
47 app-server tests all pass.
The config hot-reload PR expands process_app_request enough that the macOS test runner can overflow while exercising stdio goal methods in the full app-server suite. Route stdio app requests through a small helper and box the app-request future so the dispatcher does not carry the full app response state machine on the caller stack.

Verified with cargo fmt --all -- --check and cargo test -p codewhale-app-server --lib --locked.

Signed-off-by: Hunter B <hmbown@gmail.com>
@Hmbown Hmbown merged commit 49e8d9d into Hmbown:main Jun 28, 2026
13 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants