Prompts
A prompt is named text a person can insert into the composer. It has no model, tool set, or handler.
Declare reusable instructions
import { function agent(input: AgentInput): AgentDeclare a reusable agent: a name, a system prompt, and optionally a model. This describes an agent, it does not run one. There is no handler, and nothing executes when the mod activates. Put the result under `ai.agents` and the host registers it so the user or another agent can call it by id, `<extensionId>/<name>`. That is the line between this and the rest of the AI surface. To actually run something, use `ctx.ai.ask` for a single answer or `ctx.ai.sessions` for a conversation. To give the model a capability it can invoke, write a `tool`, which does have a handler.agent, function defineMod<const TMod extends ModDefinition>(definition: TMod & ValidateModHooks<TMod>): ModThe entry point of the SDK. A mod's module default-exports one `defineMod` call and nothing else. The returned value is data. It registers nothing, starts nothing, and touches no host state. The host folds it twice. At build time it runs the module in a throwaway sandbox and reads the value to derive the serializable manifest. At runtime it folds the same value again to wire your handlers into their lanes: ext-host for commands, tools, effects, and hooks; the sandboxed renderer for views and widgets. That double fold is why the module must be pure. It is re-executed to produce the manifest, so anything at the top level, a file read, a network call, a timer, a `console` line, runs during a build with no host attached. Put behavior inside handlers: `run`, `activate`, `handle`. The `ValidateModHooks` constraint makes the call fail to typecheck when a `hook.beforeAction` targets an action that is neither a known host action nor `command:` plus one of this mod's own command ids.defineMod, function prompt(input: PromptInput): PromptDeclare reusable prompt text the user can drop into the composer. Put the result in the mod's top-level `prompts` array. It is text and nothing more: no model, no handler, no execution. An agent is the next step up, it binds prompt text to a model under a callable name. The same `Prompt` value can serve both, handed to `agent({ prompt })` and listed in `prompts`, so the agent and the user work from one wording.prompt } from '@modular/sdk'; const const releaseReview: PromptreleaseReview = function prompt(input: PromptInput): PromptDeclare reusable prompt text the user can drop into the composer. Put the result in the mod's top-level `prompts` array. It is text and nothing more: no model, no handler, no execution. An agent is the next step up, it binds prompt text to a model under a callable name. The same `Prompt` value can serve both, handed to `agent({ prompt })` and listed in `prompts`, so the agent and the user work from one wording.prompt({ PromptInput.name: stringShort identifier, also what the user sees when picking the prompt.name: 'release-review', PromptInput.description: stringOne line telling the user what inserting this will do.description: 'Insert the release review checklist.', PromptInput.content: stringThe text inserted into the composer. {@link prompt } removes the common indentation and surrounding whitespace, so multiline template literals can follow the surrounding TypeScript indentation.content: ` # Task Review the release candidate. # Check - Correctness - Migration risk - Rollback - Observability Separate confirmed defects from open questions. `, }); const const releaseReviewer: AgentreleaseReviewer = function agent(input: AgentInput): AgentDeclare a reusable agent: a name, a system prompt, and optionally a model. This describes an agent, it does not run one. There is no handler, and nothing executes when the mod activates. Put the result under `ai.agents` and the host registers it so the user or another agent can call it by id, `<extensionId>/<name>`. That is the line between this and the rest of the AI surface. To actually run something, use `ctx.ai.ask` for a single answer or `ctx.ai.sessions` for a conversation. To give the model a capability it can invoke, write a `tool`, which does have a handler.agent({ AgentInput.name: stringShort identifier. The callable agent id is `<extensionId>/<name>`.name: 'release-reviewer', AgentInput.description: stringWhat this agent is good for. Shown to the user and read by the model when routing.description: 'Reviews a release candidate against the shared checklist.', AgentInput.prompt: string | PromptThe system prompt, either inline text or a {@link Prompt } shared with the composer.prompt: const releaseReview: PromptreleaseReview, }); export defaultdefineMod<{ readonly metadata: { readonly displayName: "Release Review"; readonly description: "Shared release-review instructions"; }; readonly prompts: readonly [Prompt]; readonly ai: { readonly agents: readonly [Agent]; }; }>(definition: { readonly metadata: { readonly displayName: "Release Review"; readonly description: "Shared release-review instructions"; }; readonly prompts: readonly [Prompt]; readonly ai: { readonly agents: readonly [Agent]; }; }): ModThe entry point of the SDK. A mod's module default-exports one `defineMod` call and nothing else. The returned value is data. It registers nothing, starts nothing, and touches no host state. The host folds it twice. At build time it runs the module in a throwaway sandbox and reads the value to derive the serializable manifest. At runtime it folds the same value again to wire your handlers into their lanes: ext-host for commands, tools, effects, and hooks; the sandboxed renderer for views and widgets. That double fold is why the module must be pure. It is re-executed to produce the manifest, so anything at the top level, a file read, a network call, a timer, a `console` line, runs during a build with no host attached. Put behavior inside handlers: `run`, `activate`, `handle`. The `ValidateModHooks` constraint makes the call fail to typecheck when a `hook.beforeAction` targets an action that is neither a known host action nor `command:` plus one of this mod's own command ids.defineMod({metadata: { readonly displayName: "Release Review"; readonly description: "Shared release-review instructions"; }metadata: { displayName: "Release Review"displayName: 'Release Review', description: "Shared release-review instructions"description: 'Shared release-review instructions', }, prompts: readonly [Prompt]prompts: [const releaseReview: PromptreleaseReview],ai: { readonly agents: readonly [Agent]; }ai: { agents: readonly [Agent]agents: [const releaseReviewer: AgentreleaseReviewer] }, });
Prompts live in the top-level prompts array because a person selects them
in the composer. Agents live under ai.agents because they are callable AI
roles.
The same Prompt value can serve both surfaces. Listing it in prompts makes
it selectable. Passing it to agent({ prompt }) makes it the agent's system
prompt.
Members
Prompt contains four fields:
| Member | Meaning |
|---|---|
name | Stable short identity |
description | What a person reads before inserting it |
content | The instruction text, normalized by prompt |
kind | The factory-owned prompt discriminator |
Write description for the chooser and content for the model. Do not repeat
the description as the first sentence of the content.
Identity and updates
name is the stable identity within the mod. Promotion replaces a prompt with
the same name and unregisters a removed prompt. Keep the name stable when the
wording changes so composer references continue to address the same concept.
A prompt has no runtime handle and accepts no arguments. prompt removes the
common indentation and surrounding whitespace from content, then the
normalized text is inserted unchanged. This keeps multiline template literals
readable without leaking TypeScript indentation into the composer.
If authors need variables, workspace reads, or branching logic, that work belongs in an executable primitive rather than an informal template syntax.
Pick the right primitive
| Need | Primitive |
|---|---|
| Static text a person inserts | prompt |
| A reusable model role | agent |
| Computation the model may invoke | tool |
If the text depends on workspace state, user input, or a network result, it is not a prompt. Put the computation in a tool and keep the prompt static.
A slash command is the user-triggered alternative when dynamic text belongs inside the current conversation. A tool is the model-triggered alternative when the model should decide whether to compute it.