Human-in-the-Loop Approval Workflow
AOF supports human-in-the-loop approval for destructive operations, ensuring that sensitive commands require explicit user approval before execution.
Overview
When an AI agent determines that a requested operation is potentially destructive (create, delete, scale down, etc.), it can request human approval before proceeding. This is implemented as a native feature of the trigger handler, with first-class support for Slack reactions.
Platform Support
Current Implementation
| Platform | Approval Method | Status |
|---|---|---|
| Slack | Reaction-based (✅/❌) | ✅ Fully implemented |
| Discord | Reaction-based | 🔄 Planned |
| Telegram | Inline keyboard buttons | 🔄 Planned |
| Microsoft Teams | Adaptive Cards | 🔄 Planned |
| Button replies | 🔄 Planned |
Architecture
The approval workflow is designed to be platform-agnostic at the handler level:
- Agent Output Parsing: The
TriggerHandlerparsesrequires_approval: truefrom agent responses - this is platform-independent - PendingApproval Storage: Approvals are stored in a shared
DashMapaccessible by all platforms - Platform-Specific UI: Each platform implements its own approval UI (reactions, buttons, etc.)
- Approval Processing: The handler processes approval events from any platform
To add approval support for a new platform:
- Implement the approval UI in the platform's
TriggerPlatformimplementation - Handle approval events (reactions, button clicks, etc.)
- Call the shared
handle_approvalmethod onTriggerHandler
Platform-Specific Approval Methods (Planned)
Each platform uses its native interaction mechanism for approvals:
| Platform | Mechanism | API | Notes |
|---|---|---|---|
| Slack | Reactions | Events API | ✅ Implemented - reaction_added events |
| Discord | Reactions | Gateway API | Similar to Slack, use emoji reactions |
| Telegram | Inline Keyboards | Bot API | Interactive buttons below message |
| Microsoft Teams | Adaptive Cards | Bot Framework | Rich card with action buttons |
| Interactive Buttons | Cloud API | Up to 3 buttons per message |
WhatsApp Business API (Planned)
WhatsApp Business API supports Interactive Messages with button replies - perfect for approval workflows:
{
"type": "interactive",
"interactive": {
"type": "button",
"header": { "type": "text", "text": "⚠️ Approval Required" },
"body": { "text": "kubectl create deployment nginx --image=nginx" },
"action": {
"buttons": [
{ "type": "reply", "reply": { "id": "approve", "title": "✅ Approve" } },
{ "type": "reply", "reply": { "id": "deny", "title": "❌ Deny" } }
]
}
}
}
Requirements:
- WhatsApp Business Account
- Cloud API access (Meta Developer Portal)
- Verified business phone number
- Webhook endpoint for button click callbacks
Planned Config:
platforms:
whatsapp:
enabled: true
access_token_env: WHATSAPP_ACCESS_TOKEN
verify_token_env: WHATSAPP_VERIFY_TOKEN
phone_number_id_env: WHATSAPP_PHONE_NUMBER_ID
# Approval whitelist (phone numbers)
approval_allowed_users:
- "+1234567890"
- "+0987654321"
How It Works
1. Agent Returns Approval Request
When the agent's response includes approval-related fields, the system intercepts and triggers the approval flow:
The agent's response should include:
- requires_approval: true
- command: "kubectl create deployment nginx --image=nginx"
2. Approval Message is Sent
The system posts an approval request message to Slack with reaction buttons:
⚠️ This action requires approval
`kubectl create deployment nginx --image=nginx`
React with ✅ to approve or ❌ to deny.
The message automatically gets ✅ and ❌ reactions added for easy approval/denial.
3. User Reacts to Approve/Deny
- ✅ (white_check_mark) - Approves and executes the command
- ❌ (x) - Denies and cancels the operation
4. Feedback After Execution
After approval and execution, the result is posted back:
✅ Command completed successfully
```kubectl create deployment nginx --image=nginx```
deployment.apps/nginx created
Agent Configuration
Configure your agent to request approval for destructive operations:
apiVersion: aof.dev/v1
kind: Agent
metadata:
name: k8s-assistant
spec:
model: google:gemini-2.5-flash
temperature: 0.3
instructions: |
You are a Kubernetes assistant.
IMPORTANT: For destructive operations (create, delete, scale, apply, patch),
you MUST return the following format:
[Your explanation]
requires_approval: true
command: "kubectl ..."
This will trigger a human approval flow before execution.
For read-only operations (get, describe, logs), execute directly
using your kubectl tool.
tools:
- kubectl
- helm
- shell
Supported Reactions
The approval system recognizes these reactions:
Approve Reactions
white_check_mark(✅) - Primary approvalheavy_check_mark(✔️)+1(👍)
Deny Reactions
x(❌) - Primary denialno_entry(⛔)-1(👎)
Technical Implementation
The approval workflow is implemented in the trigger handler (aof-triggers crate):
Key Components
-
PendingApproval Storage (
handler/mod.rs)- Stores pending approvals in a concurrent
DashMap<String, PendingApproval> - Key is the Slack message timestamp (for lookup on reaction)
- Value contains command, user, channel, and metadata
- Stores pending approvals in a concurrent
-
Output Parsing (
parse_approval_output)- Parses agent responses for
requires_approval: truepattern - Extracts the command to execute
- Parses agent responses for
-
Reaction Events (
platforms/slack.rs)- Handles
reaction_addedevents from Slack - Maps reactions to approve/deny actions
- Handles
-
Command Execution (
handle_reaction_event)- Executes approved commands via shell
- Sends result feedback to Slack
- Removes pending approval after processing
Data Flow
┌─────────────────────────────────────────────────────────────────┐
│ 1. User Message │
│ @bot "create nginx deployment" │
└─────────────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────────────┐
│ 2. Agent Processes & Returns │
│ requires_approval: true │
│ command: "kubectl create deployment nginx ..." │
└─────────────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────────────┐
│ 3. Handler Parses Output │
│ - Detects requires_approval: true │
│ - Extracts command │
│ - Posts approval message │
│ - Adds ✅ ❌ reactions │
│ - Stores PendingApproval │
└─────────────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────────────┐
│ 4. User Reacts │
│ ✅ → Approve | ❌ → Deny │
└─────────────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────────────┐
│ 5. Handler Processes Reaction │
│ - Looks up PendingApproval by message_ts │
│ - If approved: Execute command, send result │
│ - If denied: Send denial message │
│ - Remove from pending approvals │
└─────────────────────────────────────────────────────────────────┘
Slack App Configuration
To enable reaction-based approvals, your Slack app needs these additional event subscriptions:
-
Go to Event Subscriptions in your Slack app settings
-
Add to Subscribe to bot events:
reaction_added- Required for approvalreaction_removed- Optional
-
Add OAuth scope:
reactions:write- To add ✅ ❌ reactions
See Slack Bot Tutorial for complete configuration.
Example Session
User Request
@K8s Bot create an nginx deployment with 3 replicas
Bot Response (Approval Request)
I'll create an nginx deployment with 3 replicas.
⚠️ This action requires approval
`kubectl create deployment nginx --image=nginx --replicas=3`
React with ✅ to approve or ❌ to deny.
After User Approves (✅)
⚡ Executing approved command...
`kubectl create deployment nginx --image=nginx --replicas=3`
✅ Command completed successfully
```deployment.apps/nginx created```
Approved by: @user
Or If User Denies (❌)
❌ Action denied by @user
`kubectl create deployment nginx --image=nginx --replicas=3`
Role-Based Approval (RBAC)
AOF supports configuring which users are allowed to approve commands. The approval authorization system is designed to be platform-agnostic, allowing consistent RBAC policies across all platforms.
Configuration Levels
AOF supports two levels of approval configuration:
- Global Configuration (Platform-Agnostic) - Applies to all platforms
- Platform-Specific Configuration - Overrides global for a specific platform
Global Configuration (Planned)
Status: 🔄 Coming Soon - This feature is planned for a future release.
apiVersion: aof.dev/v1
kind: DaemonConfig
metadata:
name: multi-platform-bot
spec:
# Global approval settings - applies to ALL platforms
approval:
# Users who can approve commands (platform-agnostic IDs)
allowed_users:
- email:admin@company.com # Email-based (works across platforms)
- email:teamlead@company.com
- slack:U11111111 # Platform-specific ID with prefix
- discord:123456789
- teams:user@company.onmicrosoft.com
# Approval timeout in minutes (default: 30)
timeout_minutes: 30
# Require approval for these command patterns
require_for:
- "kubectl delete *"
- "kubectl scale * --replicas=0"
- "helm uninstall *"
platforms:
slack:
enabled: true
bot_token_env: SLACK_BOT_TOKEN
signing_secret_env: SLACK_SIGNING_SECRET
discord:
enabled: true
bot_token_env: DISCORD_BOT_TOKEN
teams:
enabled: true
app_id_env: TEAMS_APP_ID
app_secret_env: TEAMS_APP_SECRET
Platform-Specific Configuration (Current Implementation)
For Slack-only deployments or when you need platform-specific overrides:
platforms:
slack:
enabled: true
bot_token_env: SLACK_BOT_TOKEN
signing_secret_env: SLACK_SIGNING_SECRET
# Platform-specific: Overrides global approval.allowed_users for Slack
approval_allowed_users:
- U11111111 # Admin 1 (Slack user ID)
- U22222222 # Admin 2
- U33333333 # Team Lead
Important: After changing
approval_allowed_users, you must restart the server for changes to take effect. Hot-reload is planned for a future release (see GitHub Issue #22).
Finding Your Slack User ID
- In Slack, click on a user's profile
- Click the "..." (More) button
- Select "Copy member ID"
- The ID looks like
U015VBH1GTZ
User ID Resolution
The approval system resolves user identities across platforms:
| ID Format | Example | Status |
|---|---|---|
| Raw ID | U12345678 | ✅ Implemented (Slack) |
slack:U12345678 | Slack user ID | 🔄 Planned |
discord:123456789 | Discord user ID | 🔄 Planned |
teams:user@tenant.com | Teams UPN | 🔄 Planned |
telegram:123456789 | Telegram user ID | 🔄 Planned |
whatsapp:+1234567890 | WhatsApp phone number | 🔄 Planned |
email:user@company.com | Universal | 🔄 Planned (requires identity mapping) |
Behavior
- No whitelist configured: Anyone can approve (default)
- Global whitelist only: Applies to all platforms (planned)
- Platform-specific whitelist: Overrides global for that platform
- Unauthorized approval attempt: User sees "⚠️ @user is not authorized to approve commands"
Bot Self-Approval Prevention
The system automatically ignores reactions from the bot itself. When the bot adds ✅ and ❌ reactions to an approval message, these are filtered out and don't trigger approval.
Automatic Detection (v0.1.12+):
The bot's user ID is now automatically detected at startup using Slack's auth.test API. You no longer need to manually configure bot_user_id - it will be fetched from your bot token automatically.
You'll see this log message when the server starts:
INFO Auto-detected bot_user_id: U1234567890
This is implemented by:
- Calling Slack's
auth.testAPI at startup to get the bot's user ID - Storing the
bot_user_idin SlackConfig - Filtering
reaction_addedevents whereuser == bot_user_id
Manual Override (Optional):
If you need to override the auto-detected ID, you can still specify bot_user_id in your config:
platforms:
slack:
enabled: true
bot_user_id: U1234567890 # Optional: overrides auto-detection
Security Considerations
-
User Authorization: Configure
approval_allowed_usersin production to restrict who can approve destructive commands. -
Bot Self-Approval Prevention: The bot automatically ignores its own reactions to prevent self-approval.
-
Command Validation: Commands are executed via shell. Consider:
- Allowlist of approved command patterns
- Namespace restrictions for kubectl
- Command sanitization
-
Audit Trail: All approvals are logged with:
- User who requested
- User who approved/denied
- Command executed
- Execution result
-
Timeout: Pending approvals should timeout after a period (not yet implemented). Consider adding expiration handling.
Troubleshooting
Reactions not triggering approval
- Check Slack Event Subscriptions include
reaction_added - Verify bot has
reactions:writescope - Check server logs for reaction events
RUST_LOG=debug aofctl serve --config config.yaml
Command not parsed correctly
The command extraction regex expects format:
command: "your command here"
or
command: your command here
Pending approval not found
Approvals are keyed by message timestamp. If the bot restarts, pending approvals are lost. For production, consider persisting to Redis/database.
Implementation Status
What's Implemented Now
| Feature | Status | Notes |
|---|---|---|
| Slack approval (reactions) | ✅ Complete | ✅/❌ reactions for approve/deny |
| Bot self-approval prevention | ✅ Complete | Bot ignores its own reactions |
Platform-specific RBAC (approval_allowed_users) | ✅ Complete | Slack only for now |
| Conversation memory | ✅ Complete | Context maintained across messages |
Coming Soon
| Feature | Status | Platform |
|---|---|---|
Global spec.approval.allowed_users | 🔄 Planned | All platforms |
Config hot-reload (aofctl serve --reload) | 🔄 Issue #22 | All |
Platform-prefixed IDs (slack:U123, discord:123) | 🔄 Planned | All platforms |
| Discord approval (reactions) | 🔄 Planned | Discord |
| Teams approval (Adaptive Cards) | 🔄 Planned | Microsoft Teams |
| Telegram approval (inline buttons) | 🔄 Planned | Telegram |
| WhatsApp approval (button replies) | 🔄 Planned | |
| Email-based identity mapping | 🔄 Planned | All platforms |
Future Enhancements
- Config hot-reload without server restart (Issue #22)
- Approval timeout/expiration
- Multi-party approval (require 2+ approvals)
- Global platform-agnostic RBAC (spec.approval.allowed_users)
- Approval audit log persistence
- Approval undo/rollback
- Interactive button-based approval (in addition to reactions)