Skip to content

feat(pulse): make every optional module switchable, and fix three flags that never worked - #1748

Open
elhoim wants to merge 1 commit into
danielmiessler:mainfrom
elhoim:feat/pulse-module-toggles
Open

feat(pulse): make every optional module switchable, and fix three flags that never worked#1748
elhoim wants to merge 1 commit into
danielmiessler:mainfrom
elhoim:feat/pulse-module-toggles

Conversation

@elhoim

@elhoim elhoim commented Aug 3, 2026

Copy link
Copy Markdown

The bug underneath this

loadModules() gates three modules on config that loadPulseConfig() never supplies:

if (config.local_intelligence?.enabled !== false) { ... }   // config.local_intelligence is always undefined
if (config.hypotheses?.enabled !== false) { ... }           // same
if (config.upgrades?.enabled !== false) { ... }             // same

loadPulseConfig() builds its return object key by key and copies through voice, imessage, observability, performance, syslog, work, bunker, content, telos, hooks, da — but not those three. They're declared on PulseConfig, so it typechecks. At runtime the gate reads undefined, undefined !== false is true, and the module loads every time.

Net effect: [local_intelligence] enabled = false in PULSE.toml has never done anything. Same for [hypotheses] and [upgrades].

The wider gap

Fourteen more user-facing modules had no switch at all — wiki, memory, conduit, books, synapse, ledger, projects, assets, atlas, threatmodel, usage, evals, algorithm-tab, hermes are unconditional await import(...). And HEALTH / FINANCES / BUSINESS / GROWTH have no module of their own — observability.ts serves them at /api/life/* — so there was nothing to switch even in principle. GROWTH is the sharpest case: it's backed by an optional USER customization that isn't shipped, so out of the box its API returns note: "Growth is an optional customization; not installed." and the tab renders an empty page with no way to remove it.

Change

  • lib/modules.ts (new) — MODULE_DEFAULTS plus resolveModules(), layering defaults ← legacy [section].enabled flags ← the [modules] table. Pure and importable; pulse.ts calls main() at import time so nothing in it can be unit-tested.
  • lib.ts — re-export.
  • pulse.tsPulseConfig.modules; loadPulseConfig() returns resolveModules(parsed) (this is the passthrough fix); 12 existing gates repointed at the map; 14 previously ungateable modules wrapped; GET /api/config/modules; a route guard for the four /api/life/* surfaces that have no module.
  • PULSE.toml — documented [modules] table.

Infrastructure Pulse needs to serve anything at all — observability, hooks, tab-freshness, menubar, siri, doctor — is deliberately not switchable; a disabled one leaves a dashboard that can't render.

Compatibility

Existing configs need no changes. Legacy [section].enabled flags still work and still win over the defaults. Shipped defaults leave everything on except the already-opt-in imessage and syslog.

Verification

resolveModules against the shipped PULSE.toml and four synthetic configs:

Case Result
Shipped PULSE.toml only imessage, syslog off — no behaviour change
[modules] local/growth = false both false
Legacy [local_intelligence].enabled = false local false — the switch that previously did nothing
Junk keys / wrong types (telos = "yes") ignored, default preserved
Empty config deep-equals MODULE_DEFAULTS

bun build --target=bun --no-bundle parses clean on pulse.ts and lib/modules.ts.

Ported to my own install (which predates the synapse/ledger/atlas/hermes split, so the key set differs) and ran it live with local = false: Pulse boots, LocalIntelligence module loaded is absent from the current boot's journal, /api/local-intelligence/latest → 404, /api/config/modules reports local off among 26 keys.

Not run: a fresh-system install verification (step 3 of the contributing process).

…d flags

loadModules() gates on config.local_intelligence, config.hypotheses and
config.upgrades, but loadPulseConfig() builds its return object key by key
and never copies those three through. The gates read undefined, every one
of them is written '?.enabled !== false', and undefined !== false is true —
so all three modules always loaded and setting [local_intelligence]
enabled = false in PULSE.toml silently did nothing.

Fourteen more user-facing modules (docs, memory, conduit, books, synapse,
ledger, projects, gear, atlas, threatmodel, usage, evals, algorithm,
hermes) had no switch at all, and the HEALTH/FINANCES/BUSINESS/GROWTH
surfaces have no module of their own to switch.

Adds a [modules] table as the single place to turn a surface off, resolved
in lib/modules.ts from three layers: defaults, the older [section].enabled
flags, then [modules]. Existing configs need no changes and behave
identically — the shipped defaults leave everything on except the opt-in
imessage and syslog. GET /api/config/modules exposes the resolved map so
the dashboard can hide a disabled module's tab.

Infrastructure Pulse needs to serve anything at all (observability, hooks,
tab-freshness, menubar, siri, doctor) is deliberately not switchable.
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.

1 participant