Skip to content

Commit 64df74c

Browse files
bfreibergyaythomas
authored andcommitted
feat(power): backport fixes from skill review
1 parent d7363fe commit 64df74c

11 files changed

Lines changed: 138 additions & 124 deletions

aws-lambda-durable-functions-power/POWER.md

Lines changed: 9 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -15,63 +15,18 @@ author: "AWS"
1515

1616
Build resilient multi-step applications and AI workflows that can execute for up to 1 year while maintaining reliable progress despite interruptions.
1717

18-
## Onboarding
18+
**Works best with** the [AWS MCP server](https://docs.aws.amazon.com/aws-mcp/) but is not required. All AWS interactions in this skill use standard AWS CLI commands that work in any environment with configured AWS credentials.
1919

20-
### Step 1: Validate Prerequisites
20+
## Critical Rules
2121

22-
Before using AWS Lambda durable functions, verify:
22+
Read these before writing any code. Each one is a constraint that will silently break a function if violated.
2323

24-
1. **AWS CLI** is installed (2.33.22 or higher) and configured:
25-
26-
```bash
27-
aws --version
28-
aws sts get-caller-identity
29-
```
30-
31-
2. **Runtime environment** is ready:
32-
- For TypeScript/JavaScript: Node.js 22+ (`node --version`)
33-
- For Python: Python 3.11+ (`python --version`. Note that currently only Lambda runtime environments 3.13+ come with the Durable Execution SDK pre-installed. 3.11 is the min supported Python version by the Durable SDK itself, however, you could use OCI to bring your own container image with your own Python runtime + Durable SDK.)
34-
35-
3. **Deployment capability** exists (one of):
36-
- AWS SAM CLI (`sam --version`) 1.153.1 or higher
37-
- AWS CDK (`cdk --version`) v2.237.1 or higher
38-
- Direct Lambda deployment access
39-
40-
## Step 2: Check user and project preferences
41-
42-
Ask which IaC framework to use for new projects.
43-
Ask which programming language to use if unclear, clarify between JavaScript and TypeScript if necessary.
44-
Ask to create a git repo for projects if one doesn't exist already.
45-
46-
### Error Scenarios
47-
48-
#### Unsupported Language
49-
50-
- List detected language
51-
- State: "Durable Execution SDK is not yet available for [framework]"
52-
- Suggest supported languages as alternatives
53-
54-
#### Unsupported IaC Framework
55-
56-
- List detected framework
57-
- State: "[framework] might not support Lambda durable functions yet"
58-
- Suggest supported frameworks as alternatives
59-
60-
### Step 3: Install SDK
61-
62-
**For TypeScript/JavaScript:**
63-
64-
```bash
65-
npm install @aws/durable-execution-sdk-js
66-
npm install --save-dev @aws/durable-execution-sdk-js-testing
67-
```
68-
69-
**For Python:**
70-
71-
```bash
72-
pip install aws-durable-execution-sdk-python
73-
pip install aws-durable-execution-sdk-python-testing
74-
```
24+
1. **Durable execution must be enabled at function creation time — it cannot be retrofitted.** A new Lambda function must be created with durable execution turned on. Migrate the logic into the new function; do not attempt to install the SDK and wrap the handler of the existing function and expect it to work.
25+
2. **Durable functions must be invoked with a qualified ARN** — a specific version, an alias, or the literal `$LATEST` suffix. An unqualified function name will fail. See the *Invocation Requirements* section below for examples.
26+
3. **Durable operations cannot be nested.** You cannot call `context.step()`, `context.wait()`, or `context.invoke()` from inside another step's callback. Use `context.runInChildContext()` to group operations instead.
27+
4. **All non-deterministic code must run inside steps.** `Date.now()`, `Math.random()`, UUID generation, API calls, and database queries outside a step will produce different values on replay and corrupt execution state.
28+
5. **Closure mutations are lost on replay** - return values from steps
29+
6. **Side effects outside steps repeat** - use `context.logger` (replay-aware)
7530

7631
## When to Load Reference Files
7732

@@ -115,13 +70,6 @@ def handler(event: dict, context: DurableContext) -> dict:
11570
return result
11671
```
11772

118-
### Critical Rules
119-
120-
1. **All non-deterministic code MUST be in steps** (Date.now, Math.random, API calls)
121-
2. **Cannot nest durable operations** - use `runInChildContext` to group operations
122-
3. **Closure mutations are lost on replay** - return values from steps
123-
4. **Side effects outside steps repeat** - use `context.logger` (replay-aware)
124-
12573
### Python API Differences
12674

12775
The Python SDK differs from TypeScript in several key areas:

aws-lambda-durable-functions-power/steering/advanced-error-handling.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ Advanced error handling patterns for durable functions, including timeout handli
2929
4. Execute fallback operation in a separate step
3030

3131
**Important limitation:**
32-
In TypeScript, native setTimeout (and patterns like Promise.race using it) will fail during execution replays. To create a reliable timeout that persists across execution (expands over multi invocations), always use the timeout parameter provided by waitForCallback or waitForCondition
32+
In TypeScript, native setTimeout (and patterns like Promise.race using it) will fail during execution replays. To create a reliable timeout that persists across execution (expands over multi invocations), always use the timeout parameter provided by waitForCallback
3333

3434
## Conditional Retry Based on Error Type
3535

aws-lambda-durable-functions-power/steering/advanced-patterns.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -227,7 +227,7 @@ const results = await context.map(
227227

228228
// Only one item processed (assuming first succeeds)
229229
if (results.successCount > 0) {
230-
const match = results.getSucceeded()[0];
230+
const match = results.succeeded()[0];
231231
context.logger.info('Found match', { match });
232232
}
233233
```

aws-lambda-durable-functions-power/steering/concurrent-operations.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -196,7 +196,7 @@ results = context.map(
196196
```typescript
197197
const results = await context.map('process', items, processFunc);
198198

199-
console.log(results.status); // 'COMPLETED' | 'FAILED'
199+
console.log(results.status); // 'SUCCEEDED' | 'FAILED'
200200
console.log(results.totalCount); // Total items
201201
console.log(results.startedCount); // Items started
202202
console.log(results.successCount); // Successful items

aws-lambda-durable-functions-power/steering/deployment-iac.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -302,7 +302,7 @@ DurableFunction:
302302
RetentionPeriodInDays: 1 # Short retention
303303
Environment:
304304
Variables:
305-
LOG_LEVEL: DEBUG
305+
LOG_LEVEL: DEBUG # Use INFO or higher in non-dev — DEBUG may expose step results and execution state
306306
ENVIRONMENT: development
307307
```
308308

aws-lambda-durable-functions-power/steering/error-handling.md

Lines changed: 19 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -89,15 +89,15 @@ const result = await context.step(
8989
```python
9090
def custom_retry(error: Exception, attempt: int) -> RetryDecision:
9191
if hasattr(error, 'status_code') and 400 <= error.status_code < 500:
92-
return RetryDecision(should_retry=False)
92+
return RetryDecision.no_retry()
9393

9494
if attempt < 5:
9595
return RetryDecision(
9696
should_retry=True,
9797
delay=Duration.from_seconds(2 ** attempt)
9898
)
9999

100-
return RetryDecision(should_retry=False)
100+
return RetryDecision.no_retry()
101101
```
102102

103103
## Error Classification
@@ -221,43 +221,38 @@ def handler(event: dict, context: DurableContext) -> dict:
221221
return {'success': True, 'order_id': shipment['order_id']}
222222

223223
except Exception as error:
224-
context.logger.error('Order failed, executing compensations', error)
224+
context.logger.error(f'Order failed, executing compensations: {error}')
225225

226226
for name, comp_step, resource_id in reversed(compensations):
227227
try:
228228
context.step(comp_step(resource_id))
229229
except Exception as comp_error:
230-
context.logger.error(f'Compensation {name} failed', comp_error)
230+
context.logger.error(f'Compensation {name} failed: {comp_error}')
231231

232232
raise error
233233
```
234234

235235
## Unrecoverable Errors
236236

237-
Configure non-retryable failures to stop execution immediately:
237+
Mark errors as unrecoverable to stop execution immediately:
238238

239239
**TypeScript:**
240240

241-
The TypeScript SDK does not currently expose a public unrecoverable error type.
242-
Use a no-retry strategy when a step should fail immediately.
243-
244241
```typescript
245-
import { retryPresets } from '@aws/durable-execution-sdk-js';
246-
247242
export const handler = withDurableExecution(async (event, context: DurableContext) => {
248-
const user = await context.step('fetch-user', async () => {
249-
const user = await fetchUser(event.userId);
250-
251-
if (!user) {
252-
// This error fails the step immediately because retryPresets.noRetry
253-
// disables retries for this step.
254-
throw new Error('User not found');
255-
}
256-
257-
return user;
258-
}, {
259-
retryStrategy: retryPresets.noRetry,
260-
});
243+
const user = await context.step(
244+
'fetch-user',
245+
async () => {
246+
const user = await fetchUser(event.userId);
247+
248+
if (!user) {
249+
throw new Error('User not found');
250+
}
251+
252+
return user;
253+
},
254+
{ retryStrategy: () => ({ shouldRetry: false }) }
255+
);
261256

262257
// Continue processing...
263258
});
@@ -428,7 +423,7 @@ export const handler = withDurableExecution(async (event, context: DurableContex
428423
2. **Classify errors correctly** - distinguish retryable from non-retryable
429424
3. **Implement compensating transactions** for distributed workflows
430425
4. **Make errors deterministic** - same input produces same error
431-
5. **Disable retries for non-retryable errors** to stop execution early when appropriate
426+
5. **Use unrecoverable errors** to stop execution early when appropriate
432427
6. **Log errors with context** using `context.logger`
433428
7. **Handle partial failures** gracefully in batch operations
434429
8. **Implement circuit breakers** for external service calls

aws-lambda-durable-functions-power/steering/getting-started.md

Lines changed: 74 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,81 @@
22

33
Quick start guide for building your first durable function.
44

5-
## Check user and project preferences
5+
## Onboarding
66

7-
Ask which IaC framework to use for new projects.
8-
Ask which programming language to use if unclear, clarify between JavaScript and TypeScript if necessary.
9-
Ask to create a git repo for projects if one doesn't exist already.
7+
### Step 1: Validate Prerequisites
8+
9+
Before using AWS Lambda durable functions, verify:
10+
11+
1. **AWS CLI** is installed (2.33.22 or higher) and configured:
12+
13+
```bash
14+
aws --version
15+
aws sts get-caller-identity
16+
```
17+
18+
2. **Runtime environment** is ready:
19+
- For TypeScript/JavaScript: Node.js 22+ (`node --version`)
20+
- For Python: Python 3.11+ (`python --version`. Note that only Lambda runtime environments 3.13+ come with the Durable Execution SDK pre-installed. 3.11 is the minimum supported Python version by the Durable Execution SDK itself — use OCI to bring your own container image with an older Python runtime + Durable Execution SDK.)
21+
22+
3. **Deployment capability** exists (one of):
23+
- AWS SAM CLI (`sam --version`) 1.153.1 or higher
24+
- AWS CDK (`cdk --version`) v2.237.1 or higher
25+
- Direct Lambda deployment access
26+
27+
### Step 2: Select language and IaC framework
28+
29+
### Language Selection
30+
31+
Default: TypeScript
32+
33+
Override syntax:
34+
35+
- "use Python" → Generate Python code
36+
- "use JavaScript" → Generate JavaScript code
37+
38+
When not specified, ALWAYS use TypeScript
39+
40+
### IaC framework selection
41+
42+
Default: CDK
43+
44+
Override syntax:
45+
46+
- "use CloudFormation" → Generate YAML templates
47+
- "use SAM" → Generate YAML templates
48+
49+
When not specified, ALWAYS use CDK
50+
51+
### Error Scenarios
52+
53+
#### Unsupported Language
54+
55+
- List detected language
56+
- State: "Durable Execution SDK is not yet available for [framework]"
57+
- Suggest supported languages as alternatives
58+
59+
#### Unsupported IaC Framework
60+
61+
- List detected framework
62+
- State: "[framework] might not support Lambda durable functions yet"
63+
- Suggest supported frameworks as alternatives
64+
65+
### Step 3: Install SDK
66+
67+
**For TypeScript/JavaScript:**
68+
69+
```bash
70+
npm install @aws/durable-execution-sdk-js
71+
npm install --save-dev @aws/durable-execution-sdk-js-testing
72+
```
73+
74+
**For Python:**
75+
76+
```bash
77+
pip install aws-durable-execution-sdk-python
78+
pip install aws-durable-execution-sdk-python-testing
79+
```
1080

1181
## Basic Handler
1282

@@ -244,7 +314,6 @@ my-durable-function/
244314
│ └── retry_strategies.py
245315
├── tests/
246316
│ └── test_handler.py # Tests with DurableFunctionTestRunner
247-
│ └── test_handler.py # Tests with DurableFunctionTestRunner
248317
├── infrastructure/
249318
│ └── template.yaml # SAM/CloudFormation
250319
└── pyproject.toml # Project configuration

aws-lambda-durable-functions-power/steering/step-operations.md

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -134,15 +134,15 @@ from aws_durable_execution_sdk_python.retries import RetryDecision
134134

135135
def custom_retry(error: Exception, attempt: int) -> RetryDecision:
136136
if isinstance(error, ValidationError):
137-
return RetryDecision(should_retry=False)
137+
return RetryDecision.no_retry()
138138

139139
if attempt < 3:
140140
return RetryDecision(
141141
should_retry=True,
142142
delay=Duration.from_seconds(2 ** attempt)
143143
)
144144

145-
return RetryDecision(should_retry=False)
145+
return RetryDecision.no_retry()
146146

147147
result = context.step(
148148
risky_operation(),
@@ -214,18 +214,24 @@ import { StepSemantics } from '@aws/durable-execution-sdk-js';
214214
const result = await context.step(
215215
'charge-payment',
216216
async () => chargeCard(amount),
217-
{ semantics: StepSemantics.AtMostOncePerRetry }
217+
{
218+
semantics: StepSemantics.AtMostOncePerRetry,
219+
retryStrategy: () => ({ shouldRetry: false })
220+
}
218221
);
219222
```
220223

221224
**Python:**
222225

223226
```python
224-
from aws_durable_execution_sdk_python.config import StepSemantics
227+
from aws_durable_execution_sdk_python.config import StepSemantics, StepConfig
225228

226229
result = context.step(
227230
charge_card(amount),
228-
config=StepConfig(step_semantics=StepSemantics.AT_MOST_ONCE_PER_RETRY)
231+
config=StepConfig(
232+
step_semantics=StepSemantics.AT_MOST_ONCE_PER_RETRY,
233+
retry_strategy=lambda error, attempt: RetryDecision.no_retry()
234+
)
229235
)
230236
```
231237

aws-lambda-durable-functions-power/steering/testing-patterns.md

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,7 @@ def test_workflow():
8888
runner = DurableFunctionTestRunner(handler=handler)
8989

9090
with runner:
91-
result = runner.run(input={'user_id': '123'}, timeout=10)
91+
result = runner.run(input='{"user_id": "123"}', timeout=10)
9292

9393
assert result.status is InvocationStatus.SUCCEEDED
9494
```
@@ -272,11 +272,9 @@ it('should handle callback failure', async () => {
272272

273273
const executionPromise = runner.run({ payload: {} });
274274

275-
await new Promise(resolve => setTimeout(resolve, 100));
276-
277275
const callbackOp = runner.getOperation('wait-for-approval');
278-
279-
// Send callback failure
276+
await callbackOp.waitForData(WaitingOperationStatus.STARTED);
277+
280278
await callbackOp.sendCallbackFailure(
281279
'ApprovalDenied',
282280
'Request was rejected'
@@ -320,10 +318,9 @@ it('should handle callback heartbeats', async () => {
320318

321319
const executionPromise = runner.run({ payload: {} });
322320

323-
await new Promise(resolve => setTimeout(resolve, 100));
324-
325321
const callbackOp = runner.getOperation('long-running-process');
326-
322+
await callbackOp.waitForData(WaitingOperationStatus.STARTED);
323+
327324
// Send heartbeats
328325
await callbackOp.sendCallbackHeartbeat();
329326
await runner.skipTime({ minutes: 2 });

aws-lambda-durable-functions-power/steering/troubleshooting-executions.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ Steps:
3636
3737
1. Fetch the execution history directly:
3838
Run: aws lambda get-durable-execution-history --durable-execution-arn <durable-execution-arn> --region <region> --include-execution-data
39+
Note: execution data may contain sensitive information (PII, credentials, business data). Do not display raw step results to users without reviewing content first.
3940
4041
2. If the command succeeds, analyze and provide a user-friendly diagnosis:
4142
a. Report the execution status (RUNNING/SUCCEEDED/FAILED/STOPPED/TIMED_OUT)

0 commit comments

Comments
 (0)