feat: a memory config block, and a searchable config dialog - #103
Merged
Merged
Conversation
The config dialog discovers its rows by walking the config, so any block
that exists is already editable. But persistent memory was not reachable
at all: it had no block (the one on the wider embedder type was removed
because nothing read it), its db path and entry cap were hardcoded, and
the store was frozen at construction. A ninth block fixes that.
memory: { enabled, storage, maxEntries }
enabled is a per-call gate on recallForTask, so turning recall off
takes effect on the next task and nothing else. It defaults to true
because recall is already unconditional today — false would silently
delete a running feature on upgrade.
storage and maxEntries resolve per use rather than at construction.
Editing storage in nexus.jsonc and saving now moves the database
without a restart; the old file is left intact, and a path that cannot
be opened throws with the previous database still current rather than
leaving the store with no handle. The swap is ordered open-then-close
and contains no await, so concurrent recalls cannot observe a partial
state. The cap is a pure read and needs no reopen.
Three fixes fell out of the same work:
A relative memory.storage opened the grouped model picker, and picking
a model wrote a provider/id into the database path. A field is now a
model reference because of what it is, never because a string contains
a slash — which also fixes customRoles[].prompt, whose text nearly
always names a file, and customRoles[].displayName.
memoryBase was seeded by spreading an object literal whose keys were
present but undefined, so storage and maxEntries were dropped from
getConfig() and from every file getSaveableConfig() wrote. 1373 green
tests missed it because they all built a bare NexusConfigManager; the
orchestrator-configured path is now covered.
The Save row said "Write 3 changes to disk" while the save wrote nine
blocks. The number described what the user touched, not what was
written, so the wording no longer claims to be a count of the write.
Config dialog:
Search on the hub, matching block names and setting names, landing
straight on the field's editor. A query that matches nothing is a
selectable row, not a zero-option dialog, so the screen is escapable
and a typo is visible in the quoted query.
Dirty counts per block, and a hub header naming the number of changes
not yet written. Every marker is a glyph plus a number, so none of
them depends on colour.
A boolean toggles on its own screen in one keystroke, with a visible
way out beneath it, instead of opening a second dialog to pick from
two rows. One mutating row is never the only row on a screen.
1386 tests pass.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
Persistent memory had no config surface at all. The block on the wider embedder type (
NexusConfig) was removed years ago because nothing read it, its database path and entry cap were hardcoded inmemory-store.ts, and the store was frozen at construction. The config dialog discovers its rows by walking the config, so adding the block is what makes it editable — there was nothing to wire up in the dialog itself.Adds
memory: { enabled, storage, maxEntries }as the ninth block, and three improvements to the dialog.memoryenabledrecallForTaskstoragemaxEntriesenableddefaults totrue, because recall is already unconditional on both spawn paths. Defaulting it tofalsewould silently delete a running feature for every existing user on upgrade. It does not gate store construction: off means "read nothing", not "no database".storageandmaxEntriesused to require a restart, which made them a knob that reads like a control and is inert — the exact reason the originalmemoryblock was removed. They now resolve per use. The swap is ordered open-then-close-then-publish and contains noawait, so concurrentrecallForTaskcalls cannot observe a partial state; a path that cannot be opened throws with the previous database still open and still current, rather than leaving the store with no handle.Config dialog
select's lifetime and has noonInput— a live filter would put query state in the adapter where no test can reach it. A query matching nothing is a selectable row, not a zero-option dialog, so the screen is escapable and the quoted query makes a typo visible.Three bugs fixed along the way
A relative
memory.storageopened the model picker, and picking a model wrote aprovider/idinto the database path. A field is now a model reference because of what it is, never because a string contains a slash. This also fixescustomRoles[].prompt— role prompts are paragraphs that nearly always name a file, so the common case misclassified — andcustomRoles[].displayName.memoryBasewas seeded by spreading a literal whose keys were present butundefined, which deletedstorageandmaxEntriesfromgetConfig()and from every filegetSaveableConfig()wrote. 1373 green tests missed it because they all constructed a bareNexusConfigManager; the orchestrator-configured path is now covered.The Save row claimed a count it did not deliver — "Write 3 changes to disk" while the save wrote nine blocks. The number described what the user touched, not what was written, so the wording no longer asserts it is a count of the write.
Behaviour change to be aware of
Under
models, a value with no slash (a typo, or a note parked in the block) now opens the provider picker rather than a free-text box.modelsis an open keyed block whose leaves are model references by definition, and the picker is what you want for a wrong model id. Restoring the old fallback would need a shape check scoped tomodelsalone — I would not recommend it.How to verify
Then
/nexus config(or/nc):memory; the header showsNexus configurationcostand you getbudget.*fields — pick one and it opens its editor directlymemory.enabled; it flips in place on its own screen, gets a*, and the hub header reads1 change not writtenstorage(e.g../tmp/m.db), save, and confirm the row is a free-text prompt, not a provider listmemoryStore.pathandgetConfig().memory.storageagree — they used to be able to disagreeNotes
models/budget/... merge stays field-by-field, so a level setting onlymemory.enabledcannot blankstorage.docs/API.mdis updated: the block count, the schema fence (which a test parses structurally), and the paragraph claiming a per-scope cap.