🧠the-brain

Extension System

Extend the-brain with custom scripts loaded at runtime

the-brain supports zero-config extensions β€” drop a .ts file into ~/.the-brain/extensions/, add its name to config.json's extensions array, and it loads on the next daemon restart.

⚠️ Extensions are disabled by default for security. You must explicitly enable each extension in config.json: "extensions": ["my-extension"].

πŸ’‘ Extensions are the product. the-brain's core is a thin event bus + plugin manager. Everything that makes it useful β€” harvesters, memory strategies, trainers, notifications β€” is an extension. The built-in starter pack are extensions too. Build yours and grow the ecosystem.

Quick Start

# Creates directory + sample template
the-brain ext ensure-dir
// ~/.the-brain/extensions/my-extension.ts
export default function (brain) {
  // Access full engine: hooks, DB, storage, scheduler, config
  brain.hook("afterResponse", async (ctx) => {
    console.log(`New interaction from ${ctx.interaction.source}`);
  });

  // Register a CLI command
  brain.registerCommand("hello", async () => {
    console.log("Hello from the-brain extension!");
  });
}
the-brain ext hello    # Runs the registered command

BrainAPI Reference

Extensions receive a BrainAPI object with full engine access:

PropertyTypeDescription
brain.hook(event, handler)(string, fn) => voidRegister on any hook event
brain.emit(event, ...args)(string, ...args) => Promise<void>Fire a hook event
brain.dbBrainDB (readonly)Direct database access
brain.storageStorageBackend (readonly)Active storage backend
brain.schedulerSchedulerPlugin (readonly)Task scheduler
brain.configTheBrainConfig (readonly)Full configuration
brain.extensionNamestringName of the extension file
brain.registerCommand(name, handler)(string, fn) => Promise<void>Register CLI command (daemon-only)
brain.openDatabase(path, readonly?)(string, boolean) => DatabaseOpen external SQLite DB (bun:sqlite)

CLI Commands

# Run a registered extension command (daemon process only)
the-brain ext <command> [args...]

# List all available extension commands
the-brain ext

Note: the-brain ext runs in the CLI process, but extensions live in the daemon process. For extensions that need CLI access, use a standalone runner (see Hermes extension example below).

Loading Mechanism

  • Directory: ~/.the-brain/extensions/
  • Format: .ts files exporting a default function (brain: BrainAPI) => void
  • Security: Extensions are disabled by default. To enable one, add its name to config.json:
    { "extensions": ["hermes", "my-extension"] }
    Without this, loadAll() skips all extension files and logs a warning.
  • Skip: sample.ts is never loaded
  • Reload: ExtensionLoader.reload(name) hot-reloads an extension

Available Hooks

Extensions can hook into any event from the hook system. Common hooks:

BEFORE_PROMPT, AFTER_RESPONSE, ON_INTERACTION, HARVESTER_POLL, HARVESTER_NEW_DATA, SELECTION_EVALUATE, SELECTION_PROMOTE, DEEP_CONSOLIDATE, TRAINING_START, TRAINING_COMPLETE, DAEMON_START, DAEMON_STOP

See Hook System for the full list of 17 lifecycle events + plugin-defined custom hooks.

Example: Hermes Agent Harvester

The @the-brain-dev/plugin-harvester-hermes plugin harvests conversations from Hermes Agent. The example below shows key extension-programming techniques used to build this harvester.

What it did:

  • Auto-harvester β€” hooked harvester:poll (every 30s), read ~/.hermes/state.db, paired userβ†’assistant messages, emitted harvester:newData into the pipeline
  • On-interaction tagging β€” tagged memories with hermesChannel, hermesModel, and token counts
  • CLI commands β€” accessible via a standalone runner

CLI usage (standalone):

# Stats β€” shows harvested count, session breakdown, live DB stats
~/.the-brain/bin/hermes-ext stats

# Manual harvest β€” forces a scan of Hermes state.db
~/.the-brain/bin/hermes-ext harvest

# Context export β€” enhanced brain context for Hermes memory injection
~/.the-brain/bin/hermes-ext context
~/.the-brain/bin/hermes-ext context --json

Key techniques demonstrated:

export default function (brain) {
  // 1. Persist state between daemon restarts
  async function loadState() {
    try { return JSON.parse(await Bun.file(STATE_FILE).text()); }
    catch (_) { return defaults; }
  }

  // 2. Open external SQLite databases (brain.openDatabase)
  function openDb() {
    return brain.openDatabase("/path/to/external.db", true); // read-only
  }

  // 3. Auto-harvest on every daemon poll cycle
  brain.hook("harvester:poll", async function () {
    var interactions = doHarvest(state);
    if (!interactions.length) return;

    var emitted = await emitAll(interactions);  // -> harvester:newData
    saveState(updatedState);
  });

  // 4. Register CLI commands (via standalone runner)
  brain.registerCommand("hermes", async function (args) { ... });
}

Persistence pattern:

// ~/.the-brain/hermes-state.json
{
  "lastMessageId": 5911,
  "totalInteractions": 67,
  "totalSessions": 333
}

Why a standalone runner? brain.registerCommand() registers commands in the daemon process's memory. The CLI process (the-brain ext) has its own copy. The standalone runner (~/.the-brain/bin/hermes-ext) loads the extension with a minimal BrainAPI and dispatches directly β€” no daemon dependency for quick queries.

Example: Auto-tag Memories

export default function (brain) {
  brain.hook("harvester:newData", async (ctx) => {
    const prompt = ctx.interaction.prompt;
    const tags = [];

    if (/react|component|hook/.test(prompt)) tags.push("react");
    if (/python|django|flask/.test(prompt)) tags.push("python");

    ctx.interaction.metadata = { ...ctx.interaction.metadata, tags };
  });
}

Example: Notification on Training

export default function (brain) {
  brain.hook("training:complete", async (ctx) => {
    const duration = (ctx as any).duration;
    console.log(`Training done in ${duration}s!`);

    // Use node-notifier for system notification
    const { default: notifier } = await import("node-notifier");
    notifier.notify({
      title: "the-brain",
      message: `MLX training complete (${duration}s)`,
    });
  });
}

On this page