Skip to main content

Why Batch Management Matters

Critical System Requirement: All tool approvals within the same toolExecutionBatchId must be responded to simultaneously in a single API call. Individual responses will cause the batch to complete prematurely, potentially leaving some tools unprocessed. This is a fundamental constraint of the approval system that directly impacts both backend processing and user experience design.

Understanding Tool Execution Batches

Batch Identification

Every tool requiring approval includes a toolExecutionBatchId field that groups related tool executions:
{
  "toolExecutionApprovalRequest": [
    {
      "toolExecutionId": "exec_123",
      "toolExecutionBatchId": "batch_456",
      "toolName": "send_email",
      // ... other fields
    },
    {
      "toolExecutionId": "exec_124", 
      "toolExecutionBatchId": "batch_456",
      "toolName": "create_calendar_event",
      // ... other fields
    }
  ]
}
Both tools share batch_456, meaning they must be approved or denied together.

Why Batches Exist

Batches ensure atomic operations and maintain workflow integrity:
  • Atomic Operations: Related tools succeed or fail together
  • Workflow Consistency: Prevents partial execution of multi-step processes
  • User Context: Groups logically related actions for easier decision-making

Batch Response Requirements

Single Response Rule

The backend expects exactly one response containing decisions for all tools in a batch:
// ✅ CORRECT: Single response with all approvals
const batchResponse = {
  content: [
    {
      type: 'tool_approval_result',
      tool_approval_results: [
        {
          toolExecutionId: 'exec_123',
          toolExecutionBatchId: 'batch_456',
          approvalResult: 'APPROVED'
        },
        {
          toolExecutionId: 'exec_124',
          toolExecutionBatchId: 'batch_456', 
          approvalResult: 'DENIED'
        }
      ]
    }
  ]
};

// ❌ INCORRECT: Separate responses
// This will cause premature batch completion
await approveIndividualTool('exec_123', 'APPROVED');
await approveIndividualTool('exec_124', 'DENIED');

Incomplete Batch Consequences

When a batch receives partial responses:
  1. Premature Completion: The agent may respond before all tools are processed
  2. Undefined State: Unresponded tools may be left in pending state
  3. Workflow Failure: Multi-step processes may fail due to missing tool results

UX Implications

Batch Processing Patterns

Recommended: Collect all decisions before submitting
// Collect user decisions for entire batch
const batchDecisions = new Map();
const batchId = 'batch_456';

// User makes decisions through UI
batchDecisions.set('exec_123', 'APPROVED');
batchDecisions.set('exec_124', 'DENIED');

// Submit all decisions at once when batch is complete
if (batchDecisions.size === totalToolsInBatch) {
  submitBatchResponse(batchId, batchDecisions);
}
Avoid: Immediate individual processing
// ❌ This pattern causes batch completion issues
function onApproveButtonClick(toolExecutionId) {
  // Don't immediately submit individual approvals
  submitApproval(toolExecutionId, 'APPROVED'); // WRONG
}

UI Design Patterns

Batch-Aware Interface:
  • Group tools by toolExecutionBatchId in the UI
  • Show batch completion progress (e.g., “2 of 3 tools decided”)
  • Disable submission until all tools in batch are decided
  • Provide batch-level actions (“Approve All”, “Deny All”)
Example Batch UI State:
interface BatchState {
  batchId: string;
  tools: ToolApprovalRequest[];
  decisions: Map<string, ApprovalDecision>;
  isComplete: boolean;
  canSubmit: boolean;
}

const batchState = {
  batchId: 'batch_456',
  tools: [tool1, tool2, tool3],
  decisions: new Map([
    ['exec_123', 'APPROVED'],
    ['exec_124', 'PENDING'], // Still waiting
    ['exec_125', 'DENIED']
  ]),
  isComplete: false,
  canSubmit: false
};

Implementation Strategies

Batch Collection Pattern

class ApprovalBatchManager {
  private batches = new Map<string, BatchState>();
  
  addApprovalRequest(request: ToolApprovalRequest) {
    const batchId = request.toolExecutionBatchId;
    
    if (!this.batches.has(batchId)) {
      this.batches.set(batchId, {
        batchId,
        tools: [],
        decisions: new Map(),
        isComplete: false
      });
    }
    
    const batch = this.batches.get(batchId)!;
    batch.tools.push(request);
  }
  
  setDecision(toolExecutionId: string, decision: ApprovalDecision) {
    for (const batch of this.batches.values()) {
      if (batch.tools.find(t => t.toolExecutionId === toolExecutionId)) {
        batch.decisions.set(toolExecutionId, decision);
        
        // Check if batch is complete
        if (batch.decisions.size === batch.tools.length) {
          batch.isComplete = true;
          this.submitBatch(batch);
        }
        break;
      }
    }
  }
  
  private submitBatch(batch: BatchState) {
    const approvalResults = Array.from(batch.decisions.entries()).map(
      ([toolExecutionId, decision]) => ({
        toolExecutionId,
        toolExecutionBatchId: batch.batchId,
        approvalResult: decision
      })
    );
    
    // Submit all approvals in single API call
    submitApprovalResponse(approvalResults);
  }
}

Common Pitfalls

  1. Immediate Processing: Don’t submit approvals as soon as user clicks buttons
  2. Ignoring Batch ID: Always group tools by toolExecutionBatchId
  3. Partial Submissions: Never submit incomplete batches
  4. State Management: Don’t lose track of pending decisions across UI updates

Next Steps

I