Concepts
The model behind Decisive extensions. Five ideas; once they click, the rest is reference.
The action is the atom
Everything is built from actions. An action is one capability — a thing the AI
or a human can invoke. You declare it with defineAction (or defineTool, a
convenience that defaults to the AI surface):
defineAction<{ topic: string }>({
name: 'generate_article',
description: 'Draft an article about a topic.',
input: { type: 'object', properties: { topic: { type: 'string' } }, required: ['topic'] },
surfaces: ['ai-tool', 'slash-command:/article'],
async run(input, ctx) { /* ... */ return { data: {/*...*/}, blocks: [/*...*/] }; }
});
inputis a JSON Schema — the same shape Decisive's core tools use. The generic (<{ topic: string }>) types yourrunargument.runreturns aResult:datafor the caller,blocksfor humans, plus optionalopenModal/updateView/toast/error.
Other capability kinds wrap the same idea: defineContextSource (scheduled data
pull), definePanel (a UI tab), defineWebhook (inbound events). See the
API reference.
Surfaces
One action can be exposed on several surfaces. Decisive adapts the binding; your handler stays the same.
| Surface | Where it shows up |
|---|---|
ai-tool |
Callable by @AI in chat, comments, voice (⌘O), huddles |
agent-runner |
An MCP tool for the in-container Claude Code agents (Build + tasks) |
slash-command:/x |
A human slash command in chat |
panel |
A workspace tab/overlay (declare via definePanel) |
chat-action |
A message action a human triggers |
surfaces: ['ai-tool', 'agent-runner', 'slash-command:/article']
defineTool defaults to ['ai-tool']. Use defineAction when you want more.
Declarative vs executable
Two layers, with different trust and rendering:
- Declarative (recommended default) — UI as Block Kit data, tool schemas, nav entries. Decisive renders these natively with its own design system, so your extension always looks on-brand and the AI can read/drive it.
- Executable — your
run/sync/handlehandlers. These run in a sandbox (a per-workspace container), never in Decisive's app. Custom UI beyond blocks falls back to a sandboxed iframe.
Lean on declarative; reach for executable only where you need real logic.
The host API — the stability contract
Your plugin talks to Decisive only through ctx.* (the host API).
It never touches Decisive's internal data stores. That indirection is the whole
promise that platform updates don't break integrations: Decisive can refactor
internals freely as long as the ctx contract holds.
The surface, at a glance:
ctx.context // read workspace knowledge · upsert ambient context
ctx.chat / ctx.tasks / ctx.pages / ctx.members // read & act on the workspace
ctx.ai // Decisive's own LLM + context — your plugin brings no key
ctx.secrets // declared secrets, injected
ctx.http // fetch, under an egress policy
ctx.store // tiny per-plugin KV (cursors, state)
ctx.ui // openModal / toast mid-handler
It's versioned. Declare apiVersion on your plugin; Decisive keeps older
versions working behind compatibility shims with deprecation windows.
Code in repo, secrets in Decisive
- Your code lives in
.decisive/in your repo — versioned, PR-reviewed, rolled back with git. - Your secrets never go in the repo. Declare them with
secrets: [...]; an admin fills them in Decisive; they're encrypted at rest and injected asctx.secrets.*at runtime. - Enablement lives in Decisive too, not the repo — the repo declares which plugins exist; Decisive owns which are on.
Lifecycle
How a plugin goes from a commit to running:
- Sync — on push (webhook) or a manual reload, Decisive reads
.decisive/at a pinned commit, validates each plugin, and bundles its code. - Discover — new plugins appear in Settings → Extensions as
discovered. - Review & enable — an admin checks the declared capabilities + commit, fills secrets, and enables. Nothing runs before this.
- Run — the AI/humans invoke actions; handlers run in the sandbox with
ctx; results render natively. ScheduledcontextSourcesrun on their cron. - Update — push again; Decisive re-syncs. Code is pinned by content hash, so a bad push can't silently swap what's running.
See EXTENSIONS.md for how each step maps onto Decisive's infra.