Getting started

Build your first Decisive plugin — a tool the AI can call — and run it in your workspace.

Prerequisites

  • A Decisive workspace connected to a GitHub repo (Settings → GitHub).
  • Admin access to that workspace (only admins enable plugins and fill secrets).

1. Add the SDK

In your connected repo:

pnpm add -D @decisive/sdk

The SDK is types + tiny define*() helpers — no runtime weight. Your plugin code is bundled by Decisive at sync time.

2. Create the plugin

Decisive looks for plugins in .decisive/plugins/<name>/plugin.ts, each with a default export from definePlugin.

// .decisive/plugins/hello/plugin.ts
import { definePlugin, defineTool } from '@decisive/sdk';

export default definePlugin({
  name: 'hello',
  title: 'Hello',
  apiVersion: 1,
  actions: [
    defineTool<{ name: string }>({
      name: 'greet',
      description: 'Greet someone by name and post it to chat.',
      input: {
        type: 'object',
        properties: { name: { type: 'string', description: 'Who to greet.' } },
        required: ['name']
      },
      async run(input, ctx) {
        await ctx.chat.send(`👋 Hello, ${input.name}!`);
        return { data: { greeted: input.name } };
      }
    })
  ]
});

Two things every action returns from run:

  • data — what the AI/caller receives (becomes the tool result).
  • optionally blocks — what humans see (rendered natively). See Block Kit.

3. Push and sync

git add .decisive && git commit -m "feat: hello plugin" && git push

Decisive picks up the change (push webhook, or Settings → Extensions → Reload). The plugin appears as Discovered.

4. Review, enable, and fill secrets

In Settings → Extensions, an admin:

  1. reviews the plugin's declared capabilities and the commit it was built from,
  2. fills any declared secrets,
  3. clicks Enable.

Until enabled, nothing runs — this gate is deliberate (see Concepts → Lifecycle).

5. Use it

Mention @AI in chat: "greet Sam". The AI now has a greet tool and will call it. You can also expose the same action as a slash command or panel — see Surfaces.

Add a secret + an external call

defineTool<{ id: string }>({
  name: 'lookup_order',
  description: 'Look up an order by id.',
  input: { type: 'object', properties: { id: { type: 'string' } }, required: ['id'] },
  needs: ['SHOP_API_KEY'], // must match a declared secret on the plugin
  async run(input, ctx) {
    const res = await ctx.http(`https://api.shop.com/orders/${input.id}`, {
      headers: { authorization: `Bearer ${ctx.secrets.SHOP_API_KEY}` }
    });
    if (!res.ok) return { error: `Shop API ${res.status}` };
    return { data: await res.json() };
  }
});

Declare the secret on the plugin so Decisive prompts the admin for it:

export default definePlugin({
  name: 'shop',
  apiVersion: 1,
  secrets: [{ key: 'SHOP_API_KEY', label: 'Shop API key', help: 'Read-only token.', required: true }],
  actions: [/* ... */]
});

Never commit secrets to the repo. Declare them; an admin fills them in Decisive; they're injected as ctx.secrets.* at runtime.

Develop faster

  • Run tsc --noEmit in your repo — the SDK is fully typed, so most mistakes surface before you push.
  • Use ctx.log(...) for output that shows up in the extension's run log.
  • Keep one capability per action; compose several actions in one plugin.

Next: Concepts for the full model, or jump to the Host API reference.