Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
47 commits
Select commit Hold shift + click to select a range
695ca66
docs: update github agent action to reference AGENT_SESSIONS_BUCKET s…
dbschmigelski Jan 5, 2026
50e5e74
feat: provide extra command content as the the prompt to the agent (#…
zastrowm Jan 6, 2026
3bc34ac
[FEATURE] add MCP resource operations in MCP Tools (#1117)
xiehust Jan 6, 2026
514f402
fix: import errors for models with optional imports (#1384)
mehtarac Jan 6, 2026
9fd22d1
add BidiGeminiLiveModel and BidiOpenAIRealtimeModel to the init (#1383)
mehtarac Jan 6, 2026
2b1cf6b
bidi - async - remove cancelling call (#1357)
pgrayy Jan 7, 2026
08bf563
feat(bedrock): add guardrail_latest_message option (#1224)
aiancheruk Jan 7, 2026
1e27d79
fix(gemini): Gemini UnboundLocal Exception raised during stream (#1420)
emattiza Jan 7, 2026
2f04bc0
feat(litellm): handle litellm non streaming responses (#512)
schleidl Jan 8, 2026
0ef2288
feat(agent): introduce AgentBase Protocol as the interface for agent …
awsarron Jan 9, 2026
10a8e4a
ci: update pytest requirement from <9.0.0,>=8.0.0 to >=8.0.0,<10.0.0 …
dependabot[bot] Jan 9, 2026
cd6570b
feat(models): pass invocation_state to model providers (#1414)
tirth14 Jan 12, 2026
37d0e47
Add Security.md file (#1454)
yonib05 Jan 12, 2026
845c6f7
chore: Update release notes sop (#1456)
zastrowm Jan 12, 2026
3ffc327
fix(integ): make calculator tool more robust to LLM output variations…
cagataycali Jan 12, 2026
56676c1
fix(mcp): resolve string formatting error in MCP client error handlin…
cagataycali Jan 12, 2026
318573d
bidi - move 3.12 check to nova sonic module (#1439)
pgrayy Jan 12, 2026
68257a3
ci: update sphinx requirement from <9.0.0,>=5.0.0 to >=5.0.0,<10.0.0 …
dependabot[bot] Jan 12, 2026
0273801
fix: add concurrency protection to prevent parallel invocations from …
zastrowm Jan 13, 2026
c098b3d
fix(mcp): propagate contextvars to background thread (#1444)
cagataycali Jan 13, 2026
06c3297
Update to opus 4.5 (#1471)
Unshure Jan 13, 2026
c43dfa9
fix(mcp): prevent agent hang by checking session closure state (#1396)
Ratish1 Jan 14, 2026
368bb0f
ci: update sphinx-rtd-theme requirement (#1466)
dependabot[bot] Jan 15, 2026
c029831
ci: update websockets requirement (#1451)
dependabot[bot] Jan 15, 2026
2546aa0
style: update ruff configuration to apply pyupgrade to modernize synt…
maxrabin Jan 15, 2026
c23090f
fix(agent): extract text from citationsContent in AgentResult.__str__…
tmokmss Jan 15, 2026
dfe3ec7
Expose input messages to BeforeInvocationEvent hook (#1474)
Unshure Jan 15, 2026
058c03a
interrupts - graph - hook based (#1478)
pgrayy Jan 15, 2026
bb3052b
fix: Swap sleeps with explicit signaling (#1497)
zastrowm Jan 15, 2026
25c46a1
Fix PEP 563 incompatibility with @tool decorated tools (#1494)
zastrowm Jan 15, 2026
5e733ef
feat: override service name by OTEL_SERVICE_NAME env (#1400)
okamototk Jan 16, 2026
bce2464
fix(bedrock): disable thinking mode when forcing tool_choice (#1495)
strands-agent Jan 16, 2026
e4bd3bc
fix: a2a use artifact update event (#1401)
brycewcole Jan 16, 2026
51cbe7b
Add parallel reading support to S3SessionManager.list_messages() (#1186)
CrysisDeu Jan 20, 2026
8b7f6cc
feat(steering): allow steering on AfterModelCallEvents (#1429)
dbschmigelski Jan 20, 2026
63e58aa
fix: provide unique toolUseId for gemini models (#1201)
AirswitchAsa Jan 20, 2026
456b70a
gemini - tool_use_id_to_name - local (#1521)
pgrayy Jan 20, 2026
6dcd247
fix(litellm): handle missing usage attribute on ModelResponseStream (…
dbschmigelski Jan 20, 2026
64e1bb2
feat(agent): add configurable retry_strategy for model calls (#1424)
zastrowm Jan 21, 2026
7604e98
fix(swarm): accumulate execution_time across interrupt/resume cycles …
pgrayy Jan 21, 2026
2e23d75
Feat: graduate multiagent hook events from experimental (#1498)
JackYPCOnline Jan 21, 2026
b41a99b
Nova Sonic 2 support for BidiAgent (#1476)
lanazhang Jan 21, 2026
f87925b
fix(tests): reduce flakiness in guardrail redact output test (#1505)
dbschmigelski Jan 21, 2026
d851d06
ci: add workflow for lambda layer publish and yank
dbschmigelski Dec 9, 2025
8f6e82f
fix: update bucket name to match deployed infra
dbschmigelski Jan 16, 2026
1124534
remove yank from SOP
dbschmigelski Jan 16, 2026
d7aa0fb
rename vars.BUCKET_SALT to secrets.STRANDS_LAMBDA_LAYER_BUCKET_SALT
dbschmigelski Jan 16, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/actions/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -198,7 +198,7 @@ Your IAM role must have these permissions in order to execute:
3. **Create S3 Bucket** for session storage
4. **Add GitHub Secrets**:
- `AWS_ROLE_ARN`: The created role ARN
- `STRANDS_SESSION_BUCKET`: The S3 bucket name
- `AGENT_SESSIONS_BUCKET`: The S3 bucket name

## Security

Expand Down
2 changes: 1 addition & 1 deletion .github/actions/strands-agent-runner/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,7 @@ runs:
STRANDS_TOOL_CONSOLE_MODE: 'enabled'
BYPASS_TOOL_CONSENT: 'true'
run: |
uv run --no-project ${{ runner.temp }}/strands-agent-runner/.github/scripts/python/agent_runner.py "$INPUT_TASK"
uv run --no-project ${{ runner.temp }}/strands-agent-runner/.github/scripts/python/agent_runner.py

- name: Capture repository state
shell: bash
Expand Down
462 changes: 324 additions & 138 deletions .github/agent-sops/task-release-notes.sop.md

Large diffs are not rendered by default.

20 changes: 18 additions & 2 deletions .github/scripts/javascript/process-input.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,10 @@ async function getIssueInfo(github, context, inputs) {
const issueId = context.eventName === 'workflow_dispatch'
? inputs.issue_id
: context.payload.issue.number.toString();
const commentBody = context.payload.comment?.body || '';
const command = context.eventName === 'workflow_dispatch'
? inputs.command
: (context.payload.comment.body.match(/^\/strands\s*(.*?)$/m)?.[1]?.trim() || '');
: (commentBody.startsWith('/strands') ? commentBody.slice('/strands'.length).trim() : '');

console.log(`Event: ${context.eventName}, Issue ID: ${issueId}, Command: "${command}"`);

Expand Down Expand Up @@ -76,10 +77,25 @@ function buildPrompts(mode, issueId, isPullRequest, command, branchName, inputs)
const scriptFile = scriptFiles[mode] || scriptFiles['refiner'];
const systemPrompt = fs.readFileSync(scriptFile, 'utf8');

// Extract the user's feedback/instructions after the mode keyword
// e.g., "release-notes Move #123 to Major Features" -> "Move #123 to Major Features"
const modeKeywords = {
'release-notes': /^(?:release-notes|release notes)\s*/i,
'implementer': /^implement\s*/i,
'refiner': /^refine\s*/i
};

const modePattern = modeKeywords[mode];
const userFeedback = modePattern ? command.replace(modePattern, '').trim() : command.trim();

let prompt = (isPullRequest)
? 'The pull request id is:'
: 'The issue id is:';
prompt += `${issueId}\n${command}\nreview and continue`;
prompt += `${issueId}\n`;

// If there's any user feedback beyond the command keyword, include it as the main instruction,
// otherwise default to "review and continue"
prompt += userFeedback || 'review and continue';

return { sessionId, systemPrompt, prompt };
}
Expand Down
15 changes: 7 additions & 8 deletions .github/scripts/python/agent_runner.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@
from str_replace_based_edit_tool import str_replace_based_edit_tool

# Strands configuration constants
STRANDS_MODEL_ID = "global.anthropic.claude-sonnet-4-5-20250929-v1:0"
STRANDS_MODEL_ID = "global.anthropic.claude-opus-4-5-20251101-v1:0"
STRANDS_MAX_TOKENS = 64000
STRANDS_BUDGET_TOKENS = 8000
STRANDS_REGION = "us-west-2"
Expand Down Expand Up @@ -142,13 +142,12 @@ def run_agent(query: str):
def main() -> None:
"""Main entry point for the agent runner."""
try:
# Read task from command line arguments
if len(sys.argv) < 2:
raise ValueError("Task argument is required")

task = " ".join(sys.argv[1:])
if not task.strip():
raise ValueError("Task cannot be empty")
# Prefer INPUT_TASK env var (avoids shell escaping issues), fall back to CLI args
task = os.getenv("INPUT_TASK", "").strip()
if not task and len(sys.argv) > 1:
task = " ".join(sys.argv[1:]).strip()
if not task:
raise ValueError("Task is required (via INPUT_TASK env var or CLI argument)")
print(f"🤖 Running agent with task: {task}")

run_agent(task)
Expand Down
31 changes: 31 additions & 0 deletions .github/workflows/LAMDBA_LAYERS_SOP.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# Lambda Layers Standard Operating Procedures (SOP)

## Overview

This document defines the standard operating procedures for managing Strands Agents Lambda layers across all AWS regions, Python versions, and architectures.

**Total: 136 individual Lambda layers** (17 regions × 2 architectures × 4 Python versions). All variants must maintain the same layer version number for each PyPI package version, with only one row per PyPI version appearing in documentation.

## Deployment Process

### 1. Initial Deployment
1. Run workflow with ALL options selected (default)
2. Specify PyPI package version
3. Type "Create Lambda Layer {package_version}" to confirm
4. All 136 individual layers deploy in parallel (4 Python × 2 arch × 17 regions)
5. Each layer gets its own unique name: `strands-agents-py{PYTHON_VERSION}-{ARCH}`

### 2. Version Buffering for New Variants
When adding new variants (new Python version, architecture, or region):

1. **Determine target layer version**: Check existing variants to find the highest layer version
2. **Buffer deployment**: Deploy new variants multiple times until layer version matches existing variants
3. **Example**: If existing variants are at layer version 5, deploy new variant 5 times to reach version 5

### 3. Handling Transient Failures
When some regions fail during deployment:

1. **Identify failed regions**: Check which combinations didn't complete successfully
2. **Targeted redeployment**: Use specific region/arch/Python inputs to redeploy failed combinations
3. **Version alignment**: Continue deploying until all variants reach the same layer version
4. **Verification**: Confirm all combinations have identical layer versions before updating docs
167 changes: 167 additions & 0 deletions .github/workflows/publish-lambda-layer.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
name: Publish PyPI Package to Lambda Layer

on:
workflow_dispatch:
inputs:
package_version:
description: 'Package version to download'
required: true
type: string
layer_version:
description: 'Layer version'
required: true
type: string
python_version:
description: 'Python version'
required: true
default: 'ALL'
type: choice
options: ['ALL', '3.10', '3.11', '3.12', '3.13']
architecture:
description: 'Architecture'
required: true
default: 'ALL'
type: choice
options: ['ALL', 'x86_64', 'aarch64']
region:
description: 'AWS region'
required: true
default: 'ALL'
type: choice
# Only non opt-in regions included for now
options: ['ALL', 'us-east-1', 'us-east-2', 'us-west-1', 'us-west-2', 'ap-south-1', 'ap-northeast-1', 'ap-northeast-2', 'ap-northeast-3', 'ap-southeast-1', 'ap-southeast-2', 'ca-central-1', 'eu-central-1', 'eu-west-1', 'eu-west-2', 'eu-west-3', 'eu-north-1', 'sa-east-1']
confirm:
description: 'Type "Create Lambda Layer {PyPI version}-layer{layer version}" to confirm publishing the layer'
required: true
type: string

jobs:
validate:
runs-on: ubuntu-latest
steps:
- name: Validate confirmation
run: |
CONFIRM="${{ inputs.confirm }}"
EXPECTED="Create Lambda Layer ${{ inputs.package_version }}-layer${{ inputs.layer_version }}"
if [ "$CONFIRM" != "$EXPECTED" ]; then
echo "Confirmation failed. You must type exactly '$EXPECTED' to proceed."
exit 1
fi
echo "Confirmation validated"

package-and-upload:
needs: validate
runs-on: ubuntu-latest
strategy:
matrix:
python-version: ${{ inputs.python_version == 'ALL' && fromJson('["3.10", "3.11", "3.12", "3.13"]') || fromJson(format('["{0}"]', inputs.python_version)) }}
architecture: ${{ inputs.architecture == 'ALL' && fromJson('["x86_64", "aarch64"]') || fromJson(format('["{0}"]', inputs.architecture)) }}
region: ${{ inputs.region == 'ALL' && fromJson('["us-east-1", "us-east-2", "us-west-1", "us-west-2", "ap-south-1", "ap-northeast-1", "ap-northeast-2", "ap-northeast-3", "ap-southeast-1", "ap-southeast-2", "ca-central-1", "eu-central-1", "eu-west-1", "eu-west-2", "eu-west-3", "eu-north-1", "sa-east-1"]') || fromJson(format('["{0}"]', inputs.region)) }}

permissions:
id-token: write

steps:
- name: Set up Python
uses: actions/setup-python@v4
with:
python-version: ${{ matrix.python-version }}

- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: ${{ secrets.STRANDS_LAMBDA_LAYER_PUBLISHER_ROLE }}
aws-region: ${{ matrix.region }}

- name: Create layer directory structure
run: |
mkdir -p layer/python

- name: Download and install package
run: |
pip install strands-agents==${{ inputs.package_version }} \
--python-version ${{ matrix.python-version }} \
--platform manylinux2014_${{ matrix.architecture }} \
-t layer/python/ \
--only-binary=:all:

- name: Create layer zip
run: |
cd layer
zip -r ../lambda-layer.zip .

- name: Upload to S3
run: |
PYTHON_VERSION="${{ matrix.python-version }}"
ARCH="${{ matrix.architecture }}"
REGION="${{ matrix.region }}"
LAYER_NAME="strands-agents-py${PYTHON_VERSION//./_}-${ARCH}"
ACCOUNT_ID=$(aws sts get-caller-identity --query Account --output text)
BUCKET_NAME="strands-layer-${ACCOUNT_ID}-${{ secrets.STRANDS_LAMBDA_LAYER_BUCKET_SALT }}-${REGION}"
LAYER_KEY="$LAYER_NAME/${{ inputs.package_version }}/layer${{ inputs.layer_version }}/lambda-layer.zip"

aws s3 cp lambda-layer.zip "s3://$BUCKET_NAME/$LAYER_KEY" --region "$REGION"
echo "Uploaded layer to s3://$BUCKET_NAME/$LAYER_KEY"

publish-layer:
needs: package-and-upload
runs-on: ubuntu-latest
strategy:
matrix:
python-version: ${{ inputs.python_version == 'ALL' && fromJson('["3.10", "3.11", "3.12", "3.13"]') || fromJson(format('["{0}"]', inputs.python_version)) }}
architecture: ${{ inputs.architecture == 'ALL' && fromJson('["x86_64", "aarch64"]') || fromJson(format('["{0}"]', inputs.architecture)) }}
region: ${{ inputs.region == 'ALL' && fromJson('["us-east-1", "us-east-2", "us-west-1", "us-west-2", "ap-south-1", "ap-northeast-1", "ap-northeast-2", "ap-northeast-3", "ap-southeast-1", "ap-southeast-2", "ca-central-1", "eu-central-1", "eu-west-1", "eu-west-2", "eu-west-3", "eu-north-1", "sa-east-1"]') || fromJson(format('["{0}"]', inputs.region)) }}

permissions:
id-token: write

steps:
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: ${{ secrets.STRANDS_LAMBDA_LAYER_PUBLISHER_ROLE }}
aws-region: ${{ matrix.region }}

- name: Publish layer
run: |
PYTHON_VERSION="${{ matrix.python-version }}"
ARCH="${{ matrix.architecture }}"
REGION="${{ matrix.region }}"
LAYER_NAME="strands-agents-py${PYTHON_VERSION//./_}-${ARCH}"
ACCOUNT_ID=$(aws sts get-caller-identity --query Account --output text)
REGION_BUCKET="strands-layer-${ACCOUNT_ID}-${{ secrets.STRANDS_LAMBDA_LAYER_BUCKET_SALT }}-${REGION}"
LAYER_KEY="$LAYER_NAME/${{ inputs.package_version }}/layer${{ inputs.layer_version }}/lambda-layer.zip"

DESCRIPTION="PyPI package: strands-agents v${{ inputs.package_version }} (Python $PYTHON_VERSION, $ARCH)"

# Set compatible architecture based on matrix architecture
if [ "$ARCH" = "x86_64" ]; then
COMPATIBLE_ARCH="x86_64"
else
COMPATIBLE_ARCH="arm64"
fi

LAYER_OUTPUT=$(aws lambda publish-layer-version \
--layer-name $LAYER_NAME \
--description "$DESCRIPTION" \
--content S3Bucket=$REGION_BUCKET,S3Key=$LAYER_KEY \
--compatible-runtimes python${{ matrix.python-version }} \
--compatible-architectures $COMPATIBLE_ARCH \
--region "$REGION" \
--license-info Apache-2.0 \
--output json)

LAYER_ARN=$(echo "$LAYER_OUTPUT" | jq -r '.LayerArn')
LAYER_VERSION=$(echo "$LAYER_OUTPUT" | jq -r '.Version')

echo "Published layer version $LAYER_VERSION with ARN: $LAYER_ARN in region $REGION"

aws lambda add-layer-version-permission \
--layer-name $LAYER_NAME \
--version-number $LAYER_VERSION \
--statement-id public \
--action lambda:GetLayerVersion \
--principal '*' \
--region "$REGION"

echo "Successfully published layer version $LAYER_VERSION in region $REGION"
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,4 @@ repl_state
.kiro
uv.lock
.audio_cache
CLAUDE.md
28 changes: 23 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -204,9 +204,9 @@ It's also available on GitHub via [strands-agents/tools](https://github.com/stra
Build real-time voice and audio conversations with persistent streaming connections. Unlike traditional request-response patterns, bidirectional streaming maintains long-running conversations where users can interrupt, provide continuous input, and receive real-time audio responses. Get started with your first BidiAgent by following the [Quickstart](https://strandsagents.com/latest/documentation/docs/user-guide/concepts/experimental/bidirectional-streaming/quickstart) guide.

**Supported Model Providers:**
- Amazon Nova Sonic (`amazon.nova-sonic-v1:0`)
- Google Gemini Live (`gemini-2.5-flash-native-audio-preview-09-2025`)
- OpenAI Realtime API (`gpt-realtime`)
- Amazon Nova Sonic (v1, v2)
- Google Gemini Live
- OpenAI Realtime API

**Quick Example:**

Expand All @@ -219,7 +219,7 @@ from strands.experimental.bidi.tools import stop_conversation
from strands_tools import calculator

async def main():
# Create bidirectional agent with audio model
# Create bidirectional agent with Nova Sonic v2
model = BidiNovaSonicModel()
agent = BidiAgent(model=model, tools=[calculator, stop_conversation])

Expand All @@ -241,14 +241,19 @@ if __name__ == "__main__":
**Configuration Options:**

```python
# Configure audio settings
from strands.experimental.bidi.models import BidiNovaSonicModel

# Configure audio settings and turn detection (v2 only)
model = BidiNovaSonicModel(
provider_config={
"audio": {
"input_rate": 16000,
"output_rate": 16000,
"voice": "matthew"
},
"turn_detection": {
"endpointingSensitivity": "MEDIUM" # HIGH, MEDIUM, or LOW
},
"inference": {
"max_tokens": 2048,
"temperature": 0.7
Expand All @@ -263,6 +268,19 @@ audio_io = BidiAudioIO(
input_buffer_size=10,
output_buffer_size=10
)

# Text input mode (type messages instead of speaking)
text_io = BidiTextIO()
await agent.run(
inputs=[text_io.input()], # Use text input
outputs=[audio_io.output(), text_io.output()]
)

# Multi-modal: Both audio and text input
await agent.run(
inputs=[audio_io.input(), text_io.input()], # Speak OR type
outputs=[audio_io.output(), text_io.output()]
)
```

## Documentation
Expand Down
20 changes: 20 additions & 0 deletions SECURITY.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# Security Policy

## Supported Versions

| Version | Supported |
| ------- | ------------------ |
| 1.x.x | :white_check_mark: |
| < 1.0 | :x: |

## Reporting Security Issues

Amazon Web Services (AWS) is dedicated to the responsible disclosure of security vulnerabilities.

We kindly ask that you **do not** open a public GitHub issue to report security concerns.

Instead, please submit the issue to the AWS Vulnerability Disclosure Program via [HackerOne](https://hackerone.com/aws_vdp) or send your report via [email](mailto:aws-security@amazon.com).

For more details, visit the [AWS Vulnerability Reporting Page](http://aws.amazon.com/security/vulnerability-reporting/).

Thank you in advance for collaborating with us to help protect our customers.
Loading
Loading