From ea024c547dd1bb18d2a457893f1d538469473e75 Mon Sep 17 00:00:00 2001 From: Brent Champion Date: Wed, 3 Jun 2026 12:25:34 -0400 Subject: [PATCH] feat: update Step Functions power with InvokeHarness context --- aws-step-functions/POWER.md | 2 +- ...gent-orchestration-with-agentcore.asl.json | 146 ++++++++++++++++++ .../steering/architecture-patterns.md | 17 ++ .../steering/service-integrations.md | 32 ++++ 4 files changed, 196 insertions(+), 1 deletion(-) create mode 100644 aws-step-functions/examples/agent-orchestration-with-agentcore.asl.json diff --git a/aws-step-functions/POWER.md b/aws-step-functions/POWER.md index 619b06e..1629959 100644 --- a/aws-step-functions/POWER.md +++ b/aws-step-functions/POWER.md @@ -33,7 +33,7 @@ Load the appropriate steering file based on what the user is working on: - **Service integrations**, **Lambda invoke**, **DynamoDB**, **SNS**, **SQS**, **SDK integrations**, **Resource ARN**, **sync**, **async** → see `service-integrations.md` - **Migrating from JSONPath to JSONata**, **migration**, **JSONPath to JSONata**, **InputPath**, **Parameters**, **ResultSelector**, **ResultPath**, **OutputPath**, **intrinsic functions**, **Iterator**, **payload template** → see `migrating-from-jsonpath-to-jsonata.md` - **Validation**, **linting**, **testing**, **TestState**, **test state**, **mock**, **mocking**, **unit test**, **inspection level**, **DEBUG**, **TRACE**, **validate state**, **test in isolation** → see `validation-and-testing.md` -- **Architecture patterns**, **examples**, **polling**, **saga**, **compensation**, **scatter-gather**, **semaphore**, **lock**, **human-in-the-loop**, **escalation**, **Express to Standard** → see `architecture-patterns.md` +- **Architecture patterns**, **examples**, **polling**, **saga**, **compensation**, **scatter-gather**, **semaphore**, **lock**, **human-in-the-loop**, **escalation**, **Express to Standard**, **agent**, **harness** → see `architecture-patterns.md` - **Data transformation**, **JSONata expressions**, **filtering**, **aggregation**, **string operations**, **$reduce**, **$lookup**, **$toMillis**, **$partition**, **$parse**, **$hash**, **$uuid** → see `transforming-data.md` - **State input/output**, **$states**, **Assign**, **Output**, **Arguments**, **variable scope**, **variable limits**, **evaluation order**, **passing data between states** → see `processing-state-inputs-and-outputs.md` diff --git a/aws-step-functions/examples/agent-orchestration-with-agentcore.asl.json b/aws-step-functions/examples/agent-orchestration-with-agentcore.asl.json new file mode 100644 index 0000000..50ca342 --- /dev/null +++ b/aws-step-functions/examples/agent-orchestration-with-agentcore.asl.json @@ -0,0 +1,146 @@ +{ + "Comment": "Customer-support ticket triage using Bedrock AgentCore invokeHarness with role-specific system prompts and a DynamoDB record.", + "QueryLanguage": "JSONata", + "StartAt": "Analyze Ticket", + "States": { + "Analyze Ticket": { + "Type": "Task", + "Resource": "arn:aws:states:::bedrockagentcore:invokeHarness", + "Arguments": { + "HarnessArn": "arn:aws:bedrock-agentcore:us-east-1:123456789012:harness/support-triage", + "RuntimeSessionId": "{% $states.context.Execution.Name & '-analyze' %}", + "Messages": [ + { + "Role": "user", + "Content": [ + { + "Text": "{% 'Customer tier: ' & $states.input.ticket.customer_tier & '\\nSubject: ' & $states.input.ticket.subject & '\\nBody: ' & $states.input.ticket.body %}" + } + ] + } + ], + "SystemPrompt": [ + { + "Text": "You are a support-ticket analyst. Respond with EXACTLY one JSON object and nothing else. Do not include tags. Do not include reasoning. Do not include prose. Do not include markdown. The JSON must have exactly three keys: category (one of: billing, technical, account, other), sentiment (one of: frustrated, neutral, satisfied), urgency (one of: low, medium, high). Account for customer_tier when assessing urgency. The first character of your response must be { and the last character must be }." + } + ], + "Model": { + "BedrockModelConfig": { + "ModelId": "amazon.nova-lite-v1:0" + } + }, + "MaxTokens": 256 + }, + "Assign": { + "ticket": "{% $states.input.ticket %}", + "analysis": "{% $parse('{' & $substringBefore($substringAfter($states.result.Output.Message.Content[0].Text, '{'), '}') & '}') %}" + }, + "Retry": [ + { + "ErrorEquals": [ + "BedrockAgentCore.ThrottlingException", + "BedrockAgentCore.InternalServerException" + ], + "IntervalSeconds": 2, + "MaxAttempts": 3, + "BackoffRate": 2 + } + ], + "Catch": [ + { + "ErrorEquals": ["States.ALL"], + "Assign": { + "failedStep": "Analyze Ticket", + "errorInfo": "{% $states.errorOutput %}" + }, + "Next": "Triage Failed" + } + ], + "Next": "Recommend Action" + }, + "Recommend Action": { + "Type": "Task", + "Resource": "arn:aws:states:::bedrockagentcore:invokeHarness", + "Arguments": { + "HarnessArn": "arn:aws:bedrock-agentcore:us-east-1:123456789012:harness/support-triage", + "RuntimeSessionId": "{% $states.context.Execution.Name & '-recommend' %}", + "Messages": [ + { + "Role": "user", + "Content": [ + { + "Text": "{% 'Ticket: ' & $string($ticket) & '\\nAnalysis: ' & $string($analysis) %}" + } + ] + } + ], + "SystemPrompt": [ + { + "Text": "You are a triage director. Respond with EXACTLY one JSON object and nothing else. Do not include tags. Do not include reasoning. Do not include prose. Do not include markdown. The JSON must have exactly two keys: recommended_action (one of: auto_resolve, route_to_billing, route_to_technical, route_to_account_management, escalate_to_specialist) and summary (one or two sentences a human agent can read in five seconds). The first character of your response must be { and the last character must be }." + } + ], + "Model": { + "BedrockModelConfig": { + "ModelId": "amazon.nova-lite-v1:0" + } + }, + "MaxTokens": 512 + }, + "Assign": { + "recommendation": "{% $parse('{' & $substringBefore($substringAfter($states.result.Output.Message.Content[0].Text, '{'), '}') & '}') %}" + }, + "Retry": [ + { + "ErrorEquals": [ + "BedrockAgentCore.ThrottlingException", + "BedrockAgentCore.InternalServerException" + ], + "IntervalSeconds": 2, + "MaxAttempts": 3, + "BackoffRate": 2 + } + ], + "Catch": [ + { + "ErrorEquals": ["States.ALL"], + "Assign": { + "failedStep": "Recommend Action", + "errorInfo": "{% $states.errorOutput %}" + }, + "Next": "Triage Failed" + } + ], + "Next": "Record Triage" + }, + "Record Triage": { + "Type": "Task", + "Resource": "arn:aws:states:::dynamodb:putItem", + "Arguments": { + "TableName": "TicketTriageTable", + "Item": { + "ticket_id": { "S": "{% $ticket.id %}" }, + "category": { "S": "{% $analysis.category %}" }, + "sentiment": { "S": "{% $analysis.sentiment %}" }, + "urgency": { "S": "{% $analysis.urgency %}" }, + "recommended_action": { "S": "{% $recommendation.recommended_action %}" }, + "summary": { "S": "{% $recommendation.summary %}" }, + "triaged_at": { "S": "{% $now() %}" } + } + }, + "Output": { + "ticket_id": "{% $ticket.id %}", + "category": "{% $analysis.category %}", + "sentiment": "{% $analysis.sentiment %}", + "urgency": "{% $analysis.urgency %}", + "recommended_action": "{% $recommendation.recommended_action %}", + "summary": "{% $recommendation.summary %}" + }, + "End": true + }, + "Triage Failed": { + "Type": "Fail", + "Error": "TriageError", + "Cause": "{% 'Triage failed at ' & $failedStep & ': ' & ($exists($errorInfo.Cause) ? $errorInfo.Cause : 'Unknown error') %}" + } + } +} diff --git a/aws-step-functions/steering/architecture-patterns.md b/aws-step-functions/steering/architecture-patterns.md index 425da87..3849ea5 100644 --- a/aws-step-functions/steering/architecture-patterns.md +++ b/aws-step-functions/steering/architecture-patterns.md @@ -63,6 +63,23 @@ See `examples/human-in-the-loop-with-timeout-escalation.asl.json` --- +## Agent Orchestration with Bedrock AgentCore + +Use the `bedrockagentcore:invokeHarness` optimized integration to chain multiple AI agent calls with role-specific system prompts. Each call gets a unique `RuntimeSessionId` (derived from the execution name) so sessions don't collide. Parse structured JSON from model output using the assistant-prefill technique and JSONata `$parse`. + +See `examples/agent-orchestration-with-agentcore.asl.json` + +Key elements: +- Resource: `arn:aws:states:::bedrockagentcore:invokeHarness` — Request/Response only (`.sync` and `.waitForTaskToken` not supported). +- `RuntimeSessionId` scoped per step (`$states.context.Execution.Name & '-analyze'`) to keep sessions independent. +- Assistant prefill (`"Role": "assistant", "Content": [{"Text": "{"}]`) forces JSON output from the model. +- `$parse('{' & $substringBefore($substringAfter(..., '{'), '}') & '}')` extracts structured data from the response. +- `Assign` stores parsed results as workflow variables (`$analysis`, `$recommendation`) for downstream states. +- Retry on `BedrockAgentCore.ThrottlingException` and `BedrockAgentCore.InternalServerException` with exponential backoff. +- IAM requires `bedrock-agentcore:InvokeHarness` and `bedrock-agentcore:InvokeAgentRuntime` on the harness ARN (note: hyphen in IAM service name, no hyphen in resource URI). + +--- + ## Express → Standard Handoff Express workflows are more cost-effective for high volume State Machine Invocations, but don't support callbacks or long waits. Standard workflows handle those but cost per state transition. Use Express for fast, high-volume ingest and kick off a Standard execution for the long-running tail. diff --git a/aws-step-functions/steering/service-integrations.md b/aws-step-functions/steering/service-integrations.md index 3b7b4a8..946344c 100644 --- a/aws-step-functions/steering/service-integrations.md +++ b/aws-step-functions/steering/service-integrations.md @@ -158,6 +158,38 @@ Note: The `.sync:2` suffix waits for completion. The child output is a JSON stri --- +### Bedrock AgentCore (Invoke Harness) + +#### Optimized Integration (Request/Response Only) + +```json +"InvokeAgent": { + "Type": "Task", + "Resource": "arn:aws:states:::bedrockagentcore:invokeHarness", + "Arguments": { + "HarnessArn": "arn:aws:bedrock-agentcore:us-east-1:123456789012:harness/my-agent", + "RuntimeSessionId": "{% $states.context.Execution.Name & '-step1' %}", + "Messages": [ + { + "Role": "user", + "Content": [{ "Text": "{% $states.input.userMessage %}" }] + } + ], + "SystemPrompt": [{ "Text": "You are a helpful assistant." }], + "MaxIterations": 75, + "TimeoutSeconds": 600 + }, + "Output": "{% $states.result.Output.Message.Content[0].Text %}", + "Next": "NextState" +} +``` + +Note: Only Request/Response is supported — `.sync` and `.waitForTaskToken` are not available. The resource URI uses `bedrockagentcore` (no hyphen), but IAM ARNs use `bedrock-agentcore` (with hyphen). Maximum `TimeoutSeconds` is 900. IAM role needs both `bedrock-agentcore:InvokeHarness` and `bedrock-agentcore:InvokeAgentRuntime` permissions on the harness ARN. + +Response shape: `$states.result` contains `Output.Message` (Converse-shaped), `StopReason`, `Usage` (InputTokens, OutputTokens, TotalTokens), and `Metrics` (LatencyMs). + +--- + ### Cross-Account Access Use the `Credentials` field to assume a role in another account: