Are you an LLM? Read llms.txt for a summary of the docs, or llms-full.txt for the full context.
Skip to content

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?

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

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
FieldRequiredValues
idyesUnique, kebab-case
versionyesmajor.minor — LLM bumps based on nature of edits
triggeryesmanual, schedule("cron"), event("type")
categoryyesFinance, People, Procurement, Access, Compliance, Operations, Custom
descriptionyes1-2 sentence user-facing summary (what this does, when to use it)
accessyespublic (discoverable by all) or private (only shared users)
adminsyesList of admin principals: slack:#channel, email:user@co.com, or user:{id}
sharenoList 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:

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

| 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

| 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.

### 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

SyntaxMeaning
→ step_nameTransition to next step
→ done ✓Final step (every procedure needs at least one)
→ step_a | step_bBranch (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_approvalstep3
notify_rejectionnext
collect_submissionsdo_stuff

Output Table

| 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'tDo
Steps that just say "do the thing"Describe WHAT, WHERE, and WHAT to say
Hardcoded names/IDsUse {input.field} references
Wait without timeout handlingAlways have **On timeout**
Approval with no escalationAdd escalation after reasonable wait
Skipping Slack notificationsPeople need to know what's happening
Giant monolith stepsSplit into focused steps with clear transitions
step1, step2 namingDescriptive: route_approval, notify_team