RFD 038: Config Reset Keywords
- Status: Accepted
- Category: Design
- Authors: Jean Mertz git@jeanmertz.com
- Date: 2026-03-08
- Required by: RFD 051, RFD 070, RFD 079
Summary
This RFD extends --cfg with two reserved UPPERCASE keyword values — NONE and WORKSPACE — that each define a reset point: a known state that --cfg returns to when the keyword is encountered. Both keywords share the same reset-then-layer mechanism: each persists a ConfigDelta::Reset event that clears accumulated state to PartialAppConfig::default(), and WORKSPACE appends a ConfigDelta::Apply carrying the workspace's resolved partial on top. Subsequent --cfg directives layer on top of the reset, processed using the existing left-to-right merge model from RFD 008. NONE is additionally a pre-pipeline gate that skips all implicit config loading, providing an escape hatch when implicit config is broken or when a script wants full control.
This RFD also defines loader.reset = "none" for self-contained config entries. When a --cfg entry declares this setting, JP performs the same positional reset as --cfg=NONE immediately before applying that entry. Unlike the NONE keyword, loader.reset does not skip implicit config loading; it only affects the accumulated state at the entry's position in the --cfg directive stream.
To persist these reset semantics, ConfigDelta is promoted from a struct to an enum with Apply (existing shape) and Reset (new) variants.
This RFD focuses on reset points in the --cfg directive stream: the two keyword reset points and the entry-local loader.reset = "none" setting. Conversation-ID values for --cfg (e.g. --cfg=jp-c17528832001) and fork-implicit config are out of scope here; see Non-Goals.
Motivation
JP loads config from several sources at startup (see RFD 079 for the full load sequence). There is no way to deliberately reset that accumulated state to a known baseline mid-invocation. Users hitting broken config have no escape hatch; scripts can't start from program defaults without brittle workarounds; users who want to undo post-creation changes to a conversation must hand-pick each field to reset.
This RFD introduces two reset-point keywords (NONE and WORKSPACE) and an entry-local reset setting so users can write whatever reset they need directly:
- Scripting and automation. A script that wants predictable config regardless of the user's environment needs to bypass implicit loading entirely (
--cfg=NONE). - Broken-config recovery. If a config file has a syntax error,
jpcan't even start to accept a fix.--cfg=NONEprovides an escape hatch that skips implicit loading entirely. - Re-adopting workspace defaults. A conversation that has diverged from workspace config should be able to re-adopt it without hand-picking every changed field (
--cfg=WORKSPACE). - Self-contained config entries. A named config entry can declare
loader.reset = "none"sojp q -c entryapplies that entry on top of program defaults without requiring the user to remember--cfg=NONE -c entry.
Targeted revert of individual sources — for example, undoing a specific -c that was applied — is covered by RFD 070's -C directive, which uses claim history rather than value-based resets.
Design
UPPERCASE keywords for --cfg
The existing --cfg flag gains reserved UPPERCASE keyword values. Both keywords reset the accumulated config state at their position in the --cfg list; they differ in what they reset to. Persistence is uniform: each keyword emits a ConfigDelta::Reset event into the conversation stream, and WORKSPACE follows that with a ConfigDelta::Apply carrying the workspace's resolved partial. The Reset clears accumulated state to defaults; whatever Apply events follow (whether the keyword's own or user-supplied directives) layer on top in left-to-right order.
NONE
Resets to program defaults (the compiled-in values on AppConfig::default()) and additionally triggers a pre-pipeline gate: if NONE appears anywhere in the --cfg list, implicit config loading (described in RFD 079) is skipped entirely. Only explicit --cfg values apply on top of program defaults.
Required fields without defaults (for example assistant.model.id) must be supplied by subsequent explicit --cfg values. Otherwise validation fails with a clear error indicating which fields are missing.
WORKSPACE
Resets to the workspace's fully-resolved config — the merged result of implicit loading as described in RFD 079. This is the same state that new conversations use by default when no --cfg keywords are present.
Implicit loading includes JP_CFG_* environment variables (see RFD 079), so WORKSPACE captures whatever env vars are in effect at invocation time. The persisted reset records their values; re-running later with different env vars does not retroactively change an already-stored reset.
Note that config_load_paths (the deferred-loading search path for --cfg <name>) is a setting within the workspace config, not a separate loading mechanism. WORKSPACE includes whatever value config_load_paths has in the merged config, but merely referencing that setting doesn't load any of the files it points to — those are only loaded on explicit --cfg <name> invocation.
Entry-local reset with loader.reset
A config entry can declare that it resets accumulated config before applying itself:
[loader]
reset = "none"loader.reset = "none" is the entry-local equivalent of placing --cfg=NONE immediately before that entry in the ordered --cfg stream. It resets the accumulated config to program defaults, then applies the entry's resolved config, including its own loader.extends tree.
# If `committer` declares `loader.reset = "none"`, these are equivalent
# for config state after the `committer` entry applies:
jp q -c committer
jp q -c NONE -c committerThe equivalence is positional, not pre-pipeline. loader.reset does not skip implicit config loading, because JP must load the base config before it can resolve and read the committer entry. If implicit config is broken, the user still needs --cfg=NONE / --no-cfg.
The setting is honored only when the file is loaded as an explicit --cfg entry. If the same file is reached through another file's loader.extends, its loader.reset value is ignored. A transitive reset would let an included fragment discard its parent entry's accumulated config, which is too surprising.
When one --cfg argument resolves to multiple entries across roots, each entry is processed in root precedence order. A loader.reset = "none" on a later entry resets state at that point, discarding earlier entries from the same argument.
Only "none" is defined by this RFD. Other reset targets can be added later if there is a concrete use case.
--no-cfg shorthand
--no-cfg is shorthand for --cfg=NONE. RFD 070 extends --no-cfg to accept a value for targeted revert (--no-cfg <source>, --no-cfg key=value); the bare form retains its meaning from this RFD.
Ordering
All --cfg values — including both keywords — are processed left-to-right (RFD 008). A keyword at a given position resets the accumulated state to its target; subsequent --cfg values layer on top.
NONE has a second effect beyond the positional reset: a pre-scan of the --cfg list detects any exact NONE match and, if found, skips all implicit loading (see RFD 079 for what that entails). Implicit loading is a pipeline-level concern that must be decided before directive processing, hence the pre-scan.
The positional reset and the pre-scan gate together mean pre-NONE directives are discarded:
# Pre-scan detects NONE → skip implicit loading.
# Directive loop: `foo.toml` applied, then NONE resets to defaults.
# Result: program defaults only.
jp q --cfg=foo.toml --cfg=NONE
# Pre-scan detects NONE. Directive loop: NONE sets state to defaults,
# then foo.toml applied. Result: defaults + foo.toml.
jp q --cfg=NONE --cfg=foo.tomlPre-NONE --cfg values are still parsed by clap (which validates syntax at parse time), but the directive loop skips processing them: their file-load and merge effects are not executed, since NONE's positional reset would discard them anyway. A syntactically malformed pre-NONE --cfg value is therefore still rejected at clap parse time — NONE is not a recovery mechanism for malformed arguments on the same command line. It recovers from broken implicit config (the files JP auto-loads), which is the actual motivating use case.
Explicit paths under NONE
Since NONE skips implicit loading, no config files have been read and the resolved config_load_paths is empty. Subsequent --cfg <name> directives that rely on search-path resolution will fail — there are no search paths to look in.
This is intentional. For scripting under NONE, always reference config files by explicit path:
# Works: explicit path
jp q --cfg=NONE --cfg=./config/mre.toml
# Fails: `mre` requires config_load_paths to resolve, which NONE skipped
jp q --cfg=NONE --cfg=mreMore examples:
# Start from defaults (implicit loading skipped), layer custom config
jp q --new --cfg=NONE --cfg=./foo.toml
# Pre-NONE directives are discarded; only `fresh.toml` applies
jp q --cfg=./dev.toml --cfg=NONE --cfg=./fresh.toml
# Start from workspace config, then apply overrides
jp q --new --cfg=WORKSPACE --cfg=overrides.toml
# Escape hatch: broken workspace config, use a minimal repro
jp q --cfg=NONE --cfg=./mre.toml "test query"Conversation-ID values (--cfg=jp-c<id>) are out of scope for this RFD (see Non-Goals); if a later RFD defines them, they compose with these keywords in the same pipeline.
NONE and WORKSPACE are mutually exclusive
--cfg=NONE and --cfg=WORKSPACE cannot appear in the same --cfg list. NONE's pre-pipeline gate skips the implicit-loading step that WORKSPACE expands to, so the combination is internally inconsistent: either NONE is silently overridden, or WORKSPACE lazily re-runs the very loading the user just opted out of. Rather than choose, the directive parser rejects the combination outright:
error: --cfg=NONE and --cfg=WORKSPACE are mutually exclusive.The check runs before any directive is processed and is independent of position. To get fresh state with workspace overrides on top, run --cfg=WORKSPACE (which already resets to workspace state). To start from program defaults and layer arbitrary config explicitly, run --cfg=NONE --cfg=<paths>.
Default behavior
When no keyword is present, --cfg values layer on top of the implicit starting config:
- New conversations (
--new): starts fromWORKSPACE(current behavior, unchanged). - Continuing conversations: starts from the stream's current config state.
- Forked conversations (
--fork): out of scope here (see Non-Goals).
The implicit starting config is only relevant when no keyword appears in the --cfg list. A keyword at any position overwrites whatever came before it — including the implicit starting config — because it sets every field.
The common case (jp q and jp q --cfg=foo.toml) works exactly as today. Keywords are only needed when you want to change the starting point or reset the config state mid-conversation.
Disambiguation
UPPERCASE keywords are checked by exact string match before any other resolution. This eliminates ambiguity without heuristics:
NONE→ keywordWORKSPACE→ keywordnone→ file pathWORKSPACE.toml→ file path (not an exact keyword match)jp-c17528832001→ conversation ID
For files literally named NONE or WORKSPACE, force path interpretation with the existing @-prefix or a path-style prefix:
jp q --cfg=@WORKSPACE # treat as a path
jp q --cfg=./WORKSPACE # treat as a path (any leading `./` or `../` works)Conversation IDs (jp-c prefix + digits) are out of scope for this RFD (see Non-Goals) but share the same disambiguation pipeline: keyword matches first, then conversation IDs, then file paths.
Interaction with continuing conversations
WORKSPACE, NONE, and loader.reset = "none" work uniformly when continuing an existing conversation: each persists a ConfigDelta::Reset event in events.json representing the reset point, then any further state layers on top as Apply events.
# Continue conversation, switch to workspace config
jp q --cfg=WORKSPACE
# Continue conversation, reset to program defaults and apply custom config
# (useful for scripts that want predictable state from this point forward)
jp q --cfg=NONE --cfg=mre.tomlFor NONE: a ConfigDelta::Reset is persisted (see ConfigDelta enum) and nothing further from the keyword itself. Subsequent user-provided --cfg directives in the same invocation persist as Apply events after the Reset, so the full event stream becomes [..., Reset, Apply, Apply, ...].
For loader.reset = "none": when the resetting entry is processed, JP emits ConfigDelta::Reset before that entry's Apply. The entry itself is then persisted as a normal Apply event. Unlike the NONE keyword, no pre-pipeline gate runs.
For WORKSPACE: a ConfigDelta::Reset is persisted, immediately followed by a ConfigDelta::Apply carrying the workspace partial as it resolved at invocation time. Subsequent user-provided --cfg directives layer on top as additional Apply events, so the stream becomes [..., Reset, Apply(workspace), Apply, Apply, ...]. The persisted workspace Apply is value-stable — re-running later, even after workspace config files have been edited, does not retroactively change the persisted reset. To re-adopt updated workspace config, run --cfg=WORKSPACE again.
When folded by future invocations, Reset discards all accumulated state and restarts from PartialAppConfig::default(). Subsequent Apply events apply on top of defaults. Future invocations folding the stream see the reset as authoritative: anything before it is effectively discarded for config resolution.
Both keywords and loader.reset therefore use the same reset-then-layer machinery, and Reset events terminate RFD 070's -C walk-back regardless of which source emitted them.
Chat history (turns, messages, tool calls) is always loaded — only the config resolution is affected by Reset.
ConfigDelta enum
To persist Reset semantics, ConfigDelta is promoted from a struct to an enum:
#[derive(Debug, Clone)]
pub enum ConfigDelta {
Apply(ApplyDelta),
Reset(ResetDelta),
}
pub struct ApplyDelta {
pub timestamp: DateTime<Utc>,
pub delta: Box<PartialAppConfig>,
}
pub struct ResetDelta {
pub timestamp: DateTime<Utc>,
}Serialization is hand-rolled to preserve the existing event envelope. Today, InternalEvent::ConfigDelta is wrapped at the InternalEvent layer with {"type": "config_delta", ...} (see crates/jp_conversation/src/stream.rs). The discriminator between Apply and Reset lives inside that envelope as an op field — putting it under type would collide with the outer envelope's own type tag and break deserialization (the existing InternalEvent::deserialize dispatches on the top-level type).
Wire shapes:
// Apply (unchanged from today; no `op` field).
{"type": "config_delta", "timestamp": "...", "delta": { /* ... */ }}
// Reset (new).
{"type": "config_delta", "op": "reset", "timestamp": "..."}The Apply form is identical to today's on-disk shape. Legacy events (no op field) decode as Apply — no migration needed. deserialize_config_delta in crates/jp_conversation/src/stream.rs is updated to look for op == "reset" first and fall through to the legacy Apply parse otherwise.
Fold semantics:
Apply: mergedeltainto accumulated state, applyunsets, record claims (per RFD 070).Reset: discard accumulated state, reset toPartialAppConfig::default(). Clear any per-invocation claims state. SubsequentApplyevents apply on top of defaults.
For stream walk-back (used by RFD 070's -C revert): a Reset event terminates the walk. Anything before the Reset is unreachable — -C treats it as equivalent to reaching the base config.
New conversation creation with NONE and loader.reset
When a new conversation is created with NONE in the --cfg list:
- Pre-scan skips implicit loading, so the starting partial is
PartialAppConfig::default(). - Post-
NONEdirectives are merged on top of defaults during the directive loop, and the result is written tobase_config.jsonas today's flat partial (per RFD 054). - No
Resetevent is emitted at creation time — the base file already represents the post-reset state. Subsequent invocations load the conversation and start from this flat base.
Example:
jp q -c NONE -c dev --new foobarProduces base_config.json containing dev's contribution merged on top of program defaults. The conversation's working config starts at "just dev" regardless of what happens to workspace config files afterward.
loader.reset = "none" follows the same creation-time rule at its directive position. If committer declares loader.reset = "none", then:
jp q -c committer --new foobarwrites the post-reset state to base_config.json without emitting a Reset event at creation time.
NOTE
RFD 070 reshapes base_config.json to { base, init } and adds per-source claims. Once that lands, post-NONE directives at creation time will land in init as Apply events with their own claims rather than being merged into base. Until then, fields touched by post-NONE directives at creation time have no per-source provenance.
Drawbacks
--no-cfg alone is still incomplete. Some config fields (e.g. model) are required and have no default values. --no-cfg without subsequent --cfg values produces a config that fails validation. This is the intended behavior for the escape-hatch use case (user will add their own --cfg), but the error message must be clear about what's missing.
UPPERCASE convention is unusual. Most CLI tools use lowercase for flag values. The convention is simple to learn and eliminates disambiguation entirely, but it may surprise users initially. There is precedent: Vim uses -u NONE and -U NONE to distinguish the keyword NONE (meaning "no file") from a lowercase file path, for the same reason we do here.
Pre-NONE directives are silently dropped. Under the position-sensitive model, --cfg dev --cfg NONE discards the -c dev. This is deliberate — the user's intent is clear from the reset — but it means typos or mistaken argument order can silently lose configuration. User-facing help should note this: NONE (and by extension --no-cfg) discards everything before it.
Alternatives
Sigil-prefixed keywords
Use a prefix like @ (--cfg=@parent, --cfg=@workspace) to distinguish keywords from file paths.
Considered but rejected in favor of UPPERCASE, which requires no special characters and is visually distinct.
Lowercase reserved keywords
Use lowercase reserved words (--cfg=none, --cfg=workspace) and rely on the @-prefix or ./-prefix escape for files literally named none or workspace.
Rejected because lowercase tokens are visually indistinguishable from common file names. UPPERCASE makes the keyword nature obvious to readers and minimizes the risk of accidental shadowing — the relevant precedent is Vim's -u NONE, which uses the same uppercase convention to disambiguate against a file path.
Separate --config-base=none|workspace flag
Express the reset point via a dedicated flag instead of a value within --cfg.
Rejected because a separate flag would split the ordered directive stream into two parallel inputs and break the left-to-right composition with other -c directives that this design relies on (per RFD 008). Putting the reset point at a position in the --cfg list, alongside everything else, is what makes commands like --cfg=foo.toml --cfg=NONE --cfg=bar.toml self-explanatory.
Non-Goals
Conversation-ID inheritance.
--cfg=jp-c<id>(expanding another conversation's resolved config) and--forkimplicit config are out of scope. They share the--cfgdisambiguation and directive pipeline established here, but their semantics — implicit fork config, inner- conversation provenance collapse — belong in a future RFD. Where this RFD mentions their interaction with the pipeline, it assumes the inheriting partials behave like any other fully-populated source.STARTkeyword (reset to conversation creation state). A keyword that expands tobase + initfrombase_config.json— the full state the conversation was created with — was considered but deferred. For most users,--cfg WORKSPACE --cfg <original-source>produces a close-enough result, and RFD 070's-Chandles targeted revert of individual sources. Theinitlist introduced by RFD 070 preserves the infrastructure needed to addSTARTlater as a small follow-up RFD if demand emerges.USERkeyword. AUSERkeyword that expands to only the user's global config (skipping workspace config) was considered but deferred. The use case — portable personal defaults across projects — is real but narrow enough to add later without design changes.BASEkeyword. ABASEkeyword that expands to just thebasefield ofbase_config.json(the creation-time workspace snapshot, withoutinit's overrides) was considered but deferred. The use case — strip creation-time customization but keep the creation-era workspace config — is genuine but narrow. Added later without design changes if needed.Config diffing. Showing what changed between the inherited config and the final resolved config is useful but orthogonal.
Risks and Open Questions
Validation timing
With NONE, config validation must happen after all --cfg values are applied, not at base resolution time. The current validation flow should already handle this (validation happens on the final merged config), but it needs verification.
NONE detection ordering
Because NONE's pre-scan gate affects implicit config loading, it must be detected before load_base_partial (which performs the implicit-loading sequence from RFD 079) and before conversation-config folding. The CLI entry point scans --cfg values for an exact NONE match before either step runs; if found, both are skipped and the base partial starts at PartialAppConfig::default(). The conversation stream itself is still loaded — chat history (turns, messages, tool calls) is unaffected. The positional reset (emitting a Reset event into the stream for continuing conversations, or absorbing post-NONE directives into base_config.json for new conversations) happens during the later directive loop.
Reset and -C (from RFD 070)
A Reset event in the stream terminates RFD 070's -C walk-back: fields claimed before the Reset are unreachable. This matches the semantic model that Reset is an authoritative discard — pre-reset state doesn't contribute to future config resolution, so it shouldn't contribute to revert either. A -C dev after a Reset that discarded dev's claims behaves as "no fields currently claimed by dev" and emits the standard diagnostic.
loader.reset metadata timing
loader.reset is loader metadata. It must be read from a resolved --cfg entry before that entry is applied, but it is not part of the persisted application config. The field itself is not written to conversation deltas or base_config.json; only its reset effect is persisted through Reset events or creation-time base state.
Implementation Plan
Phase 1: ConfigDelta enum and fold-time reset
Promote ConfigDelta from a struct to an enum with Apply and Reset variants (see ConfigDelta enum for the shape).
- Rename the existing
ConfigDeltastruct toApplyDelta. - Add
ResetDelta { timestamp: DateTime<Utc> }. - Define
ConfigDeltaas a plain enum (no#[serde(tag)]). Hand-rollSerializesoApplyproduces today's flat shape (noopfield) andResetproduces{"op": "reset", "timestamp": "..."}. The outerInternalEventwrapper continues to add"type": "config_delta"unchanged. - Update the hand-rolled
deserialize_config_deltaincrates/jp_conversation/src/stream.rsto dispatch onop == "reset". Events without anopfield (including all legacy events) decode asApply. - Update
add_config_deltain the same file: the existing destructure and empty-diff suppression apply only toApply.add_config_delta(Reset)always appends —Resetevents have no diff to suppress and their presence is the whole point. - Update the stream fold (in
config(),Iter,IterMut,IntoIter, and theapply_config_deltahelper introduced by RFD 070 Phase 1) to match on the variant:Apply: existing merge + unset + claims logic.Reset: replace accumulated state withPartialAppConfig::default(); clear any in-progress claims state.
- Update RFD 070's walk-back algorithm to terminate at
Resetevents. - Add a common
timestamp()accessor onConfigDeltaso call sites that only need the timestamp don't need to match on the variant.
Tests:
ConfigDelta::Resetround-trips through fullInternalEventserialize/deserialize (not justdeserialize_config_deltain isolation), asserting the on-disk shape is{"type":"config_delta","op":"reset",...}.ConfigDelta::Applyon-disk shape is unchanged from today.- Backward compat: legacy events without
opdecode asApply. add_config_delta(ConfigDelta::Reset(_))always appends, even on a stream where adding an emptyApplywould be suppressed.- Fold: stream
[Apply(dev), Reset, Apply(fresh)]resolves to defaults + fresh. -C devwalk-back after[Apply(dev), Reset]reports no matching claims (Resetterminated the walk).
Can be merged independently.
Phase 2: Reset recognition in --cfg
Add UPPERCASE keyword recognition and entry-local loader.reset recognition to --cfg processing. All reset points share the same reset-then-layer machinery; they differ in what triggers them and what, if anything, is layered on top.
Up-front mutual-exclusion check. If both NONE and WORKSPACE appear in the same --cfg list, reject with a clear error before any directive runs (see NONE and WORKSPACE are mutually exclusive). The check is purely based on the parsed --cfg list and is independent of position.
Implement NONE:
- Pre-pipeline gate. The CLI entry point scans
--cfgvalues for an exactNONEmatch; if present,load_base_partialand conversation-config folding are skipped. The conversation stream is still loaded for chat history. Pre-NONE--cfgvalues are still parsed by clap (syntactic validation runs at parse time); the directive loop simply skips processing them. - Positional reset. In the directive loop, when
NONEis encountered:- For new conversations (
--new,--fork): no event is emitted; the new conversation'sbase_config.jsonis written with the post-NONEmerge result as today's flat partial (per RFD 054). RFD 070 later refactors this to{ base, init }so post-NONEdirectives gain per-source claims. - For continuing conversations: emit a
ConfigDelta::Resetevent intoevents.json. Post-NONEdirectives emitApplyevents after it.
- For new conversations (
Implement WORKSPACE:
- For new conversations (
--new,--fork): no events are emitted from the keyword itself — the default new-conversation behavior already starts from workspace state and writesbase_config.jsonaccordingly.--cfg=WORKSPACEat creation time is therefore equivalent to omitting it, and post-WORKSPACEdirectives merge in as today. - For continuing conversations: emit a
ConfigDelta::Resetevent followed by aConfigDelta::Applycarrying the workspace partial as resolved at invocation time. Post-WORKSPACEdirectives emitApplyevents after the pair.
Depends on Phase 1.
Implement loader.reset = "none":
- Read the setting from each resolved
--cfgentry before applying that entry's partial. - Honor it only for explicit
--cfgentries. Ignore it when the same file is reached throughloader.extends. - For continuing conversations, emit
ConfigDelta::Resetimmediately before the resetting entry'sApplyevent. - For new conversations, absorb the reset into the new base state the same way
NONEis absorbed at creation time. - Do not run the
NONEpre-pipeline gate. Broken implicit config still requires theNONEkeyword or bare--no-cfg.
Tests:
- A continuing conversation with
-c committerwherecommitterdeclaresloader.reset = "none"appends[Reset, Apply(committer)]. - A new conversation with the same entry writes post-reset state to
base_config.jsonand emits noResetevent at creation time. - A file reached through
loader.extendswithloader.reset = "none"does not reset the parent entry. - A multi-root
--cfg devwhere the later root's entry declaresloader.reset = "none"discards earlier resolved entries from that same argument. loader.reset = "none"does not skip implicit loading; a broken implicit config still fails unless the command usesNONE/--no-cfg.
Conversation-ID resolution is out of scope for this RFD (see Non-Goals).
References
- RFD 008: Ordered Tool Directives — establishes left-to-right processing for interleaved CLI flags.
- RFD 079: Config Sources and Load Order — describes the implicit loading sequence that
NONEskips andWORKSPACEre-adopts. - RFD 070: Negative Config Deltas — introduces
initinbase_config.json(the infrastructure a futureSTARTkeyword would use), extendsApplyDeltawith claims and unsets, and adds a valued form to--no-cfgfor targeted revert. - RFD 054: Split Conversation Config and Events — defines today's flat
base_config.jsonshape that this RFD writes post-NONEstate into until RFD 070 reshapes it. crates/jp_config/src/fs.rs— config loading and merging logic.crates/jp_cli/src/config_pipeline.rs— resolved--cfgentries and loader metadata.crates/jp_conversation/src/stream.rs— conversationConfigDeltaand stream fold.crates/jp_config/src/delta.rs—PartialConfigDeltatrait and partial-merge primitives.