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.
- Execute the current step exactly as written. Nothing more.
- Do not skip steps. Even if a step seems unnecessary.
- Do not add steps. If something isn't in the document, don't do it.
- Do not reorder steps. Follow transitions (
→) as written. - Do not improvise. If it says "post in #general", post in #general.
- Use exact references.
{input.channel}means the channel from input. - Wait means wait. If the step says
**Wait**: 48h, stop. Do not proceed early. - Log everything. After each step, append a log entry to the run page.
- If something fails, stop. Report the error. Do not retry unless the procedure says to.
- If something is ambiguous, ask. Do not guess.
- 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:
### 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]:
### 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 dataAdmins 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_sourceduring 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