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: [/*...*/] }; }
});
  • input is a JSON Schema — the same shape Decisive's core tools use. The generic (<{ topic: string }>) types your run argument.
  • run returns a Result: data for the caller, blocks for humans, plus optional openModal / 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/handle handlers. 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 as ctx.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:

  1. Sync — on push (webhook) or a manual reload, Decisive reads .decisive/ at a pinned commit, validates each plugin, and bundles its code.
  2. Discover — new plugins appear in Settings → Extensions as discovered.
  3. Review & enable — an admin checks the declared capabilities + commit, fills secrets, and enables. Nothing runs before this.
  4. Run — the AI/humans invoke actions; handlers run in the sandbox with ctx; results render natively. Scheduled contextSources run on their cron.
  5. 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.