Skip to main content

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): Agent
Declare 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.
@exampleA reviewer agent sharing its prompt with the composer ```ts const reviewPrompt = prompt({ name: 'review-checklist', description: 'Insert the standard review checklist.', content: 'Review correctness, cancellation, and cleanup.', }); const reviewer = agent({ name: 'reviewer', description: 'Reviews a change and reports concrete defects.', prompt: reviewPrompt, }); ```@categoryAuthoring
agent
, function defineMod<const TMod extends ModDefinition>(definition: TMod & ValidateModHooks<TMod>): Mod
The 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.
@example```ts export default defineMod({ metadata: { displayName: 'Review', description: 'Review helpers for the current change.', }, commands: [ command({ id: 'review.open', title: 'Review: Open', run: openPanel }), slashCommand({ id: 'review', description: 'Review this change', run: review }), ], ai: { tools: [reviewTool], }, }); ```@categoryAuthoring
defineMod
, function prompt(input: PromptInput): Prompt
Declare 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.
@example```ts const commitStyle = prompt({ name: 'commit-style', description: 'Insert the commit message rules for this repo.', content: ` # Commit message Start with a bracketed area, for example `[terminal]`. `, }); ```@categoryAuthoring
prompt
} from '@modular/sdk';
const const releaseReview: PromptreleaseReview = function prompt(input: PromptInput): Prompt
Declare 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.
@example```ts const commitStyle = prompt({ name: 'commit-style', description: 'Insert the commit message rules for this repo.', content: ` # Commit message Start with a bracketed area, for example `[terminal]`. `, }); ```@categoryAuthoring
prompt
({
PromptInput.name: string
Short identifier, also what the user sees when picking the prompt.
name
: 'release-review',
PromptInput.description: string
One line telling the user what inserting this will do.
description
: 'Insert the release review checklist.',
PromptInput.content: string
The 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): Agent
Declare 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.
@exampleA reviewer agent sharing its prompt with the composer ```ts const reviewPrompt = prompt({ name: 'review-checklist', description: 'Insert the standard review checklist.', content: 'Review correctness, cancellation, and cleanup.', }); const reviewer = agent({ name: 'reviewer', description: 'Reviews a change and reports concrete defects.', prompt: reviewPrompt, }); ```@categoryAuthoring
agent
({
AgentInput.name: string
Short identifier. The callable agent id is `<extensionId>/<name>`.
name
: 'release-reviewer',
AgentInput.description: string
What 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 | Prompt
The system prompt, either inline text or a {@link Prompt } shared with the composer.
prompt
: const releaseReview: PromptreleaseReview,
}); export default
defineMod<{
    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];
    };
}): Mod
The 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.
@example```ts export default defineMod({ metadata: { displayName: 'Review', description: 'Review helpers for the current change.', }, commands: [ command({ id: 'review.open', title: 'Review: Open', run: openPanel }), slashCommand({ id: 'review', description: 'Review this change', run: review }), ], ai: { tools: [reviewTool], }, }); ```@categoryAuthoring
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:

MemberMeaning
nameStable short identity
descriptionWhat a person reads before inserting it
contentThe instruction text, normalized by prompt
kindThe 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

NeedPrimitive
Static text a person insertsprompt
A reusable model roleagent
Computation the model may invoketool

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.