07-registry.md
1. ModuleRegistry overview
The ModuleRegistry centrally manages every ActionModule QPlan can execute. Implemented in src/core/moduleRegistry.ts, it offers:
| Method | Description |
|---|---|
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:
| Option | Behavior |
|---|---|
category | Filter by one category. |
categories | Match any listed category. |
tags | Match any listed tag by default. |
requireAllTags | When true, every listed tag must be present. |
query | Scores text matches across id, title, aliases, category, tags, description, inputs, usage, and type metadata. |
ids | Select only specific module IDs. |
limit | Maximum number of modules to return. |
includeExcluded | Include 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.
moduleDetail | Module info included in the prompt |
|---|---|
ids | Only id and title. |
compact | Default. id/title, description, inputs, inputType, outputType. |
usage | compact plus usage examples. |
full | usage 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
runQplan(script, { registry })parses to AST, then the executor runs steps using the provided registry (defaults to the shared singleton).- For each action, the executor calls
registry.get(moduleId). - Missing modules throw immediately and are handled via the step’s onError policy.
- 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 bothrunQplan(script, { registry: custom })andbuildAIPlanPrompt(requirement, { registry: custom }). To start empty, usenew ModuleRegistry({ seedBasicModules: false }). - Updating metadata: modifying
description/usage/inputs/inputType/outputTypeupdates theregistry.list()output immediately.
7. Module Management Best Practices
- Use lowercase, concise IDs (
search,payment). - Include every real input key in
inputsso the AI avoids typos. - Provide real QPlan snippets in
usagefor 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.