Runtime
@buildautomaton/runtime is the plugin registry, service registry, start/stop, and createRuntime. It does not know stores, sessions, or HTTP. Those ids live in @buildautomaton/plugins.
The same host API runs locally (the Local CLI) or in the cloud. The host changes. The registries do not.
Host (local-cli, cloud, test)
→ createRuntime({ plugins })
→ plugin registry
→ service registry
→ handle.start()
Plugin shape
A plugin is a named list of service contributions, plus optional start and stop. Domain fields (SQL migrations, an HTTP registrar, an opener) can sit on the plugin. The services list is what it contributes.
const ping = {
name: 'ping-tools',
services: [{
id: 'tools',
implementation: {
listTools: () => [{ name: 'ping', description: 'Health check', inputSchema: { type: 'object' } }],
callTool: async () => ({ content: [{ type: 'text', text: 'ok' }] }),
},
}],
};
One plugin can contribute several services. Several plugins can contribute the same service id. See Custom plugins.
Plugin registry
createPluginRegistry() keeps plugins in registration order.
register(plugin)adds a plugin once per nameall()returns every pluginbyName(name)returns one pluginbyService(id)returns every plugin that contributes that service id
createRuntime registers the plugins you pass in, then wires each declared service.
Services
A contribution is { id, interface?, options?, hooks?, implementation? }.
interfacenames the contract, for example{ id: 'sql-store', name: 'SqlStore' }optionsconfigures that contributionhooksare callbacks for that contributionimplementationis the object other code calls
A plugin can publish the interface in one contribution and the implementation in another, or put both on one contribution.
Service registry
createServiceRegistry() stores the contract and every contribution.
define(id, interface)records the contractprovide(record)stores one contribution, tagged with the plugin nameinterfaceOf(id)returns the contractget(id, match?)returns the first implementation.matchfilters the record, for example byoptions.schemagetAll(id)returns every record for that id, including interface-only and options-only rowsimplementations(id)returns every implementation object
createRuntime calls define and provide for each contribution before it wires services, so lookup works while a later plugin is being connected.
How they compose
- You pass plugins to
createRuntime. Plugins declare service objects. They do not wire themselves. - Each plugin is registered on the plugin registry.
- Each
servicesentry is provided to the service registry. - The runtime wires known service ids (stores, sessions, HTTP, tools). A
*handler covers ids with no specific wirer, once per plugin. start()andstop()call the same methods on each plugin.
Look up plugins with slots.pluginRegistry.byService('artifact'). Look up implementations with slots.services.implementations('sql-store') or slots.services.get('sql-store', (record) => record.options?.schema === 'work').
@buildautomaton/plugins registers how stores, sessions, harnesses, tools, and HTTP are wired. The runtime package does not know those ids.
Install
npm install @buildautomaton/runtime @buildautomaton/plugins
import { createRuntime } from '@buildautomaton/runtime';
import { coreSet } from '@buildautomaton/plugins';
const runtime = await createRuntime({
cwd: process.cwd(),
plugins: coreSet({ options: { cwd: process.cwd() } }),
});
await runtime.start();
Needs Node 18+. Built-in plugins and coreSet() live in Plugins.