From 9cc2378bf533ea951d85b2a7208e98ea71040d3f Mon Sep 17 00:00:00 2001 From: yaythomas Date: Tue, 14 Jul 2026 18:47:48 +0000 Subject: [PATCH] docs: slim AGENTS.md to defer to the developer guide The file duplicated API reference, testing, patterns, and IaC content that the AWS Durable Execution SDK Developer Guide now owns. Duplicated examples drift from the released API. The wait_for_callback examples showed a one-argument submitter, which raises TypeError against the current SDK, and imported WaitForCallbackConfig from the wrong module. Replace the duplicated content with links to the developer guide and keep only what is repo-specific: the replay-model rules in prose, the package layout, and the hatch-based contribution workflow. --- AGENTS.md | 998 ++++-------------------------------------------------- 1 file changed, 69 insertions(+), 929 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index e64b6b62..d63da7d6 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,929 +1,69 @@ -# AWS Lambda Durable Functions SDK - Agent Guide - -> Build resilient, long-running AWS Lambda functions with automatic state persistence, retry logic, and workflow orchestration. - -## Overview - -AWS Lambda durable functions extend Lambda's programming model to build multi-step applications and AI workflows with automatic state persistence. Applications can run for days or months, survive failures, and only incur charges for actual compute time. - -**Packages:** - -- **JavaScript/TypeScript**: `@aws/durable-execution-sdk-js` (testing: `@aws/durable-execution-sdk-js-testing`) -- **Python**: `aws-durable-execution-sdk-python` (testing: `aws-durable-execution-sdk-python-testing`) - -**Core Primitives:** - -- **Steps** - Execute business logic with automatic checkpointing and transparent retries -- **Waits** - Suspend execution without compute charges (for delays, human approvals, scheduled tasks) -- **Durable Invokes** - Reliable function chaining for modular, composable architectures - -## Critical Rules - -### ⚠️ The Replay Model - -Durable functions use a "replay" execution model. On replay (after wait/failure/resume), code runs from the beginning. Steps that already completed return their checkpointed results WITHOUT re-executing. Code OUTSIDE steps executes again on every replay. - -### Rule 1: Deterministic Code Outside Steps - -ALL code outside steps MUST be deterministic. - -**TypeScript:** - -```typescript -// ❌ WRONG: Non-deterministic code outside steps -const id = uuid.v4(); // Different on each replay! -const timestamp = Date.now(); // Different on each replay! - -// ✅ CORRECT: Non-deterministic code inside steps -const id = await context.step("generate-id", async () => uuid.v4()); -const timestamp = await context.step("get-time", async () => Date.now()); -``` - -**Python:** - -```python -# ❌ WRONG: Non-deterministic code outside steps -id = str(uuid.uuid4()) # Different on each replay! -timestamp = time.time() # Different on each replay! - -# ✅ CORRECT: Non-deterministic code inside steps -id = context.step(lambda _: str(uuid.uuid4()), name="generate-id") -timestamp = context.step(lambda _: time.time(), name="get-time") -``` - -**Must be in steps:** `Date.now()`, `new Date()`, `time.time()`, `Math.random()`, `random.random()`, UUID generation, API calls, database queries, file system operations. - -### Rule 2: No Nested Durable Operations - -You CANNOT call durable operations inside a step function. - -**TypeScript:** - -```typescript -// ❌ WRONG: Nested durable operations -await context.step("process", async () => { - await context.wait({ seconds: 1 }); // ERROR! - await context.step(async () => ...); // ERROR! -}); - -// ✅ CORRECT: Use runInChildContext for grouping -await context.runInChildContext("process", async (childCtx) => { - await childCtx.wait({ seconds: 1 }); - await childCtx.step(async () => ...); -}); -``` - -**Python:** - -```python -# ❌ WRONG: Nested durable operations -@durable_step -def process(step_ctx: StepContext): - context.wait(duration=Duration.from_seconds(1)) # ERROR! - -# ✅ CORRECT: Use run_in_child_context for grouping -def process(child_ctx: DurableContext): - child_ctx.wait(duration=Duration.from_seconds(1)) - child_ctx.step(some_step()) - -context.run_in_child_context(process, name="process") -``` - -### Rule 3: Closure Mutations Are Lost on Replay - -Variables mutated inside steps are NOT preserved across replays. - -**TypeScript:** - -```typescript -// ❌ WRONG: Counter mutations lost -let counter = 0; -await context.step(async () => { - counter++; -}); -console.log(counter); // 0 on replay! - -// ✅ CORRECT: Return values from steps -counter = await context.step(async () => counter + 1); -``` - -**Python:** - -```python -# ❌ WRONG: Counter mutations lost -counter = 0 -@durable_step -def increment(step_ctx: StepContext): - nonlocal counter - counter += 1 -context.step(increment()) -print(counter) # 0 on replay! - -# ✅ CORRECT: Return values from steps -counter = context.step(lambda _: counter + 1, name="increment") -``` - -### Rule 4: Side Effects Outside Steps Repeat - -Side effects (logging, API calls) outside steps happen on EVERY replay. - -**Exception:** `context.logger` is replay-aware and safe to use anywhere. - -**TypeScript:** - -```typescript -// ❌ WRONG -console.log("Starting"); // Logs multiple times! -await sendEmail(...); // Sends multiple emails! - -// ✅ CORRECT -context.logger.info("Starting"); // Deduplicated automatically -await context.step("email", async () => sendEmail(...)); -``` - -**Python:** - -```python -# ❌ WRONG -print("Starting") # Prints multiple times! -send_email(...) # Sends multiple emails! - -# ✅ CORRECT -context.logger.info("Starting") # Deduplicated automatically -context.step(lambda _: send_email(...), name="email") -``` - -## IAM Permissions - -Durable functions require the [`AWSLambdaBasicDurableExecutionRolePolicy`](https://docs.aws.amazon.com/aws-managed-policy/latest/reference/AWSLambdaBasicDurableExecutionRolePolicy.html) managed policy, which includes: - -- `logs:CreateLogGroup`, `logs:CreateLogStream`, `logs:PutLogEvents` - CloudWatch Logs -- `lambda:CheckpointDurableExecutions` - Persist execution state -- `lambda:GetDurableExecutionState` - Retrieve execution state - -For durable invokes (calling other durable functions), also add: - -- `lambda:InvokeFunction` on the target function ARN - -For callbacks from external systems: - -- External systems need `lambda:SendDurableExecutionCallbackSuccess` and `lambda:SendDurableExecutionCallbackFailure` - -## Invoking Durable Functions - -### Qualified ARNs Required - -Durable functions **require qualified identifiers** for invocation. You must use a version number, alias, or `$LATEST`. - -**✅ Valid invocations:** - -```bash -# Full ARN with version -arn:aws:lambda:us-east-1:123456789012:function:my-function:1 - -# Full ARN with alias -arn:aws:lambda:us-east-1:123456789012:function:my-function:prod - -# Full ARN with $LATEST -arn:aws:lambda:us-east-1:123456789012:function:my-function:$LATEST - -# Function name with version/alias -my-function:1 -my-function:prod -``` - -**❌ Invalid invocations:** - -```bash -# Unqualified ARN - NOT ALLOWED -arn:aws:lambda:us-east-1:123456789012:function:my-function - -# Unqualified function name - NOT ALLOWED -my-function -``` - -### Invocation Methods - -**Synchronous** - Wait for response (limited to 15 minutes): - -```bash -aws lambda invoke \ - --function-name my-durable-function:1 \ - --cli-binary-format raw-in-base64-out \ - --payload '{"orderId": "12345"}' \ - response.json -``` - -**Asynchronous** - Fire-and-forget (supports up to 1 year execution): - -```bash -aws lambda invoke \ - --function-name my-durable-function:1 \ - --invocation-type Event \ - --cli-binary-format raw-in-base64-out \ - --payload '{"orderId": "12345"}' \ - response.json -``` - -**Idempotent invocation** - Use `--durable-execution-name` to ensure the same execution is never created twice: - -```bash -aws lambda invoke \ - --function-name my-durable-function:1 \ - --invocation-type Event \ - --durable-execution-name "order-processing-12345" \ - --cli-binary-format raw-in-base64-out \ - --payload '{"orderId": "12345"}' \ - response.json -``` - -**Best Practice:** Use numbered versions or aliases for production. Use `$LATEST` only for development/prototyping. - -## SDK API Reference - -### Handler Wrapper - -**TypeScript:** - -```typescript -import { - withDurableExecution, - DurableContext, -} from "@aws/durable-execution-sdk-js"; - -export const handler = withDurableExecution( - async (event: any, context: DurableContext) => { - // Your durable workflow - return result; - }, -); -``` - -**Python:** - -```python -from aws_durable_execution_sdk_python import DurableContext, durable_execution - -@durable_execution -def handler(event: dict, context: DurableContext) -> dict: - # Your durable workflow - return result -``` - -### Steps - Atomic Operations - -**TypeScript:** - -```typescript -// Basic step -const result = await context.step(async () => fetchData()); - -// Named step (recommended) -const result = await context.step("fetch-user", async () => fetchData()); - -// With retry configuration -const result = await context.step("api-call", async () => callAPI(), { - retryStrategy: (error, attemptCount) => ({ - shouldRetry: attemptCount < 3, - delay: { seconds: Math.pow(2, attemptCount) }, - }), -}); -``` - -**Python:** - -```python -from aws_durable_execution_sdk_python import durable_step, StepContext -from aws_durable_execution_sdk_python.config import StepConfig -from aws_durable_execution_sdk_python.retries import RetryStrategyConfig, create_retry_strategy - -# Define step function with decorator -@durable_step -def fetch_user(step_ctx: StepContext, user_id: str) -> dict: - return {"id": user_id, "name": "Jane"} - -# Execute step (uses function name automatically) -result = context.step(fetch_user(user_id)) - -# Named step with lambda -result = context.step(lambda _: fetch_data(), name="fetch-user") - -# With retry configuration -retry_config = RetryStrategyConfig( - max_attempts=3, - initial_delay_seconds=1, - backoff_rate=2.0, -) -result = context.step( - fetch_user(user_id), - config=StepConfig(retry_strategy=create_retry_strategy(retry_config)) -) -``` - -### Wait - Pause Execution - -**TypeScript:** - -```typescript -await context.wait({ seconds: 30 }); -await context.wait({ hours: 1, minutes: 30 }); -await context.wait("rate-limit-delay", { days: 7 }); -``` - -**Python:** - -```python -from aws_durable_execution_sdk_python.config import Duration - -context.wait(duration=Duration.from_seconds(30)) -context.wait(duration=Duration.from_hours(1)) -context.wait(duration=Duration.from_days(7), name="rate-limit-delay") -``` - -### Invoke - Call Other Functions - -Invoke another durable Lambda function. **Must use qualified function name** (with version or alias). - -**TypeScript:** - -```typescript -const result = await context.invoke( - "process-payment", - process.env.PAYMENT_PROCESSOR_ARN!, // e.g., arn:...function:name:$LATEST - { amount: 100, currency: "USD" }, -); -``` - -**Python:** - -```python -import os - -result = context.invoke( - function_name=os.environ["PAYMENT_PROCESSOR_ARN"], - payload={"amount": 100, "currency": "USD"}, - name="process-payment" -) -``` - -### Child Context - Group Operations - -**TypeScript:** - -```typescript -const result = await context.runInChildContext( - "process-order", - async (childCtx) => { - const validated = await childCtx.step("validate", async () => - validate(data), - ); - await childCtx.wait({ seconds: 1 }); - const processed = await childCtx.step("process", async () => - process(validated), - ); - return processed; - }, -); -``` - -**Python:** - -```python -def process_order(child_ctx: DurableContext) -> dict: - validated = child_ctx.step(validate_step(data), name="validate") - child_ctx.wait(duration=Duration.from_seconds(1)) - processed = child_ctx.step(process_step(validated), name="process") - return processed - -result = context.run_in_child_context(process_order, name="process-order") -``` - -### Wait for Callback - External Integration - -**TypeScript:** - -```typescript -const result = await context.waitForCallback( - "wait-for-approval", - async (callbackId, ctx) => { - await sendApprovalEmail(callbackId); - }, - { timeout: { hours: 24 } }, -); -``` - -**Python:** - -```python -from aws_durable_execution_sdk_python.waits import WaitForCallbackConfig - -def submit_approval(callback_id: str): - send_approval_email(callback_id) - -result = context.wait_for_callback( - submitter=submit_approval, - config=WaitForCallbackConfig(timeout=Duration.from_hours(24)), - name="wait-for-approval" -) -``` - -### Wait for Condition - Polling - -**TypeScript:** - -```typescript -const finalState = await context.waitForCondition( - "wait-for-job", - async (currentState, ctx) => { - const status = await checkJobStatus(currentState.jobId); - return { ...currentState, status }; - }, - { - initialState: { jobId: "job-123", status: "pending" }, - waitStrategy: (state, attempt) => ({ - shouldContinue: state.status !== "completed", - delay: { seconds: Math.min(attempt * 2, 60) }, - }), - }, -); -``` - -**Python:** - -```python -from aws_durable_execution_sdk_python.waits import WaitForConditionConfig, ExponentialBackoff - -def check_job(state: dict, check_ctx) -> dict: - status = get_job_status(state["job_id"]) - return {"job_id": state["job_id"], "status": status} - -result = context.wait_for_condition( - check=check_job, - config=WaitForConditionConfig( - initial_state={"job_id": "job-123", "status": "pending"}, - condition=lambda state: state["status"] == "completed", - wait_strategy=ExponentialBackoff(initial_wait=Duration.from_seconds(2)) - ), - name="wait-for-job" -) -``` - -### Map - Process Arrays - -**TypeScript:** - -```typescript -const results = await context.map( - "process-items", - items, - async (ctx, item, index) => { - return await ctx.step(`process-${index}`, async () => process(item)); - }, - { - maxConcurrency: 5, - completionConfig: { - minSuccessful: 8, - toleratedFailureCount: 2, - }, - }, -); - -results.throwIfError(); -const allResults = results.getResults(); -``` - -**Python:** - -```python -from collections.abc import Sequence -from aws_durable_execution_sdk_python.concurrency import MapConfig, CompletionConfig - -def process_item(ctx: DurableContext, item: dict, index: int, items: Sequence[dict]) -> dict: - return ctx.step(lambda _: process(item), name=f"process-{index}") - -results = context.map( - items=items, - func=process_item, - config=MapConfig( - max_concurrency=5, - completion_config=CompletionConfig( - min_successful=8, - tolerated_failure_count=2 - ) - ), - name="process-items" -) - -results.throw_if_error() -all_results = results.get_results() -``` - -### Parallel - Parallel Branches - -**TypeScript:** - -```typescript -const results = await context.parallel( - "parallel-ops", - [ - { name: "task1", func: async (ctx) => ctx.step(async () => fetchData1()) }, - { name: "task2", func: async (ctx) => ctx.step(async () => fetchData2()) }, - ], - { maxConcurrency: 2 }, -); -``` - -**Python:** - -```python -from aws_durable_execution_sdk_python.concurrency import ParallelConfig - -def task1(ctx: DurableContext): - return ctx.step(lambda _: fetch_data1(), name="fetch1") - -def task2(ctx: DurableContext): - return ctx.step(lambda _: fetch_data2(), name="fetch2") - -results = context.parallel( - branches=[ - {"name": "task1", "func": task1}, - {"name": "task2", "func": task2}, - ], - config=ParallelConfig(max_concurrency=2), - name="parallel-ops" -) -``` - -## Testing Reference - -### TypeScript Setup - -```bash -npm install --save-dev @aws/durable-execution-sdk-js-testing -``` - -```typescript -import { - LocalDurableTestRunner, - OperationType, - OperationStatus, -} from "@aws/durable-execution-sdk-js-testing"; - -describe("My Durable Function", () => { - beforeAll(() => - LocalDurableTestRunner.setupTestEnvironment({ skipTime: true }), - ); - afterAll(() => LocalDurableTestRunner.teardownTestEnvironment()); - - it("should execute workflow", async () => { - const runner = new LocalDurableTestRunner({ handlerFunction: handler }); - const execution = await runner.run({ payload: { userId: "123" } }); - - expect(execution.getStatus()).toBe("SUCCEEDED"); - expect(execution.getResult()).toEqual({ success: true }); - - // Get operations BY NAME (not by index!) - const fetchStep = runner.getOperation("fetch-user"); - expect(fetchStep.getType()).toBe(OperationType.STEP); - expect(fetchStep.getStatus()).toBe(OperationStatus.SUCCEEDED); - }); -}); -``` - -### Python Setup - -```bash -pip install aws-durable-execution-sdk-python-testing -``` - -```python -import pytest -from aws_durable_execution_sdk_python_testing import InvocationStatus -from my_module import handler - -@pytest.mark.durable_execution( - handler=handler, - lambda_function_name="my_function", -) -def test_workflow(durable_runner): - """Test durable function workflow.""" - with durable_runner: - result = durable_runner.run(input={"user_id": "123"}, timeout=10) - - assert result.status is InvocationStatus.SUCCEEDED - assert result.result == {"success": True} - - # Get step by name - step_result = result.get_step("fetch-user") - assert step_result.status is InvocationStatus.SUCCEEDED -``` - -### Testing Key Points - -- ✅ Use `runner.getOperation("name")` (JS) or `result.get_step("name")` (Python) - not by index -- ✅ Name all operations for test reliability -- ✅ JSON.stringify callback parameters (JS) / ensure JSON-serializable (Python) -- ✅ Parse callback results (they're JSON strings) -- ✅ Wrap event data in `payload: {}` (JS) or `input={}` (Python) - -## Common Patterns - -### Multi-Step Workflow - -**TypeScript:** - -```typescript -export const handler = withDurableExecution(async (event, context) => { - const validated = await context.step("validate", async () => - validateInput(event), - ); - const processed = await context.step("process", async () => - processData(validated), - ); - await context.wait("cooldown", { seconds: 30 }); - await context.step("notify", async () => sendNotification(processed)); - return { success: true, data: processed }; -}); -``` - -**Python:** - -```python -@durable_execution -def handler(event: dict, context: DurableContext) -> dict: - validated = context.step(validate_input(event), name="validate") - processed = context.step(process_data(validated), name="process") - context.wait(duration=Duration.from_seconds(30), name="cooldown") - context.step(send_notification(processed), name="notify") - return {"success": True, "data": processed} -``` - -### GenAI Agent (Agentic Loop) - -**TypeScript:** - -```typescript -export const handler = withDurableExecution(async (event, context) => { - const messages = [{ role: "user", content: event.prompt }]; - - while (true) { - const { response, tool } = await context.step("invoke-model", async () => - invokeAIModel(messages), - ); - - if (tool == null) return response; - - const toolResult = await context.step(`tool-${tool.name}`, async () => - executeTool(tool, response), - ); - messages.push({ role: "assistant", content: toolResult }); - } -}); -``` - -**Python:** - -```python -@durable_execution -def handler(event: dict, context: DurableContext) -> str: - messages = [{"role": "user", "content": event["prompt"]}] - - while True: - result = context.step( - lambda _: invoke_ai_model(messages), - name="invoke-model" - ) - - if result.get("tool") is None: - return result["response"] - - tool = result["tool"] - tool_result = context.step( - lambda _: execute_tool(tool, result["response"]), - name=f"tool-{tool['name']}" - ) - messages.append({"role": "assistant", "content": tool_result}) -``` - -### Human-in-the-Loop Approval - -**TypeScript:** - -```typescript -export const handler = withDurableExecution(async (event, context) => { - const plan = await context.step("generate-plan", async () => - generatePlan(event), - ); - - const answer = await context.waitForCallback( - "wait-for-approval", - async (callbackId) => - sendApprovalEmail(event.approverEmail, plan, callbackId), - { timeout: { hours: 24 } }, - ); - - if (answer === "APPROVED") { - await context.step("execute", async () => performAction(plan)); - return { status: "completed" }; - } - return { status: "rejected" }; -}); -``` - -**Python:** - -```python -@durable_execution -def handler(event: dict, context: DurableContext) -> dict: - plan = context.step(generate_plan(event), name="generate-plan") - - def submit_approval(callback_id: str): - send_approval_email(event["approver_email"], plan, callback_id) - - answer = context.wait_for_callback( - submitter=submit_approval, - config=WaitForCallbackConfig(timeout=Duration.from_hours(24)), - name="wait-for-approval" - ) - - if answer == "APPROVED": - context.step(perform_action(plan), name="execute") - return {"status": "completed"} - return {"status": "rejected"} -``` - -### Saga Pattern (Compensating Transactions) - -**TypeScript:** - -```typescript -export const handler = withDurableExecution(async (event, context) => { - const compensations: Array<{ name: string; fn: () => Promise }> = []; - - try { - await context.step("book-flight", async () => flightClient.book(event)); - compensations.push({ - name: "cancel-flight", - fn: () => flightClient.cancel(event), - }); - - await context.step("book-hotel", async () => hotelClient.book(event)); - compensations.push({ - name: "cancel-hotel", - fn: () => hotelClient.cancel(event), - }); - - return { success: true }; - } catch (error) { - for (const comp of compensations.reverse()) { - await context.step(comp.name, async () => comp.fn()); - } - throw error; - } -}); -``` - -**Python:** - -```python -@durable_execution -def handler(event: dict, context: DurableContext) -> dict: - compensations = [] - - try: - context.step(book_flight(event), name="book-flight") - compensations.append(("cancel-flight", lambda: cancel_flight(event))) - - context.step(book_hotel(event), name="book-hotel") - compensations.append(("cancel-hotel", lambda: cancel_hotel(event))) - - return {"success": True} - except Exception as error: - for name, comp_fn in reversed(compensations): - context.step(lambda _: comp_fn(), name=name) - raise error -``` - -## Infrastructure as Code - -Deploy durable functions using CloudFormation, CDK, or SAM. All require: - -1. Enable durable execution on the function -2. Grant checkpoint permissions to the execution role -3. Publish a version or create an alias (qualified ARNs required) - -### AWS CloudFormation - -```yaml -AWSTemplateFormatVersion: "2010-09-09" -Resources: - DurableFunctionRole: - Type: AWS::IAM::Role - Properties: - AssumeRolePolicyDocument: - Version: "2012-10-17" - Statement: - - Effect: Allow - Principal: - Service: lambda.amazonaws.com - Action: sts:AssumeRole - ManagedPolicyArns: - - arn:aws:iam::aws:policy/service-role/AWSLambdaBasicDurableExecutionRolePolicy - - DurableFunction: - Type: AWS::Lambda::Function - Properties: - FunctionName: myDurableFunction - Runtime: nodejs22.x # or python3.14 - Handler: index.handler - Role: !GetAtt DurableFunctionRole.Arn - Code: - ZipFile: | - // Your durable function code - DurableConfig: - ExecutionTimeout: 3600 - RetentionPeriodInDays: 7 - - DurableFunctionVersion: - Type: AWS::Lambda::Version - Properties: - FunctionName: !Ref DurableFunction - - DurableFunctionAlias: - Type: AWS::Lambda::Alias - Properties: - FunctionName: !Ref DurableFunction - FunctionVersion: !GetAtt DurableFunctionVersion.Version - Name: prod -``` - -### AWS CDK (TypeScript) - -```typescript -import * as cdk from "aws-cdk-lib"; -import * as lambda from "aws-cdk-lib/aws-lambda"; -import * as iam from "aws-cdk-lib/aws-iam"; - -const durableFunction = new lambda.Function(this, "DurableFunction", { - runtime: lambda.Runtime.NODEJS_22_X, // or PYTHON_3_12 - handler: "index.handler", - code: lambda.Code.fromAsset("lambda"), - durableConfig: { - executionTimeout: cdk.Duration.hours(1), - retentionPeriod: cdk.Duration.days(7), - }, -}); - -// CDK automatically adds checkpoint permissions when durableConfig is set - -// Create version and alias -const version = durableFunction.currentVersion; -const alias = new lambda.Alias(this, "ProdAlias", { - aliasName: "prod", - version: version, -}); -``` - -### AWS SAM - -```yaml -AWSTemplateFormatVersion: "2010-09-09" -Transform: AWS::Serverless-2016-10-31 - -Resources: - DurableFunction: - Type: AWS::Serverless::Function - Properties: - FunctionName: myDurableFunction - Runtime: nodejs22.x # or python3.14 - Handler: index.handler - CodeUri: ./src - DurableConfig: - ExecutionTimeout: 3600 - RetentionPeriodInDays: 7 - Policies: - - arn:aws:iam::aws:policy/service-role/AWSLambdaBasicDurableExecutionRolePolicy - AutoPublishAlias: prod -``` - -## Related Documentation - -**AWS Documentation:** - -- [AWS Lambda Durable Functions Guide](https://docs.aws.amazon.com/lambda/latest/dg/durable-functions.html) -- [Lambda API Reference](https://docs.aws.amazon.com/lambda/latest/api/) -- [Invoking Durable Functions](https://docs.aws.amazon.com/lambda/latest/dg/durable-invoking.html) -- [Deploy with IaC](https://docs.aws.amazon.com/lambda/latest/dg/durable-getting-started-iac.html) -- [AWSLambdaBasicDurableExecutionRolePolicy](https://docs.aws.amazon.com/aws-managed-policy/latest/reference/AWSLambdaBasicDurableExecutionRolePolicy.html) - -**JavaScript/TypeScript SDK:** - -- [SDK Repository](https://github.com/aws/aws-durable-execution-sdk-js) -- [SDK README](https://github.com/aws/aws-durable-execution-sdk-js/blob/main/packages/aws-durable-execution-sdk-js/README.md) -- [Concepts & Use Cases](https://github.com/aws/aws-durable-execution-sdk-js/blob/main/packages/aws-durable-execution-sdk-js/src/documents/CONCEPTS.md) -- [Testing SDK](https://github.com/aws/aws-durable-execution-sdk-js/blob/main/packages/aws-durable-execution-sdk-js-testing/README.md) - -**Python SDK:** - -- [SDK Repository](https://github.com/aws/aws-durable-execution-sdk-python) -- [AWS Durable Execution Documentation](https://docs.aws.amazon.com/durable-execution/) +# AWS Durable Execution SDK for Python - Agent Guide + +This repository contains the AWS Durable Execution SDK for Python and its +companion packages, used to author AWS Lambda durable functions. + +## Use the developer guide, not this file, for APIs + +Do not rely on this file for method signatures, configuration objects, or +code examples. The canonical reference is the +[AWS Durable Execution SDK Developer Guide](https://docs.aws.amazon.com/durable-execution/), +which is maintained alongside SDK releases and covers TypeScript, Python, +and Java. Key sections: + +| Topic | Link | +| --- | --- | +| Key concepts and quickstart | | +| Steps | | +| Waits | | +| Wait for condition (polling) | | +| Callbacks and wait for callback | | +| Invoke (function chaining) | | +| Parallel | | +| Map | | +| Child contexts | | +| Errors and retries | | +| Serialization | | +| Logging and plugins | | +| Python language guide | | +| Testing (local runner, assertions) | | +| Best practices (determinism, idempotency, state) | | + +For Lambda service topics such as deployment, infrastructure as code, +invocation, IAM permissions, and quotas, see the +[Lambda durable functions guide](https://docs.aws.amazon.com/lambda/latest/dg/durable-functions.html). + +## Critical rules: the replay model + +Durable functions use checkpoint and replay. After a wait, failure, or +resume, code re-runs from the beginning. Completed steps return their +checkpointed results without re-executing, and code outside steps runs +again on every replay. This implies four rules: + +1. **Code outside steps must be deterministic.** Wrap timestamps, random + values, UUID generation, API calls, and any other non-deterministic + work in a step. +2. **Never call durable operations inside a step.** Use a child context + to group operations. +3. **Closure mutations inside steps are lost on replay.** Return values + from steps instead of mutating enclosing scope. +4. **Side effects outside steps repeat on every replay.** Put side effects + in steps. `context.logger` is the exception: it is replay-aware and + safe anywhere. + +See [Determinism and Replay](https://docs.aws.amazon.com/durable-execution/patterns/best-practices/determinism/) +for worked examples. + +## Working in this repository + +- Packages live under `packages/`: the core SDK + (`aws-durable-execution-sdk-python`), the testing library + (`aws-durable-execution-sdk-python-testing`), the OpenTelemetry plugin + (`aws-durable-execution-sdk-python-otel`), and examples + (`aws-durable-execution-sdk-python-examples`). +- Read [CONTRIBUTING.md](CONTRIBUTING.md) before making changes. Use + `hatch` for all tests, type checks, and formatting (for example + `hatch run dev-core:test`, `hatch run dev-core:typecheck`, and + `hatch fmt --check` from a package directory). +- When the developer guide and the installed SDK source disagree, trust + the source in this repository and report the discrepancy.