07-registry.md

1. ModuleRegistry overview

The ModuleRegistry centrally manages every ActionModule QPlan can execute. Implemented in src/core/moduleRegistry.ts, it offers:

MethodDescription
register(module)Register a single module. Without module.id, it warns and skips.
registerAll(modules)Register modules sequentially; internally calls register().
get(id)Used by the executor to fetch modules; returns undefined if missing.
list()Returns { id, description, usage, inputs, inputType, outputType }[] metadata for AI/docs.

Every new ModuleRegistry() auto-registers the default basicModules, so even custom instances start with the standard toolset. Pass new ModuleRegistry({ seedBasicModules: false }) when you want a blank registry, or use seedModules to preload your own list.

2. Registration example

import { registry } from "qplan";
import { httpModule } from "qplan/dist/modules/basic/http.js";

registry.register(httpModule);
registry.registerAll([htmlModule, aiModule]);
  • Each module can be registered only once; duplicate IDs raise errors.
  • Modules without IDs print a warning (AI cannot refer to this module) and remain unregistered, so even temporary modules should have IDs.

3. Metadata & AI prompts

registry.list() returns the current module info. buildAIPlanPrompt(requirement, { registry }), buildQplanSuperPrompt(customRegistry), or listRegisteredModules(registry) all consume this data directly. Module IDs may include any Unicode letter/digit plus underscores (e.g., foo, foo_bar, 분석작업), but they must start with a letter or underscore; otherwise registry.register() throws an error.

const modules = registry.list();
/*
[
  { id: "file", description: "파일 읽기/쓰기", usage: "file read path=...", inputs: ["op","path","data"], inputType: { ... }, outputType: { ... } },
  ...
]
*/

The better the metadata, the more accurately the AI produces QPlan commands. description and usage are injected verbatim into prompts. Adding inputType/outputType helps the LLM understand module I/O shapes.

4. Module Search And Prompt Filtering

When a project has many modules, avoid sending every module to the LLM. Filter candidates first with category, tags, query, and limit, then build the planning prompt. Registered modules remain available for execution, but only filtered modules are exposed in the prompt.

Add searchable metadata to modules:

registry.register({
  id: "http_get",
  title: "HTTP request",
  category: "network",
  tags: ["http", "api", "fetch"],
  aliases: ["request", "download"],
  description: "Fetch text from an API",
  usage: `http_get url="https://example.com" -> out`,
  inputs: ["url"],
  execute: async ({ url }) => fetch(String(url)).then(r => r.text()),
});

registry.search() excludes excludeInPrompt: true modules by default.

const modules = registry.search({
  categories: ["network", "data"],
  tags: ["json", "http"],
  query: "api parse",
  limit: 10,
});

Search options behave like this:

OptionBehavior
categoryFilter by one category.
categoriesMatch any listed category.
tagsMatch any listed tag by default.
requireAllTagsWhen true, every listed tag must be present.
queryScores text matches across id, title, aliases, category, tags, description, inputs, usage, and type metadata.
idsSelect only specific module IDs.
limitMaximum number of modules to return.
includeExcludedInclude excludeInPrompt modules when true.

Pass moduleFilter when building a planning prompt:

const prompt = buildAIPlanPrompt("Fetch API data and parse JSON", {
  registry,
  moduleFilter: {
    categories: ["network", "data"],
    tags: ["json", "http"],
    query: "api parse",
    limit: 8,
  },
});

The default module output detail is compact. It omits usage, category, and tags, and focuses on id/title, description, inputs, inputType, and outputType.

moduleDetailModule info included in the prompt
idsOnly id and title.
compactDefault. id/title, description, inputs, inputType, outputType.
usagecompact plus usage examples.
fullusage plus category, tags, and aliases.
const prompt = buildAIPlanPrompt("Read a file and calculate the average", {
  registry,
  moduleFilter: { categories: ["io", "math"], limit: 6 },
  moduleDetail: "compact",
});

Apps can expose only one domain of modules for a specific planning task:

const reportPrompt = buildAIPlanPrompt("Summarize revenue data", {
  registry,
  moduleFilter: {
    category: "reporting",
    limit: 12,
  },
});

Use excludeInPrompt: true for modules that are registered for execution but should not be visible during dynamic planning.

registry.register({
  id: "internal_payment_admin",
  category: "payment",
  tags: ["admin", "internal"],
  excludeInPrompt: true,
  execute() {
    return "ok";
  },
});

Category summaries can drive UI filters or RAG candidate selection screens:

const categories = registry.listCategories();
// [{ category: "data", count: 3 }, { category: "network", count: 5 }]

5. How the registry participates in execution

  1. runQplan(script, { registry }) parses to AST, then the executor runs steps using the provided registry (defaults to the shared singleton).
  2. For each action, the executor calls registry.get(moduleId).
  3. Missing modules throw immediately and are handled via the step’s onError policy.
  4. Returned results populate the ExecutionContext; future actions referencing the same names receive those values automatically.

6. Registry Extension Guide

  • Add custom modules: implement an ActionModule and call registry.register(customModule).
  • Temp/sandbox modules: still assign IDs so AI/docs can see them; otherwise registration is skipped.
  • Multiple registries: instantiate const custom = new ModuleRegistry() (basic modules included automatically), register extra modules, and pass it to both runQplan(script, { registry: custom }) and buildAIPlanPrompt(requirement, { registry: custom }). To start empty, use new ModuleRegistry({ seedBasicModules: false }).
  • Updating metadata: modifying description/usage/inputs/inputType/outputType updates the registry.list() output immediately.

7. Module Management Best Practices

  • Use lowercase, concise IDs (search, payment).
  • Include every real input key in inputs so the AI avoids typos.
  • Provide real QPlan snippets in usage for better prompt hints.
  • Log console.log(registry.list()) to inspect the registry state.

With this guide you can see how ModuleRegistry underpins QPlan’s module ecosystem and how it powers both execution and LLM integrations—now with first-class support for swapping registries at runtime.