Extensibility

Plugins

A plugin is a directory whose package.json is the manifest and whose subdirectories are the surfaces it contributes. This page covers that layout and the single public package every surface imports from.

Plugins are in beta. The plugin API (@vellumai/plugin-api) is not yet stable and can change between releases. Pin the peerDependencies range your plugin declares, and expect breaking changes until we cut a 1.0. The authoring convention and the @vellumai/plugin-api surface can both change between releases while plugins are in beta.

The other pages in this section cover each surface on its own. This page is the glue: how those surfaces sit together in one directory, what the manifest declares, and what a plugin is allowed to import from the host.

Directory layout

A plugin lives at <workspaceDir>/plugins/<name>/. The host introspects the directory at load time: the manifest names the plugin, and each named subdirectory is discovered by convention.

my-plugin/
├── package.json               # Manifest (required)
├── README.md                  # Optional plugin docs
├── hooks/                     # Lifecycle hooks, one per file
│   ├── init.ts
│   └── pre-model-call.ts
├── tools/                     # Model-visible tools, one per file
│   └── example.ts
├── skills/                    # On-demand instruction bundles
│   └── my-skill/
│       └── SKILL.md
└── src/                       # Internal modules (NOT walked by the loader)
    └── state.ts

A few rules govern how the loader walks that tree:

  • Compiled files win. When both a .js and a .ts exist for the same basename, the .js is used, matching compiled-binary semantics.
  • Missing directories are skipped. A plugin contributes only the surfaces it ships. Absent surface directories are silently omitted.
  • A broken surface file fails only itself. A surface file present but missing a usable default export is logged with attribution and skipped. Sibling plugins keep loading.
  • src/ is yours. Only the named surface directories are walked. Put shared helpers in src/ (or any other directory) and import from them normally.
  • Loading is time-boxed. Each plugin has a 10s import budget. Anything slower is treated as a load failure and the plugin is skipped.

Each surface can also be dropped straight into the workspace at /workspace/<surface>/<name>/ without wrapping it in a plugin. A plugin is what lets you ship several surfaces together as one installable unit.

The manifest

Every plugin has a package.json. The loader reads three fields and passes everything else through untouched, so your editor, linter, and publish tooling keep working as normal.

{
  "name": "@you/my-plugin",
  "version": "0.0.1",
  "peerDependencies": {
    "@vellumai/plugin-api": "^0.8.0"
  },
  "vellum": {}
}
  • name (required). Any npm-style name. The loader strips the scope (@you/) for the in-runtime plugin name, and duplicate names fail registration.
  • version. Informational, and defaults to 0.0.0 when absent.
  • peerDependencies["@vellumai/plugin-api"]. A semver range checked against the running assistant. While plugins are in beta a mismatch is logged but does not block load. Once the install path stabilizes the mismatch will harden into a hard reject, so pin a real range.
  • vellum. Reserved for future use.

The @vellumai/plugin-api surface

Plugins import everything they need from a single package, @vellumai/plugin-api. It is the only supported contract: anything not exported from there is assistant-internal and can change without notice. Most of the surface is types (the contexts the host hands your code), with a small set of runtime handles that resolve to the assistant's live singletons. Expand a group to see what it exports.

Hook contexts and constants12 exports

The context shape the host hands to each lifecycle hook, the hook signature itself, and the wired hook-name constant. Each context's full field contract is documented on the Hooks page. See the Hooks page.

ExportKindPurpose
HOOKSconstWired hook names keyed by constant (INIT, PRE_MODEL_CALL, and so on). Reference hooks by this instead of free-form strings.
HookNametypeUnion of every wired hook name declared in HOOKS.
PluginHookFntypeSignature every hook implements: (ctx) => Promise<Partial<ctx> | void>.
PluginInitContexttypePassed to the init hook at bootstrap.
PluginShutdownContexttypePassed to the shutdown hook at teardown.
UserPromptSubmitContexttypePassed to user-prompt-submit, before a turn's messages reach the agent loop.
PreModelCallContexttypePassed to pre-model-call, before each provider call.
PostToolUseContexttypePassed to post-tool-use, once per tool result.
PostModelCallContexttypePassed to post-model-call, for each finalized assistant message.
PostCompactContexttypePassed to post-compact, after the loop compacts a conversation mid-turn.
StopContexttypePassed to stop, when the model yields a response with no tool calls.
StopDecisiontypeReturn shape of a stop hook: whether to end the turn or continue.
Tool types4 exports

The author-facing tool spec and the shapes passed to and returned from a tool's execute method. Each is documented in full on the Tools page. See the Tools page.

ExportKindPurpose
ToolDefinitiontypeAuthor-facing tool spec, the default-export shape for a tools/<name>.ts file.
ToolContexttypeRuntime context passed as the second argument to a tool's execute.
ToolExecutionResulttypeReturn shape of a tool's execute: { content, isError }.
RiskLevelenumRisk bands (low, medium, high) that drive default permission gating for a tool.
Logging1 exports

The logger the host binds to your plugin name and threads onto the contexts. Log through it rather than rolling your own.

ExportKindPurpose
PluginLoggertypePino-compatible logger shape, bound to { plugin: <name> } and present on the contexts.
Runtime handles7 exports

Values, not just types, that a plugin consumes at module-load or init time. A boot-time shim rebinds each from the assistant's own namespace, so they resolve to the same live singletons the assistant uses.

ExportKindPurpose
assistantEventHubvalueThe assistant's pub/sub hub for runtime events. Subscribe to react to activity outside the hook chain.
getSecureKeyAsyncvalueRead a secret from the assistant's secure storage by key.
AssistantEventtypePayload shape of an event published on the hub.
AssistantEventHubtypeInterface of the event hub itself.
AssistantEventCallbacktypeSubscriber callback invoked for each matching event.
AssistantEventFiltertypeFilter narrowing which events a subscription receives.
AssistantEventSubscriptiontypeHandle returned by subscribing, used to unsubscribe.

The Personal AI you were promised

GET STARTED