Custom Modes
Goal: Host a workbench mode that OpenX does not ship, such as a scheduled job, next to the built-in MCP, API and Web modes.
A mode is one way a workbench runs: MCP serves tools to AI clients, API serves REST routes, Web serves pages. A mode is a name. Your workbench package declares the modes it has, and a workbench offers exactly those. A custom mode is declared the same way as a built-in one, and there is nothing to register in the workspace.
Define one
A mode is a ModeBuilder. DefineModeHolon, from @fathym/fai/workbenches, wraps a factory for one so your
package can export it by name. A Cron mode:
import { DefineModeHolon, ModeBuilder } from '@fathym/fai/workbenches';
export const CronModeHolon = DefineModeHolon(
() =>
new ModeBuilder('Cron', 'Scheduled tasks')
.Execute(async (_ctx) => ({ Vaults: {}, Close: async () => {} })),
);
CronModeHolon.Mode() returns a fresh builder each time it is called. The Execute function is where your mode
does its work. For a mode that does something real, read the built-in Web mode's source,
src/workbenches/modes/WebMode.ts in the @fathym/fai package, which is built the same way with a ModeBuilder.
Write a workbench that uses it
Pass the builder to .Modes() under the mode's name:
export default Workbench('cron-sample', 'Heartbeat workbench')
.Modes({ Cron: CronModeHolon.Mode().Ports(false) }); // a cron mode serves no port
A workbench can declare a custom mode next to built-in ones. The key you give it in .Modes({...}) is the mode's
name everywhere else: in the Modes tab, in the container and in the deploy's messages.
Deploy and host it
Add the workbench as in Host Your Own Workbench and save it. Saving reads the
modes your package declares. Then in its Modes tab choose Add mode: the list offers the modes the package
declares, your custom one among them. Pick it and Deploy. Each enabled mode gets its own container, with
FATHYM_OX_WORKBENCH_MODE set to the mode's name.
- No web address. The built-in modes listen on a port OpenX routes to. A custom mode has none, so
/ox-api/w/<workspace id>/workbenches/<apiSlug>/<mode>answers 404 "runs without ingress". Nothing outside the container can wake it, so it keeps at least one replica running. - Hosting settings apply. The replica, CPU and memory settings in the Hosting tab apply to a custom mode as they do to a built-in one. The minimum is never below one for a mode with no web address.
- No config form. The Modes tab shows a config form for MCP only; a custom mode runs with its own defaults.
- Not declared. A mode enabled on the workbench that its package does not declare is not deployed, and the
deploy says "Mode '
<mode>' is not declared by this workbench's package (...), so it was not deployed." Remove the mode from the workbench, or declare it in the package and save again. The other modes still deploy.
Walk through it
- Declare your mode in the workbench package and publish or push it.
- Drop a SurfaceWorkbench on a surface. Set its Source to the repo or package that holds your workbench, and its APISlug in Hosting, and save.
- Modes -> Add mode, pick your mode, and Deploy.
- When the workbench's status in the Hosting tab reads
Running, your mode is running in its container. A custom mode has no address to call, so what it does is whatever yourExecutecode does.
Versioning
A workbench picks up a new version of the mode's code from its own source and ref on its next deploy. If a new version drops a mode, the Modes tab says a deploy refuses it, and you remove it from the workbench.
Earlier, a custom mode was registered under Workspace Settings -> Mode types first. That step is gone. A workspace that still holds a registration keeps it, unused.
Next steps
- Let one workbench call another -> Workbenches Build on Each Other
- Host the workbench -> Host Your Own Workbench
- The addresses the built-in modes answer on -> Reach a Hosted Workbench