obi-jam pattern beginner 15 minutes

Graceful Module Disable Pattern for Always-On Agents

An always-on agent that polls dead services is worse than no agent at all. It's noise pretending to be signal.

The Problem

You have an always-on agent with pluggable modules. One of those modules monitors a service that’s been paused — the bot is off, the data is stale. The module still loads on startup, polls a dead status file every 60 seconds, and injects stale readings into your morning brief and health checks.

The conditional guards in your wiring code (if (moduleServices['weather-alerts']?.alertService)) prevent crashes. They don’t prevent noise. Your agent is spending cycles on something that isn’t running, and worse — it’s reporting stale data as if it’s real.

You need to disable the module cleanly. Not delete it. Not comment out 200 lines. Disable it in a way that’s obvious, reversible, and leaves zero residue in the runtime.

The Pattern

Three touch points. That’s it.

1. Move the module out of the loader’s path

mkdir -p modules/disabled
mv modules/weather-alerts modules/disabled/

Your module loader scans a directory. If the module isn’t in that directory, it doesn’t load. The disabled/ folder is self-documenting — anyone running ls modules/ sees exactly what’s active. Anyone running ls modules/disabled/ sees what’s paused and ready to come back.

This is better than a config flag. Config flags hide state inside a file. A folder structure shows state at a glance.

2. Remove explicit wiring

If your orchestrator has manual wiring between modules, remove the disabled module’s connections:

// Before: explicit wiring in the orchestrator
if (moduleServices['weather-alerts']?.alertService && moduleServices.tasks?.scheduler) {
  moduleServices.tasks.scheduler.setWeatherAlertService(
    moduleServices['weather-alerts'].alertService
  );
}

// After: delete the block entirely
// The module doesn't load, so the wiring has nothing to connect.

Don’t comment it out. Delete it. The code lives in disabled/ and in git history. Commented-out wiring is clutter that makes the next person (or the next agent) wonder if it’s supposed to be there.

3. Remove from health checks and briefings

This is the one people forget. Your health monitor is still checking a status file that will never be fresh:

// Before: health module checks the paused service
const WEATHER_ALERTS_STATUS_PATH = join(
  process.env.HOME, 'repos/.../services/weather-alerts/data/status.json'
);

// In runAllChecks():
results.push(checkServiceStatus(WEATHER_ALERTS_STATUS_PATH, 'Weather Alerts'));

Remove the path constant. Remove the check from runAllChecks(). If your morning brief pulls a summary from this module, that reference is already dead (the module didn’t load), but verify it doesn’t leave a gap or error in the output.

Reactivation

When the service comes back online:

mv modules/disabled/weather-alerts modules/weather-alerts

Then re-add the wiring block, re-add the health check, verify fresh data in the status file, and restart. The module code never changed — it’s exactly where you left it.

Key Takeaway

“Disabled” is a first-class state, not a hack. If your agent runtime has modules that can be paused, build the disabled/ convention from day one. It’s three touch points: move the folder, remove the wiring, remove the health checks. Fifteen minutes, fully reversible, zero stale data in your agent’s output. The goal of an always-on agent is signal — and a paused module that still loads is the opposite of that.

FAQ

How do I disable an agent module without deleting it?

Move the module folder to a disabled/ directory inside your modules path. The module loader won't find it, but the code is preserved for reactivation. Then remove any explicit wiring and health check references.

Should I use a config flag or move the folder?

Moving the folder is simpler and more visible. A disabled/ folder is self-documenting — anyone listing the modules directory immediately sees what's off and what's on. Config flags work too, but they hide state inside a file.

What if my module has a conditional guard — isn't that enough?

Conditional guards prevent crashes, not noise. If the module still loads, it still polls, still appears in health checks, and still injects stale data into briefings. Disable means disable — remove it from the runtime entirely.