# Gateway > Connect your tools. Work from your AI assistant. ## Getting Started Get up and running with Gateway in under 5 minutes. ### 1. Create an Account Go to [gateway.oxc.dev](https://gateway.oxc.dev) and sign in. You'll land on your dashboard. ### 2. Connect Your Providers Click **Connect** next to each provider you want to use: | Provider | What It Gives You | | ---------- | --------------------------------------------------- | | **Linear** | Issues, cycles, projects, comments, team management | | **Notion** | Pages, databases, blocks, comments, search | | **Slack** | Messages, channels, threads, reactions, search | Each provider uses OAuth — you'll be redirected to authorize Gateway, then sent back to your dashboard. ### 3. Install in Your Editor #### Cursor Click the **Install to Cursor** button on your dashboard. It opens Cursor with the MCP server pre-configured. #### Claude Code Run this in your terminal: ```bash claude mcp add gateway --transport http https://gateway-mcp.oxc.dev/mcp ``` #### OpenCode Add to your `opencode.json`: ```json { "mcp": { "gateway": { "type": "http", "url": "https://gateway-mcp.oxc.dev/mcp" } } } ``` #### Other Clients Any MCP-compatible client can connect via bearer token. Go to the **Other** tab on your dashboard to get your configuration. ```json { "gateway": { "type": "http", "url": "https://gateway-mcp.oxc.dev/mcp", "headers": { "Authorization": "Bearer YOUR_TOKEN" } } } ``` ### 4. Try It Out Once connected, ask your AI assistant: ``` Search for recent Linear issues assigned to me ``` Gateway will use the `search` tool to discover the right action, then `exec` to run it. You should see your Linear issues come back. ### Next Steps * [How It Works](/how-it-works) — Understand the architecture * [Your First Search](/guides/first-search) — Guided walkthrough * [Daily Briefing](/guides/daily-briefing) — Set up your morning overview ## How It Works You talk to your AI assistant. Your AI assistant talks to your tools. That's it. ### The Flow You → AI Assistant → Gateway MCP Server → Linear, Notion, Slack When you say "Find my Linear issues about the auth bug", your AI: 1. Asks Gateway what's available 2. Finds the right action (search Linear issues) 3. Runs it and gets results 4. Gives you a clean answer You never interact with Gateway directly — your AI handles it all behind the scenes. ### What Can It Do? Gateway gives your AI access to **71 actions** across three providers: | Provider | Actions | What Your AI Can Do | | ---------- | ------- | ----------------------------------------------------------------------------------------------------------- | | **Linear** | 24 | Search issues, create/update issues, manage sprints, add comments, assign work, track projects, initiatives | | **Notion** | 21 | Search pages, read/write docs, query databases, manage blocks, create databases | | **Slack** | 26 | Send messages, search conversations, manage channels, react to messages, read threads, DMs | Your AI discovers these automatically — you just describe what you want in plain language. ### Authentication Gateway supports two ways to connect: * **Automatic (Cursor, Claude Code)** — Your editor handles login for you. Just click install and authorize when prompted. * **Token-based (any other tool)** — Copy a config snippet from your dashboard. Works with any MCP-compatible client. Both give you the same access. Your connected providers, your data, your permissions. ### Procedures Beyond one-off actions, Gateway supports **procedures** — structured documents that encode repeatable processes like expense reports, onboarding, and approvals. Your AI reads the procedure and executes it step by step. Procedures can wait for approvals, handle timeouts, and hand off between people — even when you're offline. See [Procedures](/procedures/overview) for more. ### Privacy * **Interactive use** — Gateway uses your OAuth tokens to call provider APIs on your behalf. You only see data you already have access to in each tool. * **Procedure runs** — In-flight data is stored in Gateway's internal database, not in your Notion or Slack. Only you, assigned people, and procedure admins can see your run data. * **Autonomous execution** — When Gateway acts on its own (approvals, timeouts), it uses a bot identity and sandboxed access. It can only touch what the procedure explicitly declares. * Tokens are stored securely and never shared * No data is indexed or stored beyond what's needed for the request ## Installation Detailed setup instructions for every supported editor and environment. ### Cursor The fastest way. Click **Install to Cursor** on your [dashboard](https://gateway.oxc.dev) and Cursor will configure the MCP server automatically. Alternatively, add to your `.cursor/mcp.json`: ```json { "mcpServers": { "gateway": { "type": "http", "url": "https://gateway-mcp.oxc.dev/mcp" } } } ``` ### Claude Code (CLI) ```bash claude mcp add gateway --transport http https://gateway-mcp.oxc.dev/mcp ``` This registers the Gateway MCP server. Claude Code handles OAuth authentication automatically — it will open a browser window for you to authorize on first use. ### OpenCode Add to your `opencode.json` at the project root: ```json { "mcp": { "gateway": { "type": "http", "url": "https://gateway-mcp.oxc.dev/mcp" } } } ``` ### Other MCP Clients (Bearer Token) For any MCP client that supports HTTP transport with custom headers: 1. Go to your [Gateway dashboard](https://gateway.oxc.dev) 2. Open the **Other** tab in the MCP integration section 3. Copy the configuration with your embedded bearer token ```json { "gateway": { "type": "http", "url": "https://gateway-mcp.oxc.dev/mcp", "headers": { "Authorization": "Bearer YOUR_TOKEN" } } } ``` The bearer token is a JWT valid for 90 days. It authenticates you the same way OAuth does — your connected providers and permissions are identical. ### Verifying Your Connection After installation, ask your AI assistant: ``` Search for available Gateway actions ``` You should see a list of your connected providers and their available actions. If you see "no providers connected", go back to your dashboard and connect at least one provider. ### Troubleshooting #### "No providers connected" You've installed the MCP server but haven't connected any providers yet. Go to [gateway.oxc.dev](https://gateway.oxc.dev) and click Connect on Linear, Notion, or Slack. #### OAuth window doesn't open Some MCP clients don't support OAuth. Use the bearer token method instead (Other tab on your dashboard). #### Token expired Bearer tokens expire after 90 days. Go to your dashboard and copy the updated configuration from the Other tab. ## Access Control Reference Technical reference for the Gateway ACL model — principals, roles, group resolution, and D1 schema. For the non-technical guide, see [Sharing & Permissions](/procedures/sharing). ### Two-Role Model Gateway uses a flat two-role model. No hierarchies, no granular permissions. | Role | Procedures | Runs | | --------- | ---------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------ | | **Admin** | Full read/write. Edit document, manage access, view all runs, trigger exports. | See all runs for their procedures. Full data. | | **User** | Discover (if public or shared). Invoke (if public or shared). Cannot read procedure internals. | See own runs (creator). See assigned runs (from assignment step onward). | ### Principals A principal is an identity that can be granted access. Three formats: | Format | Example | Resolves to | | ---------------- | --------------------- | ---------------------------------------------- | | `slack:#channel` | `slack:#finance-team` | All members of the Slack channel | | `email:addr` | `email:alice@co.com` | Single user, matched by email across providers | | `user:id` | `user:abc123` | Single user by Gateway ID | #### Slack channels as groups Slack channels are the primary group primitive. Membership is resolved dynamically via the Slack API and cached in D1 with a 1-hour TTL. ```sql -- group_cache table CREATE TABLE group_cache ( group_ref TEXT NOT NULL, -- e.g. "slack:#finance-team" user_email TEXT NOT NULL, resolved_at TEXT NOT NULL, PRIMARY KEY (group_ref, user_email) ); ``` Resolution flow: 1. ACL check needs to evaluate `slack:#finance-team` 2. Check `group_cache` for entries where `resolved_at` is within 1 hour 3. If cache miss or stale → call Slack API `conversations.members` + `users.info` to resolve emails 4. Upsert cache entries 5. Check if the user's email is in the resolved set #### Cross-provider identity Email is the join key. A user connects Notion, Linear, and Slack with potentially different usernames, but the same email links them. Gateway stores `provider_user_email` in `linked_accounts` for this mapping. ### Procedure Access Stored in D1 `procedure_access` table: ```sql CREATE TABLE procedure_access ( procedure_id TEXT NOT NULL, principal TEXT NOT NULL, -- "slack:#finance", "email:a@b.com", "user:xyz" principal_type TEXT NOT NULL, -- "slack_channel", "email", "user" role TEXT NOT NULL, -- "admin" or "user" is_creator INTEGER DEFAULT 0, created_at TEXT DEFAULT (datetime('now')), PRIMARY KEY (procedure_id, principal) ); ``` #### Sync from frontmatter When a procedure document is saved, `syncProcedureAccess` reads the frontmatter and upserts the `procedure_access` table: ``` frontmatter.admins → role: "admin" frontmatter.share → role: "user" procedure creator → role: "admin", is_creator: 1 ``` #### Check flow ``` canDiscover(userId, procedureId) → procedure public? yes → true → user is admin? yes → true → user in share list (direct or via group)? yes → true → false canReadProcedure(userId, procedureId) → user is admin? yes → true → false (users can discover/invoke but not read internals) ``` ### Run Access Stored in D1 `run_access` table: ```sql CREATE TABLE run_access ( run_id TEXT NOT NULL, user_id TEXT NOT NULL, role TEXT NOT NULL, -- "creator" or "assigned" assigned_at_step TEXT, -- which step triggered assignment created_at TEXT DEFAULT (datetime('now')), PRIMARY KEY (run_id, user_id) ); ``` #### Lifecycle 1. **Run created** → creator gets `role: "creator"` in `run_access` 2. **Step with assignment** → assigned user gets `role: "assigned"` with `assigned_at_step` 3. **ACL check** → `canAccessRun` checks `run_access` + procedure admin status ``` canAccessRun(userId, runId) → user in run_access? yes → true → user is admin of the run's procedure? yes → true → false canCancelRun(userId, runId) → user is run creator? yes → true → user is admin of the run's procedure? yes → true → false ``` ### D1 Tables Summary | Table | Purpose | | ------------------ | ----------------------------------------------- | | `procedure_access` | Who can discover/invoke/admin a procedure | | `run_access` | Who can see a specific run (creator + assigned) | | `group_cache` | Cached Slack channel membership (1h TTL) | | `runs` | Run state, procedure snapshot, wait metadata | | `run_submissions` | Data collected during execution steps | | `run_log` | Execution log entries | | `slack_workspace` | Bot token per Slack workspace | ### API Functions #### `acl.ts` | Function | Purpose | | -------------------------------------------------------------- | ------------------------------------------------ | | `hasRole(userId, procedureId, role, db)` | Check if user has a specific role on a procedure | | `canDiscover(userId, procedureId, db)` | Can user find this procedure? | | `canReadProcedure(userId, procedureId, db)` | Can user read procedure internals? (admin only) | | `canAccessRun(userId, runId, db)` | Can user see this run? | | `canCancelRun(userId, runId, db)` | Can user cancel this run? | | `grantRunAccess(runId, userId, role, step, db)` | Add user to run's access list | | `resolveGroup(groupRef, resolver, db)` | Resolve Slack channel → emails (with cache) | | `syncProcedureAccess(procedureId, frontmatter, creatorId, db)` | Sync frontmatter → `procedure_access` table | #### `runs.ts` | Function | Purpose | | --------------------------------------------- | ---------------------------------- | | `createRun(...)` | Create run + grant creator access | | `loadRun(runId, userId, db)` | Load run (ACL-gated) | | `submitData(runId, step, data, userId, db)` | Record step submission (ACL-gated) | | `logStep(runId, step, entry, autonomous, db)` | Append to execution log | | `parkRun(runId, waitUntil, waitContext, db)` | Set status to waiting | | `completeRun(runId, db)` | Set status to completed | | `cancelRun(runId, userId, db)` | Cancel run (ACL-gated) | | `listRuns(procedureId, userId, db)` | List runs user can see | ## Prompts Gateway provides built-in MCP prompts that generate structured instructions for common workflows. ### `daily-360` A personal 360° overview of activity across all connected services. #### Description Generates a comprehensive briefing covering what's done, in progress, blocked, and what actions are needed — pulling data from every connected provider. #### Arguments | Name | Type | Required | Default | Description | | ------- | ------ | -------- | ------- | ------------------------ | | `hours` | string | no | `"24"` | Lookback window in hours | #### What It Covers The prompt instructs the AI to check each connected provider and compile: | Section | Source | | ----------------------------- | ------------------------------------------------- | | **Summary** | Cross-provider synthesis | | **Completed** | Linear issues moved to Done, Notion pages updated | | **In Progress** | Active Linear issues, ongoing Notion edits | | **Blocked / Needs Attention** | Overdue issues, stale items | | **Key Conversations** | Slack threads, mentions, decisions | | **Upcoming** | Cycle deadlines, milestones | | **Actions Needed** | Prioritized list of what to do today | #### Provider-Specific Data **Linear** (when connected): * Recent issues assigned to you (last N hours) * Active cycle and its issues * Issues by state (done, in progress, blocked) **Notion** (when connected): * Recently edited pages * Pages you modified **Slack** (when connected): * Channels you're in * Recent messages and threads * Mentions and DMs #### Usage In MCP clients that support prompts, select `daily-360` from the prompt picker. Otherwise: ``` Give me my daily 360 overview ``` For a longer lookback (e.g., after a weekend): ``` Give me a 360 overview for the last 72 hours ``` ## Resources Gateway exposes MCP resources that provide reference documentation to the AI during conversations. ### `gateway://skills/authoring-procedures` **MIME type:** `text/markdown` A comprehensive guide for turning company processes into structured procedure documents. Your AI reads this resource when you ask it to create a new procedure. #### When It's Used * "Help me create a procedure for expense reports" * "I want to formalize our onboarding process" * "Turn this Slack description of how we handle PTO into a procedure" The AI loads this resource to understand the procedure document format, required sections, step patterns, and quality checklist before helping you author. #### What It Contains * Interview flow (questions to ask when encoding a process) * Document structure (frontmatter, input, state, steps, output) * Step patterns (approvals, collection, branching, periodic checks) * Quality checklist * Anti-patterns to avoid * Links to examples *** ### `gateway://skills/running-procedures` **MIME type:** `text/markdown` Strict execution rules for running procedure documents. The AI reads this resource before executing any procedure. #### When It's Used * "Run the expense report procedure" * "Expense my Denver conference trip, $2,400 total" * "Start onboarding for the new hire" The AI loads this resource to understand the execution rules — follow steps exactly, don't skip or improvise, log everything, respect wait steps. #### What It Contains * Step-at-a-time execution flow (`get_procedure` → collect input → `start_run` → `get_run` → execute → log → advance → repeat) * 12 execution rules (one step at a time, never read ahead, execute exactly, don't skip, etc.) * Branching, wait, and resume patterns * Assignment handling * Log format * Prohibited actions (including: never call `get_procedure_source` during execution) ## Security Model Reference Technical reference for Gateway's execution security — token delegation, capability manifests, and D1 sandboxing. For the non-technical version, see [What Happens When You're Away](/procedures/autonomous). ### Threat Model The primary threat is **prompt injection during autonomous execution**. A malicious run input attempts to hijack the LLM into accessing resources beyond the procedure's scope — reading private Notion pages, posting unauthorized Slack messages, or exfiltrating data via provider tokens. Secondary concerns: * Users gaming procedures by reading internal evaluation criteria * Cross-run data leakage between users * Token misuse during multi-party procedure execution ### D1 as Security Boundary During execution, all run data (submissions, logs, state) lives in D1 — not in any provider. This is the core security property. D1 security boundary: all reads/writes during execution, export to Provider APIs on close only A prompt injection during step execution has **no provider token in scope** for D1 reads/writes. Provider tokens only fire for explicitly declared capabilities (Slack posts, Linear issues). ### Token Delegation Different tokens are used at different points in a procedure's lifecycle. #### Interactive execution (user present) | Action | Token used | Why | | -------------------------- | ------------------- | ------------------------------------- | | Read procedure from Notion | User's Notion token | User has read access to the procedure | | Create run in D1 | N/A (internal) | No provider token needed | | Post Slack message | User's Slack token | Message appears as the user | | Create Linear issue | User's Linear token | Attribution to the user | #### Autonomous execution (cron/Gemini) | Action | Token used | Why | | ------------------------- | -------------------------------- | -------------------------------------- | | Read procedure snapshot | N/A (from D1) | Snapshotted at run creation | | Read/write run state | N/A (D1) | Internal database, no provider token | | Post Slack message | Workspace bot token | Message appears as "Gateway \[BOT]" | | Create Linear issue | Run creator's Linear token | Attribution to whoever started the run | | Export to Notion on close | Procedure creator's Notion token | Scoped to their visibility in Notion | #### Key constraints * **No user impersonation during autonomous execution.** The bot token is clearly identified. The run creator's token is used for attribution only. * **No Notion reads during execution.** The procedure document is snapshotted into D1 at run creation. Gemini works from the snapshot, not from live Notion. * **No provider token for D1 operations.** Run data, submissions, and logs are stored in D1 with code-enforced ACL. No OAuth token can be hijacked to access them. #### Notion integration access The Gateway Notion integration can only access pages explicitly shared with it. In practice: | Scenario | Access | | ------------------------------------------ | ---------------------------------------------------------------------------------- | | Pages inside the Gateway → Procedures tree | Automatic — Gateway created them | | Export databases created on run close | Automatic — Gateway creates them as children of the procedure page | | Pages created manually outside the tree | **No access** — must be shared with the Gateway integration or moved into the tree | This means procedure pages must live inside the Gateway-managed Notion hierarchy. The bootstrap flow (triggered on first Notion connection) creates a `Gateway` page with a `Procedures` child database. Everything inside that tree is accessible to both the procedure creator's token and the Gateway integration. ### Token-Based Scoping Rather than declaring explicit capability manifests in procedure documents, access is naturally scoped by the actor's tokens at each point in execution. The procedure document describes *what to do*; the token model constrains *what's reachable*. #### Enforcement layers **Layer 1 — Token scoping:** Each actor can only access what their OAuth token allows. The bot token can post to channels it's been added to. The run creator's Linear token only reaches their teams. The procedure creator's Notion token only sees their workspace. **Layer 2 — Prompt-level:** The AI only receives the current step's instructions via `get_run`, never the full procedure source. The runner skill enforces a step-at-a-time execution model. This is a soft boundary — it relies on the LLM following the instructions faithfully — but the information surface is minimized. **Layer 3 — D1 ACL:** Run data access is gated by `run_access` table joins. Even if prompt-level enforcement is bypassed, the SQL queries enforce who can read what. ### Procedure Version Snapshots When a run starts, the full procedure document is snapshotted into the run's D1 record. This means: * Autonomous execution reads from D1, not from live Notion * Procedure edits don't affect in-flight runs (until migration) * The snapshot locks the procedure logic to the version that started the run #### Version mismatch | Scenario | Detection | Resolution | | ---------------------- | ------------------------------------------ | ------------------------------------------------ | | Minor edit (1.0 → 1.1) | LLM compares snapshot vs current on resume | Adapts — applies new step language going forward | | Major edit (1.1 → 2.0) | LLM detects structural change | Sets `needs_migration` — admin review required | | Procedure deleted | Run has snapshot, can still complete | Logged as orphaned; admin notified | ### Slack Bot Gateway is installed as a Slack app with bot scopes for autonomous execution. #### Scopes | Scope | Used for | | ------------------ | ----------------------------------------------- | | `chat:write` | Post messages as Gateway bot | | `reactions:read` | Poll for approval reactions | | `channels:read` | Resolve channel membership for groups | | `users:read` | Map Slack users to emails | | `users:read.email` | Get email addresses for cross-provider identity | #### Token storage Bot tokens are stored per Slack workspace in D1: ```sql CREATE TABLE slack_workspace ( workspace_id TEXT PRIMARY KEY, bot_token TEXT NOT NULL, team_name TEXT, created_at TEXT DEFAULT (datetime('now')), updated_at TEXT DEFAULT (datetime('now')) ); ``` User tokens (for interactive execution) remain in the `linked_accounts` table. ## Tools Gateway exposes two MCP tools. All provider actions flow through this pair. ### `search` Discover available actions. Works like `--help` for your connected providers. #### Parameters | Name | Type | Required | Description | | ------- | ------ | -------- | --------------------------------------- | | `query` | string | yes | Provider name, action name, or keywords | #### Usage Examples ```bash # List everything available for a provider search("slack") # Fuzzy match across all providers search("send message") # Get full details for a specific action search("linear.create_issue") # See parameter spec and examples search("notion.query_database") ``` #### Response Returns matching actions with: * Action name and description * Parameter definitions (name, type, required, description) * Usage examples * User context (which providers are connected, which aren't) *** ### `exec` Execute a provider action. Format: `provider.action`. #### Parameters | Name | Type | Required | Description | | -------- | ------ | -------- | ----------------------------------------------------- | | `action` | string | yes | `provider.action` format (e.g. `linear.create_issue`) | | `params` | object | yes | Action-specific parameters | #### Usage Examples ```bash # Search Linear issues exec("linear.search", { query: "auth bug" }) # Create a Notion page exec("notion.create_page", { parent: { database_id: "abc123" }, properties: { title: "New Spec" } }) # Send a Slack message exec("slack.send_message", { channel: "#engineering", text: "Deploy complete ✅" }) # Batch create Linear issues exec("linear.batch_create_issues", { issues: [ { teamId: "...", title: "Fix login" }, { teamId: "...", title: "Fix signup" } ] }) ``` #### Error Handling If the action doesn't exist, `exec` returns an error suggesting you use `search` to find the right action. If parameters are missing or invalid, it returns the parameter spec so the AI can fix the call. #### Built-in Behavior The `exec` tool description includes a live **procedure catalog** — the names and categories of all available procedures are injected at session start. When a user says "I need to expense something", the AI immediately knows the `expense-tracking` procedure exists without any tool calls. The description also includes the execution protocol: > Before handling any company/admin operation (expenses, leave, time off, approvals, onboarding), ALWAYS check for a matching procedure. This means your AI will discover and invoke tracked procedures rather than improvising. #### Procedure Actions The `gateway` provider exposes procedure-specific actions through `exec`: | Action | Purpose | | ------------------------------ | ------------------------------------------------------------------------- | | `gateway.list_procedures` | List available procedures (id, name, category, trigger) | | `gateway.get_procedure` | Get summary: input table, output table, step names — no execution details | | `gateway.get_procedure_source` | Get full markdown source (authoring/editing only) | | `gateway.start_run` | Start a tracked run with validated input | | `gateway.get_run` | Load run state including **current step instructions only** | | `gateway.log_step` | Record what was done after completing a step | | `gateway.advance_step` | Move the run to the next step | | `gateway.park_run` | Park on a wait condition | | `gateway.complete_run` | Mark run as done | | `gateway.cancel_run` | Cancel a run | | `gateway.create_procedure` | Create a new procedure in Notion | | `gateway.update_procedure` | Update an existing procedure's markdown | The key design: `get_procedure` returns a summary (enough to collect input and start a run), while `get_run` returns the current step's instructions. The AI never sees the full procedure source during execution. ## Linear 27 actions for managing issues, cycles, projects, and team workflow. ### Issues #### `create_issue` Create a new Linear issue. | Parameter | Type | Required | Description | | ------------- | ------ | -------- | --------------------------- | | `teamId` | string | yes | Team to create the issue in | | `title` | string | yes | Issue title | | `description` | string | no | Markdown description | | `priority` | number | no | 0 (none) to 4 (urgent) | | `assigneeId` | string | no | User to assign | | `labelIds` | array | no | Label IDs to apply | | `cycleId` | string | no | Cycle to add to | | `projectId` | string | no | Project to add to | ```bash exec("linear.create_issue", { teamId: "TEAM_ID", title: "Fix authentication timeout", description: "Users report 30s timeout on login page", priority: 2 }) ``` #### `update_issue` | Parameter | Type | Required | Description | | ------------- | ------ | -------- | ------------------ | | `issueId` | string | yes | Issue to update | | `title` | string | no | New title | | `description` | string | no | New description | | `priority` | number | no | New priority | | `stateId` | string | no | New workflow state | | `assigneeId` | string | no | New assignee | | `labelIds` | array | no | New labels | | `cycleId` | string | no | New cycle | #### `get_issue` Get an issue by ID with all relations. | Parameter | Type | Required | | --------- | ------ | -------- | | `issueId` | string | yes | #### `batch_create_issues` Create up to 50 issues at once. Each item has the same shape as `create_issue`. | Parameter | Type | Required | | --------- | ----- | -------- | | `issues` | array | yes | #### `archive_issue` | Parameter | Type | Required | | --------- | ------ | -------- | | `issueId` | string | yes | #### `assign_issue` | Parameter | Type | Required | | ------------ | ------ | -------- | | `issueId` | string | yes | | `assigneeId` | string | yes | #### `search` Search issues by title and description. | Parameter | Type | Required | | --------- | ------ | -------- | | `query` | string | yes | ### Relations #### `create_issue_relation` | Parameter | Type | Required | Description | | ---------------- | ------ | -------- | ---------------------------------------------- | | `issueId` | string | yes | Source issue | | `relatedIssueId` | string | yes | Target issue | | `type` | string | yes | `blocks`, `blocked_by`, `related`, `duplicate` | #### `list_issue_relations` | Parameter | Type | Required | | --------- | ------ | -------- | | `issueId` | string | yes | ### Comments #### `add_comment` | Parameter | Type | Required | Description | | --------- | ------ | -------- | --------------------- | | `issueId` | string | yes | Issue to comment on | | `body` | string | yes | Markdown comment body | #### `list_comments` | Parameter | Type | Required | | --------- | ------ | -------- | | `issueId` | string | yes | ### Cycles (Sprints) #### `get_active_cycle` | Parameter | Type | Required | | --------- | ------ | -------- | | `teamId` | string | yes | #### `list_cycles` | Parameter | Type | Required | Description | | --------- | ------ | -------- | -------------------------- | | `teamId` | string | yes | Team to list cycles for | | `first` | number | no | Number of cycles to return | #### `add_issue_to_cycle` | Parameter | Type | Required | | --------- | ------ | -------- | | `issueId` | string | yes | | `cycleId` | string | yes | ### Initiatives #### `list_initiatives` No parameters. Returns all roadmap initiatives. #### `get_initiative` | Parameter | Type | Required | | --------- | ------ | -------- | | `id` | string | yes | ### Attachments #### `create_attachment` Attach an external resource to an issue. | Parameter | Type | Required | Description | | ---------- | ------ | -------- | ------------------ | | `issueId` | string | yes | Issue to attach to | | `title` | string | yes | Attachment title | | `url` | string | yes | External URL | | `subtitle` | string | no | Subtitle text | | `iconUrl` | string | no | Icon URL | ### Workspace #### `list_teams` List all teams and their members. No parameters. #### `list_workflow_states` | Parameter | Type | Required | | --------- | ------ | -------- | | `teamId` | string | no | #### `list_labels` No parameters. Returns all workspace labels. #### `list_projects` No parameters. Returns all projects. #### `get_viewer` No parameters. Returns the authenticated user. #### `list_users` No parameters. Returns all workspace members. ### GraphQL #### `graphql` Run an arbitrary GraphQL query or mutation against the Linear API. | Parameter | Type | Required | Description | | ----------- | ------ | -------- | -------------------- | | `query` | string | yes | GraphQL query string | | `variables` | object | no | Query variables | ```bash exec("linear.graphql", { query: "query { viewer { id name email } }" }) ``` :::warning Use specific actions when possible. The `graphql` action is an escape hatch for queries not covered by named actions. ::: ## Notion 21 actions for managing pages, databases, blocks, comments, and search. ### Pages #### `create_page` | Parameter | Type | Required | Description | | ------------ | ------ | -------- | ------------------------------------------------------------------------- | | `parent` | object | yes | `{ database_id: "..." }` or `{ page_id: "..." }` or `{ workspace: true }` | | `properties` | object | yes | Page properties (title, etc.) | | `children` | array | no | Block content to add | ```bash exec("notion.create_page", { parent: { database_id: "abc123" }, properties: { title: [{ text: { content: "New Spec Document" } }] } }) ``` #### `update_page` | Parameter | Type | Required | | ------------ | ------ | -------- | | `pageId` | string | yes | | `properties` | object | yes | #### `get_page` | Parameter | Type | Required | | --------- | ------ | -------- | | `pageId` | string | yes | #### `archive_page` Soft-delete a page. | Parameter | Type | Required | | --------- | ------ | -------- | | `pageId` | string | yes | #### `get_page_property` | Parameter | Type | Required | | ------------ | ------ | -------- | | `pageId` | string | yes | | `propertyId` | string | yes | #### `get_page_markdown` Get page content as markdown. Useful for reading full documents. | Parameter | Type | Required | | --------- | ------ | -------- | | `pageId` | string | yes | #### `update_page_markdown` Replace page body from markdown. | Parameter | Type | Required | | ---------- | ------ | -------- | | `pageId` | string | yes | | `markdown` | string | yes | ### Databases #### `query_database` Query a database with filters and sorts. | Parameter | Type | Required | Description | | ------------ | ------ | -------- | -------------------- | | `databaseId` | string | yes | Database to query | | `filter` | object | no | Notion filter object | | `sorts` | array | no | Sort instructions | ```bash exec("notion.query_database", { databaseId: "db123", filter: { property: "Status", select: { equals: "In Progress" } }, sorts: [{ property: "Priority", direction: "descending" }] }) ``` #### `get_database` Get database schema (properties, title, description). | Parameter | Type | Required | | ------------ | ------ | -------- | | `databaseId` | string | yes | #### `create_database` | Parameter | Type | Required | Description | | ------------ | ------ | -------- | -------------------- | | `parent` | object | yes | `{ page_id: "..." }` | | `properties` | object | yes | Database schema | | `title` | string | no | Database title | #### `update_database` | Parameter | Type | Required | | ------------- | ------ | -------- | | `databaseId` | string | yes | | `title` | string | no | | `description` | string | no | | `properties` | object | no | ### Blocks #### `get_block` | Parameter | Type | Required | | --------- | ------ | -------- | | `blockId` | string | yes | #### `update_block` | Parameter | Type | Required | | --------- | ------ | -------- | | `blockId` | string | yes | | `content` | object | yes | #### `delete_block` | Parameter | Type | Required | | --------- | ------ | -------- | | `blockId` | string | yes | #### `append_blocks` Add content blocks to a page or existing block. | Parameter | Type | Required | | ---------- | ------ | -------- | | `blockId` | string | yes | | `children` | array | yes | #### `get_block_children` Get all content blocks of a page or block. | Parameter | Type | Required | | --------- | ------ | -------- | | `blockId` | string | yes | ### Comments #### `create_comment` | Parameter | Type | Required | Description | | --------------- | ------ | -------- | -------------------------------------- | | `rich_text` | array | yes | Comment content | | `parent` | object | no | `{ page_id: "..." }` for page comments | | `discussion_id` | string | no | Reply to existing discussion thread | #### `list_comments` | Parameter | Type | Required | | --------- | ------ | -------- | | `blockId` | string | yes | ### Search & Users #### `search` Search across all pages and databases. | Parameter | Type | Required | Description | | ----------- | ------ | -------- | ----------------------------- | | `query` | string | yes | Search text | | `page_size` | number | no | Results per page (default 10) | #### `list_users` No parameters. Returns all workspace users. #### `get_user` | Parameter | Type | Required | | --------- | ------ | -------- | | `userId` | string | yes | ## Slack 28 actions for messaging, channels, reactions, search, and user management. ### Messages #### `send_message` | Parameter | Type | Required | Description | | --------- | ------ | -------- | ------------------ | | `channel` | string | yes | Channel ID or name | | `text` | string | yes | Message text | | `blocks` | array | no | Block Kit blocks | ```bash exec("slack.send_message", { channel: "#engineering", text: "Deploy v2.1 complete ✅" }) ``` #### `reply_thread` | Parameter | Type | Required | Description | | ----------- | ------ | -------- | ----------------------------- | | `channel` | string | yes | Channel containing the thread | | `thread_ts` | string | yes | Timestamp of parent message | | `text` | string | yes | Reply text | #### `update_message` | Parameter | Type | Required | | --------- | ------ | -------- | | `channel` | string | yes | | `ts` | string | yes | | `text` | string | yes | | `blocks` | array | no | #### `delete_message` | Parameter | Type | Required | | --------- | ------ | -------- | | `channel` | string | yes | | `ts` | string | yes | #### `schedule_message` Schedule a message up to 120 days in the future. | Parameter | Type | Required | Description | | --------- | ------ | -------- | --------------------------- | | `channel` | string | yes | Channel to post in | | `text` | string | yes | Message text | | `post_at` | number | yes | Unix timestamp for delivery | #### `get_permalink` Get a permanent URL for a message. | Parameter | Type | Required | | ------------ | ------ | -------- | | `channel` | string | yes | | `message_ts` | string | yes | ### Reading #### `get_channel_history` Get recent messages from a channel. | Parameter | Type | Required | Description | | --------- | ------ | -------- | ---------------------- | | `channel` | string | yes | Channel ID | | `limit` | number | no | Max messages to return | | `oldest` | string | no | Start timestamp (Unix) | | `latest` | string | no | End timestamp (Unix) | #### `get_thread_replies` | Parameter | Type | Required | Description | | --------- | ------ | -------- | ----------------------------- | | `channel` | string | yes | Channel containing the thread | | `ts` | string | yes | Parent message timestamp | | `limit` | number | no | Max replies to return | #### `search_messages` Search messages with Slack query syntax. | Parameter | Type | Required | Description | | --------- | ------ | -------- | --------------------------------------------------------------- | | `query` | string | yes | Slack search query (supports `in:#channel`, `from:@user`, etc.) | | `count` | number | no | Max results | | `sort` | string | no | `"score"` or `"timestamp"` | ```bash exec("slack.search_messages", { query: "deploy failed in:#engineering", sort: "timestamp" }) ``` ### Reactions #### `add_reaction` | Parameter | Type | Required | Description | | ----------- | ------ | -------- | ---------------------------------------------------- | | `channel` | string | yes | Channel | | `timestamp` | string | yes | Message timestamp | | `name` | string | yes | Emoji name (e.g. `"thumbsup"`, `"white_check_mark"`) | #### `remove_reaction` | Parameter | Type | Required | | ----------- | ------ | -------- | | `channel` | string | yes | | `timestamp` | string | yes | | `name` | string | yes | #### `get_reactions` | Parameter | Type | Required | | ----------- | ------ | -------- | | `channel` | string | yes | | `timestamp` | string | yes | ### Channels #### `get_channel` | Parameter | Type | Required | | --------- | ------ | -------- | | `channel` | string | yes | #### `list_channels` No parameters. Returns all public and private channels. #### `create_channel` | Parameter | Type | Required | Description | | ------------ | ------- | -------- | ---------------- | | `name` | string | yes | Channel name | | `is_private` | boolean | no | Private channel? | #### `archive_channel` | Parameter | Type | Required | | --------- | ------ | -------- | | `channel` | string | yes | #### `invite_to_channel` | Parameter | Type | Required | Description | | --------- | ------ | -------- | ------------------------ | | `channel` | string | yes | Channel ID | | `users` | string | yes | Comma-separated user IDs | #### `kick_from_channel` | Parameter | Type | Required | | --------- | ------ | -------- | | `channel` | string | yes | | `user` | string | yes | #### `list_channel_members` | Parameter | Type | Required | | --------- | ------ | -------- | | `channel` | string | yes | #### `set_topic` | Parameter | Type | Required | | --------- | ------ | -------- | | `channel` | string | yes | | `topic` | string | yes | ### Direct Messages #### `open_conversation` Open a DM or group DM. Returns the channel ID for sending messages. | Parameter | Type | Required | Description | | --------- | ------ | -------- | ------------------------ | | `users` | string | yes | Comma-separated user IDs | ### Users #### `get_user_info` | Parameter | Type | Required | | --------- | ------ | -------- | | `user` | string | yes | #### `list_users` No parameters. Returns all workspace users. ### Pins #### `pin_message` | Parameter | Type | Required | | ----------- | ------ | -------- | | `channel` | string | yes | | `timestamp` | string | yes | #### `unpin_message` | Parameter | Type | Required | | ----------- | ------ | -------- | | `channel` | string | yes | | `timestamp` | string | yes | #### `list_pins` | Parameter | Type | Required | | --------- | ------ | -------- | | `channel` | string | yes | ## Authoring Procedures How to turn your company's informal processes into structured procedure documents that Gateway can execute. ### Interview Flow When creating a procedure, work through these questions: 1. **What is this?** Name it. One sentence on what it does. 2. **Who triggers it?** Manual (someone asks), scheduled (recurring), or event-driven? 3. **What info is needed to start?** These become Input fields. 4. **What are the steps?** Walk through the happy path first, then edge cases. 5. **Where does it wait?** Which steps need human input, approval, or a deadline? 6. **What gets tracked?** Collections (Notion DBs) vs. scalar state. 7. **What's the output?** What does "done" look like? 8. **Who needs to know?** Slack notifications, approvals, announcements. 9. **What does it touch?** Which Slack channels, Linear teams, Notion databases? :::tip If you're unsure how to structure a process, ask your AI: ``` I need a procedure for [your process]. Can you help me author it? ``` Gateway has a built-in authoring skill that guides you through these questions conversationally. ::: :::warning Procedures must live inside the **Gateway → Procedures** tree in Notion for automation to work. Gateway creates this hierarchy when you first connect Notion — don't move procedure pages outside of it. If you create a procedure page elsewhere in Notion, Gateway won't be able to read it or export run data to it. ::: ### Document Structure Every procedure follows this structure: ``` [frontmatter] # Title [1-2 sentence description] ## Input [table of fields needed to start] ## State [table of what's tracked during execution] ## Steps [named steps with descriptions and transitions] ## Output [table of what the completed procedure produces] ``` ### Frontmatter ```yaml id: expense-report version: 1.0 trigger: manual category: Finance description: Submit expenses for reimbursement with multi-tier approval routing. access: public admins: - slack:#finance-team share: - slack:#all-employees ``` | Field | Required | Values | | ------------- | -------- | ------------------------------------------------------------------------------- | | `id` | yes | Unique, kebab-case | | `version` | yes | `major.minor` — LLM bumps based on nature of edits | | `trigger` | yes | `manual`, `schedule("cron")`, `event("type")` | | `category` | yes | Finance, People, Procurement, Access, Compliance, Operations, Custom | | `description` | yes | 1-2 sentence user-facing summary (what this does, when to use it) | | `access` | yes | `public` (discoverable by all) or `private` (only shared users) | | `admins` | yes | List of admin principals: `slack:#channel`, `email:user@co.com`, or `user:{id}` | | `share` | no | List of principals who can discover and invoke (if `private`) | #### Principal Format Access control uses principals that can reference Slack channels (as groups), emails, or user IDs: ```yaml admins: - slack:#it-internal # everyone in this Slack channel is an admin - email:cfo@company.com # specific user by email share: - slack:#all-employees # everyone in the company - email:contractor@ext.com # specific external user ``` Slack channel membership is dynamically resolved — when someone joins or leaves the channel, their access updates automatically. ### Input Table ```markdown | Field | Type | Required | Default | Description | |-------|------|----------|---------|-------------| | amount | number | yes | | Total in USD | | category | string | yes | | meals / travel / software | ``` **Types:** `string`, `number`, `date`, `boolean`, `string[]`, `object` Mark fields required only if the procedure can't start without them. Always provide sensible defaults where possible. ### State Table ```markdown | Field | Storage | Description | |-------|---------|-------------| | expense_record | Notion DB row | The expense entry | | current_step | invocation | Where we are | ``` **Storage values:** * `Notion DB` — for collections (submissions, approvals, checklist items) * `Notion DB: {auto}` — AI creates the schema from step context * `Notion DB row` — single row in an existing database * `invocation` — scalar value stored on the run record Always include `current_step | invocation | Where we are`. ### Steps Each step is an `### h3` heading under `## Steps`. ```markdown ### route_approval Determine the approver based on amount. Post in #finance-approvals for approval. **Wait**: 48h **On event** `approved`: → process_reimbursement **On event** `rejected`: → notify_rejection **On timeout**: Escalate — ping approver again, CC finance lead. → process_reimbursement ``` #### Conventions | Syntax | Meaning | | ----------------------------------- | ----------------------------------------------- | | `→ step_name` | Transition to next step | | `→ done ✓` | Final step (every procedure needs at least one) | | `→ step_a \| step_b` | Branch (AI chooses based on conditions) | | `**Wait until**: {condition}` | Pause until date/time | | `**Wait**: {duration}` | Pause for a duration (3d, 48h) | | `**On event** \`name\`: \[action]\` | Handle event during wait | | `**On timeout**: [action]` | Handle wait expiry | | `{input.field}` / `{state.field}` | References to input and state | #### Step Naming Use `snake_case`, descriptive names. | ✅ Good | ❌ Bad | | --------------------- | ---------- | | `route_approval` | `step3` | | `notify_rejection` | `next` | | `collect_submissions` | `do_stuff` | ### Output Table ```markdown | Field | Type | Description | |-------|------|-------------| | expense_id | string | Notion page ID | | status | string | approved / rejected | ``` What the completed procedure produces — IDs of created records, summary stats, status strings. ### Quality Checklist Before finalizing a procedure, verify: * [ ] Every step has a `→` transition (no dead ends) * [ ] At least one `→ done ✓` final step * [ ] All `{input.field}` refs exist in the Input table * [ ] All `{state.field}` refs exist in the State table * [ ] Wait steps have both `On event` and `On timeout` * [ ] Slack notifications say WHO needs to act and HOW * [ ] ID is unique and kebab-case * [ ] `description` is 1-2 sentences, user-facing * [ ] `access` is set (`public` or `private`) * [ ] `admins` lists at least one principal * [ ] `version` uses `major.minor` format ### Anti-Patterns | Don't | Do | | ---------------------------------- | ----------------------------------------------- | | Steps that just say "do the thing" | Describe WHAT, WHERE, and WHAT to say | | Hardcoded names/IDs | Use `{input.field}` references | | Wait without timeout handling | Always have `**On timeout**` | | Approval with no escalation | Add escalation after reasonable wait | | Skipping Slack notifications | People need to know what's happening | | Giant monolith steps | Split into focused steps with clear transitions | | `step1`, `step2` naming | Descriptive: `route_approval`, `notify_team` | ## What Happens When You're Away Many procedures involve waiting — for an approval, a deadline, or someone else's input. Here's what Gateway does while you're offline. ### The Basics When you start a procedure and it reaches a step that says "wait 48 hours" or "wait for approval", Gateway parks the run and you go on with your day. You don't need to keep your editor open or stay online. Behind the scenes, Gateway periodically checks on all parked runs. When a wait condition is met — deadline passed, approval received, response collected — it picks up where you left off and executes the next step automatically. ### How Approvals Work The most common wait is an approval. Here's what the full cycle looks like: **1. You kick things off** ``` You: "Expense my Denver conference trip — $2,400 total" ``` **2. Gateway runs the procedure steps and reaches the approval step** The procedure knows $2,400 requires COO approval, so it posts in the right Slack channel: ``` Gateway [BOT] 💰 Expense approval needed Mike submitted $2,400 for "Denver conference trip" React ✅ to approve, ❌ to reject ``` **3. Gateway waits** The run is parked. You get a DM confirming it's been submitted and who needs to approve. **4. Someone approves** The COO reacts with ✅ on the Slack message. Within the next check cycle (roughly 30–60 minutes), Gateway detects the reaction and resumes the run. **5. Gateway finishes the job** It follows the procedure's "on approved" path — maybe posting a confirmation in #finance, updating a tracker, and DM'ing you that your expense was approved. You might not even be at your desk when this all resolves. You'll just see the DM later: "Your $2,400 expense was approved by Sarah." ### How Timeouts Work Procedures can define what happens if nobody responds in time. ``` Wait: 48h for approval On timeout: Ping the approver again, CC their manager. ``` If 48 hours pass with no reaction, Gateway automatically sends a nudge. Some procedures escalate — pinging a backup approver or a manager. Others just close the run with a note. ### What Gateway Can Do Autonomously When executing steps on its own, Gateway is sandboxed. It can only access what the procedure explicitly declares: * **Send Slack messages** — post in specific channels, DM specific people * **Create Linear issues** — in the teams the procedure is allowed to touch * **Export data to Notion** — write to the procedure's own database It cannot browse your Notion, read your Slack DMs, or access anything outside the procedure's scope. :::tip For autonomous execution to work, procedures must live inside the **Gateway → Procedures** tree in Notion. Gateway creates this tree when you first connect Notion and automatically has access to everything inside it. If a procedure page is moved outside the tree, automation will stop working for it. ::: ### Where Your Data Lives During Execution While a run is in progress, all the data — submissions, log entries, who's assigned — lives in Gateway's internal database. It doesn't appear in Notion yet. Why? Privacy. If the data were in Notion during execution, anyone with Notion access to that workspace could see in-flight expense reports, pending evaluations, or other sensitive submissions. By keeping it internal, Gateway enforces that only authorized people (you, admins, and people assigned to your run) can see the data. Once the run completes, the data exports to a Notion database under the procedure page — visible to admins and the people involved. ### Checking on Your Runs You don't need to wait for a run to finish. Ask your AI anytime: ``` "What's the status of my expense report?" "Show me my open runs" "Did the COO approve my Denver trip yet?" ``` Gateway will check your runs and give you the latest status, including which step it's on and what it's waiting for. ### What Happens if Something Goes Wrong If a step fails during autonomous execution — say a Slack channel was archived or a token expired — Gateway stops the run and marks it as failed. It doesn't retry or guess. Admins get notified and can investigate. If a procedure was updated while your run was parked: * **Small changes** (wording tweaks, minor additions) — Gateway adapts and keeps going * **Big changes** (steps removed, logic restructured) — the run is flagged for admin review before continuing ## Document Format Reference Complete reference for the procedure document format — every section, field, and convention. ### Full Template ```markdown id: my-procedure version: 1.0 trigger: manual category: Operations description: One to two sentences describing what this procedure does and when to use it. access: public admins: - slack:#ops-team share: - slack:#all-employees # My Procedure One to two sentences describing what this procedure does and when to use it. ## Input | Field | Type | Required | Default | Description | |-------|------|----------|---------|-------------| | field_name | string | yes | | What this field is for | ## State | Field | Storage | Description | |-------|---------|-------------| | current_step | invocation | Where we are | ## Steps ### first_step Description of what to do. → next_step ### next_step Description of what to do. → done ✓ ### done ✓ Close the run. ## Output | Field | Type | Description | |-------|------|-------------| | result | string | What was produced | ``` ### Frontmatter Fields | Field | Type | Required | Values | | ------------- | ------ | -------- | -------------------------------------------------------------------- | | `id` | string | yes | Unique, kebab-case identifier | | `version` | string | yes | `major.minor` — start at `1.0`, LLM bumps based on edit nature | | `trigger` | string | yes | `manual`, `schedule("0 9 * * MON")`, `event("type")` | | `category` | string | yes | Finance, People, Procurement, Access, Compliance, Operations, Custom | | `description` | string | yes | User-facing summary — what it does and when to use it | | `access` | string | yes | `public` (discoverable by all) or `private` (only shared principals) | | `admins` | list | yes | Principals who can manage, edit, and view all runs | | `share` | list | no | Principals who can discover and invoke (for `private` procedures) | #### Principal Formats | Format | Example | Resolves to | | ---------------- | --------------------- | ------------------------------------------ | | `slack:#channel` | `slack:#finance-team` | All members of the Slack channel (dynamic) | | `email:addr` | `email:alice@co.com` | Single user by email | | `user:id` | `user:abc123` | Single user by Gateway ID | ### Input Field Types | Type | Description | Example | | ---------- | --------------- | ------------------------------- | | `string` | Text value | `"Fix the login bug"` | | `number` | Numeric value | `85.50` | | `date` | ISO date | `2026-03-15` | | `boolean` | True/false | `true` | | `string[]` | List of strings | `["alice", "bob"]` | | `object` | Structured data | `{ name: "...", email: "..." }` | ### State Storage Types During execution, all state lives in D1. On run completion, collections export to Notion. | Storage | Use For | In-Flight (D1) | On Export (Notion) | | ------------------- | --------------------------------------- | ------------------------ | ------------------------------ | | `Notion DB` | Collections the user should see | `run_submissions` rows | Notion DB under procedure page | | `Notion DB: {auto}` | Collections where AI designs the schema | `run_submissions` rows | Auto-created Notion DB | | `Notion DB row` | Single row in an existing database | `run_submissions` row | Notion DB row | | `invocation` | Scalar values for internal tracking | `runs.current_step` etc. | Run metadata in Notion | ### Step Patterns #### Simple Transition ```markdown ### create_record Create the record in the database. Notify the submitter. → review ``` #### Approval with Escalation ```markdown ### approve Post in #channel for approval. - If amount ≤ $200 → team lead approves - If amount > $200 → department head approves **Wait**: 48h **On event** `approved`: → next_step **On event** `rejected`: → rejection_step **On timeout**: Escalate — ping approver again, CC their manager. ``` #### Collect Over Time ```markdown ### collect_responses **Wait until**: {input.deadline} **On event** `response`: Add to {state.responses}. Confirm to sender. **On timeout**: Continue with whatever was collected. → process ``` #### Branch by Condition ```markdown ### route Based on {state.decision}: **If approved:** - Create Linear issue - → execute **If rejected:** - Notify submitter - → done ✓ **If deferred:** - Schedule reminder for 3 months - → done ✓ ``` #### Periodic Check-in ```markdown ### track_progress **Wait until**: {input.target_date} - 14d Weekly on Monday: - Query status from Linear / Notion - Post update in {input.channel} - Flag blocked items **On event** `ready`: → next_step ``` #### Multi-System Provisioning ```markdown ### setup_accounts Create or verify: - Google Workspace account - Slack invite → add to channels - Notion access → share team spaces - Linear team membership - GitHub access (if engineering) Create checklist in Notion to track completion. ``` ## Procedures Procedures are structured documents that encode repeatable company processes — expense reports, onboarding, content publishing, approvals — as executable state machines that your AI assistant can run. ### The Idea Every company has processes. "How do I submit an expense?" "What's the onboarding checklist?" "How do we evaluate a partnership?" These processes usually live in someone's head, a stale wiki page, or a Slack thread that gets lost. Procedures turn them into structured documents that: 1. **Humans can read** — They're markdown, stored in Notion 2. **AI can execute** — The document IS the program 3. **Access is controlled** — Only authorized people see run data, with automatic handoffs between steps ### How It Works Procedure execution flow: Document → AI Engine → Provider Actions + Run State → Export to Notion There is no rigid workflow engine. The AI discovers available procedures from the tool description, fetches a summary (input requirements, step names — no execution details), collects input from the user, starts a tracked run, then executes steps one at a time using Gateway tools — posting in Slack, creating Linear issues, updating Notion. The AI only ever sees the current step's instructions, never the full document. In-flight run data lives in an internal database behind code-enforced access control, keeping your data private during execution. On completion, everything exports to a Notion database under the procedure page where admins can browse it. ### Example Here's what running an expense report looks like after a business trip: ``` You: "I need to expense my Denver conference trip — $2,400 total. Flights were $680, hotel $1,200 for 3 nights, meals $320, ground transport $200. Receipts are in the Drive folder I shared." AI: [finds expense-report procedure in catalog] [gets summary: needs expense, amount, category, receipt_url] [starts tracked run, gets first step instructions] [creates row in Expenses database: "travel: Denver conference trip — $2,400"] [logs step, advances → amount > $1,000, routes to COO] [posts approval request in #finance-approvals with receipt link] [DMs you: "Expense logged: $2,400. Routing to COO for approval."] [parks run — waiting for approval...] ``` No spreadsheet. No tracking down who needs to approve what. The procedure knows that $2,400 requires COO sign-off, posts in the right channel, and follows up if nobody responds in 48 hours. ### Key Concepts | Concept | Description | | ------------------ | ------------------------------------------------------------------------------------ | | **Procedure** | A markdown document defining a process as a state machine | | **Run** | A single execution of a procedure (D1 during execution, exported to Notion on close) | | **Step** | A named unit of work within a procedure | | **Transition** | `→ next_step` — how steps connect | | **Wait** | Pause execution until a deadline or event | | **State** | Data tracked during execution (D1 in-flight, Notion on export) | | **Access Control** | Who can discover, invoke, and manage a procedure and its runs | ### Getting Started 1. **Use existing procedures** — Ask your AI: `"What procedures are available?"` to see what's already set up 2. **Author new ones** — Follow the [Authoring Guide](/procedures/authoring) to encode your processes 3. **See examples** — Browse [real procedure examples](/procedures/examples/expense-report) for patterns ### Categories Procedures are organized by category: | Category | Covers | | --------------- | --------------------------------------------------------------------- | | **Finance** | Expense reports, reimbursements, budget requests, invoice approval | | **People** | Leave/PTO, onboarding, offboarding, role changes, performance reviews | | **Procurement** | Software licenses, equipment, vendor onboarding, purchase orders | | **Access** | Tool/repo access requests, permission grants, admin roles | | **Compliance** | Policy acknowledgments, training, incident reports, audits | | **Operations** | Reporting cycles, OKR check-ins, meeting prep, announcements | | **Custom** | Team-specific or one-off procedures | ## Running Procedures How procedures execute, park on waits, and resume — in both interactive and autonomous modes. ### Execution Rules When executing a procedure, the AI is an **operator, not a collaborator**. It follows the document exactly. 1. **Execute the current step exactly as written.** Nothing more. 2. **Do not skip steps.** Even if a step seems unnecessary. 3. **Do not add steps.** If something isn't in the document, don't do it. 4. **Do not reorder steps.** Follow transitions (`→`) as written. 5. **Do not improvise.** If it says "post in #general", post in #general. 6. **Use exact references.** `{input.channel}` means the channel from input. 7. **Wait means wait.** If the step says `**Wait**: 48h`, stop. Do not proceed early. 8. **Log everything.** After each step, append a log entry to the run page. 9. **If something fails, stop.** Report the error. Do not retry unless the procedure says to. 10. **If something is ambiguous, ask.** Do not guess. 11. **Respect the actor's permissions.** Only access resources the current actor has tokens for. ### Execution Modes #### Interactive Mode The user's AI executes steps while the user is present. The AI has the user's provider tokens and can ask for clarification. When it hits a wait step, it parks and tells the user what's being waited on. #### Autonomous Mode Gateway's internal AI executes steps without a user present. This happens when a scheduled sweep detects a wait condition is met (deadline passed, approval received). The AI is sandboxed — it uses the workspace bot token for Slack and the run creator's tokens for other providers. A procedure might start interactively (user kicks it off), park on a wait, then resume autonomously (the system handles the timeout). ### Execution Sequence The AI never sees the full procedure source during execution. It works one step at a time, stopping after each: ``` 1. Call get_procedure(pageId) → see required input, step names, no instructions 2. Collect all required input from the user 3. Call start_run(procedureId, pageId, input, firstStep) → creates run in D1 4. Call get_run(runId) → see current step instructions ONLY 5. Execute the current step exactly as written 6. Call log_step(runId, step, entry) → record what was done 7. STOP. Report what was done and the result to the user. 8. If wait → call park_run, stop. Turn is over until user asks to resume. 9. If → done ✓ → call complete_run, stop. 10. When user says "continue" → call advance_step(runId, nextStep), go to step 4. ``` The AI stops after each step and waits for the user before continuing. After parking, it does NOT auto-resume — the user must explicitly ask to resume. ### Parking a Wait When a step has `**Wait**` or `**Wait until**`: * Set run status to `waiting` * Record `wait_until` (ISO timestamp when the wait expires) * Record `wait_context` (e.g., Slack message reference for approval polling) * Log the wait in the run page * **Stop execution** — do NOT proceed to the next step ### Resuming When a wait resolves, execution resumes with context about what triggered the resume: | Trigger | Context | | ----------------- | ---------------------------------------------------------------- | | `timeout` | Deadline expired — follow `**On timeout**` handler | | `event: approved` | Approval received — follow `**On event** \`approved\`\` handler | | `event: rejected` | Rejection received — follow `**On event** \`rejected\`\` handler | ### Run Log Format After each step, a log entry is written to the D1 `run_log` table: ```markdown ### route_approval — 2026-03-15 09:30 Posted approval request in #finance-approvals for $2,400 Denver conference trip. Approver: COO (amount > $1,000). Result: waiting for approval (48h timeout) ``` For autonomous execution, entries are prefixed with `[auto]`: ```markdown ### process_reimbursement — 2026-03-17 10:00 [auto] Approval received from @sarah. Updated expense status to "Approved". Posted reimbursement request in #finance. Trigger: event:approved by @sarah Result: waiting for finance to confirm (5d timeout) ``` ### Run States | Status | Meaning | | ----------------- | -------------------------------------------------------- | | `running` | Actively executing steps | | `waiting` | Parked on a wait condition | | `completed` | Reached `→ done ✓` | | `failed` | A step failed and couldn't recover | | `cancelled` | Manually stopped by a user or admin | | `needs_migration` | Procedure had a major version bump while run was waiting | | `token_expired` | A provider token failed during autonomous execution | ### Storage #### During Execution (D1) In-flight runs live in D1 behind code-enforced ACL. No Notion page exists during execution — admins and users query run data via the LLM. | Table | What it stores | | ----------------- | -------------------------------------------- | | `runs` | Run state, procedure snapshot, wait metadata | | `run_submissions` | Data collected during steps | | `run_log` | Execution log entries | | `run_access` | Creator + dynamically assigned users | #### After Completion (Notion Export) On any terminal status (completed, failed, cancelled), data exports to a Notion database under the procedure page: ``` Procedures (database) ├── expense-report (page) │ ├── [procedure document] │ └── Completed Runs (auto-created Notion DB) │ ├── Run #1 — completed — $150 team lunch │ └── Run #2 — cancelled — partial data ``` Admins can also manually trigger export at any time via `procedure.export(runId)`. ### Access Control Runs are ACL-gated. Who sees what: | Who | What they see | | ----------------- | ------------------------------------------------------ | | **Admin** | All runs for procedures they admin. Full data. | | **Creator** | Their own runs. Full data. | | **Assigned** | Runs they're assigned to. Data from their step onward. | | **Everyone else** | Nothing. The run doesn't exist to them. | Dynamic assignment happens via `**Assign**` in procedure steps — when the LLM executes that step, it grants the assigned user access to the run. ### Version Migration Runs snapshot the procedure version at start. If the procedure is updated while a run is parked: | Version change | What happens | | ------------------------------ | -------------------------------------------------------------------------------------------- | | **Minor** (e.g. `1.0` → `1.1`) | LLM adapts on next resume — new step language applies going forward | | **Major** (e.g. `1.1` → `2.0`) | Run status moves to `needs_migration` — admin must review or the LLM auto-migrates on resume | The LLM decides whether an edit is major or minor based on the nature of the change (structural vs cosmetic). ### What the AI Must NOT Do * Call `get_procedure_source` during execution (that's for authoring only) * Read ahead to future steps or the full procedure document * Summarize steps instead of executing them * Combine multiple steps into one action * Skip notification steps ("they probably already know") * Change approval thresholds or routing logic * Continue past a wait step * Modify the procedure document during execution * Execute steps out of order * Access resources beyond the actor's token scope * Read procedure internals on behalf of non-admin users ## Sharing & Permissions Control who can find, run, and manage your procedures. ### Public vs Private Every procedure is either **public** or **private**. | Visibility | Who can find it | Who can run it | | ----------- | ----------------------------- | ----------------------------- | | **Public** | Everyone in your workspace | Everyone in your workspace | | **Private** | Only people you share it with | Only people you share it with | Set this in your procedure's frontmatter: ```yaml access: public # everyone can see and run it access: private # only shared people can see it ``` Most company-wide procedures (expense reports, PTO requests) should be **public**. Team-internal procedures (hiring evaluations, performance reviews) should be **private**. ### Sharing with People and Groups Share procedures using Slack channels or email addresses. #### Share with a Slack channel Everyone in the channel gets access. When people join or leave the channel, their access updates automatically. ```yaml share: - slack:#engineering - slack:#finance-team ``` #### Share with specific people ```yaml share: - email:alice@company.com - email:contractor@external.com ``` #### Combine both ```yaml share: - slack:#engineering - email:contractor@external.com ``` ### Admins Admins can edit the procedure, view all of its runs, and export run data. Set admins the same way you set sharing — Slack channels or emails. ```yaml admins: - slack:#it-internal - email:cfo@company.com ``` :::tip Use a Slack channel for admins rather than individual emails. When someone joins the team, they automatically get admin access. ::: ### Who Can See Runs When someone runs a procedure, the run data is private by default. | Person | What they can see | | ---------------------------------------- | --------------------------------------- | | **You** (the person who started the run) | Everything about your run | | **Admins** of the procedure | All runs — they manage the process | | **Assigned people** | Only the runs they're assigned to | | **Everyone else** | Nothing — the run doesn't exist to them | #### How assignment works Some procedures hand off between people. For example, an expense report might start with you, then get assigned to your manager for approval. When the procedure reaches a step that involves someone else, Gateway automatically grants them access to the run so they can see the relevant data. They don't see the full history — just what's needed from their step onward. ### Common Patterns #### Company-wide procedure with a dedicated admin team ```yaml access: public admins: - slack:#finance-team ``` Everyone can submit expenses. The finance team can see all submissions and manage the process. #### Team-internal procedure ```yaml access: private admins: - slack:#hiring-committee share: - slack:#engineering-managers ``` Only engineering managers can find and use the hiring evaluation procedure. The hiring committee manages it. #### Cross-team procedure with restricted visibility ```yaml access: private admins: - email:coo@company.com share: - slack:#department-heads ``` Only department heads can initiate partnership evaluations. The COO oversees all of them. ### Why Can't Users See Procedure Details? Regular users can see a procedure's **name**, **category**, and **description** — enough to know what it does and when to use it. They can't see the internal steps, approval thresholds, or evaluation criteria. This is intentional. If an expense procedure has tiered approval rules ($200 team lead, $1000 department head, $5000+ COO), you don't want people splitting expenses to stay under a threshold. If a hiring evaluation has scoring rubrics, you don't want candidates coaching their referrals on what to say. Admins always have full visibility into the procedure document. ## Content Publishing Pipeline Your team wants to publish a blog post, case study, or guide. This procedure coordinates the entire pipeline — from creating a brief, through writing and review, to publishing and announcement. It keeps author, editor, designer, and publisher in sync without anyone playing project manager. **Use it when:** Someone has a content idea that needs to go from draft to published. Works for blog posts, case studies, guides, announcements, and newsletters. **What makes it interesting:** It has review loops — if the editor requests changes, the procedure sends the author back to drafting. It nudges authors who are falling behind on deadlines, and handles different content types (blog vs. newsletter) with different publishing checks. *** ### Walkthrough #### Header ```markdown id: content-publishing version: 1 trigger: manual category: Operations # Content Publishing Pipeline Take a content piece from idea through writing, review, design, and publishing. Covers blog posts, case studies, and educational guides. ``` #### Input The inputs capture the content brief. SEO keywords and a topic brief are optional but help the author get started faster. ```markdown ## Input | Field | Type | Required | Default | Description | |-------|------|----------|---------|-------------| | title | string | yes | | Working title | | type | string | yes | | blog-post / case-study / guide / announcement / newsletter | | author | string | yes | | Who's writing it | | target_date | date | no | +2 weeks | Target publish date | | brief | string | no | | Topic brief, angle, audience, key points | | seo_keywords | string | no | | Target keywords | | channel | string | no | #marketing | Slack channel for updates | ``` #### State ```markdown ## State | Field | Storage | Description | |-------|---------|-------------| | content_record | Notion DB row | Row in Content Calendar | | draft_page | Notion page | The actual draft | | current_step | invocation | Where we are | ``` #### Steps **Step 1 — Create the brief.** Set up a Content Calendar entry and a draft page. If no brief was provided, the procedure creates a template for the author to fill in. ```markdown ### create_brief Create a row in the **Content Calendar** database with title, type, author, status "Drafting", target date, and SEO keywords. Create a Notion page for the draft. If brief provided, add it as the opening section. Otherwise, add a template: ## Brief **Audience:** [who is this for?] **Angle:** [what's the unique take?] **Key points:** [3-5 bullet points] **CTA:** [what should the reader do next?] DM {input.author}: "Content piece created: '{input.title}'. Draft page: [link]. Target: {input.target_date}." → write_draft ``` **Step 2 — Wait for the draft.** The procedure waits until one week before the publish date. If the author goes quiet for too long, it sends a reminder. ```markdown ### write_draft **Wait until**: {input.target_date} - 7d (one week before publish) **On event** `draft_ready` → review Periodic nudge if no event after 50% of time: DM {input.author}: "Reminder: '{input.title}' draft is due soon." → review ``` **Step 3 — Review.** Post the draft for the team to review. If the reviewer requests changes, execution loops back to the writing step. ```markdown ### review Update content record status to "In Review". Post in {input.channel}: > 📝 Ready for review: **{input.title}** > Author: {input.author} | Type: {input.type} > Draft: [link] > Please review and leave comments. React ✅ when approved. **Wait**: 3d - **On event** `approved` → prepare_publish - **On event** `changes_requested`: DM author with feedback, → write_draft - **On timeout**: Ping reviewer again, CC marketing lead. → prepare_publish ``` **Step 4 — Prepare for publishing.** Run content-type-specific checks before handoff to the publisher. ```markdown ### prepare_publish Update status to "Ready to Publish". For blog-post or guide: - Check SEO metadata (title tag, meta description, keywords) - Check for at least one image/graphic - If no image, request one in {input.channel} For newsletter: - Prepare email subject line and preview text DM publisher: "'{input.title}' is ready to publish. All assets attached." → publish ``` **Step 5 — Publish.** Go live and announce to the company. ```markdown ### publish Update status to "Published". Set Published Date to today. Post in {input.channel}: > 🚀 Published: **{input.title}** — [URL] > Author: {input.author}. Please share on socials! Post in #general: "New on the blog: **{input.title}** — [link]" → done ✓ ``` #### Output ```markdown ## Output | Field | Type | Description | |-------|------|-------------| | published_url | string | URL of the published piece | | content_record_id | string | Notion page ID in Content Calendar | | days_to_publish | number | Days from creation to publish | | type | string | Content type | ``` *** ### Key Patterns This procedure demonstrates patterns you can reuse: * **Review loops** — `changes_requested` sends the author back to drafting * **Periodic nudges** — the author gets reminded before deadlines * **Content-type branching** — different checks for blog posts vs. newsletters * **Multi-role coordination** — author, reviewer, designer, and publisher stay in sync View full procedure file → ## Course / Program Launch Launching a new course, certification, or workshop involves product, content, engineering, and marketing — all needing to coordinate over weeks. This procedure replaces a project manager by creating the project plan, tracking progress with weekly check-ins, running a go/no-go review, and monitoring post-launch metrics. **Use it when:** You're launching a new learning product — a course, certification, workshop, or learning path — and need cross-functional coordination without a dedicated PM. **What makes it interesting:** It creates a full Linear project broken down by function (content, engineering, marketing), posts automated weekly progress updates, runs a formal go/no-go review that can loop back if there are blockers, and follows up a week after launch with enrollment metrics. *** ### Walkthrough #### Header ```markdown id: course-launch version: 1 trigger: manual category: Operations # Course / Program Launch Coordinate a new course or learning program from concept through launch. Involves product, content, engineering, marketing, and sometimes external instructors. ``` #### Input The inputs capture the course details and who's driving it. The `has_external_instructor` flag triggers extra steps for instructor agreements and content review. ```markdown ## Input | Field | Type | Required | Default | Description | |-------|------|----------|---------|-------------| | name | string | yes | | Course or program name | | type | string | yes | | course / certification / workshop / learning-path | | owner | string | yes | | Product/content owner driving this | | target_launch | date | yes | | Target launch date | | audience | string | yes | | Who this is for | | description | string | yes | | What learners will get out of it | | has_external_instructor | boolean | no | false | External instructor involved? | | channel | string | no | #product | Slack channel for updates | ``` #### State ```markdown ## State | Field | Storage | Description | |-------|---------|-------------| | launch_record | Notion DB row | Row in Product Launches database | | checklist | Notion DB | Launch checklist tasks | | linear_project | invocation | Linear project URL | | current_step | invocation | Where we are | ``` #### Steps **Step 1 — Kickoff.** Create the launch record, spin up a Linear project, and populate it with issues grouped by function. If there's an external instructor, additional tasks are added. ```markdown ### kickoff Create a row in **Product Launches** database with name, type, owner, target date, audience, and status "Planning". Create a Linear project: "{input.name} Launch" Create Linear issues grouped by function: **Content:** - Write course outline / syllabus - Create lesson content (modules, exercises, assessments) - Review content for accuracy and pedagogy - Create supplementary materials **Engineering:** - Set up course in LMS / platform - Configure enrollment flow - Test end-to-end learner experience - Set up analytics / tracking **Marketing:** - Write landing page copy - Create promotional assets - Set up launch email sequence - Prep launch announcement **If external instructor:** - Finalize instructor agreement - Record / review instructor content - Set up instructor payment Post in {input.channel}: > 🚀 Kicking off: **{input.name}** ({input.type}) > Owner: {input.owner} | Target: {input.target_launch} > Linear project: [link] | Checklist: [link] → track_progress ``` **Step 2 — Track progress.** Every Monday, the procedure checks Linear and the checklist, then posts an automated status update. It DMs the owner about anything blocked or overdue. ```markdown ### track_progress **Wait until**: {input.target_launch} - 14d Weekly check-in every Monday: - Query Linear project for issue status - Query launch checklist for completion % - Post in {input.channel}: > **{input.name} launch update** — X% complete > ✅ Done: [items] · 🔄 In progress: [items] · ⚠️ Blocked: [items] > N days until launch. DM {input.owner} about blocked or overdue items. **On event** `ready_for_review` → pre_launch_review → pre_launch_review ``` **Step 3 — Pre-launch review.** A formal go/no-go. If the team flags blockers, it loops back to tracking. ```markdown ### pre_launch_review Update status to "In Review". Post in {input.channel}: > 🔍 **Pre-launch review for {input.name}** > Content: X/Y · Engineering: X/Y · Marketing: X/Y > **Go / No-go?** React 🟢 for go, 🔴 for blockers. **Wait**: 3d - **On event** `go` → launch - **On event** `no_go`: Flag blockers, DM owner, → track_progress → launch ``` **Step 4 — Launch.** Go live, announce to the company, trigger email sequences. ```markdown ### launch Update status to "Launched". Set Launch Date to today. Post in #general: > 🎉 **{input.name}** is live! {input.description} Trigger launch email sequence. Draft social posts for approval. DM {input.owner}: "Congrats! Monitor enrollment and feedback this week." → post_launch ``` **Step 5 — Post-launch monitoring.** Wait a week, then gather and report metrics. ```markdown ### post_launch **Wait**: 7d after launch Gather and post metrics: > 📊 **{input.name} — Week 1 Report** > Enrollments: X | Feedback: [highlights] | Issues: X (Y resolved) DM {input.owner} with recommendations. → done ✓ ``` #### Output ```markdown ## Output | Field | Type | Description | |-------|------|-------------| | launch_date | date | Actual launch date | | linear_project_url | string | Linear project URL | | enrollments_week_1 | number | First week enrollments | | checklist_completion | object | { completed, total } | | days_to_launch | number | Days from kickoff to launch | ``` *** ### Key Patterns This procedure demonstrates patterns you can reuse: * **Cross-functional coordination** — content, engineering, and marketing tracked in one project * **Weekly progress tracking** — automated Slack updates every Monday * **Go/no-go review** — formal checkpoint that can loop back if blockers exist * **Post-launch monitoring** — metrics collection a week after launch * **Conditional steps** — extra tasks when an external instructor is involved View full procedure file → ## Expense Report An employee spends company money — lunch with a client, a flight, a software license — and needs to get reimbursed. This procedure handles the entire flow: logging the expense, routing it to the right approver based on amount, waiting for approval, and confirming reimbursement. **Use it when:** Someone on your team incurs a business expense and needs to submit it for reimbursement. Common categories include meals, travel, software, equipment, and events. **What makes it interesting:** The approval chain changes depending on how much was spent. Small amounts go to a team lead; bigger amounts escalate to department heads or the CEO. If an approver goes silent, the procedure automatically escalates. *** ### Walkthrough #### Header Every procedure starts with metadata — an ID, version, trigger type, and category. This tells Gateway how to register and find the procedure. ```markdown id: expense-report version: 1 trigger: manual category: Finance # Expense Report Submit, approve, and reimburse employee expenses. Handles receipt collection, manager approval (with escalation for amounts over threshold), finance processing, and reimbursement confirmation. ``` #### Input The input section defines what information is needed to start the procedure. These fields are collected when someone triggers it. ```markdown ## Input | Field | Type | Required | Default | Description | |-------|------|----------|---------|-------------| | submitter | string | yes | | Who's submitting (name or Slack user ID) | | amount | number | yes | | Total amount in USD | | category | string | yes | | meals / travel / software / equipment / events / other | | description | string | yes | | What the expense is for | | receipt_url | string | yes | | Link to receipt image/PDF (Google Drive, Notion, etc.) | | date | date | no | today | Date expense was incurred | ``` #### State State fields track data that persists across steps. They're stored in Notion so humans can see and edit them at any point. ```markdown ## State | Field | Storage | Description | |-------|---------|-------------| | expense_record | Notion DB row | The expense entry in Expenses database | | approver | invocation | Manager assigned to approve | | current_step | invocation | Where we are | ``` #### Steps Steps are where the action happens. Each step is a named unit of work. The `→` arrow at the end shows where execution goes next. **Step 1 — Log the expense.** Create a database entry and notify the submitter. ```markdown ### log_expense Create a row in the **Expenses** database: - Title: "{input.category}: {input.description}" - Amount: {input.amount} - Submitted By: {input.submitter} - Date: {input.date} - Receipt: {input.receipt_url} - Status: "Pending Approval" DM the submitter on Slack: "Expense logged: ${input.amount} for {input.description}. Routing for approval." → route_approval ``` **Step 2 — Route to the right approver.** The procedure picks the approver based on amount thresholds, then posts in Slack and waits. ```markdown ### route_approval Determine the approver: - If amount ≤ $200 → team lead - If amount > $200 and ≤ $1,000 → department head - If amount > $1,000 → COO/CEO Post in #finance-approvals: > 💰 Expense approval needed > **{input.submitter}** submitted ${input.amount} for "{input.description}" > Category: {input.category} | Receipt: {input.receipt_url} > React ✅ to approve, ❌ to reject, or reply with questions. **Wait**: 48h for response **On event** `approved`: → process_reimbursement **On event** `rejected`: → notify_rejection **On timeout**: Escalate — ping approver again, CC finance lead. → process_reimbursement ``` **Step 3 — Process the reimbursement.** Once approved, notify finance and wait for them to confirm payment. ```markdown ### process_reimbursement Update expense record status to "Approved". Post in #finance: "Expense approved — ${input.amount} to {input.submitter}. Please process reimbursement." **Wait**: 5d for finance to confirm **On event** `reimbursed`: → confirm → confirm ``` **Step 4 — Handle rejection.** If the approver says no, update the record and tell the submitter. ```markdown ### notify_rejection Update expense record status to "Rejected". DM submitter: "Your expense for {input.description} (${input.amount}) was not approved." → done ✓ ``` **Step 5 — Confirm.** Mark the expense as reimbursed and close the loop. ```markdown ### confirm Update expense record status to "Reimbursed". DM submitter: "Your expense of ${input.amount} for {input.description} has been reimbursed." → done ✓ ``` #### Output The output section defines what the procedure returns when it completes. Other procedures or reports can reference these fields. ```markdown ## Output | Field | Type | Description | |-------|------|-------------| | expense_id | string | Notion page ID of the expense record | | status | string | approved / rejected / reimbursed | | approved_by | string | Who approved | | processed_at | date | When reimbursement was processed | ``` *** ### Key Patterns This procedure demonstrates several patterns you can reuse: * **Tiered approval routing** — different approvers based on amount thresholds * **Escalation on timeout** — if the approver doesn't respond in 48 hours, escalate automatically * **Multi-step async flow** — two separate wait periods (approval, then reimbursement processing) * **Status updates** — the submitter gets a DM at every state change View full procedure file → ## New Hire Onboarding A new person is joining your company. They need accounts in a dozen tools, access to the right Slack channels, an onboarding buddy, and a structured first week. This procedure handles all of it — from pre-start provisioning through a first-week check-in and close-out. **Use it when:** Someone new is joining. Run it before their start date so everything is ready on day one. **What makes it interesting:** It spans multiple days with steps tied to specific dates (before start, day 1, day 5). It auto-assigns an onboarding buddy if one isn't specified, tracks a checklist in Notion, and follows up until everything is confirmed complete. *** ### Walkthrough #### Header ```markdown id: new-hire-onboarding version: 1 trigger: manual category: People # New Hire Onboarding Onboard a new team member across all company systems — Slack, Notion, Linear, Google Workspace, and role-specific tools. Assigns a buddy, tracks checklist completion, and ensures a productive first week. ``` #### Input The inputs capture who's joining, what their role is, and when they start. The buddy field is optional — if left blank, the procedure will auto-assign one from the same department. ```markdown ## Input | Field | Type | Required | Default | Description | |-------|------|----------|---------|-------------| | name | string | yes | | New hire's full name | | email | string | yes | | Work email (Google Workspace) | | role | string | yes | | Job title | | department | string | yes | | engineering / product / marketing / ops / content | | start_date | date | yes | | First day | | manager | string | yes | | Direct manager | | buddy | string | no | | Onboarding buddy (auto-assigned if not provided) | | equipment | string | no | | Hardware notes | ``` #### State The checklist is stored as a Notion database, so the manager (or anyone) can check progress at any time by opening the Notion page. ```markdown ## State | Field | Storage | Description | |-------|---------|-------------| | checklist | Notion DB | Onboarding tasks with status | | current_step | invocation | Where we are | ``` #### Steps **Step 1 — Prepare accounts (before start date).** Provision all tools and create the onboarding checklist. If no buddy was specified, pick one from the department. ```markdown ### prep_accounts **Before start date.** Create or verify: - Google Workspace account ({input.email}) - Slack invite → add to #general, #team-{input.department}, #random - Notion workspace access → share relevant team spaces - Linear team membership → add to {input.department} team - GitHub/GitLab access (if engineering/product) - Role-specific tools (Figma, Hubspot, Mixpanel, etc.) Create the **Onboarding Checklist** database with tasks: - [ ] Google Workspace active - [ ] Slack joined, channels added - [ ] Notion access granted - [ ] Linear team added - [ ] Equipment ordered/shipped - [ ] 1Password/security setup - [ ] Role-specific tools provisioned - [ ] Welcome doc shared - [ ] First week calendar populated If no buddy assigned, pick someone from the same department (3+ months tenure). DM {input.manager}: "Onboarding for {input.name} starting {input.start_date}. Buddy: {state.buddy}. Checklist: [link]" → welcome_day_one ``` **Step 2 — Welcome on day one.** Post a company-wide welcome, send the new hire their first-day instructions, and activate the buddy. ```markdown ### welcome_day_one **On {input.start_date}:** Post in #general: > 👋 Welcome {input.name}! Joining as {input.role} on the {input.department} team. Their buddy is {state.buddy}. Say hi! DM {input.name} with first-day instructions: 1. Check email for tool invites 2. Buddy {state.buddy} will reach out 3. 1:1 with {input.manager} scheduled 4. Team handbook link DM {state.buddy}: "You're buddying {input.name} this week. Please reach out today." Schedule a 30-min check-in with {input.name} + {input.manager} for day 3. → first_week_check ``` **Step 3 — First week check-in.** Wait until day 5, then ask both the new hire and their manager how things are going. Flag any incomplete checklist items. ```markdown ### first_week_check **Wait until**: {input.start_date} + 5d DM {input.name}: > How's your first week? Do you have access to everything? Any blockers? DM {input.manager}: "Check in with {input.name}. Checklist status: [link]" Review checklist — flag incomplete items. **Wait**: 2d for responses - **On event** `all_clear` → close_onboarding - **On event** `issues`: Address issues, then → close_onboarding → close_onboarding ``` **Step 4 — Close onboarding.** Wrap up, post a summary, and let the new hire know they're all set. ```markdown ### close_onboarding Update all checklist items. Post summary to #ops: > ✅ Onboarding complete for {input.name} ({input.role}) > Checklist: X/Y items done. Outstanding: [list if any] DM {input.name}: "You're all set! Buddy {state.buddy} and manager {input.manager} are your go-to people." → done ✓ ``` #### Output ```markdown ## Output | Field | Type | Description | |-------|------|-------------| | checklist_status | object | { completed, total, outstanding_items } | | accounts_provisioned | array | List of tools/accounts set up | | buddy | string | Assigned buddy | | days_to_complete | number | Calendar days from start to close | ``` *** ### Key Patterns This procedure demonstrates patterns you can reuse: * **Multi-day timeline** — steps tied to specific dates (before start, day 1, day 5) * **Automated provisioning** — sets up accounts across multiple systems * **Checklist tracking** — progress visible in a Notion database * **Buddy auto-assignment** — falls back to picking someone from the department * **Timed check-ins** — follows up at day 1, day 5, and close View full procedure file → ## Partnership & Integration Evaluation Someone proposes a partnership — an integration, a reseller deal, an academic collaboration. Instead of ad-hoc emails and gut feelings, this procedure gives you structured scoring, team input, and clear decision routing. The outcome is always one of three: proceed, pass, or defer. **Use it when:** An inbound or outbound partnership opportunity comes in and you want a consistent, transparent way to evaluate it. **What makes it interesting:** It uses a four-criteria scoring system (strategic fit, effort, revenue potential, risk) to produce a numeric score, then branches into three different paths depending on the result. If the team decides to defer, it schedules a reminder for 3 months later. *** ### Walkthrough #### Header ```markdown id: partnership-evaluation version: 1 trigger: manual category: Operations # Partnership & Integration Evaluation Evaluate an inbound or outbound partnership opportunity — integrations, resellers, content providers, academic partners, or sponsors. ``` #### Input The inputs capture who the partner is, what they want, and how urgent the evaluation needs to be. ```markdown ## Input | Field | Type | Required | Default | Description | |-------|------|----------|---------|-------------| | partner_name | string | yes | | Organization name | | contact_name | string | yes | | Primary contact | | contact_email | string | yes | | Contact email | | type | string | yes | | integration / reseller / content / academic / sponsorship | | source | string | no | inbound | inbound / outbound / referral | | summary | string | yes | | What they want, why it matters | | urgency | string | no | normal | normal / time-sensitive | | channel | string | no | #partnerships | Slack channel for discussion | ``` #### State The evaluation score and decision are tracked as invocation state (in-memory during execution), while the partnership record lives in Notion for long-term visibility. ```markdown ## State | Field | Storage | Description | |-------|---------|-------------| | eval_record | Notion DB row | Row in Partnerships database | | score | invocation | Evaluation score (0-20) | | decision | invocation | proceed / pass / defer | | current_step | invocation | Where we are | ``` #### Steps **Step 1 — Log the opportunity.** Create a record and start a discussion thread for the team. ```markdown ### log_opportunity Create a row in the **Partnerships** database with name, contact, type, source, status "Evaluating", and summary. Post in {input.channel}: > 🤝 New partnership opportunity > **{input.partner_name}** ({input.type}) > {input.summary} > Source: {input.source} | Contact: {input.contact_name} > Thread below for discussion. → evaluate ``` **Step 2 — Evaluate and score.** Wait for team input (shorter window if urgent), then score across four criteria. Each is rated 1–5, producing a total out of 20. ```markdown ### evaluate **Wait**: 3d for team input (1d if time-sensitive) Collect thread responses. Score against criteria: - **Strategic fit** (1-5): Alignment with roadmap and target market - **Effort estimate** (1-5): Engineering/product time required - **Revenue potential** (1-5): Direct revenue, user growth, or market access - **Risk** (1-5, inverted): Legal, technical, or reputational concerns Post summary in thread: > **Evaluation: {input.partner_name}** > Strategic fit: X/5 | Effort: X/5 | Revenue: X/5 | Risk: X/5 > Score: X/20 > Recommendation: proceed / pass / defer Scoring thresholds: - Score ≥ 14 → recommend proceed - Score 8-13 → recommend defer - Score < 8 → recommend pass → route_decision ``` **Step 3 — Route the decision.** Different actions depending on the outcome. "Proceed" creates a Linear project with kickoff tasks. "Defer" schedules a 3-month reminder. ```markdown ### route_decision Based on {state.decision}: **If proceed:** - Update status to "Active" - Create Linear project: "{input.partner_name} Integration/Partnership" - Create issues: kickoff call, technical scoping, legal review - DM owner with project link **If pass:** - Update status to "Passed" **If defer:** - Update status to "Deferred" - Schedule reminder for 3 months → notify_partner ``` **Step 4 — Draft a response.** The procedure drafts an email for a human to review and send — it never contacts external parties directly. ```markdown ### notify_partner Draft a response email (post in channel for human to review and send): **If proceed:** "We'd love to move forward. Let's schedule a kickoff call." **If pass:** "Not the right fit at this stage. We'll keep you in mind." **If defer:** "Interested but can't commit now. Can we revisit in Q[next]?" → done ✓ ``` #### Output ```markdown ## Output | Field | Type | Description | |-------|------|-------------| | partner_name | string | Organization name | | decision | string | proceed / pass / defer | | score | number | Composite score out of 20 | | linear_project | string | Linear project URL (if proceed) | | eval_record_id | string | Notion page ID | ``` *** ### Key Patterns This procedure demonstrates patterns you can reuse: * **Structured scoring** — multiple criteria combined into a single numeric score * **Three-way branching** — proceed, pass, or defer, each with different actions * **Deferred follow-up** — "defer" schedules a reminder for later re-evaluation * **Human-in-the-loop** — drafts partner communication for a human to send, never auto-sends View full procedure file → ## Best Practices Patterns and tips for getting the most out of Gateway. ### What Works Well #### Be Specific About What You Want :::code-group ```bash [Good] Search Linear for issues assigned to me in the current sprint ``` ```bash [Less Good] Show me my tasks ``` ::: Mentioning the tool name and specific details gets you faster, more accurate results. #### Describe the Full Workflow :::code-group ```bash [Good] Create a Linear issue for "Fix login timeout", assign it to me, and post it in #bugs with the issue link ``` ```bash [Less Good] Make a bug report about login timeouts ``` ::: When you spell out each step, the AI executes them in sequence without asking follow-up questions. #### Ask What's Possible ``` What can I do with Notion databases? ``` Gateway will show you every available action — creating databases, querying with filters, updating schemas, and more. Great when you're not sure what's available. #### Name Things Explicitly ``` Query the "Product Roadmap" database in Notion for items due this month ``` Specific names (databases, channels, teams) get exact results. Vague descriptions sometimes match the wrong thing. ### Common Workflows #### Morning Routine **You:** ``` Give me my daily 360 overview, then list any Linear issues that are overdue or blocked ``` **You get:** A full briefing plus a focused list of items that need your attention today. #### Weekly Review **You:** ``` What issues did the engineering team complete this week? Also check Notion for any updated spec docs. ``` **You get:** A summary of closed issues by person, plus a list of recently edited Notion pages. #### Quick Triage **You:** ``` Search Slack #support for messages from today, summarize the themes, and create Linear issues for anything that looks like a bug ``` **You get:** A theme summary (e.g., "3 reports about slow loading, 2 about broken search") with bug issues auto-created and linked to the Slack messages. #### Sprint Planning **You:** ``` List all unassigned issues in the product backlog. What's the current sprint's remaining capacity? ``` **You get:** The backlog items ready for assignment and how many points are left in the sprint. ### What to Avoid #### Don't Ask for Everything at Once ``` ❌ "Tell me everything about my entire workspace" ``` Too broad — you'll get a wall of text. Ask focused questions instead. #### Don't Be Vague About Destinations ``` ❌ "Post that in the channel" ``` Which channel? Always name it: "Post in #engineering". #### Don't Repeat What Gateway Already Knows ``` ❌ "Search Slack using the search_messages function with query parameter..." ``` You don't need to specify how. Just say what you want: "Search Slack for messages about the deploy." ### Power User Tips * **Chain across tools**: "Find the Notion spec, then create Linear issues for each section" works in one go * **Use time ranges**: "Messages from this week" or "Issues completed since Monday" narrows results * **Follow up naturally**: After getting results, say "Assign the first three to me" — the AI remembers context * **Ask for drafts**: "Draft a Slack message summarizing..." lets you review before sending ## Cross-Tool Workflows Gateway's real power shows when you work across Linear, Notion, and Slack in a single conversation. ### Create an Issue → Discuss → Document **You:** ``` Create a Linear issue for "Migrate auth to OAuth 2.1" on the platform team, post it in #engineering for discussion, and create a Notion spec page for it ``` **AI responds:** > Done! Here's what I set up: > > * ✅ Created **\[PLAT-77] Migrate auth to OAuth 2.1** in Linear (platform team, backlog) > * ✅ Posted in **#engineering**: "New issue: Migrate auth to OAuth 2.1 — \[link to PLAT-77]. Thread for discussion 👇" > * ✅ Created Notion page: **"OAuth 2.1 Migration Spec"** with a link back to the issue > > The spec page has a template ready for you to fill in. Want me to add an outline? ### Triage Support → Create Bugs **You:** ``` Search Slack #support for messages about the login error this week, then create a Linear issue summarizing the reports ``` **AI responds:** > Found 7 messages in **#support** about login errors this week. Common themes: > > * 4 reports: "Login hangs for 30+ seconds then times out" > * 2 reports: "Password reset email never arrives" > * 1 report: "SSO redirect loops on Safari" > > Created **\[ENG-162] Login timeout + auth issues (7 support reports)** with all details and Slack message links. Assigned to triage. Want me to post a status update in #support? ### Check Progress → Post Update **You:** ``` Check the Q1 OKRs database in Notion and post a progress summary in #leadership ``` **AI responds:** > Pulled Q1 OKR data from Notion. Posted this update in **#leadership**: > > > 📊 **Q1 OKR Progress — Week 11** > > > > * **Revenue**: 87% of target (on track) > > * **User growth**: 112% of target ✅ > > * **API v2 launch**: 60% complete (at risk — blocked on auth migration) > > * **Customer NPS**: 72 → 78 (improving) > > > > 3 of 5 key results on track. API v2 needs attention. ### Sprint Summary → Standup Post **You:** ``` What's in the current engineering sprint? Post a summary in #engineering-standup ``` **AI responds:** > Current sprint ends Tuesday. Posted to **#engineering-standup**: > > > **Sprint 14 — Day 8 of 10** > > ✅ Done (6): ENG-142, ENG-145, ENG-148, ENG-150, ENG-151, PROD-90 > > 🔄 In Progress (3): ENG-156, ENG-160, ENG-161 > > ⚠️ Blocked (1): ENG-153 — waiting on DevOps > > 📊 Velocity: 24/31 points complete (77%) ### Research → Action Items **You:** ``` Find the competitive analysis doc in Notion, pull out the action items, and create Linear issues for each one ``` **AI responds:** > Found **"Competitive Analysis — March 2026"** in Notion. Extracted 4 action items and created issues: > > 1. **\[PROD-95]** "Add SSO support — competitor parity" (Product team) > 2. **\[ENG-163]** "Improve search latency to \< 200ms" (Engineering team) > 3. **\[MKT-48]** "Publish comparison landing page" (Marketing team) > 4. **\[PROD-96]** "Evaluate AI summarization feature" (Product team) > > All linked back to the Notion analysis doc. Want me to assign them? ### Tips * **Be explicit about the sequence** — "First search..., then create..., then post..." gives clear results * **Name your channels and teams** — "Post in #engineering" is clearer than "post in the engineering channel" * **Reference previous results** — "Use that issue link in the Slack message" carries context between steps * **Batch when possible** — "Create issues for each of these" is faster than one at a time ## Daily Briefing Get a personal overview of everything happening across your connected tools — what's done, what's in progress, what needs your attention. ### How to Use It Just ask your AI assistant: ``` Give me my daily 360 overview ``` ### What You Get Here's a typical briefing: *** > **Daily 360 — Friday March 14, 2026** > > #### Summary > > Productive day — 4 issues closed, 2 spec docs updated, active discussions in #engineering about the API migration. One blocked item needs your attention. > > #### ✅ Completed > > * Closed **ENG-142** "Auth timeout on login page" > * Closed **ENG-145** "Fix rate limiter edge case" > * Closed **ENG-148** "Update OAuth scopes" > * Closed **PROD-90** "Write API v2 migration guide" > * Updated Notion pages: "API v2 Spec", "Q1 OKR Tracker" > > #### 🔄 In Progress > > * **ENG-156** "OAuth token refresh failing" — assigned to you, started today > * **ENG-160** "Q1 infra budget review" — waiting on finance > * Editing: "Auth Migration Runbook" in Notion > > #### ⚠️ Needs Attention > > * **ENG-153** "Database connection pooling" — blocked for 3 days, waiting on DevOps > * @jake mentioned you in #engineering: "Can you review the caching PR?" > * Unanswered DM from @sarah about the onboarding timeline > > #### 💬 Key Conversations > > * **#engineering**: Team agreed to ship API v2 behind a feature flag (thread with 12 replies) > * **#product**: New partnership inquiry from Acme Corp — @lisa is evaluating > * **#general**: Office closed next Friday for team offsite > > #### 📅 Upcoming > > * Sprint ends Tuesday — 3 issues remaining > * 1:1 with @manager Monday at 10am > * API v2 launch target: March 21 > > #### 🎯 Suggested Actions > > 1. Unblock **ENG-153** — ping DevOps or reassign > 2. Reply to @jake's review request in #engineering > 3. Respond to @sarah's DM about onboarding > 4. Close out remaining sprint issues before Tuesday *** ### After a Weekend or Days Off Ask for a longer lookback: ``` Give me a 360 overview for the last 72 hours ``` Great for Monday mornings — catches everything from Friday through the weekend. ### The More You Connect, the Better It Gets | Connected | Your briefing includes | | ----------------------- | ----------------------------------------- | | Linear only | Issues, sprints, assignments | | Linear + Notion | + docs you edited, database updates | | Linear + Notion + Slack | + conversations, mentions, decisions, DMs | ### When to Use It * **Morning standup prep** — get your update ready in 30 seconds * **Monday morning** — catch up on the weekend * **Before 1:1s** — know what your team has been working on * **End of day** — summarize what you accomplished for async teams ## Your First Search A step-by-step walkthrough of using Gateway to search across your connected tools. ### Prerequisites * At least one provider connected (Linear, Notion, or Slack) * Gateway installed in your AI editor ([Installation](/installation)) ### Step 1: See What's Connected Start by asking what you can do: **You:** ``` What can I do with Gateway? ``` **AI responds:** > You have 3 providers connected: > > * **Linear** — issues, sprints, projects, comments, teams > * **Notion** — pages, databases, search, comments > * **Slack** — messages, channels, threads, reactions, search > > You can search across all of them, create and update items, send messages, and more. What would you like to do? ### Step 2: Search for Something Ask for what you need in plain language: **You:** ``` Find all Linear issues about the authentication bug ``` **AI responds:** > Found 3 issues matching "authentication bug": > > 1. **\[ENG-142] Auth timeout on login page** — In Progress, assigned to Sarah > 2. **\[ENG-156] OAuth token refresh failing** — Backlog, unassigned > 3. **\[ENG-89] Fix session expiry handling** — Done (completed Mar 10) > > Would you like to open any of these, or take action on them? ### Step 3: Take Action on Results You can act on what you found right away: **You:** ``` Assign ENG-156 to me and add a comment that I'm looking into it ``` **AI responds:** > Done! I've: > > * ✅ Assigned **ENG-156 "OAuth token refresh failing"** to you > * ✅ Added comment: "Looking into this now." > > The issue is now in your backlog. Want me to move it to In Progress? ### Step 4: Search Across Tools The real power is working across multiple tools at once: **You:** ``` Find the Q1 planning doc in Notion and any related Linear issues ``` **AI responds:** > **Notion** — found 2 pages: > > * 📄 **Q1 2026 Planning** (last edited 2 days ago) > * 📄 **Q1 OKR Tracker** (last edited today) > > **Linear** — found 5 related issues: > > * \[PROD-88] Q1 roadmap: auth migration — In Progress > * \[PROD-91] Q1 roadmap: API v2 — Backlog > * \[PROD-93] Q1 planning: resource allocation — Done > * \[ENG-160] Q1 infra budget review — In Review > * \[MKT-45] Q1 launch prep — In Progress > > The planning doc mentions the auth migration and API v2 as top priorities. Want me to summarize the doc or update any issues? ### Tips * **Be specific** — "Find Linear issues assigned to me in the current sprint" works better than "show my tasks" * **Chain actions** — "Create the issue, then post it in #engineering" works in one go * **Use provider names** — "Search Notion for..." is more direct than "search my documents for..." * **Ask what's possible** — "What can I do with Slack channels?" shows you everything available