new·The score now tells you which way it movedA brain's exam only ever grows: its own material writes questions, and so does every question a real caller asked and did not get answered. The score is a percentage over that growing set, so a brain that learned more could post a smaller number — and this week three did. One of them answered two MORE questions than the week before and showed eighteen points less. Printed as a single percentage, that reads as decline to a reader and as punishment to anyone who contributes material.all news →
mozg.beta
Sign in

Temporal · Concepts · all subjects

approval/pattern

15 notes, read out of this brain and free to use. Each one was extracted from a source and is re-checked against its exam.

Approval pattern benefits

The Approval pattern captures rich context — approver identity, reasons, and timestamps — alongside each decision. All approval data is recorded in the Workflow history as Signal events, giving you a built-in audit trail. Timeout handling is automatic: you define the maximum wait time and the Workflow handles the fallback. The pattern supports multi-level, conditional, and escalating approval chains. You can check approval status at any time through Query methods without modifying Workflow state. Because all decisions are recorded in the event history, the Workflow is deterministic and replay-safe.

Approval pattern trade-offs

The Approval pattern requires an external system to send approval Signals, which means you need a separate approval interface. The Workflow blocks until the approval arrives or the timeout expires, so you must define a maximum wait time. Large approval data objects increase the size of the Workflow history.

Comparison of approval approaches

| Approach | Rich data | Built-in wait | Caller gets result | Complexity | Use case | | :--- | :--- | :--- | :--- | :--- | :--- | | Signal with data | Yes | Yes | No | Low | Approval Workflows | | Update | Yes | No | Yes | Low | Synchronous validation with immediate confirmation | | Boolean Signal | No | Yes | No | Low | Yes/no decisions | | Polling Activity | Yes | Yes | Yes | High | External approval systems | Signals are fire-and-forget: the caller receives an acknowledgement from the server but cannot wait for the Workflow to process the Signal or receive a result. Updates are synchronous: the caller blocks until the handler completes and can receive a return value or error. If the approver's interface needs immediate confirmation that the approval was accepted and valid, consider using an Update with a validator instead of a Signal.

Approval pattern best practices

Use custom data objects to capture rich approval context (approver identity, comments, timestamps) rather than a plain boolean. Set reasonable timeouts that balance responsiveness with the time approvers realistically need to respond. Add Query methods to expose current approval status so external systems can check progress without sending a Signal. Validate Signal data by verifying approver permissions and data completeness before accepting an approval. Log approval events for audit trails and compliance. Handle timeouts gracefully by defining clear timeout behavior such as rejection, escalation, or notification. Support cancellation to allow Workflows to be cancelled if the request is withdrawn. Ensure idempotency by handling duplicate approval Signals safely so that re-delivery does not corrupt state. Include timestamps to record when each approval was submitted to support time-based auditing. Expose approval history by providing a Query method that returns all approval attempts, not only the final decision.

Approval pattern common pitfalls

No timeout: Without a timeout, the Workflow waits indefinitely for an approval that may never arrive. Missing validation: Accepting approvals from unauthorized users compromises the integrity of the process. Lost context: Failing to capture the approver's identity or reason makes audit trails incomplete. Assuming non-deterministic races: Temporal processes events in a deterministic, single-threaded order, so a Signal and a timer cannot truly race. However, if the Signal arrives after the timer fires in the event history, the wait will have already returned with a timeout result. Design your timeout path to account for late-arriving Signals. No audit trail: Skipping approval logging makes it difficult to meet compliance requirements. Tight timeouts: Setting the timeout too short causes legitimate approvals to be rejected. Boolean-only Signals: Using a plain boolean instead of a rich data object limits your ability to capture decision context. No status Query: Without a Query method, external systems have no way to check approval progress. No duplicate handling: Receiving multiple approval Signals without deduplication can overwrite earlier decisions. No escalation path: Without a fallback when the initial approval times out, requests stall or are silently rejected.

Signals may be duplicated in rare cases

Signals may be duplicated in rare cases, so use idempotency keys when necessary to handle duplicate approval Signals safely so that re-delivery does not corrupt state.

Approval pattern overview and use case

The Approval pattern implements human-in-the-loop Workflows where execution blocks until an external decision is made. It uses Workflow Signals with custom input data to unblock Workflows, enabling approval processes, manual reviews, and decision gates in automated business processes. The pattern is suitable for purchase order approvals, expense report reviews, code deployment gates, contract signing Workflows, manual quality checks, compliance reviews, budget authorization, and access request approvals.

Approval pattern not suitable for

The Approval pattern is not a good fit for fully automated processes that require no human input, real-time decisions that need synchronous API responses, or processes that require sub-second response times. If you only need a boolean yes/no without any context, a plain boolean Signal may be sufficient.

Basic approval workflow structure with timeout

The basic approval pattern captures the approval decision in a structured data object rather than a plain boolean. The Workflow blocks until either the approval data arrives via Signal or the timeout expires. Each SDK uses a different mechanism: Java uses `Workflow.await()` taking a timeout and condition lambda returning false on timeout; TypeScript uses `condition()` taking a predicate and timeout returning false on timeout; Python uses `workflow.wait_condition()` taking a lambda and timeout raising asyncio.TimeoutError on timeout; Go uses `workflow.AwaitWithTimeout()` taking a timeout and condition function returning ok=false on timeout.

Condition evaluation constraints in approval workflows

The condition used in approval workflows is evaluated on every state transition, so it must not call blocking operations, mutate Workflow state, or use time-based checks. When the Signal handler sets the approval data, the condition evaluates to true and the Workflow unblocks.

ApprovalData structure

The ApprovalData type holds the following fields: approver (string), decision (string with values "APPROVED", "REJECTED", or "ESCALATED"), comments (string), and timestamp (integer or int64). This type provides a structured way to pass rich context through the Signal rather than a plain boolean.

Python approval workflow implementation

```python # models.py from dataclasses import dataclass @dataclass class ApprovalData: approver: str decision: str # "APPROVED", "REJECTED", "ESCALATED" comments: str timestamp: int # workflows.py import asyncio from datetime import timedelta from temporalio import workflow from models import ApprovalData @workflow.defn class ApprovalWorkflow: def __init__(self) -> None: self.approval_data: ApprovalData | None = None self.status = "PENDING" @workflow.run async def run(self, request_id: str, timeout_seconds: int) -> str: try: # Block until the Signal sets approval_data, or raise on timeout await workflow.wait_condition( lambda: self.approval_data is not None, timeout=timedelta(seconds=timeout_seconds), ) self.status = self.approval_data.decision return f"Request {request_id} {self.status} by {self.approval_data.approver}" except asyncio.TimeoutError: self.status = "TIMEOUT" return f"Request {request_id} timed out" # Signal handler: an external approver submits the decision @workflow.signal def submit_approval(self, data: ApprovalData) -> None: self.approval_data = data # Query handler: read current status without mutating state @workflow.query def get_status(self) -> str: return self.status ``` This shows a basic approval Workflow in Python that blocks with a timeout and processes approval data from a Signal.

Go approval workflow implementation

```go // types.go type ApprovalData struct { Approver string Decision string // "APPROVED", "REJECTED", "ESCALATED" Comments string Timestamp int64 } // workflow.go func ApprovalWorkflow(ctx workflow.Context, requestId string, timeout time.Duration) (string, error) { var approvalData *ApprovalData status := "PENDING" // Query handler: expose current status without mutating state err := workflow.SetQueryHandler(ctx, "getStatus", func() (string, error) { return status, nil }) if err != nil { return "", err } // Receive the approval signal in a goroutine workflow.Go(ctx, func(ctx workflow.Context) { signalChan := workflow.GetSignalChannel(ctx, "submitApproval") signalChan.Receive(ctx, &approvalData) }) // Block until the signal sets approvalData, or time out (ok=false) approved, err := workflow.AwaitWithTimeout(ctx, timeout, func() bool { return approvalData != nil }) if err != nil { return "", err } if approved { status = approvalData.Decision return fmt.Sprintf("Request %s %s by %s", requestId, status, approvalData.Approver), nil } status = "TIMEOUT" return fmt.Sprintf("Request %s timed out", requestId), nil } ``` This shows a basic approval Workflow in Go that uses AwaitWithTimeout to block until a Signal arrives or timeout expires.

Java approval workflow implementation

```java // ApprovalData.java public class ApprovalData { private String approver; private String decision; // "APPROVED", "REJECTED", "ESCALATED" private String comments; private long timestamp; // Constructor, getters, setters } // ApprovalWorkflowImpl.java public class ApprovalWorkflowImpl implements ApprovalWorkflow { private ApprovalData approvalData; private String status = "PENDING"; @Override public String execute(String requestId, Duration timeout) { // Block until the Signal sets approvalData, or time out (returns false) boolean approved = Workflow.await(timeout, () -> approvalData != null); if (approved) { status = approvalData.getDecision(); return "Request " + requestId + " " + status + " by " + approvalData.getApprover(); } else { status = "TIMEOUT"; return "Request " + requestId + " timed out"; } } // Signal handler: an external approver submits the decision @Override public void submitApproval(ApprovalData data) { this.approvalData = data; } // Query handler: read current status without mutating state @Override public String getStatus() { return status; } } ``` This shows a basic approval Workflow in Java using Workflow.await() to block until a Signal arrives or timeout expires.

TypeScript approval workflow implementation

```typescript // types.ts export interface ApprovalData { approver: string; decision: 'APPROVED' | 'REJECTED' | 'ESCALATED'; comments: string; timestamp: number; } // workflows.ts import * as wf from '@temporalio/workflow'; import { ApprovalData } from './types'; export const submitApprovalSignal = wf.defineSignal<[ApprovalData]>('submitApproval'); export const getStatusQuery = wf.defineQuery<string>('getStatus'); export async function approvalWorkflow( requestId: string, timeout: string | number, // ms or Duration string ): Promise<string> { let approvalData: ApprovalData | undefined; let status = 'PENDING'; // Signal handler: an external approver submits the decision wf.setHandler(submitApprovalSignal, (data: ApprovalData) => { approvalData = data; }); // Query handler: read current status without mutating state wf.setHandler(getStatusQuery, () => status); // Block until the Signal sets approvalData, or time out (returns false) const approved = await wf.condition(() => approvalData !== undefined, timeout); if (approved) { status = approvalData!.decision; return `Request ${requestId} ${status} by ${approvalData!.approver}`; } else { status = 'TIMEOUT'; return `Request ${requestId} timed out`; } } ``` This shows a basic approval Workflow in TypeScript using wf.condition() to block until a Signal arrives or timeout expires.

Give your agent this brain