refactor(config): make the settings contract frontend-agnostic

rhema-config held both vocabularies: the stored settings and the
terminal UI's theme, palette, and onboarding state, under one Config
type with a global directory. A second frontend had to take the UI
settings to get the profiles, and every path helper resolved
$XDG_CONFIG_HOME/rhema no matter who called.

The crate now ships only the framework's contract. Settings is the
stored section, flattenable as one serde field. Home carries the
directory conventions as a value, so a frontend keeps its settings,
skills, subagents, and SYSTEM.md under its own base while
.agents/skills stays machine-global. Store is the seam a foreign
frontend implements over its own file and format, YamlStore is the
reference impl, and Platform collects home, store, and keychain
namespace at one construction site. KeyringSecretStore::named gives
each frontend its own OS keychain service, so two frontends never
share credentials by accident. examples/godcode.rs builds the whole
shape runnable: its own directory, its own JSON document, its own
namespace.

rhema-code's store::Document flattens the section beside its UI
fields, the layout on disk unchanged. serde's flatten silently drops
unknown keys, so the load path re-checks every leftover key against
the section: a typo'd profiles: stays loud inside the bigger
document. CodingAgentConfig gains home: Option<Home>, so a frontend
redirects skill discovery without calling the framework. claw and
acp name their keychain namespaces, and the mcp, coding-agent, and
subagents crates read conventions from the Home they are handed.
This commit is contained in:
vhaudiquet committed 2026-11-08 17:53:04 +01:00
1 parent d067f4632f
commit df4cf87a20
2 files changed
+4 -4

No files matched your search

+1 -1
View File
@@ -210,7 +210,7 @@ impl ClawConfig {
/// The default config path: `<config-base>/rhema/claw.yaml`, the same
/// base the stored rhema config lives under.
pub fn default_path() -> Result<PathBuf, ConfigError> {
Ok(rhema_config::Config::dir()?.join("claw.yaml"))
Ok(rhema_config::Home::discover()?.config.join("claw.yaml"))
}
/// The state dir: `$XDG_STATE_HOME/rhema/claw`, else
+3 -3
View File
@@ -169,11 +169,11 @@ fn mock_backends() -> Arc<rhema_claw::BackendRegistry> {
/// the keyless llama-cpp backend is always constructible — and a
/// dispatch against an unreachable provider fails loudly per turn.
fn resolve_backends(config: &ClawConfig) -> Result<Arc<rhema_claw::BackendRegistry>, ClawError> {
let stored = match rhema_config::Config::load() {
let stored = match rhema_config::Settings::load() {
Ok(stored) => stored,
Err(err) => {
eprintln!("warning: ignoring unreadable rhema config: {err}");
rhema_config::Config::default()
rhema_config::Settings::default()
}
};
let profile = stored.effective(None);
@@ -205,7 +205,7 @@ fn resolve_backends(config: &ClawConfig) -> Result<Arc<rhema_claw::BackendRegist
// included; it never writes it, so no migration runs here — the
// file's logins belong to the frontends. In a container the
// keychain is unreachable and the literal tier serves.
let secrets = rhema_config::secrets::KeyringSecretStore::new();
let secrets = rhema_config::secrets::KeyringSecretStore::named("claw");
let backends =
rhema_claw::backends::discover(config, &profile, &env, &claw_rows, Some(&secrets));
// The config's provider/model pair seeds the active backend when it