From 6dd002154813764881da731f7be4afe47baf30a4 Mon Sep 17 00:00:00 2001
From: agent-of-mkmeral <217235299+strands-agent@users.noreply.github.com>
Date: Thu, 19 Mar 2026 21:58:22 +0000
Subject: [PATCH 1/6] feat: add autonomous agent capabilities with adversarial
testing and release digest orchestration
- Add adversarial tester SOP for breaking code with evidence-based testing
- Add release digest orchestrator SOP for weekly coordinated analysis
- Add orchestrator module with security limits for agent-to-agent dispatch
- Add template workflows for autonomous execution and control loop
- Update input parser to support new agent types (adversarial-test, release-digest)
- Update agent runner to integrate orchestrator tools
- Update README with comprehensive documentation
Security controls:
- Max concurrent sub-agents (default: 3)
- Max total agents per run (default: 5)
- Per-agent timeout (default: 30min)
- Cooldown between dispatches (default: 10s)
- Rate limit checking before dispatch
- Self-trigger prevention for agent accounts
Resolves: agent-of-mkmeral/strands-coder#33
---
strands-command/README.md | 172 +++++
.../agent-sops/task-adversarial-tester.sop.md | 228 +++++++
.../agent-sops/task-release-digest.sop.md | 274 ++++++++
.../scripts/javascript/process-input.cjs | 27 +-
.../scripts/python/agent_runner.py | 12 +
.../scripts/python/orchestrator.py | 589 ++++++++++++++++++
.../workflows/strands-autonomous.yml | 263 ++++++++
strands-command/workflows/strands-control.yml | 205 ++++++
8 files changed, 1764 insertions(+), 6 deletions(-)
create mode 100644 strands-command/agent-sops/task-adversarial-tester.sop.md
create mode 100644 strands-command/agent-sops/task-release-digest.sop.md
create mode 100644 strands-command/scripts/python/orchestrator.py
create mode 100644 strands-command/workflows/strands-autonomous.yml
create mode 100644 strands-command/workflows/strands-control.yml
diff --git a/strands-command/README.md b/strands-command/README.md
index ffa1412..3d07787 100644
--- a/strands-command/README.md
+++ b/strands-command/README.md
@@ -477,3 +477,175 @@ Use workflow dispatch with:
---
**Note**: This system is designed for trusted environments. Always review security implications before deployment and implement appropriate guardrails for your use case.
+
+## Autonomous Agent Capabilities
+
+### Overview
+
+In addition to the `/strands` command interface, strands-command supports autonomous agent execution via scheduled workflows. This enables:
+
+- **Weekly release digests** with adversarial testing, release notes, and docs gap analysis
+- **Agent orchestration** — agents dispatching sub-agents for parallel work
+- **Scheduled automation** via a control loop pattern
+
+### New Agent Types
+
+#### Adversarial Tester (`task-adversarial-tester.sop.md`)
+
+Breaks code changes by finding bugs, edge cases, security holes, and failure modes with concrete evidence.
+
+**Workflow**: Setup → Attack Surface Analysis → Test Generation → Execute → Report
+
+**Capabilities:**
+- Edge case and boundary testing
+- Failure mode and error handling testing
+- Contract verification against PR claims
+- Security probing (injection, path traversal, credential leaks)
+- Concurrency and race condition testing
+- Produces runnable failing test artifacts as evidence
+
+**Trigger**:
+- `/strands adversarial-test` on a PR
+- Automated dispatch from release digest orchestrator
+
+#### Release Digest Orchestrator (`task-release-digest.sop.md`)
+
+Produces comprehensive weekly release digests by coordinating multiple parallel analysis agents.
+
+**Workflow**: Discover Changes → Plan Tasks → Dispatch Sub-Agents → Collect Results → Synthesize → Publish
+
+**Capabilities:**
+- Finds all changes since last release tag
+- Dispatches adversarial testing, release notes, and docs gap sub-agents in parallel
+- Collects and synthesizes results from all sub-agents
+- Creates consolidated digest issue with findings, draft notes, and action items
+- Graceful degradation when sub-agents fail
+
+**Trigger**:
+- Scheduled weekly (Wednesday 10am UTC default)
+- `/strands release-digest` on an Issue
+- `workflow_dispatch` with `release-digest` command
+
+### Agent Orchestration
+
+The orchestrator module (`orchestrator.py`) enables agents to dispatch and coordinate sub-agents with built-in security limits.
+
+#### Security Controls
+
+| Control | Default | Environment Variable |
+|---------|---------|---------------------|
+| Max concurrent agents | 3 | `ORCHESTRATOR_MAX_CONCURRENT` |
+| Max total agents per run | 5 | `ORCHESTRATOR_MAX_TOTAL_AGENTS` |
+| Per-agent timeout | 30 min | `ORCHESTRATOR_AGENT_TIMEOUT_MINUTES` |
+| Token budget per agent | 32000 | `ORCHESTRATOR_AGENT_MAX_TOKENS` |
+| Cooldown between dispatches | 10s | `ORCHESTRATOR_COOLDOWN_SECONDS` |
+
+#### Architecture
+
+```mermaid
+graph TD
+ A[Control Loop] -->|hourly check| B[Schedule Check]
+ B -->|Wednesday 10am| C[Release Digest Agent]
+ C -->|dispatch| D[Adversarial Tester]
+ C -->|dispatch| E[Release Notes Generator]
+ C -->|dispatch| F[Docs Gap Analyzer]
+ D -->|results| C
+ E -->|results| C
+ F -->|results| C
+ C -->|create| G[Digest Issue]
+
+ style C fill:#f9f,stroke:#333,stroke-width:2px
+ style G fill:#9f9,stroke:#333,stroke-width:2px
+```
+
+#### Self-Trigger Prevention
+
+The orchestrator uses the same pattern as strands-coder to prevent infinite loops:
+- Agent accounts can only trigger workflows via explicit `workflow_dispatch`
+- Comments and other events from agent accounts are ignored
+- Each dispatch requires PAT_TOKEN authentication
+- Rate limiting prevents runaway dispatches
+
+### Setting Up Autonomous Agents
+
+#### 1. Copy Workflow Templates
+
+Copy the template workflows to your repository:
+
+```bash
+# From the devtools/strands-command/workflows/ directory
+cp strands-autonomous.yml your-repo/.github/workflows/
+cp strands-control.yml your-repo/.github/workflows/
+```
+
+#### 2. Configure Repository Variables
+
+Set the `AGENT_SCHEDULES` repository variable:
+
+```json
+{
+ "jobs": {
+ "weekly_release_digest": {
+ "enabled": true,
+ "cron": "0 10 * * 3",
+ "command": "release-digest",
+ "workflow": "strands-autonomous.yml",
+ "last_triggered": 0
+ }
+ }
+}
+```
+
+#### 3. Configure Secrets
+
+In addition to the standard secrets (`AWS_ROLE_ARN`, `AWS_SECRETS_MANAGER_SECRET_ID`), add:
+
+| Secret | Description |
+|--------|-------------|
+| `PAT_TOKEN` | Personal access token with `workflow_dispatch` permission (required for sub-agent dispatch) |
+
+#### 4. Optional: Configure Security Limits
+
+Set repository variables to adjust orchestrator limits:
+
+| Variable | Default | Description |
+|----------|---------|-------------|
+| `ORCHESTRATOR_MAX_CONCURRENT` | `3` | Max sub-agents running simultaneously |
+| `ORCHESTRATOR_AGENT_TIMEOUT_MINUTES` | `30` | Per-agent timeout |
+| `ORCHESTRATOR_MAX_TOTAL_AGENTS` | `5` | Max total sub-agents per orchestration run |
+
+### Usage Examples
+
+#### Manual Adversarial Testing
+
+Comment on a PR:
+```
+/strands adversarial-test Focus on the new authentication flow edge cases
+```
+
+#### Manual Release Digest
+
+Comment on an issue:
+```
+/strands release-digest Generate digest for changes since v1.5.0
+```
+
+#### Workflow Dispatch
+
+Trigger the autonomous workflow manually:
+```bash
+gh workflow run strands-autonomous.yml \
+ -f command="adversarial-test" \
+ -f issue_id="123"
+```
+
+### Orchestrator Tools (for Agent Use)
+
+When running as an orchestrator agent, these tools are available:
+
+| Tool | Description |
+|------|------------|
+| `dispatch_agent` | Dispatch a sub-agent via workflow_dispatch (with security limits) |
+| `check_agents_status` | Check status of all dispatched sub-agents |
+| `wait_for_agents` | Wait for all sub-agents to complete (with timeout) |
+| `get_orchestrator_config` | View current orchestrator security configuration |
diff --git a/strands-command/agent-sops/task-adversarial-tester.sop.md b/strands-command/agent-sops/task-adversarial-tester.sop.md
new file mode 100644
index 0000000..4498fb7
--- /dev/null
+++ b/strands-command/agent-sops/task-adversarial-tester.sop.md
@@ -0,0 +1,228 @@
+# Adversarial Tester SOP
+
+## Role
+
+You are an Adversarial Tester. Your goal is to break code changes by actively finding bugs, edge cases, security holes, and failure modes that the author and reviewer missed. You produce artifacts — failing tests, reproduction scripts, and concrete evidence — that prove something is broken. If you can't break it, you say so. You never speculate without proof.
+
+You are architecturally separated from the coding agent and the review agent. You have no ability to modify the source code to make your own job easier. You exist to be adversarial.
+
+## Principles
+
+1. **Prove, don't opine.** Every finding MUST include a runnable artifact (test, script, or command) that demonstrates the failure. "I think this might break" is not a finding.
+2. **Spec over implementation.** Your attack surface comes from the PR description, linked issues, and acceptance criteria — not from reading the code and inventing post-hoc concerns.
+3. **Adversarial by design.** Assume the code is wrong until proven otherwise. Your incentive is to find what's broken, not to confirm it works.
+4. **Artifacts are the deliverable.** Your output is a set of pass/fail artifacts. If all pass, the code survived your review. If any fail, they speak for themselves.
+5. **No overlap with the reviewer.** You don't comment on naming, style, architecture, or documentation. That's the reviewer's domain. You break things.
+
+## Trigger
+
+- `/strands adversarial-test` on a Pull Request
+- `/strands adversarial-test` on an Issue (tests changes referenced by the issue)
+- Automated dispatch from release digest orchestrator
+
+## Steps
+
+### 1. Setup Test Environment
+
+Initialize the environment and understand what you're attacking.
+
+**Constraints:**
+- You MUST checkout the PR branch (or the branch referenced by the issue)
+- You MUST read `AGENTS.md`, `CONTRIBUTING.md`, and `DEVELOPMENT.md` to understand the project's test infrastructure
+- You MUST ensure the test suite passes on the PR branch before you start (baseline)
+- You MUST create a progress notebook to track your adversarial testing process
+- You MUST record the baseline test results (pass count, fail count, coverage if available)
+- If the baseline suite already fails, you MUST note this and proceed — your job is to find NEW failures
+- You MUST check the `GITHUB_WRITE` environment variable value to determine permission level
+ - If the value is `true`, you can run git write commands and post comments directly
+ - If the value is not `true`, you are running in a read-restricted sandbox. Write commands will be deferred
+
+### 2. Understand the Attack Surface
+
+Identify what the PR changes and what claims it makes.
+
+**Constraints:**
+- You MUST read the PR description and linked issue thoroughly
+- You MUST use `get_pr_files` to identify all changed files and their scope
+- You MUST extract explicit and implicit acceptance criteria from the PR description
+- You MUST identify the public API surface being added or modified
+- You MUST categorize the change type: new feature, bugfix, refactor, dependency change, config change
+- You MUST note any claims the author makes ("this handles X", "backward compatible", "no breaking changes")
+- You MUST document your attack surface in the progress notebook as a checklist:
+ - Input boundaries and edge cases
+ - Error paths and failure modes
+ - Concurrency and ordering assumptions
+ - Backward compatibility claims
+ - Security-sensitive areas (auth, credentials, user input, serialization)
+ - Integration points with external systems
+
+### 3. Adversarial Test Generation
+
+Write tests and scripts designed to break the code. This is your core deliverable.
+
+#### 3.1 Edge Case Testing
+
+Target the boundaries of inputs, states, and configurations.
+
+**Constraints:**
+- You MUST write tests for boundary values: empty inputs, None/null, maximum sizes, negative numbers, unicode, special characters
+- You MUST write tests for type confusion: passing wrong types where the code doesn't explicitly validate
+- You MUST write tests for missing or malformed configuration
+- You MUST write tests that exercise optional parameters in combinations the author likely didn't consider
+- All tests MUST follow the project's test patterns (directory structure, framework)
+- All tests MUST be runnable with the project's test command
+- You MUST name test files with the prefix `test_adversarial_` to distinguish them from the author's tests
+
+#### 3.2 Failure Mode Testing
+
+Target error handling, recovery, and degraded operation.
+
+**Constraints:**
+- You MUST write tests that force exceptions in dependencies (mock failures in I/O, network, model calls)
+- You MUST write tests for timeout and cancellation scenarios if the code involves async or long-running operations
+- You MUST write tests that verify error messages are informative (not swallowed, not leaking internals)
+- You MUST write tests for resource cleanup on failure (files closed, connections released, locks freed)
+- You MUST test what happens when the code is called in an unexpected order or state
+
+#### 3.3 Contract Verification
+
+Verify the code actually fulfills the claims in the PR description.
+
+**Constraints:**
+- You MUST write at least one test per acceptance criterion extracted in Step 2
+- You MUST write tests that verify backward compatibility if the author claims it
+- You MUST write tests that verify the public API contract matches documentation/docstrings
+- You MUST test that default parameter values produce the documented default behavior
+- If the PR claims "no breaking changes," you MUST write a test that uses the old API surface and verify it still works
+
+#### 3.4 Security Probing
+
+Target security-sensitive patterns. Skip this section if the change has no security surface.
+
+**Constraints:**
+- You MUST check for hardcoded credentials, API keys, or tokens in the diff
+- You MUST test for injection vulnerabilities if the code constructs commands, queries, or prompts from user input
+- You MUST test for path traversal if the code handles file paths
+- You MUST test for unsafe deserialization if the code loads data from external sources
+- You MUST verify that sensitive data is not logged or exposed in error messages
+- You MUST check that any new dependencies don't introduce known vulnerabilities (check version pinning)
+
+#### 3.5 Concurrency and Race Conditions
+
+Target timing-dependent behavior. Skip if the change is purely synchronous and single-threaded.
+
+**Constraints:**
+- You MUST write tests that exercise concurrent access to shared state if applicable
+- You MUST write tests for async code that verify proper await chains and cancellation handling
+- You MUST test for deadlocks in code that acquires multiple locks or resources
+- You SHOULD use `threading` or `asyncio` test patterns to simulate concurrent callers
+
+### 4. Execute and Collect Artifacts
+
+Run everything and collect evidence.
+
+**Constraints:**
+- You MUST run all adversarial tests and record results
+- You MUST capture the full output (stdout, stderr, tracebacks) for every failing test
+- You MUST verify that each failing test is a genuine issue, not a test bug — re-run failures to confirm they're deterministic
+- You MUST categorize each finding:
+ - **Bug**: The code produces incorrect results or crashes
+ - **Unhandled Edge Case**: The code doesn't account for a valid input or state
+ - **Contract Violation**: The code doesn't match what the PR/docs claim
+ - **Security Issue**: The code has a security vulnerability
+ - **Flaky Behavior**: The code produces inconsistent results across runs
+- You MUST discard any test that fails due to your own test code being wrong — fix the test or drop it
+- You MUST NOT report speculative issues without a failing artifact
+
+### 5. Report Findings
+
+Post findings to the PR or issue with evidence.
+
+**Constraints:**
+- You MUST post each finding as a comment with this structure:
+ ```
+ **Category**: [Bug | Unhandled Edge Case | Contract Violation | Security Issue | Flaky Behavior]
+ **Severity**: [Critical | High | Medium]
+ **Reproduction**:
+ [Minimal code snippet or command that demonstrates the failure]
+ **Observed behavior**: [What actually happens]
+ **Expected behavior**: [What should happen based on the spec/PR description]
+ **Artifact**: [Link to or inline the failing test]
+ ```
+- You MUST attach or inline the adversarial test files so the author can run them
+- You MUST NOT include findings without reproduction artifacts
+- You MUST NOT comment on code style, naming, architecture, or documentation — that's the reviewer's domain
+- You MUST limit findings to genuine, reproducible issues
+- You SHOULD prioritize: Critical > High > Medium
+- If comment posting is deferred, continue with the workflow and note the deferred status
+
+### 6. Summary
+
+Provide a concise adversarial testing summary.
+
+**Constraints:**
+- You MUST create a summary comment or review with an overall assessment
+- You MUST use this format:
+ ```
+ **Adversarial Testing Result**: [PASS — no issues found | FAIL — N issues found]
+
+ **Scope**: [Brief description of what was tested]
+ **Tests written**: [count]
+ **Tests passing**: [count]
+ **Tests failing (findings)**: [count]
+
+
+ Findings Summary
+
+ | # | Category | Severity | Description |
+ |---|----------|----------|-------------|
+ | 1 | Bug | Critical | [one-line description] |
+ | 2 | Edge Case | Medium | [one-line description] |
+
+
+
+ **Artifacts**: [Location of adversarial test files]
+ ```
+- If no issues were found, you MUST explicitly state: "The changes survived adversarial testing. No reproducible issues found."
+- You MUST NOT pad the report with speculative concerns or "things to watch out for"
+
+## What You Do NOT Do
+
+- You do NOT review code quality, style, or architecture
+- You do NOT suggest refactors or improvements
+- You do NOT praise good code
+- You do NOT speculate without evidence
+- You do NOT modify the source code under test
+- You do NOT write tests that test your own test code
+- You do NOT duplicate work the reviewer already covers
+
+## Troubleshooting
+
+### Large PRs
+- Focus on the public API surface and integration points first
+- Prioritize security-sensitive and error-handling paths
+- Skip internal refactors that don't change behavior
+
+### Deferred Operations
+When GitHub tools are deferred (GITHUB_WRITE=false):
+- Continue with the workflow as if the operation succeeded
+- Note the deferred status in your progress tracking
+- The operations will be executed after agent completion
+- Do not retry or attempt alternative approaches for deferred operations
+
+### Unfamiliar Codebase
+- Read `AGENTS.md` and test fixtures to understand mocking patterns
+- Look at existing tests for the modified files to understand expected patterns
+- Use existing test fixtures when writing adversarial tests
+
+### Flaky Tests
+- Run failing tests 3 times before reporting
+- If a test fails intermittently, categorize as "Flaky Behavior" and note the failure rate
+- Ensure your tests don't depend on execution order or global state
+
+## Desired Outcome
+
+* A set of adversarial tests that exercise edge cases, failure modes, and contract violations
+* Concrete, reproducible findings with runnable artifacts
+* A clear pass/fail assessment of the code changes
+* No speculative concerns — only proven issues
diff --git a/strands-command/agent-sops/task-release-digest.sop.md b/strands-command/agent-sops/task-release-digest.sop.md
new file mode 100644
index 0000000..50919cc
--- /dev/null
+++ b/strands-command/agent-sops/task-release-digest.sop.md
@@ -0,0 +1,274 @@
+# Release Digest Orchestrator SOP
+
+## Role
+
+You are a Release Digest Orchestrator. Your goal is to produce a comprehensive weekly release digest by coordinating multiple parallel analysis tasks. You find all changes since the last release, dispatch sub-agents for adversarial testing, release notes generation, and documentation gap analysis, collect their results, and compile everything into a single consolidated digest issue.
+
+You are the coordinator. You dispatch work, collect results, and synthesize findings. You do not do the detailed analysis yourself — you delegate to specialized agents.
+
+## Trigger
+
+- Automated weekly schedule (e.g., Wednesday 10am UTC)
+- `/strands release-digest` on an Issue
+- `workflow_dispatch` with release-digest prompt
+
+## Principles
+
+1. **Orchestrate, don't do.** Your job is coordination and synthesis, not detailed analysis. Delegate to specialized agents.
+2. **Parallel when possible.** Dispatch independent tasks simultaneously to minimize wall-clock time.
+3. **Fail gracefully.** If a sub-agent fails, report what you have. Never block the entire digest on one failure.
+4. **Security first.** Enforce limits on concurrent agents, token budgets, and execution time.
+5. **Single artifact.** Your final output is ONE consolidated digest issue with all findings.
+
+## Security & Limits
+
+### Agent Dispatch Limits
+- **Max concurrent sub-agents**: 3 (configurable via `ORCHESTRATOR_MAX_CONCURRENT`)
+- **Per-agent timeout**: 30 minutes (configurable via `ORCHESTRATOR_AGENT_TIMEOUT_MINUTES`)
+- **Max total sub-agents per run**: 5 (configurable via `ORCHESTRATOR_MAX_TOTAL_AGENTS`)
+- **Cooldown between dispatches**: 10 seconds minimum
+- **Token budget per sub-agent**: 32000 tokens (configurable via `ORCHESTRATOR_AGENT_MAX_TOKENS`)
+
+### Authentication
+- Sub-agent dispatch requires `PAT_TOKEN` with `workflow_dispatch` permission
+- Sub-agents inherit repository-level permissions only
+- No credential passthrough between agents — each authenticates independently
+
+### Rate Limiting
+- Check GitHub API rate limits before dispatching
+- If rate limited, wait and retry with exponential backoff (max 3 retries)
+- Log all dispatch attempts and their outcomes
+
+## Steps
+
+### 1. Discover Changes Since Last Release
+
+Identify the scope of changes to analyze.
+
+**Constraints:**
+- You MUST find the most recent release tag using `git tag --sort=-v:refname | head -1` or GitHub API
+- You MUST identify the current HEAD or target branch
+- You MUST compute the diff between last release and current HEAD:
+ - Count of merged PRs
+ - List of PR numbers and titles
+ - Files changed summary
+ - Contributors involved
+- You MUST handle the case where no previous release exists (use repository creation as baseline)
+- You MUST record the git references (base tag, head ref) for sub-agent inputs
+- You MUST create a progress notebook to track orchestration status
+
+### 2. Plan Sub-Agent Tasks
+
+Determine which sub-agents to dispatch based on the changes found.
+
+**Constraints:**
+- You MUST plan the following sub-agent tasks:
+
+ | Task | Agent Type | Input | Output |
+ |------|-----------|-------|--------|
+ | Adversarial Testing | `adversarial-test` | List of PRs with significant code changes | Findings report per PR |
+ | Release Notes | `release-notes` | Base and head git references | Formatted release notes |
+ | Documentation Gaps | `docs-gap` | List of PRs with new/changed APIs | Missing docs report |
+
+- You MUST skip adversarial testing if there are no code-changing PRs (only docs/CI/test changes)
+- You MUST skip documentation gap analysis if there are no API-changing PRs
+- You MUST record the planned tasks in your notebook with expected inputs/outputs
+- You MUST NOT dispatch more than `ORCHESTRATOR_MAX_TOTAL_AGENTS` sub-agents
+
+### 3. Dispatch Sub-Agents
+
+Dispatch sub-agents for parallel execution.
+
+**Constraints:**
+- You MUST use the orchestrator module (`orchestrator.py`) to dispatch sub-agents
+- You MUST call the `dispatch_agent` function for each planned task
+- You MUST respect the concurrent agent limit — wait for slots before dispatching
+- You MUST wait the minimum cooldown between dispatches
+- You MUST pass appropriate inputs to each sub-agent:
+ - **Adversarial tester**: PR numbers, branch references
+ - **Release notes**: Base tag, head reference, repository
+ - **Docs gap**: PR numbers with API changes, repository docs structure
+- You MUST record each dispatch in the notebook:
+ - Agent type
+ - Dispatch time
+ - Workflow run ID (if available)
+ - Status (dispatched/failed/timed-out)
+- You MUST handle dispatch failures gracefully — log the error and continue with other tasks
+- If dispatch fails for ALL sub-agents, proceed to Step 5 with what information you gathered in Step 1
+
+### 4. Collect Results
+
+Wait for sub-agents to complete and gather their outputs.
+
+**Constraints:**
+- You MUST poll for sub-agent completion using the orchestrator module
+- You MUST enforce the per-agent timeout — if a sub-agent exceeds its timeout, mark it as timed out
+- You MUST collect results from completed sub-agents:
+ - Check for new issues or comments created by the sub-agent
+ - Check for gists created by the sub-agent
+ - Check workflow run logs for output artifacts
+- You MUST handle partial results — if some agents succeed and others fail, use what's available
+- You MUST record collection status in the notebook for each sub-agent
+- You SHOULD wait for all agents to complete before synthesizing, up to the timeout limit
+
+### 5. Synthesize Release Digest
+
+Compile all results into a comprehensive digest.
+
+**Constraints:**
+- You MUST create a single consolidated digest with the following sections:
+
+```markdown
+# 📦 Weekly Release Digest — [Date]
+
+**Period**: [Last Release Tag] → [Current HEAD]
+**PRs Merged**: [count]
+**Contributors**: [list]
+
+---
+
+## 🔍 Changes Overview
+
+[Summary of what changed: features, fixes, refactors, docs]
+
+---
+
+## 🔴 Adversarial Testing Findings
+
+[Results from adversarial tester sub-agent]
+
+| PR | Category | Severity | Finding |
+|----|----------|----------|---------|
+| #123 | Bug | Critical | [description] |
+
+[Or: "All changes passed adversarial testing. No issues found."]
+
+---
+
+## 📝 Release Notes (Draft)
+
+[Results from release notes sub-agent]
+
+### Major Features
+[Features with code examples]
+
+### Major Bug Fixes
+[Bug fixes with impact descriptions]
+
+---
+
+## 📚 Documentation Gaps
+
+[Results from docs gap sub-agent]
+
+| PR | API Change | Missing Documentation |
+|----|------------|----------------------|
+| #456 | New `Agent.stream()` method | No docstring, no usage example |
+
+[Or: "All API changes have adequate documentation."]
+
+---
+
+## ⚠️ Action Items
+
+- [ ] [Critical issue from adversarial testing that needs fixing]
+- [ ] [Missing docs that should be added before release]
+- [ ] [Release notes need review/approval]
+
+---
+
+## 📊 Orchestration Report
+
+| Sub-Agent | Status | Duration | Output |
+|-----------|--------|----------|--------|
+| Adversarial Tester | ✅ Complete | 15m | [link] |
+| Release Notes | ✅ Complete | 8m | [link] |
+| Docs Gap | ⏱️ Timed Out | 30m | Partial results |
+```
+
+- You MUST include results from ALL sub-agents that completed (even partially)
+- You MUST clearly mark which sections had sub-agent failures
+- You MUST list concrete action items for the team
+- You MUST include the orchestration report showing sub-agent status
+
+### 6. Publish Digest
+
+Create the digest as a GitHub issue.
+
+**Constraints:**
+- You MUST create a new GitHub issue with the digest content
+- You MUST use the title format: `📦 Release Digest — [YYYY-MM-DD]`
+- You MUST add appropriate labels (e.g., `release-digest`, `automated`)
+- You MUST include a link to the workflow run for audit trail
+- If issue creation is deferred, continue and note the deferred status
+- You MAY also create a GitHub Gist with the full digest for easier sharing
+- You MUST record the created issue/gist URL in your notebook
+
+## Configuration
+
+### Environment Variables
+
+| Variable | Default | Description |
+|----------|---------|-------------|
+| `ORCHESTRATOR_MAX_CONCURRENT` | `3` | Max sub-agents running simultaneously |
+| `ORCHESTRATOR_AGENT_TIMEOUT_MINUTES` | `30` | Per-agent timeout |
+| `ORCHESTRATOR_MAX_TOTAL_AGENTS` | `5` | Max total sub-agents per orchestration run |
+| `ORCHESTRATOR_AGENT_MAX_TOKENS` | `32000` | Token budget per sub-agent |
+| `ORCHESTRATOR_COOLDOWN_SECONDS` | `10` | Minimum time between dispatches |
+
+### Workflow Variables
+
+The orchestrator reads scheduling configuration from the `AGENT_SCHEDULES` repository variable. Example schedule entry:
+
+```json
+{
+ "jobs": {
+ "weekly_release_digest": {
+ "enabled": true,
+ "cron": "0 10 * * 3",
+ "prompt": "Run the weekly release digest. Find all changes since the last release, dispatch adversarial testing and release notes sub-agents, and compile a comprehensive digest issue.",
+ "system_prompt": "You are a Release Digest Orchestrator following the task-release-digest SOP.",
+ "workflow": "strands-command.yml",
+ "tools": ""
+ }
+ }
+}
+```
+
+## Troubleshooting
+
+### No Changes Found
+If there are no changes since the last release:
+- Create a minimal digest noting "No changes since last release"
+- Skip all sub-agent dispatches
+- Post the minimal digest and exit
+
+### All Sub-Agents Failed
+If all sub-agent dispatches fail:
+- Create the digest with information gathered in Step 1
+- Include the error details in the orchestration report
+- List the failures as action items
+- Mark the digest as "Partial — sub-agent failures"
+
+### Rate Limiting
+If GitHub API rate limits are hit:
+- Log the rate limit status
+- Retry with exponential backoff (1min, 2min, 4min)
+- If still rate limited after 3 retries, proceed with what's available
+
+### Deferred Operations
+When GitHub tools are deferred (GITHUB_WRITE=false):
+- Continue with the workflow as if the operation succeeded
+- Note the deferred status in your progress tracking
+- The operations will be executed after agent completion
+
+## Desired Outcome
+
+* A single, comprehensive release digest issue containing:
+ * Overview of all changes since last release
+ * Adversarial testing findings (or clean bill of health)
+ * Draft release notes with code examples
+ * Documentation gap analysis
+ * Concrete action items for the team
+* All sub-agent results properly collected and synthesized
+* Clear audit trail of orchestration decisions and outcomes
diff --git a/strands-command/scripts/javascript/process-input.cjs b/strands-command/scripts/javascript/process-input.cjs
index 82de3b4..5fb701c 100644
--- a/strands-command/scripts/javascript/process-input.cjs
+++ b/strands-command/scripts/javascript/process-input.cjs
@@ -85,16 +85,27 @@ function buildPrompts(mode, issueId, isPullRequest, command, branchName, inputs)
'implementer': 'devtools/strands-command/agent-sops/task-implementer.sop.md',
'refiner': 'devtools/strands-command/agent-sops/task-refiner.sop.md',
'release-notes': 'devtools/strands-command/agent-sops/task-release-notes.sop.md',
- 'reviewer': 'devtools/strands-command/agent-sops/task-reviewer.sop.md'
+ 'reviewer': 'devtools/strands-command/agent-sops/task-reviewer.sop.md',
+ 'adversarial-test': 'devtools/strands-command/agent-sops/task-adversarial-tester.sop.md',
+ 'release-digest': 'devtools/strands-command/agent-sops/task-release-digest.sop.md'
};
const scriptFile = scriptFiles[mode] || scriptFiles['refiner'];
const systemPrompt = fs.readFileSync(scriptFile, 'utf8');
- let prompt = (isPullRequest)
- ? 'The pull request id is:'
- : 'The issue id is:';
- prompt += `${issueId}\n${command}\nreview and continue`;
+ let prompt;
+ if (mode === 'release-digest') {
+ prompt = `Run the weekly release digest for this repository.\n${command}\nreview and continue`;
+ } else if (mode === 'adversarial-test') {
+ prompt = (isPullRequest)
+ ? `Run adversarial testing on pull request #${issueId}.\n${command}\nreview and continue`
+ : `Run adversarial testing for the changes referenced in issue #${issueId}.\n${command}\nreview and continue`;
+ } else {
+ prompt = (isPullRequest)
+ ? 'The pull request id is:'
+ : 'The issue id is:';
+ prompt += `${issueId}\n${command}\nreview and continue`;
+ }
return { sessionId, systemPrompt, prompt };
}
@@ -107,7 +118,11 @@ module.exports = async (context, github, core, inputs) => {
// Determine mode based on explicit command first, then context
let mode;
- if (command.startsWith('release-notes') || command.startsWith('release notes')) {
+ if (command.startsWith('release-digest') || command.startsWith('digest')) {
+ mode = 'release-digest';
+ } else if (command.startsWith('adversarial-test') || command.startsWith('adversarial')) {
+ mode = 'adversarial-test';
+ } else if (command.startsWith('release-notes') || command.startsWith('release notes')) {
mode = 'release-notes';
} else if (command.startsWith('implement')) {
mode = 'implementer';
diff --git a/strands-command/scripts/python/agent_runner.py b/strands-command/scripts/python/agent_runner.py
index f3f3d93..44bedbe 100644
--- a/strands-command/scripts/python/agent_runner.py
+++ b/strands-command/scripts/python/agent_runner.py
@@ -41,6 +41,14 @@
# Import local tools we need
from handoff_to_user import handoff_to_user
+
+# Import orchestrator tools for agent-to-agent coordination
+try:
+ from orchestrator import dispatch_agent, check_agents_status, wait_for_agents, get_orchestrator_config
+ _ORCHESTRATOR_AVAILABLE = True
+except ImportError:
+ _ORCHESTRATOR_AVAILABLE = False
+ print("ℹ️ Orchestrator tools not available (orchestrator.py not found)")
from notebook import notebook
from str_replace_based_edit_tool import str_replace_based_edit_tool
@@ -179,6 +187,10 @@ def _get_all_tools() -> list[Any]:
notebook,
handoff_to_user,
]
+
+ # Add orchestrator tools if available
+ if _ORCHESTRATOR_AVAILABLE:
+ tools.extend([dispatch_agent, check_agents_status, wait_for_agents, get_orchestrator_config])
def run_agent(query: str):
diff --git a/strands-command/scripts/python/orchestrator.py b/strands-command/scripts/python/orchestrator.py
new file mode 100644
index 0000000..81b3d34
--- /dev/null
+++ b/strands-command/scripts/python/orchestrator.py
@@ -0,0 +1,589 @@
+"""Agent Orchestrator for Strands Command.
+
+Enables agents to dispatch and coordinate sub-agents via GitHub Actions
+workflow_dispatch events. Provides security limits, rate limiting, and
+result collection for multi-agent orchestration.
+
+Key Features:
+1. Sub-agent dispatch via GitHub workflow_dispatch API
+2. Configurable security limits (concurrency, token budgets, timeouts)
+3. Rate limiting with cooldown between dispatches
+4. Result collection via workflow run polling
+5. Graceful failure handling for partial results
+
+Usage:
+ from orchestrator import AgentOrchestrator
+
+ orch = AgentOrchestrator(repo="owner/repo")
+ run_id = orch.dispatch_agent(
+ agent_type="adversarial-test",
+ prompt="Test PR #123 for edge cases",
+ system_prompt="You are an adversarial tester...",
+ )
+ result = orch.wait_for_completion(run_id, timeout_minutes=30)
+
+Security:
+ - Max concurrent agents: ORCHESTRATOR_MAX_CONCURRENT (default: 3)
+ - Max total agents per run: ORCHESTRATOR_MAX_TOTAL_AGENTS (default: 5)
+ - Per-agent timeout: ORCHESTRATOR_AGENT_TIMEOUT_MINUTES (default: 30)
+ - Cooldown between dispatches: ORCHESTRATOR_COOLDOWN_SECONDS (default: 10)
+ - Token budget: ORCHESTRATOR_AGENT_MAX_TOKENS (default: 32000)
+"""
+
+import json
+import os
+import time
+from dataclasses import dataclass, field
+from datetime import datetime, timezone
+from enum import Enum
+from typing import Any
+
+import requests
+from strands import tool
+
+
+class AgentStatus(str, Enum):
+ """Status of a dispatched sub-agent."""
+ PENDING = "pending"
+ DISPATCHED = "dispatched"
+ RUNNING = "running"
+ COMPLETED = "completed"
+ FAILED = "failed"
+ TIMED_OUT = "timed_out"
+
+
+@dataclass
+class AgentTask:
+ """Represents a dispatched sub-agent task."""
+ agent_type: str
+ prompt: str
+ system_prompt: str = ""
+ status: AgentStatus = AgentStatus.PENDING
+ run_id: str | None = None
+ dispatch_time: datetime | None = None
+ completion_time: datetime | None = None
+ result: str | None = None
+ error: str | None = None
+ workflow: str = "strands-command.yml"
+
+
+@dataclass
+class OrchestratorConfig:
+ """Configuration for the agent orchestrator."""
+ max_concurrent: int = 3
+ max_total_agents: int = 5
+ agent_timeout_minutes: int = 30
+ agent_max_tokens: int = 32000
+ cooldown_seconds: int = 10
+ poll_interval_seconds: int = 30
+
+ @classmethod
+ def from_env(cls) -> "OrchestratorConfig":
+ """Create config from environment variables."""
+ return cls(
+ max_concurrent=int(os.getenv("ORCHESTRATOR_MAX_CONCURRENT", "3")),
+ max_total_agents=int(os.getenv("ORCHESTRATOR_MAX_TOTAL_AGENTS", "5")),
+ agent_timeout_minutes=int(os.getenv("ORCHESTRATOR_AGENT_TIMEOUT_MINUTES", "30")),
+ agent_max_tokens=int(os.getenv("ORCHESTRATOR_AGENT_MAX_TOKENS", "32000")),
+ cooldown_seconds=int(os.getenv("ORCHESTRATOR_COOLDOWN_SECONDS", "10")),
+ poll_interval_seconds=int(os.getenv("ORCHESTRATOR_POLL_INTERVAL_SECONDS", "30")),
+ )
+
+
+class AgentOrchestrator:
+ """Orchestrates sub-agent dispatch and result collection.
+
+ Provides security-limited agent-to-agent coordination via
+ GitHub Actions workflow_dispatch events.
+ """
+
+ def __init__(
+ self,
+ repo: str | None = None,
+ config: OrchestratorConfig | None = None,
+ ):
+ self.repo = repo or os.getenv("GITHUB_REPOSITORY", "")
+ self.config = config or OrchestratorConfig.from_env()
+ self.token = os.getenv("PAT_TOKEN", os.getenv("GITHUB_TOKEN", ""))
+ self.tasks: list[AgentTask] = []
+ self._last_dispatch_time: float = 0
+ self._total_dispatched: int = 0
+
+ if not self.repo:
+ raise ValueError("Repository not specified and GITHUB_REPOSITORY not set")
+ if not self.token:
+ raise ValueError("No GitHub token available (PAT_TOKEN or GITHUB_TOKEN)")
+
+ @property
+ def _headers(self) -> dict[str, str]:
+ return {
+ "Authorization": f"Bearer {self.token}",
+ "Accept": "application/vnd.github+json",
+ "X-GitHub-Api-Version": "2022-11-28",
+ }
+
+ def _check_rate_limit(self) -> bool:
+ """Check if we're within GitHub API rate limits."""
+ try:
+ resp = requests.get(
+ "https://api.github.com/rate_limit",
+ headers=self._headers,
+ timeout=10,
+ )
+ resp.raise_for_status()
+ data = resp.json()
+ remaining = data.get("resources", {}).get("core", {}).get("remaining", 0)
+ return remaining > 10 # Keep a buffer
+ except Exception:
+ return True # Optimistic on failure
+
+ def _enforce_cooldown(self) -> None:
+ """Enforce minimum cooldown between dispatches."""
+ elapsed = time.time() - self._last_dispatch_time
+ if elapsed < self.config.cooldown_seconds:
+ wait_time = self.config.cooldown_seconds - elapsed
+ print(f"⏳ Cooldown: waiting {wait_time:.1f}s before next dispatch")
+ time.sleep(wait_time)
+
+ def _get_active_count(self) -> int:
+ """Count currently active (dispatched or running) agents."""
+ return sum(
+ 1 for t in self.tasks
+ if t.status in (AgentStatus.DISPATCHED, AgentStatus.RUNNING)
+ )
+
+ def can_dispatch(self) -> bool:
+ """Check if we can dispatch another agent."""
+ if self._total_dispatched >= self.config.max_total_agents:
+ print(f"⚠️ Total agent limit reached ({self.config.max_total_agents})")
+ return False
+ if self._get_active_count() >= self.config.max_concurrent:
+ print(f"⚠️ Concurrent agent limit reached ({self.config.max_concurrent})")
+ return False
+ if not self._check_rate_limit():
+ print("⚠️ GitHub API rate limit approaching")
+ return False
+ return True
+
+ def dispatch_agent(
+ self,
+ agent_type: str,
+ prompt: str,
+ system_prompt: str = "",
+ workflow: str = "strands-command.yml",
+ extra_inputs: dict[str, str] | None = None,
+ ) -> AgentTask:
+ """Dispatch a sub-agent via workflow_dispatch.
+
+ Args:
+ agent_type: Type of agent (e.g., "adversarial-test", "release-notes")
+ prompt: Task prompt for the sub-agent
+ system_prompt: System prompt override for the sub-agent
+ workflow: Target workflow file (default: strands-command.yml)
+ extra_inputs: Additional workflow inputs
+
+ Returns:
+ AgentTask with dispatch status
+
+ Raises:
+ RuntimeError: If dispatch limits are exceeded
+ """
+ task = AgentTask(
+ agent_type=agent_type,
+ prompt=prompt,
+ system_prompt=system_prompt,
+ workflow=workflow,
+ )
+
+ # Security checks
+ if not self.can_dispatch():
+ task.status = AgentStatus.FAILED
+ task.error = "Dispatch limit exceeded"
+ self.tasks.append(task)
+ return task
+
+ # Enforce cooldown
+ self._enforce_cooldown()
+
+ # Parse workflow target (same-repo or cross-repo)
+ if workflow.count("/") >= 2:
+ # Cross-repo: "owner/repo/workflow.yml"
+ parts = workflow.split("/", 2)
+ dispatch_repo = f"{parts[0]}/{parts[1]}"
+ dispatch_workflow = parts[2]
+ else:
+ dispatch_repo = self.repo
+ dispatch_workflow = workflow
+
+ # Build inputs
+ inputs: dict[str, str] = {
+ "command": prompt,
+ }
+ if system_prompt:
+ inputs["system_prompt"] = system_prompt
+
+ if extra_inputs:
+ inputs.update(extra_inputs)
+
+ # Dispatch via GitHub API
+ url = f"https://api.github.com/repos/{dispatch_repo}/actions/workflows/{dispatch_workflow}/dispatches"
+ payload = {
+ "ref": "main",
+ "inputs": inputs,
+ }
+
+ try:
+ print(f"🚀 Dispatching {agent_type} agent to {dispatch_repo}/{dispatch_workflow}")
+ resp = requests.post(
+ url,
+ headers=self._headers,
+ json=payload,
+ timeout=30,
+ )
+
+ if resp.status_code == 204:
+ task.status = AgentStatus.DISPATCHED
+ task.dispatch_time = datetime.now(timezone.utc)
+ self._last_dispatch_time = time.time()
+ self._total_dispatched += 1
+ print(f"✅ Dispatched {agent_type} agent successfully")
+
+ # Try to find the run ID (poll recent runs)
+ time.sleep(2) # Brief wait for GitHub to register the run
+ run_id = self._find_recent_run(dispatch_repo, dispatch_workflow)
+ if run_id:
+ task.run_id = run_id
+ print(f" Run ID: {run_id}")
+ else:
+ task.status = AgentStatus.FAILED
+ task.error = f"HTTP {resp.status_code}: {resp.text}"
+ print(f"❌ Dispatch failed: {task.error}")
+
+ except Exception as e:
+ task.status = AgentStatus.FAILED
+ task.error = str(e)
+ print(f"❌ Dispatch error: {e}")
+
+ self.tasks.append(task)
+ return task
+
+ def _find_recent_run(self, repo: str, workflow: str) -> str | None:
+ """Find the most recent workflow run (just dispatched)."""
+ try:
+ url = f"https://api.github.com/repos/{repo}/actions/workflows/{workflow}/runs"
+ resp = requests.get(
+ url,
+ headers=self._headers,
+ params={"per_page": 1, "status": "queued"},
+ timeout=10,
+ )
+ resp.raise_for_status()
+ runs = resp.json().get("workflow_runs", [])
+ if runs:
+ return str(runs[0]["id"])
+ except Exception:
+ pass
+ return None
+
+ def check_run_status(self, task: AgentTask) -> AgentStatus:
+ """Check the status of a dispatched agent's workflow run.
+
+ Args:
+ task: The agent task to check
+
+ Returns:
+ Updated AgentStatus
+ """
+ if not task.run_id:
+ return task.status
+
+ try:
+ url = f"https://api.github.com/repos/{self.repo}/actions/runs/{task.run_id}"
+ resp = requests.get(url, headers=self._headers, timeout=10)
+ resp.raise_for_status()
+ run_data = resp.json()
+
+ status = run_data.get("status", "")
+ conclusion = run_data.get("conclusion", "")
+
+ if status == "completed":
+ if conclusion == "success":
+ task.status = AgentStatus.COMPLETED
+ task.completion_time = datetime.now(timezone.utc)
+ else:
+ task.status = AgentStatus.FAILED
+ task.error = f"Workflow concluded: {conclusion}"
+ task.completion_time = datetime.now(timezone.utc)
+ elif status in ("queued", "in_progress"):
+ task.status = AgentStatus.RUNNING
+
+ # Check timeout
+ if task.dispatch_time:
+ elapsed = (datetime.now(timezone.utc) - task.dispatch_time).total_seconds()
+ if elapsed > self.config.agent_timeout_minutes * 60:
+ task.status = AgentStatus.TIMED_OUT
+ task.error = f"Exceeded {self.config.agent_timeout_minutes}m timeout"
+ task.completion_time = datetime.now(timezone.utc)
+
+ except Exception as e:
+ print(f"⚠️ Status check failed for run {task.run_id}: {e}")
+
+ return task.status
+
+ def wait_for_all(self, timeout_minutes: int | None = None) -> list[AgentTask]:
+ """Wait for all dispatched agents to complete.
+
+ Args:
+ timeout_minutes: Overall timeout (default: config.agent_timeout_minutes)
+
+ Returns:
+ List of all tasks with final statuses
+ """
+ timeout = (timeout_minutes or self.config.agent_timeout_minutes) * 60
+ start_time = time.time()
+
+ active_tasks = [
+ t for t in self.tasks
+ if t.status in (AgentStatus.DISPATCHED, AgentStatus.RUNNING)
+ ]
+
+ while active_tasks and (time.time() - start_time) < timeout:
+ for task in active_tasks:
+ self.check_run_status(task)
+
+ active_tasks = [
+ t for t in self.tasks
+ if t.status in (AgentStatus.DISPATCHED, AgentStatus.RUNNING)
+ ]
+
+ if active_tasks:
+ elapsed = int(time.time() - start_time)
+ print(f"⏳ Waiting for {len(active_tasks)} agent(s)... ({elapsed}s elapsed)")
+ time.sleep(self.config.poll_interval_seconds)
+
+ # Mark remaining active tasks as timed out
+ for task in active_tasks:
+ if task.status in (AgentStatus.DISPATCHED, AgentStatus.RUNNING):
+ task.status = AgentStatus.TIMED_OUT
+ task.error = "Overall orchestration timeout exceeded"
+ task.completion_time = datetime.now(timezone.utc)
+
+ return self.tasks
+
+ def get_summary(self) -> dict[str, Any]:
+ """Get a summary of all orchestrated tasks.
+
+ Returns:
+ Dictionary with task counts and details
+ """
+ summary = {
+ "total": len(self.tasks),
+ "completed": sum(1 for t in self.tasks if t.status == AgentStatus.COMPLETED),
+ "failed": sum(1 for t in self.tasks if t.status == AgentStatus.FAILED),
+ "timed_out": sum(1 for t in self.tasks if t.status == AgentStatus.TIMED_OUT),
+ "tasks": [],
+ }
+
+ for task in self.tasks:
+ duration = None
+ if task.dispatch_time and task.completion_time:
+ duration = (task.completion_time - task.dispatch_time).total_seconds()
+
+ summary["tasks"].append({
+ "agent_type": task.agent_type,
+ "status": task.status.value,
+ "run_id": task.run_id,
+ "duration_seconds": duration,
+ "error": task.error,
+ })
+
+ return summary
+
+ def format_report(self) -> str:
+ """Format a markdown report of orchestration results.
+
+ Returns:
+ Markdown-formatted orchestration report
+ """
+ summary = self.get_summary()
+ lines = [
+ "## 📊 Orchestration Report",
+ "",
+ f"**Total agents**: {summary['total']} | "
+ f"**Completed**: {summary['completed']} | "
+ f"**Failed**: {summary['failed']} | "
+ f"**Timed out**: {summary['timed_out']}",
+ "",
+ "| Sub-Agent | Status | Duration | Run ID | Error |",
+ "|-----------|--------|----------|--------|-------|",
+ ]
+
+ status_icons = {
+ "completed": "✅",
+ "failed": "❌",
+ "timed_out": "⏱️",
+ "dispatched": "🚀",
+ "running": "🔄",
+ "pending": "⏳",
+ }
+
+ for task_info in summary["tasks"]:
+ icon = status_icons.get(task_info["status"], "❓")
+ duration = ""
+ if task_info["duration_seconds"]:
+ mins = int(task_info["duration_seconds"] // 60)
+ secs = int(task_info["duration_seconds"] % 60)
+ duration = f"{mins}m {secs}s"
+
+ error = task_info["error"] or ""
+ if len(error) > 40:
+ error = error[:40] + "..."
+
+ lines.append(
+ f"| {task_info['agent_type']} | {icon} {task_info['status']} | "
+ f"{duration} | {task_info['run_id'] or 'N/A'} | {error} |"
+ )
+
+ return "\n".join(lines)
+
+
+# =============================================================================
+# Tool interface for agent usage
+# =============================================================================
+
+# Module-level orchestrator instance (lazy-initialized)
+_orchestrator: AgentOrchestrator | None = None
+
+
+def _get_orchestrator() -> AgentOrchestrator:
+ """Get or create the module-level orchestrator instance."""
+ global _orchestrator
+ if _orchestrator is None:
+ _orchestrator = AgentOrchestrator()
+ return _orchestrator
+
+
+@tool
+def dispatch_agent(
+ agent_type: str,
+ prompt: str,
+ system_prompt: str = "",
+ workflow: str = "strands-command.yml",
+) -> str:
+ """Dispatch a sub-agent via GitHub Actions workflow_dispatch.
+
+ Security limits are enforced:
+ - Max concurrent agents (default: 3)
+ - Max total agents per run (default: 5)
+ - Cooldown between dispatches (default: 10s)
+ - Rate limit checking
+
+ Args:
+ agent_type: Type of agent (e.g., "adversarial-test", "release-notes", "docs-gap")
+ prompt: Task prompt for the sub-agent
+ system_prompt: System prompt override (optional)
+ workflow: Target workflow file (default: strands-command.yml).
+ Use "owner/repo/workflow.yml" for cross-repo dispatch.
+
+ Returns:
+ Status message with dispatch result
+ """
+ try:
+ orch = _get_orchestrator()
+ task = orch.dispatch_agent(
+ agent_type=agent_type,
+ prompt=prompt,
+ system_prompt=system_prompt,
+ workflow=workflow,
+ )
+
+ if task.status == AgentStatus.DISPATCHED:
+ return (
+ f"✅ Agent dispatched: {agent_type}\n"
+ f" Run ID: {task.run_id or 'pending'}\n"
+ f" Workflow: {workflow}\n"
+ f" Active agents: {orch._get_active_count()}/{orch.config.max_concurrent}\n"
+ f" Total dispatched: {orch._total_dispatched}/{orch.config.max_total_agents}"
+ )
+ else:
+ return f"❌ Dispatch failed: {task.error}"
+
+ except Exception as e:
+ return f"❌ Orchestrator error: {e}"
+
+
+@tool
+def check_agents_status() -> str:
+ """Check the status of all dispatched sub-agents.
+
+ Returns:
+ Markdown-formatted status report
+ """
+ try:
+ orch = _get_orchestrator()
+
+ if not orch.tasks:
+ return "No sub-agents have been dispatched yet."
+
+ # Update statuses
+ for task in orch.tasks:
+ if task.status in (AgentStatus.DISPATCHED, AgentStatus.RUNNING):
+ orch.check_run_status(task)
+
+ return orch.format_report()
+
+ except Exception as e:
+ return f"❌ Status check error: {e}"
+
+
+@tool
+def wait_for_agents(timeout_minutes: int = 30) -> str:
+ """Wait for all dispatched sub-agents to complete.
+
+ Polls GitHub Actions API at regular intervals until all agents
+ complete, fail, or timeout.
+
+ Args:
+ timeout_minutes: Maximum time to wait (default: 30)
+
+ Returns:
+ Final orchestration report
+ """
+ try:
+ orch = _get_orchestrator()
+
+ if not orch.tasks:
+ return "No sub-agents have been dispatched yet."
+
+ active = [t for t in orch.tasks if t.status in (AgentStatus.DISPATCHED, AgentStatus.RUNNING)]
+ if not active:
+ return "All sub-agents have already completed.\n\n" + orch.format_report()
+
+ print(f"⏳ Waiting for {len(active)} agent(s) with {timeout_minutes}m timeout...")
+ orch.wait_for_all(timeout_minutes=timeout_minutes)
+
+ return orch.format_report()
+
+ except Exception as e:
+ return f"❌ Wait error: {e}"
+
+
+@tool
+def get_orchestrator_config() -> str:
+ """Get the current orchestrator security configuration.
+
+ Returns:
+ Current configuration values
+ """
+ config = OrchestratorConfig.from_env()
+ return (
+ f"## Orchestrator Configuration\n\n"
+ f"| Setting | Value |\n"
+ f"|---------|-------|\n"
+ f"| Max concurrent agents | {config.max_concurrent} |\n"
+ f"| Max total agents per run | {config.max_total_agents} |\n"
+ f"| Agent timeout | {config.agent_timeout_minutes} minutes |\n"
+ f"| Agent max tokens | {config.agent_max_tokens} |\n"
+ f"| Cooldown between dispatches | {config.cooldown_seconds} seconds |\n"
+ f"| Poll interval | {config.poll_interval_seconds} seconds |\n"
+ )
diff --git a/strands-command/workflows/strands-autonomous.yml b/strands-command/workflows/strands-autonomous.yml
new file mode 100644
index 0000000..0a77fda
--- /dev/null
+++ b/strands-command/workflows/strands-autonomous.yml
@@ -0,0 +1,263 @@
+# Strands Autonomous Agent Workflow Template
+#
+# This is a TEMPLATE workflow for repositories that want autonomous agent
+# capabilities. Copy this file to your repository's .github/workflows/
+# directory and configure the secrets and variables.
+#
+# Features:
+# - Scheduled autonomous runs (e.g., weekly release digests)
+# - Workflow dispatch for manual agent triggers
+# - Agent orchestration for sub-agent parallelization
+# - Security controls (authorization, rate limiting, timeouts)
+#
+# Required Secrets:
+# AWS_ROLE_ARN: IAM role for AWS access
+# AWS_SECRETS_MANAGER_SECRET_ID: Secret containing agent config
+# PAT_TOKEN: Personal access token for workflow dispatch (sub-agents)
+#
+# Required Variables:
+# AGENT_SCHEDULES: JSON schedule configuration (see control loop template)
+#
+# Optional Variables:
+# ORCHESTRATOR_MAX_CONCURRENT: Max concurrent sub-agents (default: 3)
+# ORCHESTRATOR_AGENT_TIMEOUT_MINUTES: Per-agent timeout (default: 30)
+# ORCHESTRATOR_MAX_TOTAL_AGENTS: Max total sub-agents per run (default: 5)
+
+name: Strands Autonomous Agent
+
+on:
+ # Weekly release digest: Wednesdays at 10am UTC
+ schedule:
+ - cron: '0 10 * * 3'
+
+ # Manual trigger with configurable inputs
+ workflow_dispatch:
+ inputs:
+ issue_id:
+ description: 'Issue ID to process'
+ required: false
+ type: string
+ command:
+ description: 'Agent command (e.g., "release-digest", "adversarial-test")'
+ required: false
+ type: string
+ default: 'release-digest'
+ session_id:
+ description: 'Session ID for resuming'
+ required: false
+ type: string
+ default: ''
+
+permissions: write-all
+
+jobs:
+ # ─────────────────────────────────────────────────────────
+ # Authorization Gate
+ # ─────────────────────────────────────────────────────────
+ authorization-check:
+ if: >-
+ github.event_name == 'schedule' ||
+ github.event_name == 'workflow_dispatch'
+ name: Check access
+ permissions: read-all
+ runs-on: ubuntu-latest
+ outputs:
+ approval-env: ${{ steps.auth.outputs.approval-env }}
+ steps:
+ - name: Check Authorization
+ id: auth
+ shell: bash
+ run: |
+ # Scheduled runs and workflow_dispatch are always authorized
+ if [ "${{ github.event_name }}" = "schedule" ]; then
+ echo "✅ Scheduled run - authorized"
+ echo "approval-env=" >> $GITHUB_OUTPUT
+ exit 0
+ fi
+
+ if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then
+ echo "✅ Workflow dispatch - authorized"
+ echo "approval-env=" >> $GITHUB_OUTPUT
+ exit 0
+ fi
+
+ echo "❌ Unknown trigger"
+ exit 1
+
+ # ─────────────────────────────────────────────────────────
+ # Determine Agent Type from Schedule/Command
+ # ─────────────────────────────────────────────────────────
+ setup:
+ needs: [authorization-check]
+ runs-on: ubuntu-latest
+ outputs:
+ agent_type: ${{ steps.determine.outputs.agent_type }}
+ system_prompt: ${{ steps.determine.outputs.system_prompt }}
+ task_prompt: ${{ steps.determine.outputs.task_prompt }}
+ session_id: ${{ steps.determine.outputs.session_id }}
+ steps:
+ - name: Determine agent type
+ id: determine
+ shell: bash
+ run: |
+ COMMAND="${{ github.event.inputs.command || 'release-digest' }}"
+ SESSION_ID="${{ github.event.inputs.session_id }}"
+ ISSUE_ID="${{ github.event.inputs.issue_id }}"
+
+ # Generate session ID if not provided
+ if [ -z "$SESSION_ID" ]; then
+ SESSION_ID="${COMMAND}-$(date +%s)"
+ fi
+ echo "session_id=$SESSION_ID" >> $GITHUB_OUTPUT
+
+ case "$COMMAND" in
+ release-digest|digest)
+ echo "agent_type=release-digest" >> $GITHUB_OUTPUT
+ echo "system_prompt<> $GITHUB_OUTPUT
+ cat << 'SOP_EOF' >> $GITHUB_OUTPUT
+ You are a Release Digest Orchestrator following the task-release-digest SOP.
+ Your goal is to produce a comprehensive weekly release digest by coordinating
+ multiple parallel analysis tasks. You find all changes since the last release,
+ dispatch sub-agents for adversarial testing and release notes generation,
+ collect their results, and compile everything into a single consolidated digest issue.
+ SOP_EOF
+ echo "PROMPT_EOF" >> $GITHUB_OUTPUT
+
+ echo "task_prompt<> $GITHUB_OUTPUT
+ echo "Run the weekly release digest. Find all changes since the last release tag, analyze merged PRs, dispatch adversarial testing and release notes sub-agents, and compile a comprehensive digest issue with findings, draft release notes, and action items." >> $GITHUB_OUTPUT
+ echo "TASK_EOF" >> $GITHUB_OUTPUT
+ ;;
+
+ adversarial-test|adversarial)
+ echo "agent_type=adversarial-test" >> $GITHUB_OUTPUT
+ echo "system_prompt<> $GITHUB_OUTPUT
+ cat << 'SOP_EOF' >> $GITHUB_OUTPUT
+ You are an Adversarial Tester following the task-adversarial-tester SOP.
+ Your goal is to break code changes by actively finding bugs, edge cases,
+ security holes, and failure modes. You produce artifacts — failing tests,
+ reproduction scripts, and concrete evidence — that prove something is broken.
+ SOP_EOF
+ echo "PROMPT_EOF" >> $GITHUB_OUTPUT
+
+ TASK="Run adversarial testing on recent changes."
+ if [ -n "$ISSUE_ID" ]; then
+ TASK="Run adversarial testing for issue #${ISSUE_ID}. Check the referenced PR for code changes to test."
+ fi
+ echo "task_prompt=$TASK" >> $GITHUB_OUTPUT
+ ;;
+
+ release-notes)
+ echo "agent_type=release-notes" >> $GITHUB_OUTPUT
+ echo "system_prompt<> $GITHUB_OUTPUT
+ cat << 'SOP_EOF' >> $GITHUB_OUTPUT
+ You are a Release Notes Generator following the task-release-notes SOP.
+ Your goal is to create high-quality release notes highlighting Major Features
+ and Major Bug Fixes. Analyze merged PRs, extract code examples, validate them,
+ and format into well-structured markdown.
+ SOP_EOF
+ echo "PROMPT_EOF" >> $GITHUB_OUTPUT
+
+ echo "task_prompt=Generate release notes for the latest unreleased changes. Find the most recent release tag and analyze all merged PRs since then." >> $GITHUB_OUTPUT
+ ;;
+
+ *)
+ echo "agent_type=custom" >> $GITHUB_OUTPUT
+ echo "system_prompt=You are an autonomous GitHub agent." >> $GITHUB_OUTPUT
+ echo "task_prompt=$COMMAND" >> $GITHUB_OUTPUT
+ ;;
+ esac
+
+ echo "📋 Agent type: $(cat $GITHUB_OUTPUT | grep agent_type | cut -d= -f2)"
+
+ # ─────────────────────────────────────────────────────────
+ # Execute Agent
+ # ─────────────────────────────────────────────────────────
+ execute-agent:
+ needs: [setup]
+ permissions:
+ contents: write
+ issues: write
+ pull-requests: write
+ id-token: write
+ runs-on: ubuntu-latest
+ timeout-minutes: 60
+ env:
+ # Orchestrator security limits
+ ORCHESTRATOR_MAX_CONCURRENT: ${{ vars.ORCHESTRATOR_MAX_CONCURRENT || '3' }}
+ ORCHESTRATOR_AGENT_TIMEOUT_MINUTES: ${{ vars.ORCHESTRATOR_AGENT_TIMEOUT_MINUTES || '30' }}
+ ORCHESTRATOR_MAX_TOTAL_AGENTS: ${{ vars.ORCHESTRATOR_MAX_TOTAL_AGENTS || '5' }}
+ steps:
+ - name: Checkout repository
+ uses: actions/checkout@v4
+
+ # Checkout devtools for scripts and SOPs
+ - name: Checkout devtools
+ uses: actions/checkout@v5
+ with:
+ repository: strands-agents/devtools
+ ref: main
+ sparse-checkout: |
+ strands-command/scripts
+ strands-command/agent-sops
+ path: devtools
+
+ - name: Copy devtools to safe directory
+ shell: bash
+ run: |
+ mkdir -p ${{ runner.temp }}/strands-agent-runner
+ cp -r devtools/strands-command ${{ runner.temp }}/strands-agent-runner/
+
+ - name: Set up Python
+ uses: actions/setup-python@v4
+ with:
+ python-version: '3.13'
+
+ - name: Install uv
+ uses: astral-sh/setup-uv@v3
+
+ - name: Install dependencies
+ shell: bash
+ run: |
+ uv pip install --system -r ${{ runner.temp }}/strands-agent-runner/strands-command/scripts/python/requirements.txt --quiet
+
+ - name: Configure Git
+ shell: bash
+ run: |
+ git config --global user.name "Strands Agent"
+ git config --global user.email "217235299+strands-agent@users.noreply.github.com"
+ git config --global core.pager cat
+
+ - name: Configure AWS credentials
+ uses: aws-actions/configure-aws-credentials@v4
+ with:
+ role-to-assume: ${{ secrets.AWS_ROLE_ARN }}
+ role-session-name: GitHubActions-StrandsAutonomous-${{ github.run_id }}
+ aws-region: us-west-2
+ mask-aws-account-id: true
+
+ - name: Retrieve secrets
+ id: secrets
+ shell: bash
+ run: |
+ if [ -n "${{ secrets.AWS_SECRETS_MANAGER_SECRET_ID }}" ]; then
+ SECRET_JSON=$(aws secretsmanager get-secret-value --secret-id "${{ secrets.AWS_SECRETS_MANAGER_SECRET_ID }}" --query SecretString --output text --region us-east-1)
+ SESSIONS_BUCKET=$(echo $SECRET_JSON | jq -r '.AGENT_SESSIONS_BUCKET // empty')
+ [ -n "$SESSIONS_BUCKET" ] && echo "::add-mask::$SESSIONS_BUCKET"
+ echo "sessions_bucket=$SESSIONS_BUCKET" >> $GITHUB_OUTPUT
+ fi
+
+ - name: Execute autonomous agent
+ shell: bash
+ env:
+ GITHUB_WRITE: 'true'
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ PAT_TOKEN: ${{ secrets.PAT_TOKEN }}
+ S3_SESSION_BUCKET: ${{ steps.secrets.outputs.sessions_bucket }}
+ SESSION_ID: ${{ needs.setup.outputs.session_id }}
+ INPUT_SYSTEM_PROMPT: ${{ needs.setup.outputs.system_prompt }}
+ INPUT_TASK: ${{ needs.setup.outputs.task_prompt }}
+ STRANDS_TOOL_CONSOLE_MODE: 'enabled'
+ BYPASS_TOOL_CONSENT: 'true'
+ run: |
+ echo "🤖 Running autonomous agent: ${{ needs.setup.outputs.agent_type }}"
+ uv run --no-project ${{ runner.temp }}/strands-agent-runner/strands-command/scripts/python/agent_runner.py "$INPUT_TASK"
diff --git a/strands-command/workflows/strands-control.yml b/strands-command/workflows/strands-control.yml
new file mode 100644
index 0000000..c8efed1
--- /dev/null
+++ b/strands-command/workflows/strands-control.yml
@@ -0,0 +1,205 @@
+# Strands Control Loop Workflow Template
+#
+# Hourly control loop that checks AGENT_SCHEDULES repository variable
+# and dispatches autonomous agent workflows when jobs are due.
+#
+# This is a TEMPLATE. Copy to .github/workflows/ in your repository.
+#
+# Required Variables:
+# AGENT_SCHEDULES: JSON with job definitions (see format below)
+#
+# Required Secrets:
+# PAT_TOKEN: Personal access token for workflow dispatch
+#
+# Schedule Format (AGENT_SCHEDULES variable):
+# {
+# "jobs": {
+# "weekly_digest": {
+# "enabled": true,
+# "cron": "0 10 * * 3",
+# "prompt": "Run the weekly release digest",
+# "system_prompt": "You are a Release Digest Orchestrator",
+# "workflow": "strands-autonomous.yml",
+# "last_triggered": 0
+# }
+# }
+# }
+
+name: Strands Control Loop
+
+on:
+ schedule:
+ - cron: '0 * * * *' # Every hour
+ workflow_dispatch:
+ inputs:
+ force_check:
+ description: 'Force check schedules regardless of time'
+ required: false
+ type: boolean
+ default: false
+
+permissions:
+ contents: read
+ actions: write
+
+jobs:
+ control:
+ runs-on: ubuntu-latest
+ steps:
+ - name: Check Schedules and Dispatch Jobs
+ env:
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ PAT_TOKEN: ${{ secrets.PAT_TOKEN }}
+ AGENT_SCHEDULES: ${{ vars.AGENT_SCHEDULES }}
+ run: |
+ #!/bin/bash
+ set -e
+
+ echo "🕐 Control Loop - $(date -u '+%Y-%m-%d %H:%M') UTC"
+ echo "Repository: ${{ github.repository }}"
+
+ if [ -z "$AGENT_SCHEDULES" ] || [ "$AGENT_SCHEDULES" = "{}" ]; then
+ echo "ℹ️ No schedules configured (AGENT_SCHEDULES is empty)"
+ exit 0
+ fi
+
+ CURRENT_MINUTE=$(date -u '+%M' | sed 's/^0//')
+ CURRENT_HOUR=$(date -u '+%H' | sed 's/^0//')
+ CURRENT_DAY=$(date -u '+%d' | sed 's/^0//')
+ CURRENT_MONTH=$(date -u '+%m' | sed 's/^0//')
+ CURRENT_DOW=$(date -u '+%w')
+ CURRENT_EPOCH=$(date -u '+%s')
+
+ [ -z "$CURRENT_MINUTE" ] && CURRENT_MINUTE=0
+ [ -z "$CURRENT_HOUR" ] && CURRENT_HOUR=0
+ [ -z "$CURRENT_DAY" ] && CURRENT_DAY=1
+ [ -z "$CURRENT_MONTH" ] && CURRENT_MONTH=1
+
+ echo "Current time: minute=$CURRENT_MINUTE hour=$CURRENT_HOUR day=$CURRENT_DAY month=$CURRENT_MONTH dow=$CURRENT_DOW"
+
+ # Catch-up window: allow jobs to be triggered up to 24h late
+ CATCH_UP_WINDOW=86400
+
+ cron_field_matches() {
+ local field="$1" value="$2"
+ if [ "$field" = "*" ]; then return 0; fi
+ if [[ "$field" == "*/"* ]]; then
+ local step="${field#*/}"; [ $((value % step)) -eq 0 ] && return 0; return 1
+ fi
+ if [[ "$field" == *","* ]]; then
+ IFS=',' read -ra VALUES <<< "$field"
+ for v in "${VALUES[@]}"; do [ "$v" -eq "$value" ] 2>/dev/null && return 0; done; return 1
+ fi
+ if [[ "$field" == *"-"* ]] && [[ "$field" != *"/"* ]]; then
+ local start="${field%-*}" end="${field#*-}"
+ [ "$value" -ge "$start" ] && [ "$value" -le "$end" ] && return 0; return 1
+ fi
+ [ "$field" -eq "$value" ] 2>/dev/null && return 0; return 1
+ }
+
+ cron_should_trigger() {
+ local cron="$1" last_triggered="$2"
+ read -r cron_minute cron_hour cron_dom cron_month cron_dow <<< "$cron"
+ if [ -z "$last_triggered" ] || [ "$last_triggered" = "null" ] || [ "$last_triggered" = "0" ]; then
+ last_triggered=0
+ fi
+ local target_hour="$cron_hour" target_minute="$cron_minute"
+ [ "$target_hour" = "*" ] && target_hour="$CURRENT_HOUR"
+ [ "$target_minute" = "*" ] && target_minute=0
+ [ "$cron_dow" != "*" ] && ! cron_field_matches "$cron_dow" "$CURRENT_DOW" && return 1
+ [ "$cron_dom" != "*" ] && ! cron_field_matches "$cron_dom" "$CURRENT_DAY" && return 1
+ [ "$cron_month" != "*" ] && ! cron_field_matches "$cron_month" "$CURRENT_MONTH" && return 1
+ local today_date=$(date -u '+%Y-%m-%d')
+ local scheduled_time="${today_date}T$(printf '%02d' $target_hour):$(printf '%02d' $target_minute):00"
+ local scheduled_epoch=$(date -u -d "$scheduled_time" '+%s' 2>/dev/null || echo "0")
+ [ "$scheduled_epoch" = "0" ] && return 1
+ if [ "$scheduled_epoch" -le "$CURRENT_EPOCH" ] && \
+ [ "$last_triggered" -lt "$scheduled_epoch" ] && \
+ [ $((CURRENT_EPOCH - scheduled_epoch)) -le "$CATCH_UP_WINDOW" ]; then
+ return 0
+ fi
+ return 1
+ }
+
+ JOBS_TO_RUN="" JOBS_TO_UPDATE="" JOB_COUNT=0
+ TOKEN="${PAT_TOKEN:-$GITHUB_TOKEN}"
+
+ for job_id in $(echo "$AGENT_SCHEDULES" | jq -r '.jobs | keys[]' 2>/dev/null); do
+ echo ""
+ echo "━━━ Checking job: $job_id ━━━"
+ enabled=$(echo "$AGENT_SCHEDULES" | jq -r ".jobs[\"$job_id\"].enabled // true")
+ cron_expr=$(echo "$AGENT_SCHEDULES" | jq -r ".jobs[\"$job_id\"].cron // \"\"")
+ last_triggered=$(echo "$AGENT_SCHEDULES" | jq -r ".jobs[\"$job_id\"].last_triggered // 0")
+
+ [ "$enabled" != "true" ] && echo " ⏭️ Skipped (disabled)" && continue
+
+ should_run=false
+ if [ -n "$cron_expr" ] && [ "$cron_expr" != "null" ]; then
+ echo " Cron: $cron_expr"
+ if [ "${{ inputs.force_check }}" = "true" ]; then
+ should_run=true
+ elif cron_should_trigger "$cron_expr" "$last_triggered"; then
+ should_run=true
+ JOBS_TO_UPDATE="$JOBS_TO_UPDATE $job_id"
+ fi
+ fi
+
+ if [ "$should_run" = "true" ]; then
+ JOBS_TO_RUN="$JOBS_TO_RUN $job_id"
+ JOB_COUNT=$((JOB_COUNT + 1))
+ echo " ✅ Will dispatch"
+ fi
+ done
+
+ echo ""
+
+ if [ $JOB_COUNT -eq 0 ]; then
+ echo "📭 No jobs scheduled to run at this time"
+ exit 0
+ fi
+
+ echo "🚀 Dispatching $JOB_COUNT job(s)..."
+ DISPATCHED_JOBS="" updated_schedules="$AGENT_SCHEDULES"
+
+ for job_id in $JOBS_TO_RUN; do
+ echo "📤 Dispatching: $job_id"
+
+ target_workflow=$(echo "$AGENT_SCHEDULES" | jq -r ".jobs[\"$job_id\"].workflow // \"strands-autonomous.yml\"")
+ command=$(echo "$AGENT_SCHEDULES" | jq -r ".jobs[\"$job_id\"].command // \"release-digest\"")
+
+ inputs_json=$(jq -n --arg command "$command" '{command: $command}')
+
+ response=$(curl -s -w "\n%{http_code}" -X POST \
+ -H "Accept: application/vnd.github+json" \
+ -H "Authorization: Bearer $TOKEN" \
+ -H "X-GitHub-Api-Version: 2022-11-28" \
+ "https://api.github.com/repos/${{ github.repository }}/actions/workflows/${target_workflow}/dispatches" \
+ -d "{\"ref\": \"main\", \"inputs\": $inputs_json}")
+
+ http_code=$(echo "$response" | tail -n1)
+
+ if [ "$http_code" = "204" ]; then
+ echo " ✅ Dispatched"
+ DISPATCHED_JOBS="$DISPATCHED_JOBS $job_id"
+ else
+ echo " ❌ Failed: HTTP $http_code"
+ fi
+ done
+
+ # Update last_triggered timestamps
+ for job_id in $DISPATCHED_JOBS; do
+ echo "$JOBS_TO_UPDATE" | grep -qw "$job_id" && \
+ updated_schedules=$(echo "$updated_schedules" | jq ".jobs[\"$job_id\"].last_triggered = $CURRENT_EPOCH")
+ done
+
+ if [ "$updated_schedules" != "$AGENT_SCHEDULES" ]; then
+ echo "💾 Saving updated schedule..."
+ curl -s -o /dev/null -X PATCH \
+ -H "Accept: application/vnd.github+json" \
+ -H "Authorization: Bearer $TOKEN" \
+ -H "X-GitHub-Api-Version: 2022-11-28" \
+ "https://api.github.com/repos/${{ github.repository }}/actions/variables/AGENT_SCHEDULES" \
+ -d "{\"name\": \"AGENT_SCHEDULES\", \"value\": $(echo "$updated_schedules" | jq -c . | jq -Rs .)}"
+ fi
+
+ echo "🏁 Control loop complete - dispatched $JOB_COUNT job(s)"
From f70e3e963e6ebaa3cc8c3d1419f7615c3c9b1f04 Mon Sep 17 00:00:00 2001
From: agent-of-mkmeral <265349452+agent-of-mkmeral@users.noreply.github.com>
Date: Thu, 19 Mar 2026 22:09:28 +0000
Subject: [PATCH 2/6] refactor: separate sub-agent workflows, update model to
Opus 4.6, remove hourly control loop
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Addresses feedback from issue #33 comment:
1. Remove hourly control loop (strands-control.yml)
- Not needed for our use case. The Wednesday 10am cron is directly
in strands-autonomous.yml — no AGENT_SCHEDULES machinery required.
2. Update model to Opus 4.6 with adaptive thinking and 1M context
- Model: global.anthropic.claude-opus-4-6-v1
- Max tokens: 128,000 (was 64,000)
- Thinking: adaptive (was fixed 8k budget)
- Removed STRANDS_BUDGET_TOKENS constant
- Matches strands-coder settings
3. Separate sub-agent workflows for isolated testing
- strands-adversarial-test.yml — adversarial testing agent
- strands-release-notes-agent.yml — release notes generation
- strands-docs-gap.yml — documentation gap analysis
- Each can be triggered independently via workflow_dispatch
- Orchestrator dispatches to dedicated workflows
4. Bug fix: _get_all_tools() had dead code after return statement
- Orchestrator tools were never actually loaded
- Fixed to return after appending all tools
Files: 6 modified, 3 new, 1 deleted
---
strands-command/README.md | 90 ++++++--
.../agent-sops/task-release-digest.sop.md | 18 +-
.../scripts/python/agent_runner.py | 19 +-
.../scripts/python/orchestrator.py | 16 +-
.../workflows/strands-adversarial-test.yml | 138 +++++++++++
.../workflows/strands-autonomous.yml | 214 +++++-------------
strands-command/workflows/strands-control.yml | 205 -----------------
.../workflows/strands-docs-gap.yml | 137 +++++++++++
.../workflows/strands-release-notes-agent.yml | 145 ++++++++++++
9 files changed, 569 insertions(+), 413 deletions(-)
create mode 100644 strands-command/workflows/strands-adversarial-test.yml
delete mode 100644 strands-command/workflows/strands-control.yml
create mode 100644 strands-command/workflows/strands-docs-gap.yml
create mode 100644 strands-command/workflows/strands-release-notes-agent.yml
diff --git a/strands-command/README.md b/strands-command/README.md
index 3d07787..ebca2d7 100644
--- a/strands-command/README.md
+++ b/strands-command/README.md
@@ -544,11 +544,10 @@ The orchestrator module (`orchestrator.py`) enables agents to dispatch and coord
```mermaid
graph TD
- A[Control Loop] -->|hourly check| B[Schedule Check]
- B -->|Wednesday 10am| C[Release Digest Agent]
- C -->|dispatch| D[Adversarial Tester]
- C -->|dispatch| E[Release Notes Generator]
- C -->|dispatch| F[Docs Gap Analyzer]
+ A[strands-autonomous.yml] -->|"Wed 10am cron"| C[Release Digest Orchestrator]
+ C -->|dispatch| D[strands-adversarial-test.yml]
+ C -->|dispatch| E[strands-release-notes-agent.yml]
+ C -->|dispatch| F[strands-docs-gap.yml]
D -->|results| C
E -->|results| C
F -->|results| C
@@ -556,8 +555,13 @@ graph TD
style C fill:#f9f,stroke:#333,stroke-width:2px
style G fill:#9f9,stroke:#333,stroke-width:2px
+ style D fill:#fcc,stroke:#333
+ style E fill:#ccf,stroke:#333
+ style F fill:#cfc,stroke:#333
```
+Each sub-agent workflow can also be triggered independently via `workflow_dispatch` for isolated testing.
+
#### Self-Trigger Prevention
The orchestrator uses the same pattern as strands-coder to prevent infinite loops:
@@ -575,26 +579,12 @@ Copy the template workflows to your repository:
```bash
# From the devtools/strands-command/workflows/ directory
cp strands-autonomous.yml your-repo/.github/workflows/
-cp strands-control.yml your-repo/.github/workflows/
+cp strands-adversarial-test.yml your-repo/.github/workflows/
+cp strands-release-notes-agent.yml your-repo/.github/workflows/
+cp strands-docs-gap.yml your-repo/.github/workflows/
```
-#### 2. Configure Repository Variables
-
-Set the `AGENT_SCHEDULES` repository variable:
-
-```json
-{
- "jobs": {
- "weekly_release_digest": {
- "enabled": true,
- "cron": "0 10 * * 3",
- "command": "release-digest",
- "workflow": "strands-autonomous.yml",
- "last_triggered": 0
- }
- }
-}
-```
+The Wednesday 10am schedule is configured directly in `strands-autonomous.yml` via cron — no additional schedule variables needed.
#### 3. Configure Secrets
@@ -649,3 +639,57 @@ When running as an orchestrator agent, these tools are available:
| `check_agents_status` | Check status of all dispatched sub-agents |
| `wait_for_agents` | Wait for all sub-agents to complete (with timeout) |
| `get_orchestrator_config` | View current orchestrator security configuration |
+
+
+### Autonomous Agent Capabilities
+
+The Strands command system supports autonomous agent execution for:
+
+- **Adversarial Testing**: Automated testing that actively tries to break code changes
+- **Release Digest**: Weekly orchestrated analysis combining adversarial testing, release notes, and documentation gap analysis
+
+#### Workflow Architecture
+
+Each agent type has its own dedicated workflow for **isolated testing and execution**:
+
+| Workflow | Purpose | Trigger |
+|----------|---------|---------|
+| `strands-autonomous.yml` | Release digest orchestrator | Wednesday 10am UTC cron + manual |
+| `strands-adversarial-test.yml` | Adversarial testing agent | Dispatched by orchestrator + manual |
+| `strands-release-notes-agent.yml` | Release notes generation | Dispatched by orchestrator + manual |
+| `strands-docs-gap.yml` | Documentation gap analysis | Dispatched by orchestrator + manual |
+
+#### Agent Orchestration
+
+The orchestrator module (`orchestrator.py`) enables agents to dispatch and coordinate sub-agents via dedicated workflows:
+
+```
+strands-autonomous.yml (Release Digest Orchestrator — Wed 10am)
+ ├── strands-adversarial-test.yml (isolated workflow)
+ ├── strands-release-notes-agent.yml (isolated workflow)
+ └── strands-docs-gap.yml (isolated workflow)
+```
+
+Each sub-agent runs in its own workflow, so you can:
+- **Test in isolation**: Trigger any sub-agent independently via `workflow_dispatch`
+- **Debug independently**: Check workflow run logs per agent type
+- **Customize timeouts**: Each workflow has its own `timeout-minutes`
+
+Security limits:
+- Max concurrent sub-agents: 3 (configurable)
+- Per-agent timeout: 30 minutes
+- Max total sub-agents per run: 5
+- Rate limiting and cooldown between dispatches
+
+#### Model Configuration
+
+All agents use **Opus 4.6** with adaptive thinking and 1M context window:
+- Model: `global.anthropic.claude-opus-4-6-v1`
+- Max tokens: 128,000
+- Thinking: adaptive
+
+#### Setup
+
+1. Copy the workflow files to `.github/workflows/` in your repository
+2. Configure `PAT_TOKEN` secret with `workflow_dispatch` permission
+3. Configure orchestrator limits via repository variables (optional)
\ No newline at end of file
diff --git a/strands-command/agent-sops/task-release-digest.sop.md b/strands-command/agent-sops/task-release-digest.sop.md
index 50919cc..0b82f51 100644
--- a/strands-command/agent-sops/task-release-digest.sop.md
+++ b/strands-command/agent-sops/task-release-digest.sop.md
@@ -64,11 +64,11 @@ Determine which sub-agents to dispatch based on the changes found.
**Constraints:**
- You MUST plan the following sub-agent tasks:
- | Task | Agent Type | Input | Output |
- |------|-----------|-------|--------|
- | Adversarial Testing | `adversarial-test` | List of PRs with significant code changes | Findings report per PR |
- | Release Notes | `release-notes` | Base and head git references | Formatted release notes |
- | Documentation Gaps | `docs-gap` | List of PRs with new/changed APIs | Missing docs report |
+ | Task | Agent Type | Workflow | Input | Output |
+ |------|-----------|----------|-------|--------|
+ | Adversarial Testing | `adversarial-test` | `strands-adversarial-test.yml` | List of PRs with significant code changes | Findings report per PR |
+ | Release Notes | `release-notes` | `strands-release-notes-agent.yml` | Base and head git references | Formatted release notes |
+ | Documentation Gaps | `docs-gap` | `strands-docs-gap.yml` | List of PRs with new/changed APIs | Missing docs report |
- You MUST skip adversarial testing if there are no code-changing PRs (only docs/CI/test changes)
- You MUST skip documentation gap analysis if there are no API-changing PRs
@@ -80,10 +80,14 @@ Determine which sub-agents to dispatch based on the changes found.
Dispatch sub-agents for parallel execution.
**Constraints:**
-- You MUST use the orchestrator module (`orchestrator.py`) to dispatch sub-agents
+- You MUST use the orchestrator module (`orchestrator.py`) to dispatch sub-agents to their **dedicated workflows** (each runs in isolation)
- You MUST call the `dispatch_agent` function for each planned task
- You MUST respect the concurrent agent limit — wait for slots before dispatching
- You MUST wait the minimum cooldown between dispatches
+- You MUST dispatch each sub-agent to its dedicated workflow:
+ - **Adversarial tester** → `strands-adversarial-test.yml`
+ - **Release notes** → `strands-release-notes-agent.yml`
+ - **Docs gap** → `strands-docs-gap.yml`
- You MUST pass appropriate inputs to each sub-agent:
- **Adversarial tester**: PR numbers, branch references
- **Release notes**: Base tag, head reference, repository
@@ -228,7 +232,7 @@ The orchestrator reads scheduling configuration from the `AGENT_SCHEDULES` repos
"cron": "0 10 * * 3",
"prompt": "Run the weekly release digest. Find all changes since the last release, dispatch adversarial testing and release notes sub-agents, and compile a comprehensive digest issue.",
"system_prompt": "You are a Release Digest Orchestrator following the task-release-digest SOP.",
- "workflow": "strands-command.yml",
+ "workflow": "strands-autonomous.yml" // orchestrator only,
"tools": ""
}
}
diff --git a/strands-command/scripts/python/agent_runner.py b/strands-command/scripts/python/agent_runner.py
index 44bedbe..b373ac7 100644
--- a/strands-command/scripts/python/agent_runner.py
+++ b/strands-command/scripts/python/agent_runner.py
@@ -52,10 +52,10 @@
from notebook import notebook
from str_replace_based_edit_tool import str_replace_based_edit_tool
-# Strands configuration constants
-STRANDS_MODEL_ID = "global.anthropic.claude-opus-4-5-20251101-v1:0"
-STRANDS_MAX_TOKENS = 64000
-STRANDS_BUDGET_TOKENS = 8000
+# Strands configuration constants — matches strands-coder settings
+# Opus 4.6 with adaptive thinking and 1M context window
+STRANDS_MODEL_ID = "global.anthropic.claude-opus-4-6-v1"
+STRANDS_MAX_TOKENS = 128000
STRANDS_REGION = "us-west-2"
# Default values for environment variables used only in this file
@@ -157,7 +157,7 @@ def _get_trace_attributes() -> dict:
}
def _get_all_tools() -> list[Any]:
- return [
+ tools = [
# File editing
str_replace_based_edit_tool,
@@ -191,6 +191,8 @@ def _get_all_tools() -> list[Any]:
# Add orchestrator tools if available
if _ORCHESTRATOR_AVAILABLE:
tools.extend([dispatch_agent, check_agents_status, wait_for_agents, get_orchestrator_config])
+
+ return tools
def run_agent(query: str):
@@ -203,13 +205,10 @@ def run_agent(query: str):
# Get tools and create model
tools = _get_all_tools()
- # Create Bedrock model with inlined configuration
+ # Create Bedrock model — Opus 4.6 with adaptive thinking and 1M context
additional_request_fields = {}
- additional_request_fields["anthropic_beta"] = ["interleaved-thinking-2025-05-14"]
-
additional_request_fields["thinking"] = {
- "type": "enabled",
- "budget_tokens": STRANDS_BUDGET_TOKENS
+ "type": "adaptive",
}
model = BedrockModel(
diff --git a/strands-command/scripts/python/orchestrator.py b/strands-command/scripts/python/orchestrator.py
index 81b3d34..f5f6db4 100644
--- a/strands-command/scripts/python/orchestrator.py
+++ b/strands-command/scripts/python/orchestrator.py
@@ -64,7 +64,7 @@ class AgentTask:
completion_time: datetime | None = None
result: str | None = None
error: str | None = None
- workflow: str = "strands-command.yml"
+ workflow: str = ""
@dataclass
@@ -170,7 +170,7 @@ def dispatch_agent(
agent_type: str,
prompt: str,
system_prompt: str = "",
- workflow: str = "strands-command.yml",
+ workflow: str = "",
extra_inputs: dict[str, str] | None = None,
) -> AgentTask:
"""Dispatch a sub-agent via workflow_dispatch.
@@ -179,7 +179,10 @@ def dispatch_agent(
agent_type: Type of agent (e.g., "adversarial-test", "release-notes")
prompt: Task prompt for the sub-agent
system_prompt: System prompt override for the sub-agent
- workflow: Target workflow file (default: strands-command.yml)
+ workflow: Target workflow file. Each sub-agent has a dedicated workflow:
+ - strands-adversarial-test.yml
+ - strands-release-notes-agent.yml
+ - strands-docs-gap.yml
extra_inputs: Additional workflow inputs
Returns:
@@ -468,7 +471,7 @@ def dispatch_agent(
agent_type: str,
prompt: str,
system_prompt: str = "",
- workflow: str = "strands-command.yml",
+ workflow: str = "",
) -> str:
"""Dispatch a sub-agent via GitHub Actions workflow_dispatch.
@@ -482,7 +485,10 @@ def dispatch_agent(
agent_type: Type of agent (e.g., "adversarial-test", "release-notes", "docs-gap")
prompt: Task prompt for the sub-agent
system_prompt: System prompt override (optional)
- workflow: Target workflow file (default: strands-command.yml).
+ workflow: Target workflow file. Each sub-agent has a dedicated workflow:
+ - strands-adversarial-test.yml
+ - strands-release-notes-agent.yml
+ - strands-docs-gap.yml.
Use "owner/repo/workflow.yml" for cross-repo dispatch.
Returns:
diff --git a/strands-command/workflows/strands-adversarial-test.yml b/strands-command/workflows/strands-adversarial-test.yml
new file mode 100644
index 0000000..ede1749
--- /dev/null
+++ b/strands-command/workflows/strands-adversarial-test.yml
@@ -0,0 +1,138 @@
+# Strands Adversarial Test Agent Workflow
+#
+# Dedicated workflow for adversarial testing — runs in isolation.
+# Triggered by the release digest orchestrator or manually via workflow_dispatch.
+#
+# Required Secrets:
+# AWS_ROLE_ARN: IAM role for AWS access
+# AWS_SECRETS_MANAGER_SECRET_ID: Secret containing agent config
+#
+# Usage:
+# - Dispatched by strands-autonomous.yml (release digest orchestrator)
+# - Manual: workflow_dispatch with issue_id or pr_number
+# - Comment: /strands adversarial-test on a PR
+
+name: Strands Adversarial Test
+
+on:
+ workflow_dispatch:
+ inputs:
+ command:
+ description: 'Task prompt for the adversarial tester'
+ required: false
+ type: string
+ default: 'Run adversarial testing on recent changes since the last release.'
+ issue_id:
+ description: 'Issue or PR number to test'
+ required: false
+ type: string
+ session_id:
+ description: 'Session ID for resuming'
+ required: false
+ type: string
+ default: ''
+
+permissions: write-all
+
+jobs:
+ adversarial-test:
+ runs-on: ubuntu-latest
+ timeout-minutes: 45
+ steps:
+ - name: Checkout repository
+ uses: actions/checkout@v4
+
+ - name: Checkout devtools
+ uses: actions/checkout@v5
+ with:
+ repository: strands-agents/devtools
+ ref: main
+ sparse-checkout: |
+ strands-command/scripts
+ strands-command/agent-sops
+ path: devtools
+
+ - name: Copy devtools to safe directory
+ shell: bash
+ run: |
+ mkdir -p ${{ runner.temp }}/strands-agent-runner
+ cp -r devtools/strands-command ${{ runner.temp }}/strands-agent-runner/
+
+ - name: Set up Python
+ uses: actions/setup-python@v4
+ with:
+ python-version: '3.13'
+
+ - name: Install uv
+ uses: astral-sh/setup-uv@v3
+
+ - name: Install dependencies
+ shell: bash
+ run: |
+ uv pip install --system -r ${{ runner.temp }}/strands-agent-runner/strands-command/scripts/python/requirements.txt --quiet
+
+ - name: Configure Git
+ shell: bash
+ run: |
+ git config --global user.name "Strands Agent"
+ git config --global user.email "217235299+strands-agent@users.noreply.github.com"
+ git config --global core.pager cat
+
+ - name: Configure AWS credentials
+ uses: aws-actions/configure-aws-credentials@v4
+ with:
+ role-to-assume: ${{ secrets.AWS_ROLE_ARN }}
+ role-session-name: GitHubActions-AdversarialTest-${{ github.run_id }}
+ aws-region: us-west-2
+ mask-aws-account-id: true
+
+ - name: Retrieve secrets
+ id: secrets
+ shell: bash
+ run: |
+ if [ -n "${{ secrets.AWS_SECRETS_MANAGER_SECRET_ID }}" ]; then
+ SECRET_JSON=$(aws secretsmanager get-secret-value --secret-id "${{ secrets.AWS_SECRETS_MANAGER_SECRET_ID }}" --query SecretString --output text --region us-east-1)
+ SESSIONS_BUCKET=$(echo $SECRET_JSON | jq -r '.AGENT_SESSIONS_BUCKET // empty')
+ [ -n "$SESSIONS_BUCKET" ] && echo "::add-mask::$SESSIONS_BUCKET"
+ echo "sessions_bucket=$SESSIONS_BUCKET" >> $GITHUB_OUTPUT
+ fi
+
+ - name: Build task prompt
+ id: prompt
+ shell: bash
+ run: |
+ COMMAND="${{ github.event.inputs.command }}"
+ ISSUE_ID="${{ github.event.inputs.issue_id }}"
+ SESSION_ID="${{ github.event.inputs.session_id }}"
+
+ if [ -z "$SESSION_ID" ]; then
+ SESSION_ID="adversarial-test-$(date +%s)"
+ fi
+ echo "session_id=$SESSION_ID" >> $GITHUB_OUTPUT
+
+ if [ -n "$ISSUE_ID" ]; then
+ TASK="Run adversarial testing on PR #${ISSUE_ID}. Check the code changes for edge cases, failure modes, and security issues."
+ else
+ TASK="${COMMAND:-Run adversarial testing on recent changes since the last release.}"
+ fi
+ echo "task=$TASK" >> $GITHUB_OUTPUT
+
+ - name: Execute adversarial test agent
+ shell: bash
+ env:
+ GITHUB_WRITE: 'true'
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ PAT_TOKEN: ${{ secrets.PAT_TOKEN }}
+ S3_SESSION_BUCKET: ${{ steps.secrets.outputs.sessions_bucket }}
+ SESSION_ID: ${{ steps.prompt.outputs.session_id }}
+ INPUT_SYSTEM_PROMPT: |
+ You are an Adversarial Tester following the task-adversarial-tester SOP.
+ Your goal is to break code changes by actively finding bugs, edge cases,
+ security holes, and failure modes. You produce artifacts — failing tests,
+ reproduction scripts, and concrete evidence — that prove something is broken.
+ INPUT_TASK: ${{ steps.prompt.outputs.task }}
+ STRANDS_TOOL_CONSOLE_MODE: 'enabled'
+ BYPASS_TOOL_CONSENT: 'true'
+ run: |
+ echo "🔴 Running adversarial test agent"
+ uv run --no-project ${{ runner.temp }}/strands-agent-runner/strands-command/scripts/python/agent_runner.py "$INPUT_TASK"
diff --git a/strands-command/workflows/strands-autonomous.yml b/strands-command/workflows/strands-autonomous.yml
index 0a77fda..ad9bbb0 100644
--- a/strands-command/workflows/strands-autonomous.yml
+++ b/strands-command/workflows/strands-autonomous.yml
@@ -1,47 +1,35 @@
-# Strands Autonomous Agent Workflow Template
+# Strands Release Digest Orchestrator Workflow
#
-# This is a TEMPLATE workflow for repositories that want autonomous agent
-# capabilities. Copy this file to your repository's .github/workflows/
-# directory and configure the secrets and variables.
+# This is the orchestrator that runs every Wednesday at 10am UTC.
+# It finds changes since the last release and dispatches sub-agents
+# to their dedicated workflows for parallel execution:
+# - strands-adversarial-test.yml (adversarial testing)
+# - strands-release-notes-agent.yml (release notes generation)
+# - strands-docs-gap.yml (documentation gap analysis)
#
-# Features:
-# - Scheduled autonomous runs (e.g., weekly release digests)
-# - Workflow dispatch for manual agent triggers
-# - Agent orchestration for sub-agent parallelization
-# - Security controls (authorization, rate limiting, timeouts)
+# Each sub-agent workflow can also be triggered independently for
+# isolated testing via workflow_dispatch.
#
# Required Secrets:
# AWS_ROLE_ARN: IAM role for AWS access
# AWS_SECRETS_MANAGER_SECRET_ID: Secret containing agent config
-# PAT_TOKEN: Personal access token for workflow dispatch (sub-agents)
-#
-# Required Variables:
-# AGENT_SCHEDULES: JSON schedule configuration (see control loop template)
-#
-# Optional Variables:
-# ORCHESTRATOR_MAX_CONCURRENT: Max concurrent sub-agents (default: 3)
-# ORCHESTRATOR_AGENT_TIMEOUT_MINUTES: Per-agent timeout (default: 30)
-# ORCHESTRATOR_MAX_TOTAL_AGENTS: Max total sub-agents per run (default: 5)
+# PAT_TOKEN: Personal access token for dispatching sub-agent workflows
-name: Strands Autonomous Agent
+name: Strands Release Digest
on:
- # Weekly release digest: Wednesdays at 10am UTC
+ # Weekly: Wednesdays at 10am UTC
schedule:
- cron: '0 10 * * 3'
- # Manual trigger with configurable inputs
+ # Manual trigger
workflow_dispatch:
inputs:
- issue_id:
- description: 'Issue ID to process'
- required: false
- type: string
command:
- description: 'Agent command (e.g., "release-digest", "adversarial-test")'
+ description: 'Override task prompt for the orchestrator'
required: false
type: string
- default: 'release-digest'
+ default: ''
session_id:
description: 'Session ID for resuming'
required: false
@@ -51,134 +39,7 @@ on:
permissions: write-all
jobs:
- # ─────────────────────────────────────────────────────────
- # Authorization Gate
- # ─────────────────────────────────────────────────────────
- authorization-check:
- if: >-
- github.event_name == 'schedule' ||
- github.event_name == 'workflow_dispatch'
- name: Check access
- permissions: read-all
- runs-on: ubuntu-latest
- outputs:
- approval-env: ${{ steps.auth.outputs.approval-env }}
- steps:
- - name: Check Authorization
- id: auth
- shell: bash
- run: |
- # Scheduled runs and workflow_dispatch are always authorized
- if [ "${{ github.event_name }}" = "schedule" ]; then
- echo "✅ Scheduled run - authorized"
- echo "approval-env=" >> $GITHUB_OUTPUT
- exit 0
- fi
-
- if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then
- echo "✅ Workflow dispatch - authorized"
- echo "approval-env=" >> $GITHUB_OUTPUT
- exit 0
- fi
-
- echo "❌ Unknown trigger"
- exit 1
-
- # ─────────────────────────────────────────────────────────
- # Determine Agent Type from Schedule/Command
- # ─────────────────────────────────────────────────────────
- setup:
- needs: [authorization-check]
- runs-on: ubuntu-latest
- outputs:
- agent_type: ${{ steps.determine.outputs.agent_type }}
- system_prompt: ${{ steps.determine.outputs.system_prompt }}
- task_prompt: ${{ steps.determine.outputs.task_prompt }}
- session_id: ${{ steps.determine.outputs.session_id }}
- steps:
- - name: Determine agent type
- id: determine
- shell: bash
- run: |
- COMMAND="${{ github.event.inputs.command || 'release-digest' }}"
- SESSION_ID="${{ github.event.inputs.session_id }}"
- ISSUE_ID="${{ github.event.inputs.issue_id }}"
-
- # Generate session ID if not provided
- if [ -z "$SESSION_ID" ]; then
- SESSION_ID="${COMMAND}-$(date +%s)"
- fi
- echo "session_id=$SESSION_ID" >> $GITHUB_OUTPUT
-
- case "$COMMAND" in
- release-digest|digest)
- echo "agent_type=release-digest" >> $GITHUB_OUTPUT
- echo "system_prompt<> $GITHUB_OUTPUT
- cat << 'SOP_EOF' >> $GITHUB_OUTPUT
- You are a Release Digest Orchestrator following the task-release-digest SOP.
- Your goal is to produce a comprehensive weekly release digest by coordinating
- multiple parallel analysis tasks. You find all changes since the last release,
- dispatch sub-agents for adversarial testing and release notes generation,
- collect their results, and compile everything into a single consolidated digest issue.
- SOP_EOF
- echo "PROMPT_EOF" >> $GITHUB_OUTPUT
-
- echo "task_prompt<> $GITHUB_OUTPUT
- echo "Run the weekly release digest. Find all changes since the last release tag, analyze merged PRs, dispatch adversarial testing and release notes sub-agents, and compile a comprehensive digest issue with findings, draft release notes, and action items." >> $GITHUB_OUTPUT
- echo "TASK_EOF" >> $GITHUB_OUTPUT
- ;;
-
- adversarial-test|adversarial)
- echo "agent_type=adversarial-test" >> $GITHUB_OUTPUT
- echo "system_prompt<> $GITHUB_OUTPUT
- cat << 'SOP_EOF' >> $GITHUB_OUTPUT
- You are an Adversarial Tester following the task-adversarial-tester SOP.
- Your goal is to break code changes by actively finding bugs, edge cases,
- security holes, and failure modes. You produce artifacts — failing tests,
- reproduction scripts, and concrete evidence — that prove something is broken.
- SOP_EOF
- echo "PROMPT_EOF" >> $GITHUB_OUTPUT
-
- TASK="Run adversarial testing on recent changes."
- if [ -n "$ISSUE_ID" ]; then
- TASK="Run adversarial testing for issue #${ISSUE_ID}. Check the referenced PR for code changes to test."
- fi
- echo "task_prompt=$TASK" >> $GITHUB_OUTPUT
- ;;
-
- release-notes)
- echo "agent_type=release-notes" >> $GITHUB_OUTPUT
- echo "system_prompt<> $GITHUB_OUTPUT
- cat << 'SOP_EOF' >> $GITHUB_OUTPUT
- You are a Release Notes Generator following the task-release-notes SOP.
- Your goal is to create high-quality release notes highlighting Major Features
- and Major Bug Fixes. Analyze merged PRs, extract code examples, validate them,
- and format into well-structured markdown.
- SOP_EOF
- echo "PROMPT_EOF" >> $GITHUB_OUTPUT
-
- echo "task_prompt=Generate release notes for the latest unreleased changes. Find the most recent release tag and analyze all merged PRs since then." >> $GITHUB_OUTPUT
- ;;
-
- *)
- echo "agent_type=custom" >> $GITHUB_OUTPUT
- echo "system_prompt=You are an autonomous GitHub agent." >> $GITHUB_OUTPUT
- echo "task_prompt=$COMMAND" >> $GITHUB_OUTPUT
- ;;
- esac
-
- echo "📋 Agent type: $(cat $GITHUB_OUTPUT | grep agent_type | cut -d= -f2)"
-
- # ─────────────────────────────────────────────────────────
- # Execute Agent
- # ─────────────────────────────────────────────────────────
- execute-agent:
- needs: [setup]
- permissions:
- contents: write
- issues: write
- pull-requests: write
- id-token: write
+ release-digest:
runs-on: ubuntu-latest
timeout-minutes: 60
env:
@@ -189,8 +50,9 @@ jobs:
steps:
- name: Checkout repository
uses: actions/checkout@v4
+ with:
+ fetch-depth: 0 # Full history for release tag discovery
- # Checkout devtools for scripts and SOPs
- name: Checkout devtools
uses: actions/checkout@v5
with:
@@ -231,7 +93,7 @@ jobs:
uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: ${{ secrets.AWS_ROLE_ARN }}
- role-session-name: GitHubActions-StrandsAutonomous-${{ github.run_id }}
+ role-session-name: GitHubActions-ReleaseDigest-${{ github.run_id }}
aws-region: us-west-2
mask-aws-account-id: true
@@ -246,18 +108,44 @@ jobs:
echo "sessions_bucket=$SESSIONS_BUCKET" >> $GITHUB_OUTPUT
fi
- - name: Execute autonomous agent
+ - name: Build session ID
+ id: setup
+ shell: bash
+ run: |
+ SESSION_ID="${{ github.event.inputs.session_id }}"
+ if [ -z "$SESSION_ID" ]; then
+ SESSION_ID="release-digest-$(date +%s)"
+ fi
+ echo "session_id=$SESSION_ID" >> $GITHUB_OUTPUT
+
+ - name: Execute release digest orchestrator
shell: bash
env:
GITHUB_WRITE: 'true'
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
PAT_TOKEN: ${{ secrets.PAT_TOKEN }}
S3_SESSION_BUCKET: ${{ steps.secrets.outputs.sessions_bucket }}
- SESSION_ID: ${{ needs.setup.outputs.session_id }}
- INPUT_SYSTEM_PROMPT: ${{ needs.setup.outputs.system_prompt }}
- INPUT_TASK: ${{ needs.setup.outputs.task_prompt }}
+ SESSION_ID: ${{ steps.setup.outputs.session_id }}
+ INPUT_SYSTEM_PROMPT: |
+ You are a Release Digest Orchestrator following the task-release-digest SOP.
+ Your goal is to produce a comprehensive weekly release digest by coordinating
+ multiple parallel analysis tasks. You find all changes since the last release,
+ dispatch sub-agents for adversarial testing and release notes generation,
+ collect their results, and compile everything into a single consolidated digest issue.
+
+ Sub-agent workflows (dispatch to these via the orchestrator):
+ - strands-adversarial-test.yml — Adversarial testing
+ - strands-release-notes-agent.yml — Release notes generation
+ - strands-docs-gap.yml — Documentation gap analysis
+
+ Each sub-agent runs in its own isolated workflow and can be tested independently.
STRANDS_TOOL_CONSOLE_MODE: 'enabled'
BYPASS_TOOL_CONSENT: 'true'
run: |
- echo "🤖 Running autonomous agent: ${{ needs.setup.outputs.agent_type }}"
- uv run --no-project ${{ runner.temp }}/strands-agent-runner/strands-command/scripts/python/agent_runner.py "$INPUT_TASK"
+ TASK="${{ github.event.inputs.command }}"
+ if [ -z "$TASK" ]; then
+ TASK="Run the weekly release digest. Find all changes since the last release tag, analyze merged PRs, dispatch adversarial testing (strands-adversarial-test.yml), release notes (strands-release-notes-agent.yml), and docs gap (strands-docs-gap.yml) sub-agents to their dedicated workflows, and compile a comprehensive digest issue with findings, draft release notes, and action items."
+ fi
+
+ echo "🚀 Running release digest orchestrator"
+ uv run --no-project ${{ runner.temp }}/strands-agent-runner/strands-command/scripts/python/agent_runner.py "$TASK"
diff --git a/strands-command/workflows/strands-control.yml b/strands-command/workflows/strands-control.yml
deleted file mode 100644
index c8efed1..0000000
--- a/strands-command/workflows/strands-control.yml
+++ /dev/null
@@ -1,205 +0,0 @@
-# Strands Control Loop Workflow Template
-#
-# Hourly control loop that checks AGENT_SCHEDULES repository variable
-# and dispatches autonomous agent workflows when jobs are due.
-#
-# This is a TEMPLATE. Copy to .github/workflows/ in your repository.
-#
-# Required Variables:
-# AGENT_SCHEDULES: JSON with job definitions (see format below)
-#
-# Required Secrets:
-# PAT_TOKEN: Personal access token for workflow dispatch
-#
-# Schedule Format (AGENT_SCHEDULES variable):
-# {
-# "jobs": {
-# "weekly_digest": {
-# "enabled": true,
-# "cron": "0 10 * * 3",
-# "prompt": "Run the weekly release digest",
-# "system_prompt": "You are a Release Digest Orchestrator",
-# "workflow": "strands-autonomous.yml",
-# "last_triggered": 0
-# }
-# }
-# }
-
-name: Strands Control Loop
-
-on:
- schedule:
- - cron: '0 * * * *' # Every hour
- workflow_dispatch:
- inputs:
- force_check:
- description: 'Force check schedules regardless of time'
- required: false
- type: boolean
- default: false
-
-permissions:
- contents: read
- actions: write
-
-jobs:
- control:
- runs-on: ubuntu-latest
- steps:
- - name: Check Schedules and Dispatch Jobs
- env:
- GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- PAT_TOKEN: ${{ secrets.PAT_TOKEN }}
- AGENT_SCHEDULES: ${{ vars.AGENT_SCHEDULES }}
- run: |
- #!/bin/bash
- set -e
-
- echo "🕐 Control Loop - $(date -u '+%Y-%m-%d %H:%M') UTC"
- echo "Repository: ${{ github.repository }}"
-
- if [ -z "$AGENT_SCHEDULES" ] || [ "$AGENT_SCHEDULES" = "{}" ]; then
- echo "ℹ️ No schedules configured (AGENT_SCHEDULES is empty)"
- exit 0
- fi
-
- CURRENT_MINUTE=$(date -u '+%M' | sed 's/^0//')
- CURRENT_HOUR=$(date -u '+%H' | sed 's/^0//')
- CURRENT_DAY=$(date -u '+%d' | sed 's/^0//')
- CURRENT_MONTH=$(date -u '+%m' | sed 's/^0//')
- CURRENT_DOW=$(date -u '+%w')
- CURRENT_EPOCH=$(date -u '+%s')
-
- [ -z "$CURRENT_MINUTE" ] && CURRENT_MINUTE=0
- [ -z "$CURRENT_HOUR" ] && CURRENT_HOUR=0
- [ -z "$CURRENT_DAY" ] && CURRENT_DAY=1
- [ -z "$CURRENT_MONTH" ] && CURRENT_MONTH=1
-
- echo "Current time: minute=$CURRENT_MINUTE hour=$CURRENT_HOUR day=$CURRENT_DAY month=$CURRENT_MONTH dow=$CURRENT_DOW"
-
- # Catch-up window: allow jobs to be triggered up to 24h late
- CATCH_UP_WINDOW=86400
-
- cron_field_matches() {
- local field="$1" value="$2"
- if [ "$field" = "*" ]; then return 0; fi
- if [[ "$field" == "*/"* ]]; then
- local step="${field#*/}"; [ $((value % step)) -eq 0 ] && return 0; return 1
- fi
- if [[ "$field" == *","* ]]; then
- IFS=',' read -ra VALUES <<< "$field"
- for v in "${VALUES[@]}"; do [ "$v" -eq "$value" ] 2>/dev/null && return 0; done; return 1
- fi
- if [[ "$field" == *"-"* ]] && [[ "$field" != *"/"* ]]; then
- local start="${field%-*}" end="${field#*-}"
- [ "$value" -ge "$start" ] && [ "$value" -le "$end" ] && return 0; return 1
- fi
- [ "$field" -eq "$value" ] 2>/dev/null && return 0; return 1
- }
-
- cron_should_trigger() {
- local cron="$1" last_triggered="$2"
- read -r cron_minute cron_hour cron_dom cron_month cron_dow <<< "$cron"
- if [ -z "$last_triggered" ] || [ "$last_triggered" = "null" ] || [ "$last_triggered" = "0" ]; then
- last_triggered=0
- fi
- local target_hour="$cron_hour" target_minute="$cron_minute"
- [ "$target_hour" = "*" ] && target_hour="$CURRENT_HOUR"
- [ "$target_minute" = "*" ] && target_minute=0
- [ "$cron_dow" != "*" ] && ! cron_field_matches "$cron_dow" "$CURRENT_DOW" && return 1
- [ "$cron_dom" != "*" ] && ! cron_field_matches "$cron_dom" "$CURRENT_DAY" && return 1
- [ "$cron_month" != "*" ] && ! cron_field_matches "$cron_month" "$CURRENT_MONTH" && return 1
- local today_date=$(date -u '+%Y-%m-%d')
- local scheduled_time="${today_date}T$(printf '%02d' $target_hour):$(printf '%02d' $target_minute):00"
- local scheduled_epoch=$(date -u -d "$scheduled_time" '+%s' 2>/dev/null || echo "0")
- [ "$scheduled_epoch" = "0" ] && return 1
- if [ "$scheduled_epoch" -le "$CURRENT_EPOCH" ] && \
- [ "$last_triggered" -lt "$scheduled_epoch" ] && \
- [ $((CURRENT_EPOCH - scheduled_epoch)) -le "$CATCH_UP_WINDOW" ]; then
- return 0
- fi
- return 1
- }
-
- JOBS_TO_RUN="" JOBS_TO_UPDATE="" JOB_COUNT=0
- TOKEN="${PAT_TOKEN:-$GITHUB_TOKEN}"
-
- for job_id in $(echo "$AGENT_SCHEDULES" | jq -r '.jobs | keys[]' 2>/dev/null); do
- echo ""
- echo "━━━ Checking job: $job_id ━━━"
- enabled=$(echo "$AGENT_SCHEDULES" | jq -r ".jobs[\"$job_id\"].enabled // true")
- cron_expr=$(echo "$AGENT_SCHEDULES" | jq -r ".jobs[\"$job_id\"].cron // \"\"")
- last_triggered=$(echo "$AGENT_SCHEDULES" | jq -r ".jobs[\"$job_id\"].last_triggered // 0")
-
- [ "$enabled" != "true" ] && echo " ⏭️ Skipped (disabled)" && continue
-
- should_run=false
- if [ -n "$cron_expr" ] && [ "$cron_expr" != "null" ]; then
- echo " Cron: $cron_expr"
- if [ "${{ inputs.force_check }}" = "true" ]; then
- should_run=true
- elif cron_should_trigger "$cron_expr" "$last_triggered"; then
- should_run=true
- JOBS_TO_UPDATE="$JOBS_TO_UPDATE $job_id"
- fi
- fi
-
- if [ "$should_run" = "true" ]; then
- JOBS_TO_RUN="$JOBS_TO_RUN $job_id"
- JOB_COUNT=$((JOB_COUNT + 1))
- echo " ✅ Will dispatch"
- fi
- done
-
- echo ""
-
- if [ $JOB_COUNT -eq 0 ]; then
- echo "📭 No jobs scheduled to run at this time"
- exit 0
- fi
-
- echo "🚀 Dispatching $JOB_COUNT job(s)..."
- DISPATCHED_JOBS="" updated_schedules="$AGENT_SCHEDULES"
-
- for job_id in $JOBS_TO_RUN; do
- echo "📤 Dispatching: $job_id"
-
- target_workflow=$(echo "$AGENT_SCHEDULES" | jq -r ".jobs[\"$job_id\"].workflow // \"strands-autonomous.yml\"")
- command=$(echo "$AGENT_SCHEDULES" | jq -r ".jobs[\"$job_id\"].command // \"release-digest\"")
-
- inputs_json=$(jq -n --arg command "$command" '{command: $command}')
-
- response=$(curl -s -w "\n%{http_code}" -X POST \
- -H "Accept: application/vnd.github+json" \
- -H "Authorization: Bearer $TOKEN" \
- -H "X-GitHub-Api-Version: 2022-11-28" \
- "https://api.github.com/repos/${{ github.repository }}/actions/workflows/${target_workflow}/dispatches" \
- -d "{\"ref\": \"main\", \"inputs\": $inputs_json}")
-
- http_code=$(echo "$response" | tail -n1)
-
- if [ "$http_code" = "204" ]; then
- echo " ✅ Dispatched"
- DISPATCHED_JOBS="$DISPATCHED_JOBS $job_id"
- else
- echo " ❌ Failed: HTTP $http_code"
- fi
- done
-
- # Update last_triggered timestamps
- for job_id in $DISPATCHED_JOBS; do
- echo "$JOBS_TO_UPDATE" | grep -qw "$job_id" && \
- updated_schedules=$(echo "$updated_schedules" | jq ".jobs[\"$job_id\"].last_triggered = $CURRENT_EPOCH")
- done
-
- if [ "$updated_schedules" != "$AGENT_SCHEDULES" ]; then
- echo "💾 Saving updated schedule..."
- curl -s -o /dev/null -X PATCH \
- -H "Accept: application/vnd.github+json" \
- -H "Authorization: Bearer $TOKEN" \
- -H "X-GitHub-Api-Version: 2022-11-28" \
- "https://api.github.com/repos/${{ github.repository }}/actions/variables/AGENT_SCHEDULES" \
- -d "{\"name\": \"AGENT_SCHEDULES\", \"value\": $(echo "$updated_schedules" | jq -c . | jq -Rs .)}"
- fi
-
- echo "🏁 Control loop complete - dispatched $JOB_COUNT job(s)"
diff --git a/strands-command/workflows/strands-docs-gap.yml b/strands-command/workflows/strands-docs-gap.yml
new file mode 100644
index 0000000..bf7f8f9
--- /dev/null
+++ b/strands-command/workflows/strands-docs-gap.yml
@@ -0,0 +1,137 @@
+# Strands Docs Gap Analysis Agent Workflow
+#
+# Dedicated workflow for documentation gap analysis — runs in isolation.
+# Triggered by the release digest orchestrator or manually via workflow_dispatch.
+#
+# Required Secrets:
+# AWS_ROLE_ARN: IAM role for AWS access
+# AWS_SECRETS_MANAGER_SECRET_ID: Secret containing agent config
+#
+# Usage:
+# - Dispatched by strands-autonomous.yml (release digest orchestrator)
+# - Manual: workflow_dispatch with pr_numbers
+
+name: Strands Docs Gap Analysis
+
+on:
+ workflow_dispatch:
+ inputs:
+ command:
+ description: 'Task prompt for the docs gap analyzer'
+ required: false
+ type: string
+ default: 'Analyze recent changes for documentation gaps.'
+ pr_numbers:
+ description: 'Comma-separated PR numbers with API changes to analyze'
+ required: false
+ type: string
+ session_id:
+ description: 'Session ID for resuming'
+ required: false
+ type: string
+ default: ''
+
+permissions: write-all
+
+jobs:
+ docs-gap:
+ runs-on: ubuntu-latest
+ timeout-minutes: 30
+ steps:
+ - name: Checkout repository
+ uses: actions/checkout@v4
+
+ - name: Checkout devtools
+ uses: actions/checkout@v5
+ with:
+ repository: strands-agents/devtools
+ ref: main
+ sparse-checkout: |
+ strands-command/scripts
+ strands-command/agent-sops
+ path: devtools
+
+ - name: Copy devtools to safe directory
+ shell: bash
+ run: |
+ mkdir -p ${{ runner.temp }}/strands-agent-runner
+ cp -r devtools/strands-command ${{ runner.temp }}/strands-agent-runner/
+
+ - name: Set up Python
+ uses: actions/setup-python@v4
+ with:
+ python-version: '3.13'
+
+ - name: Install uv
+ uses: astral-sh/setup-uv@v3
+
+ - name: Install dependencies
+ shell: bash
+ run: |
+ uv pip install --system -r ${{ runner.temp }}/strands-agent-runner/strands-command/scripts/python/requirements.txt --quiet
+
+ - name: Configure Git
+ shell: bash
+ run: |
+ git config --global user.name "Strands Agent"
+ git config --global user.email "217235299+strands-agent@users.noreply.github.com"
+ git config --global core.pager cat
+
+ - name: Configure AWS credentials
+ uses: aws-actions/configure-aws-credentials@v4
+ with:
+ role-to-assume: ${{ secrets.AWS_ROLE_ARN }}
+ role-session-name: GitHubActions-DocsGap-${{ github.run_id }}
+ aws-region: us-west-2
+ mask-aws-account-id: true
+
+ - name: Retrieve secrets
+ id: secrets
+ shell: bash
+ run: |
+ if [ -n "${{ secrets.AWS_SECRETS_MANAGER_SECRET_ID }}" ]; then
+ SECRET_JSON=$(aws secretsmanager get-secret-value --secret-id "${{ secrets.AWS_SECRETS_MANAGER_SECRET_ID }}" --query SecretString --output text --region us-east-1)
+ SESSIONS_BUCKET=$(echo $SECRET_JSON | jq -r '.AGENT_SESSIONS_BUCKET // empty')
+ [ -n "$SESSIONS_BUCKET" ] && echo "::add-mask::$SESSIONS_BUCKET"
+ echo "sessions_bucket=$SESSIONS_BUCKET" >> $GITHUB_OUTPUT
+ fi
+
+ - name: Build task prompt
+ id: prompt
+ shell: bash
+ run: |
+ COMMAND="${{ github.event.inputs.command }}"
+ PR_NUMBERS="${{ github.event.inputs.pr_numbers }}"
+ SESSION_ID="${{ github.event.inputs.session_id }}"
+
+ if [ -z "$SESSION_ID" ]; then
+ SESSION_ID="docs-gap-$(date +%s)"
+ fi
+ echo "session_id=$SESSION_ID" >> $GITHUB_OUTPUT
+
+ TASK="$COMMAND"
+ if [ -n "$PR_NUMBERS" ]; then
+ TASK="$TASK Analyze PRs: $PR_NUMBERS for missing documentation, docstrings, and usage examples."
+ fi
+ echo "task=$TASK" >> $GITHUB_OUTPUT
+
+ - name: Execute docs gap agent
+ shell: bash
+ env:
+ GITHUB_WRITE: 'true'
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ PAT_TOKEN: ${{ secrets.PAT_TOKEN }}
+ S3_SESSION_BUCKET: ${{ steps.secrets.outputs.sessions_bucket }}
+ SESSION_ID: ${{ steps.prompt.outputs.session_id }}
+ INPUT_SYSTEM_PROMPT: |
+ You are a Documentation Gap Analyzer. Your goal is to find missing or
+ inadequate documentation for new or changed APIs. You check for missing
+ docstrings, missing usage examples, undocumented parameters, and gaps
+ between the code and the published documentation. You produce a structured
+ report of documentation gaps with specific recommendations.
+ INPUT_TASK: ${{ steps.prompt.outputs.task }}
+ STRANDS_TOOL_CONSOLE_MODE: 'enabled'
+ BYPASS_TOOL_CONSENT: 'true'
+ run: |
+ echo "📚 Running docs gap analysis agent"
+ uv run --no-project ${{ runner.temp }}/strands-agent-runner/strands-command/scripts/python/agent_runner.py "$INPUT_TASK"
diff --git a/strands-command/workflows/strands-release-notes-agent.yml b/strands-command/workflows/strands-release-notes-agent.yml
new file mode 100644
index 0000000..5886d09
--- /dev/null
+++ b/strands-command/workflows/strands-release-notes-agent.yml
@@ -0,0 +1,145 @@
+# Strands Release Notes Agent Workflow
+#
+# Dedicated workflow for release notes generation — runs in isolation.
+# Triggered by the release digest orchestrator or manually via workflow_dispatch.
+#
+# Required Secrets:
+# AWS_ROLE_ARN: IAM role for AWS access
+# AWS_SECRETS_MANAGER_SECRET_ID: Secret containing agent config
+#
+# Usage:
+# - Dispatched by strands-autonomous.yml (release digest orchestrator)
+# - Manual: workflow_dispatch with base_ref and head_ref
+# - Comment: /strands release-notes on an issue
+
+name: Strands Release Notes Agent
+
+on:
+ workflow_dispatch:
+ inputs:
+ command:
+ description: 'Task prompt for the release notes generator'
+ required: false
+ type: string
+ default: 'Generate release notes for the latest unreleased changes.'
+ base_ref:
+ description: 'Base git reference (e.g., last release tag)'
+ required: false
+ type: string
+ head_ref:
+ description: 'Head git reference (default: HEAD)'
+ required: false
+ type: string
+ default: 'HEAD'
+ session_id:
+ description: 'Session ID for resuming'
+ required: false
+ type: string
+ default: ''
+
+permissions: write-all
+
+jobs:
+ release-notes:
+ runs-on: ubuntu-latest
+ timeout-minutes: 30
+ steps:
+ - name: Checkout repository
+ uses: actions/checkout@v4
+ with:
+ fetch-depth: 0 # Full history for tag discovery
+
+ - name: Checkout devtools
+ uses: actions/checkout@v5
+ with:
+ repository: strands-agents/devtools
+ ref: main
+ sparse-checkout: |
+ strands-command/scripts
+ strands-command/agent-sops
+ path: devtools
+
+ - name: Copy devtools to safe directory
+ shell: bash
+ run: |
+ mkdir -p ${{ runner.temp }}/strands-agent-runner
+ cp -r devtools/strands-command ${{ runner.temp }}/strands-agent-runner/
+
+ - name: Set up Python
+ uses: actions/setup-python@v4
+ with:
+ python-version: '3.13'
+
+ - name: Install uv
+ uses: astral-sh/setup-uv@v3
+
+ - name: Install dependencies
+ shell: bash
+ run: |
+ uv pip install --system -r ${{ runner.temp }}/strands-agent-runner/strands-command/scripts/python/requirements.txt --quiet
+
+ - name: Configure Git
+ shell: bash
+ run: |
+ git config --global user.name "Strands Agent"
+ git config --global user.email "217235299+strands-agent@users.noreply.github.com"
+ git config --global core.pager cat
+
+ - name: Configure AWS credentials
+ uses: aws-actions/configure-aws-credentials@v4
+ with:
+ role-to-assume: ${{ secrets.AWS_ROLE_ARN }}
+ role-session-name: GitHubActions-ReleaseNotes-${{ github.run_id }}
+ aws-region: us-west-2
+ mask-aws-account-id: true
+
+ - name: Retrieve secrets
+ id: secrets
+ shell: bash
+ run: |
+ if [ -n "${{ secrets.AWS_SECRETS_MANAGER_SECRET_ID }}" ]; then
+ SECRET_JSON=$(aws secretsmanager get-secret-value --secret-id "${{ secrets.AWS_SECRETS_MANAGER_SECRET_ID }}" --query SecretString --output text --region us-east-1)
+ SESSIONS_BUCKET=$(echo $SECRET_JSON | jq -r '.AGENT_SESSIONS_BUCKET // empty')
+ [ -n "$SESSIONS_BUCKET" ] && echo "::add-mask::$SESSIONS_BUCKET"
+ echo "sessions_bucket=$SESSIONS_BUCKET" >> $GITHUB_OUTPUT
+ fi
+
+ - name: Build task prompt
+ id: prompt
+ shell: bash
+ run: |
+ COMMAND="${{ github.event.inputs.command }}"
+ BASE_REF="${{ github.event.inputs.base_ref }}"
+ HEAD_REF="${{ github.event.inputs.head_ref }}"
+ SESSION_ID="${{ github.event.inputs.session_id }}"
+
+ if [ -z "$SESSION_ID" ]; then
+ SESSION_ID="release-notes-$(date +%s)"
+ fi
+ echo "session_id=$SESSION_ID" >> $GITHUB_OUTPUT
+
+ TASK="$COMMAND"
+ if [ -n "$BASE_REF" ]; then
+ TASK="$TASK Base reference: $BASE_REF. Head reference: ${HEAD_REF:-HEAD}."
+ fi
+ echo "task=$TASK" >> $GITHUB_OUTPUT
+
+ - name: Execute release notes agent
+ shell: bash
+ env:
+ GITHUB_WRITE: 'true'
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ PAT_TOKEN: ${{ secrets.PAT_TOKEN }}
+ S3_SESSION_BUCKET: ${{ steps.secrets.outputs.sessions_bucket }}
+ SESSION_ID: ${{ steps.prompt.outputs.session_id }}
+ INPUT_SYSTEM_PROMPT: |
+ You are a Release Notes Generator following the task-release-notes SOP.
+ Your goal is to create high-quality release notes highlighting Major Features
+ and Major Bug Fixes. Analyze merged PRs, extract code examples, validate them,
+ and format into well-structured markdown.
+ INPUT_TASK: ${{ steps.prompt.outputs.task }}
+ STRANDS_TOOL_CONSOLE_MODE: 'enabled'
+ BYPASS_TOOL_CONSENT: 'true'
+ run: |
+ echo "📝 Running release notes agent"
+ uv run --no-project ${{ runner.temp }}/strands-agent-runner/strands-command/scripts/python/agent_runner.py "$INPUT_TASK"
From df6b36a8da6616d7f8c25658637654d307c442fc Mon Sep 17 00:00:00 2001
From: agent-of-mkmeral <265349452+agent-of-mkmeral@users.noreply.github.com>
Date: Thu, 19 Mar 2026 23:06:49 +0000
Subject: [PATCH 3/6] refactor: replace workflow dispatch with use_agent
sub-agents
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Major architecture simplification:
- Delete orchestrator.py (589 lines) — no more workflow dispatch
- Delete 3 sub-agent workflows — all runs in-process now
- Add use_agent from strands_tools to agent_runner.py
- One agent per package (sdk-python, sdk-ts, tools, evals)
- Sub-agents spawned via use_agent with own system prompts
- No PAT_TOKEN needed, no self-trigger prevention needed
- Update model to Opus 4.6 with adaptive thinking
Files deleted:
- scripts/python/orchestrator.py
- workflows/strands-adversarial-test.yml
- workflows/strands-release-notes-agent.yml
- workflows/strands-docs-gap.yml
Refs: agent-of-mkmeral/strands-coder#33
---
strands-command/README.md | 231 +------
.../agent-sops/task-adversarial-tester.sop.md | 254 ++------
.../agent-sops/task-release-digest.sop.md | 310 ++++-----
.../scripts/python/agent_runner.py | 19 +-
.../scripts/python/orchestrator.py | 595 ------------------
.../workflows/strands-adversarial-test.yml | 138 ----
.../workflows/strands-autonomous.yml | 54 +-
.../workflows/strands-docs-gap.yml | 137 ----
.../workflows/strands-release-notes-agent.yml | 145 -----
9 files changed, 238 insertions(+), 1645 deletions(-)
delete mode 100644 strands-command/scripts/python/orchestrator.py
delete mode 100644 strands-command/workflows/strands-adversarial-test.yml
delete mode 100644 strands-command/workflows/strands-docs-gap.yml
delete mode 100644 strands-command/workflows/strands-release-notes-agent.yml
diff --git a/strands-command/README.md b/strands-command/README.md
index ebca2d7..8100471 100644
--- a/strands-command/README.md
+++ b/strands-command/README.md
@@ -478,218 +478,49 @@ Use workflow dispatch with:
**Note**: This system is designed for trusted environments. Always review security implications before deployment and implement appropriate guardrails for your use case.
-## Autonomous Agent Capabilities
-
-### Overview
-
-In addition to the `/strands` command interface, strands-command supports autonomous agent execution via scheduled workflows. This enables:
-
-- **Weekly release digests** with adversarial testing, release notes, and docs gap analysis
-- **Agent orchestration** — agents dispatching sub-agents for parallel work
-- **Scheduled automation** via a control loop pattern
-
-### New Agent Types
-
-#### Adversarial Tester (`task-adversarial-tester.sop.md`)
-
-Breaks code changes by finding bugs, edge cases, security holes, and failure modes with concrete evidence.
-
-**Workflow**: Setup → Attack Surface Analysis → Test Generation → Execute → Report
-
-**Capabilities:**
-- Edge case and boundary testing
-- Failure mode and error handling testing
-- Contract verification against PR claims
-- Security probing (injection, path traversal, credential leaks)
-- Concurrency and race condition testing
-- Produces runnable failing test artifacts as evidence
-
-**Trigger**:
-- `/strands adversarial-test` on a PR
-- Automated dispatch from release digest orchestrator
-
-#### Release Digest Orchestrator (`task-release-digest.sop.md`)
-
-Produces comprehensive weekly release digests by coordinating multiple parallel analysis agents.
-
-**Workflow**: Discover Changes → Plan Tasks → Dispatch Sub-Agents → Collect Results → Synthesize → Publish
-
-**Capabilities:**
-- Finds all changes since last release tag
-- Dispatches adversarial testing, release notes, and docs gap sub-agents in parallel
-- Collects and synthesizes results from all sub-agents
-- Creates consolidated digest issue with findings, draft notes, and action items
-- Graceful degradation when sub-agents fail
-
-**Trigger**:
-- Scheduled weekly (Wednesday 10am UTC default)
-- `/strands release-digest` on an Issue
-- `workflow_dispatch` with `release-digest` command
-
-### Agent Orchestration
-
-The orchestrator module (`orchestrator.py`) enables agents to dispatch and coordinate sub-agents with built-in security limits.
-
-#### Security Controls
-
-| Control | Default | Environment Variable |
-|---------|---------|---------------------|
-| Max concurrent agents | 3 | `ORCHESTRATOR_MAX_CONCURRENT` |
-| Max total agents per run | 5 | `ORCHESTRATOR_MAX_TOTAL_AGENTS` |
-| Per-agent timeout | 30 min | `ORCHESTRATOR_AGENT_TIMEOUT_MINUTES` |
-| Token budget per agent | 32000 | `ORCHESTRATOR_AGENT_MAX_TOKENS` |
-| Cooldown between dispatches | 10s | `ORCHESTRATOR_COOLDOWN_SECONDS` |
-
-#### Architecture
-
-```mermaid
-graph TD
- A[strands-autonomous.yml] -->|"Wed 10am cron"| C[Release Digest Orchestrator]
- C -->|dispatch| D[strands-adversarial-test.yml]
- C -->|dispatch| E[strands-release-notes-agent.yml]
- C -->|dispatch| F[strands-docs-gap.yml]
- D -->|results| C
- E -->|results| C
- F -->|results| C
- C -->|create| G[Digest Issue]
-
- style C fill:#f9f,stroke:#333,stroke-width:2px
- style G fill:#9f9,stroke:#333,stroke-width:2px
- style D fill:#fcc,stroke:#333
- style E fill:#ccf,stroke:#333
- style F fill:#cfc,stroke:#333
-```
-
-Each sub-agent workflow can also be triggered independently via `workflow_dispatch` for isolated testing.
-
-#### Self-Trigger Prevention
-
-The orchestrator uses the same pattern as strands-coder to prevent infinite loops:
-- Agent accounts can only trigger workflows via explicit `workflow_dispatch`
-- Comments and other events from agent accounts are ignored
-- Each dispatch requires PAT_TOKEN authentication
-- Rate limiting prevents runaway dispatches
-
-### Setting Up Autonomous Agents
-
-#### 1. Copy Workflow Templates
-
-Copy the template workflows to your repository:
-
-```bash
-# From the devtools/strands-command/workflows/ directory
-cp strands-autonomous.yml your-repo/.github/workflows/
-cp strands-adversarial-test.yml your-repo/.github/workflows/
-cp strands-release-notes-agent.yml your-repo/.github/workflows/
-cp strands-docs-gap.yml your-repo/.github/workflows/
-```
-
-The Wednesday 10am schedule is configured directly in `strands-autonomous.yml` via cron — no additional schedule variables needed.
-
-#### 3. Configure Secrets
-
-In addition to the standard secrets (`AWS_ROLE_ARN`, `AWS_SECRETS_MANAGER_SECRET_ID`), add:
-
-| Secret | Description |
-|--------|-------------|
-| `PAT_TOKEN` | Personal access token with `workflow_dispatch` permission (required for sub-agent dispatch) |
-
-#### 4. Optional: Configure Security Limits
-
-Set repository variables to adjust orchestrator limits:
-
-| Variable | Default | Description |
-|----------|---------|-------------|
-| `ORCHESTRATOR_MAX_CONCURRENT` | `3` | Max sub-agents running simultaneously |
-| `ORCHESTRATOR_AGENT_TIMEOUT_MINUTES` | `30` | Per-agent timeout |
-| `ORCHESTRATOR_MAX_TOTAL_AGENTS` | `5` | Max total sub-agents per orchestration run |
+---
-### Usage Examples
+## 🤖 Autonomous Agents: Release Digest
-#### Manual Adversarial Testing
+The strands-command system includes autonomous agent capabilities for automated release analysis.
-Comment on a PR:
-```
-/strands adversarial-test Focus on the new authentication flow edge cases
-```
+### Architecture
-#### Manual Release Digest
+A single orchestrator agent runs weekly (Wednesday 10am UTC) and uses `use_agent` from `strands_tools` to spawn per-package sub-agents **in-process**. No workflow dispatch, no PAT tokens, no self-trigger prevention needed.
-Comment on an issue:
```
-/strands release-digest Generate digest for changes since v1.5.0
+strands-autonomous.yml (Wed 10am cron / manual dispatch)
+└── Release Digest Orchestrator Agent
+ ├── use_agent → SDK Python Analyzer
+ ├── use_agent → SDK TypeScript Analyzer
+ ├── use_agent → Tools Analyzer
+ ├── use_agent → Evals Analyzer
+ └── Compiles results → Creates digest issue
```
-#### Workflow Dispatch
+### How It Works
-Trigger the autonomous workflow manually:
-```bash
-gh workflow run strands-autonomous.yml \
- -f command="adversarial-test" \
- -f issue_id="123"
-```
-
-### Orchestrator Tools (for Agent Use)
-
-When running as an orchestrator agent, these tools are available:
-
-| Tool | Description |
-|------|------------|
-| `dispatch_agent` | Dispatch a sub-agent via workflow_dispatch (with security limits) |
-| `check_agents_status` | Check status of all dispatched sub-agents |
-| `wait_for_agents` | Wait for all sub-agents to complete (with timeout) |
-| `get_orchestrator_config` | View current orchestrator security configuration |
-
-
-### Autonomous Agent Capabilities
-
-The Strands command system supports autonomous agent execution for:
-
-- **Adversarial Testing**: Automated testing that actively tries to break code changes
-- **Release Digest**: Weekly orchestrated analysis combining adversarial testing, release notes, and documentation gap analysis
-
-#### Workflow Architecture
-
-Each agent type has its own dedicated workflow for **isolated testing and execution**:
-
-| Workflow | Purpose | Trigger |
-|----------|---------|---------|
-| `strands-autonomous.yml` | Release digest orchestrator | Wednesday 10am UTC cron + manual |
-| `strands-adversarial-test.yml` | Adversarial testing agent | Dispatched by orchestrator + manual |
-| `strands-release-notes-agent.yml` | Release notes generation | Dispatched by orchestrator + manual |
-| `strands-docs-gap.yml` | Documentation gap analysis | Dispatched by orchestrator + manual |
-
-#### Agent Orchestration
-
-The orchestrator module (`orchestrator.py`) enables agents to dispatch and coordinate sub-agents via dedicated workflows:
-
-```
-strands-autonomous.yml (Release Digest Orchestrator — Wed 10am)
- ├── strands-adversarial-test.yml (isolated workflow)
- ├── strands-release-notes-agent.yml (isolated workflow)
- └── strands-docs-gap.yml (isolated workflow)
-```
+1. **Orchestrator** discovers changes across all packages since their last release tag
+2. **Per-package sub-agents** are spawned via `use_agent` with read-only tools (`shell`, `http_request`)
+3. Each sub-agent analyzes PRs, runs adversarial testing, generates draft release notes, and finds docs gaps
+4. **Orchestrator** collects all results and creates a single consolidated digest issue
-Each sub-agent runs in its own workflow, so you can:
-- **Test in isolation**: Trigger any sub-agent independently via `workflow_dispatch`
-- **Debug independently**: Check workflow run logs per agent type
-- **Customize timeouts**: Each workflow has its own `timeout-minutes`
+### Files
-Security limits:
-- Max concurrent sub-agents: 3 (configurable)
-- Per-agent timeout: 30 minutes
-- Max total sub-agents per run: 5
-- Rate limiting and cooldown between dispatches
+| File | Purpose |
+|------|---------|
+| `workflows/strands-autonomous.yml` | Template workflow — cron + manual dispatch |
+| `agent-sops/task-release-digest.sop.md` | Orchestrator SOP (uses `use_agent`) |
+| `agent-sops/task-adversarial-tester.sop.md` | Adversarial testing SOP (used as sub-agent system prompt) |
+| `scripts/python/agent_runner.py` | Agent runner with `use_agent` tool |
-#### Model Configuration
+### Setup
-All agents use **Opus 4.6** with adaptive thinking and 1M context window:
-- Model: `global.anthropic.claude-opus-4-6-v1`
-- Max tokens: 128,000
-- Thinking: adaptive
+1. Copy `workflows/strands-autonomous.yml` to your repo's `.github/workflows/`
+2. Configure secrets: `AWS_ROLE_ARN`, `AWS_SECRETS_MANAGER_SECRET_ID`
+3. The workflow runs automatically every Wednesday at 10am UTC, or trigger manually via `workflow_dispatch`
-#### Setup
+### Commands
-1. Copy the workflow files to `.github/workflows/` in your repository
-2. Configure `PAT_TOKEN` secret with `workflow_dispatch` permission
-3. Configure orchestrator limits via repository variables (optional)
\ No newline at end of file
+- `/strands adversarial-test` — Run adversarial testing on a PR
+- `/strands release-digest` — Trigger a release digest manually
diff --git a/strands-command/agent-sops/task-adversarial-tester.sop.md b/strands-command/agent-sops/task-adversarial-tester.sop.md
index 4498fb7..547fa0e 100644
--- a/strands-command/agent-sops/task-adversarial-tester.sop.md
+++ b/strands-command/agent-sops/task-adversarial-tester.sop.md
@@ -2,227 +2,97 @@
## Role
-You are an Adversarial Tester. Your goal is to break code changes by actively finding bugs, edge cases, security holes, and failure modes that the author and reviewer missed. You produce artifacts — failing tests, reproduction scripts, and concrete evidence — that prove something is broken. If you can't break it, you say so. You never speculate without proof.
+You are an Adversarial Tester. Your goal is to break code changes by actively finding bugs, edge cases, security holes, and failure modes that the author and reviewer missed. You produce concrete evidence — failing test scenarios, reproduction steps, and specific code paths that are broken.
-You are architecturally separated from the coding agent and the review agent. You have no ability to modify the source code to make your own job easier. You exist to be adversarial.
-
-## Principles
-
-1. **Prove, don't opine.** Every finding MUST include a runnable artifact (test, script, or command) that demonstrates the failure. "I think this might break" is not a finding.
-2. **Spec over implementation.** Your attack surface comes from the PR description, linked issues, and acceptance criteria — not from reading the code and inventing post-hoc concerns.
-3. **Adversarial by design.** Assume the code is wrong until proven otherwise. Your incentive is to find what's broken, not to confirm it works.
-4. **Artifacts are the deliverable.** Your output is a set of pass/fail artifacts. If all pass, the code survived your review. If any fail, they speak for themselves.
-5. **No overlap with the reviewer.** You don't comment on naming, style, architecture, or documentation. That's the reviewer's domain. You break things.
+You can run as a standalone agent (via `/strands adversarial-test` on a PR) or as a sub-agent spawned by the Release Digest Orchestrator via `use_agent`.
## Trigger
- `/strands adversarial-test` on a Pull Request
-- `/strands adversarial-test` on an Issue (tests changes referenced by the issue)
-- Automated dispatch from release digest orchestrator
-
-## Steps
-
-### 1. Setup Test Environment
+- Spawned as a sub-agent by the Release Digest Orchestrator
+- `workflow_dispatch` with adversarial-test prompt
-Initialize the environment and understand what you're attacking.
+## Principles
-**Constraints:**
-- You MUST checkout the PR branch (or the branch referenced by the issue)
-- You MUST read `AGENTS.md`, `CONTRIBUTING.md`, and `DEVELOPMENT.md` to understand the project's test infrastructure
-- You MUST ensure the test suite passes on the PR branch before you start (baseline)
-- You MUST create a progress notebook to track your adversarial testing process
-- You MUST record the baseline test results (pass count, fail count, coverage if available)
-- If the baseline suite already fails, you MUST note this and proceed — your job is to find NEW failures
-- You MUST check the `GITHUB_WRITE` environment variable value to determine permission level
- - If the value is `true`, you can run git write commands and post comments directly
- - If the value is not `true`, you are running in a read-restricted sandbox. Write commands will be deferred
+1. **Break things with evidence.** Every finding must include a concrete reproduction scenario or failing test.
+2. **Think like an attacker.** Consider malicious inputs, race conditions, resource exhaustion, injection attacks.
+3. **Focus on what changed.** Only test the code that was actually modified — don't audit the entire codebase.
+4. **Categorize severity.** Critical (data loss/security) > High (crashes/wrong results) > Medium (edge cases) > Low (style/minor).
+5. **Be specific.** "This might break" is useless. "Passing `None` to `Agent.__init__(model=None)` on line 45 raises `AttributeError` instead of `ValueError`" is useful.
-### 2. Understand the Attack Surface
+## Steps
-Identify what the PR changes and what claims it makes.
+### 1. Understand the Changes
**Constraints:**
-- You MUST read the PR description and linked issue thoroughly
-- You MUST use `get_pr_files` to identify all changed files and their scope
-- You MUST extract explicit and implicit acceptance criteria from the PR description
-- You MUST identify the public API surface being added or modified
-- You MUST categorize the change type: new feature, bugfix, refactor, dependency change, config change
-- You MUST note any claims the author makes ("this handles X", "backward compatible", "no breaking changes")
-- You MUST document your attack surface in the progress notebook as a checklist:
- - Input boundaries and edge cases
- - Error paths and failure modes
- - Concurrency and ordering assumptions
- - Backward compatibility claims
- - Security-sensitive areas (auth, credentials, user input, serialization)
- - Integration points with external systems
+- You MUST read the actual diffs (via `shell` with `git diff` or via the PR's changed files)
+- You MUST identify: what modules changed, what APIs were added/modified, what tests exist
+- You MUST categorize changes: new feature, bug fix, refactor, configuration change
+- You MUST NOT skip reading the actual code — summaries are insufficient
-### 3. Adversarial Test Generation
+### 2. Adversarial Analysis
-Write tests and scripts designed to break the code. This is your core deliverable.
+For each significant change, run these attack vectors:
-#### 3.1 Edge Case Testing
+**Edge Cases:**
+- Empty inputs, None values, extremely large inputs
+- Boundary conditions (0, -1, MAX_INT, empty string, empty list)
+- Unicode, special characters, very long strings
+- Concurrent access, race conditions
-Target the boundaries of inputs, states, and configurations.
+**Contract Violations:**
+- Does the function handle all documented parameter types?
+- Are error messages clear and not leaking internals?
+- Are return types consistent with documentation?
+- Do default values make sense?
-**Constraints:**
-- You MUST write tests for boundary values: empty inputs, None/null, maximum sizes, negative numbers, unicode, special characters
-- You MUST write tests for type confusion: passing wrong types where the code doesn't explicitly validate
-- You MUST write tests for missing or malformed configuration
-- You MUST write tests that exercise optional parameters in combinations the author likely didn't consider
-- All tests MUST follow the project's test patterns (directory structure, framework)
-- All tests MUST be runnable with the project's test command
-- You MUST name test files with the prefix `test_adversarial_` to distinguish them from the author's tests
+**Security:**
+- Input injection (SQL, command, path traversal)
+- Credential/secret exposure in logs or error messages
+- Unsafe deserialization
+- Missing input validation
-#### 3.2 Failure Mode Testing
+**Breaking Changes:**
+- Does this change any public API signatures?
+- Will existing callers break?
+- Are there deprecation warnings where needed?
+- Is backward compatibility maintained?
-Target error handling, recovery, and degraded operation.
+### 3. Produce Findings
**Constraints:**
-- You MUST write tests that force exceptions in dependencies (mock failures in I/O, network, model calls)
-- You MUST write tests for timeout and cancellation scenarios if the code involves async or long-running operations
-- You MUST write tests that verify error messages are informative (not swallowed, not leaking internals)
-- You MUST write tests for resource cleanup on failure (files closed, connections released, locks freed)
-- You MUST test what happens when the code is called in an unexpected order or state
+- You MUST format each finding as:
-#### 3.3 Contract Verification
+```markdown
+### Finding: [Short Title]
-Verify the code actually fulfills the claims in the PR description.
+**Severity:** Critical | High | Medium | Low
+**Category:** Bug | Edge Case | Security | Breaking Change | Documentation
+**Location:** `file:line` or PR reference
-**Constraints:**
-- You MUST write at least one test per acceptance criterion extracted in Step 2
-- You MUST write tests that verify backward compatibility if the author claims it
-- You MUST write tests that verify the public API contract matches documentation/docstrings
-- You MUST test that default parameter values produce the documented default behavior
-- If the PR claims "no breaking changes," you MUST write a test that uses the old API surface and verify it still works
+**Description:**
+[What's wrong]
-#### 3.4 Security Probing
+**Reproduction:**
+[Exact steps or code to reproduce]
-Target security-sensitive patterns. Skip this section if the change has no security surface.
+**Expected Behavior:**
+[What should happen]
-**Constraints:**
-- You MUST check for hardcoded credentials, API keys, or tokens in the diff
-- You MUST test for injection vulnerabilities if the code constructs commands, queries, or prompts from user input
-- You MUST test for path traversal if the code handles file paths
-- You MUST test for unsafe deserialization if the code loads data from external sources
-- You MUST verify that sensitive data is not logged or exposed in error messages
-- You MUST check that any new dependencies don't introduce known vulnerabilities (check version pinning)
-
-#### 3.5 Concurrency and Race Conditions
-
-Target timing-dependent behavior. Skip if the change is purely synchronous and single-threaded.
-
-**Constraints:**
-- You MUST write tests that exercise concurrent access to shared state if applicable
-- You MUST write tests for async code that verify proper await chains and cancellation handling
-- You MUST test for deadlocks in code that acquires multiple locks or resources
-- You SHOULD use `threading` or `asyncio` test patterns to simulate concurrent callers
+**Actual Behavior:**
+[What actually happens]
+```
-### 4. Execute and Collect Artifacts
+- You MUST rank findings by severity (Critical first)
+- You MUST include at least the reproduction steps — no vague findings
+- If you find no issues, explicitly state "No adversarial findings" with a brief explanation of what you tested
-Run everything and collect evidence.
+## Output Format
-**Constraints:**
-- You MUST run all adversarial tests and record results
-- You MUST capture the full output (stdout, stderr, tracebacks) for every failing test
-- You MUST verify that each failing test is a genuine issue, not a test bug — re-run failures to confirm they're deterministic
-- You MUST categorize each finding:
- - **Bug**: The code produces incorrect results or crashes
- - **Unhandled Edge Case**: The code doesn't account for a valid input or state
- - **Contract Violation**: The code doesn't match what the PR/docs claim
- - **Security Issue**: The code has a security vulnerability
- - **Flaky Behavior**: The code produces inconsistent results across runs
-- You MUST discard any test that fails due to your own test code being wrong — fix the test or drop it
-- You MUST NOT report speculative issues without a failing artifact
-
-### 5. Report Findings
-
-Post findings to the PR or issue with evidence.
-
-**Constraints:**
-- You MUST post each finding as a comment with this structure:
- ```
- **Category**: [Bug | Unhandled Edge Case | Contract Violation | Security Issue | Flaky Behavior]
- **Severity**: [Critical | High | Medium]
- **Reproduction**:
- [Minimal code snippet or command that demonstrates the failure]
- **Observed behavior**: [What actually happens]
- **Expected behavior**: [What should happen based on the spec/PR description]
- **Artifact**: [Link to or inline the failing test]
- ```
-- You MUST attach or inline the adversarial test files so the author can run them
-- You MUST NOT include findings without reproduction artifacts
-- You MUST NOT comment on code style, naming, architecture, or documentation — that's the reviewer's domain
-- You MUST limit findings to genuine, reproducible issues
-- You SHOULD prioritize: Critical > High > Medium
-- If comment posting is deferred, continue with the workflow and note the deferred status
-
-### 6. Summary
-
-Provide a concise adversarial testing summary.
-
-**Constraints:**
-- You MUST create a summary comment or review with an overall assessment
-- You MUST use this format:
- ```
- **Adversarial Testing Result**: [PASS — no issues found | FAIL — N issues found]
-
- **Scope**: [Brief description of what was tested]
- **Tests written**: [count]
- **Tests passing**: [count]
- **Tests failing (findings)**: [count]
-
-
- Findings Summary
-
- | # | Category | Severity | Description |
- |---|----------|----------|-------------|
- | 1 | Bug | Critical | [one-line description] |
- | 2 | Edge Case | Medium | [one-line description] |
-
-
-
- **Artifacts**: [Location of adversarial test files]
- ```
-- If no issues were found, you MUST explicitly state: "The changes survived adversarial testing. No reproducible issues found."
-- You MUST NOT pad the report with speculative concerns or "things to watch out for"
-
-## What You Do NOT Do
-
-- You do NOT review code quality, style, or architecture
-- You do NOT suggest refactors or improvements
-- You do NOT praise good code
-- You do NOT speculate without evidence
-- You do NOT modify the source code under test
-- You do NOT write tests that test your own test code
-- You do NOT duplicate work the reviewer already covers
-
-## Troubleshooting
-
-### Large PRs
-- Focus on the public API surface and integration points first
-- Prioritize security-sensitive and error-handling paths
-- Skip internal refactors that don't change behavior
-
-### Deferred Operations
-When GitHub tools are deferred (GITHUB_WRITE=false):
-- Continue with the workflow as if the operation succeeded
-- Note the deferred status in your progress tracking
-- The operations will be executed after agent completion
-- Do not retry or attempt alternative approaches for deferred operations
-
-### Unfamiliar Codebase
-- Read `AGENTS.md` and test fixtures to understand mocking patterns
-- Look at existing tests for the modified files to understand expected patterns
-- Use existing test fixtures when writing adversarial tests
-
-### Flaky Tests
-- Run failing tests 3 times before reporting
-- If a test fails intermittently, categorize as "Flaky Behavior" and note the failure rate
-- Ensure your tests don't depend on execution order or global state
+When running as a sub-agent (via `use_agent`), return your findings as structured markdown that the orchestrator can include in the digest. When running standalone on a PR, post findings as PR comments.
## Desired Outcome
-* A set of adversarial tests that exercise edge cases, failure modes, and contract violations
-* Concrete, reproducible findings with runnable artifacts
-* A clear pass/fail assessment of the code changes
-* No speculative concerns — only proven issues
+* Concrete, evidence-based findings with reproduction steps
+* Findings ranked by severity
+* Clear enough that a developer can immediately understand and fix each issue
diff --git a/strands-command/agent-sops/task-release-digest.sop.md b/strands-command/agent-sops/task-release-digest.sop.md
index 0b82f51..c72ca34 100644
--- a/strands-command/agent-sops/task-release-digest.sop.md
+++ b/strands-command/agent-sops/task-release-digest.sop.md
@@ -2,277 +2,191 @@
## Role
-You are a Release Digest Orchestrator. Your goal is to produce a comprehensive weekly release digest by coordinating multiple parallel analysis tasks. You find all changes since the last release, dispatch sub-agents for adversarial testing, release notes generation, and documentation gap analysis, collect their results, and compile everything into a single consolidated digest issue.
+You are a Release Digest Orchestrator. Your goal is to produce a comprehensive weekly release digest for the Strands Agents ecosystem by spawning specialized sub-agents for each package using the `use_agent` tool. You coordinate the analysis, collect results, and compile everything into a single consolidated digest issue.
-You are the coordinator. You dispatch work, collect results, and synthesize findings. You do not do the detailed analysis yourself — you delegate to specialized agents.
+## Architecture
+
+You run as a single agent with `use_agent` from `strands_tools`. Sub-agents run **in-process** — no workflow dispatch, no PAT tokens, no self-trigger concerns. Each sub-agent gets its own system prompt and tool set, runs its analysis, and returns results to you.
+
+```
+Release Digest Orchestrator (you)
+├── Sub-agent: SDK Python Analyzer
+│ └── Analyzes strands-agents/sdk-python changes
+├── Sub-agent: SDK TypeScript Analyzer
+│ └── Analyzes strands-agents/sdk-typescript changes
+├── Sub-agent: Tools Analyzer
+│ └── Analyzes strands-agents/tools changes
+├── Sub-agent: Evals Analyzer
+│ └── Analyzes strands-agents/evals changes
+├── Sub-agent: Docs Gap Analyzer (optional)
+│ └── Cross-package documentation analysis
+└── You: Compile all results → create digest issue
+```
## Trigger
-- Automated weekly schedule (e.g., Wednesday 10am UTC)
+- Automated weekly schedule (Wednesday 10am UTC via cron)
- `/strands release-digest` on an Issue
- `workflow_dispatch` with release-digest prompt
## Principles
-1. **Orchestrate, don't do.** Your job is coordination and synthesis, not detailed analysis. Delegate to specialized agents.
-2. **Parallel when possible.** Dispatch independent tasks simultaneously to minimize wall-clock time.
+1. **Orchestrate via `use_agent`.** Spawn one sub-agent per package. Each runs in-process with its own context.
+2. **One agent per package.** SDK Python, SDK TypeScript, Tools, and Evals each get a dedicated sub-agent.
3. **Fail gracefully.** If a sub-agent fails, report what you have. Never block the entire digest on one failure.
-4. **Security first.** Enforce limits on concurrent agents, token budgets, and execution time.
-5. **Single artifact.** Your final output is ONE consolidated digest issue with all findings.
-
-## Security & Limits
-
-### Agent Dispatch Limits
-- **Max concurrent sub-agents**: 3 (configurable via `ORCHESTRATOR_MAX_CONCURRENT`)
-- **Per-agent timeout**: 30 minutes (configurable via `ORCHESTRATOR_AGENT_TIMEOUT_MINUTES`)
-- **Max total sub-agents per run**: 5 (configurable via `ORCHESTRATOR_MAX_TOTAL_AGENTS`)
-- **Cooldown between dispatches**: 10 seconds minimum
-- **Token budget per sub-agent**: 32000 tokens (configurable via `ORCHESTRATOR_AGENT_MAX_TOKENS`)
-
-### Authentication
-- Sub-agent dispatch requires `PAT_TOKEN` with `workflow_dispatch` permission
-- Sub-agents inherit repository-level permissions only
-- No credential passthrough between agents — each authenticates independently
-
-### Rate Limiting
-- Check GitHub API rate limits before dispatching
-- If rate limited, wait and retry with exponential backoff (max 3 retries)
-- Log all dispatch attempts and their outcomes
+4. **Single artifact.** Your final output is ONE consolidated digest issue with all findings.
+5. **Keep it simple.** No workflow dispatch, no orchestrator module, no PAT tokens. Just `use_agent`.
## Steps
-### 1. Discover Changes Since Last Release
+### 1. Discover Packages and Changes
-Identify the scope of changes to analyze.
+Identify which packages have changes since their last release.
**Constraints:**
-- You MUST find the most recent release tag using `git tag --sort=-v:refname | head -1` or GitHub API
-- You MUST identify the current HEAD or target branch
-- You MUST compute the diff between last release and current HEAD:
- - Count of merged PRs
- - List of PR numbers and titles
- - Files changed summary
- - Contributors involved
-- You MUST handle the case where no previous release exists (use repository creation as baseline)
-- You MUST record the git references (base tag, head ref) for sub-agent inputs
-- You MUST create a progress notebook to track orchestration status
+- You MUST check each of these repositories for changes since their last release tag:
+ - `strands-agents/sdk-python`
+ - `strands-agents/sdk-typescript`
+ - `strands-agents/tools`
+ - `strands-agents/evals`
+- For each repo, use `shell` to run: `git ls-remote --tags https://github.com/{repo}.git | sort -t '/' -k 3 -V | tail -1`
+- Use the GitHub API (`http_request`) to get merged PRs since the last release tag date
+- You MUST record which packages have changes and which are unchanged
+- You MUST skip sub-agent creation for packages with no changes since last release
-### 2. Plan Sub-Agent Tasks
+### 2. Spawn Per-Package Sub-Agents
-Determine which sub-agents to dispatch based on the changes found.
+For each package with changes, spawn a dedicated sub-agent using `use_agent`.
**Constraints:**
-- You MUST plan the following sub-agent tasks:
-
- | Task | Agent Type | Workflow | Input | Output |
- |------|-----------|----------|-------|--------|
- | Adversarial Testing | `adversarial-test` | `strands-adversarial-test.yml` | List of PRs with significant code changes | Findings report per PR |
- | Release Notes | `release-notes` | `strands-release-notes-agent.yml` | Base and head git references | Formatted release notes |
- | Documentation Gaps | `docs-gap` | `strands-docs-gap.yml` | List of PRs with new/changed APIs | Missing docs report |
-
-- You MUST skip adversarial testing if there are no code-changing PRs (only docs/CI/test changes)
-- You MUST skip documentation gap analysis if there are no API-changing PRs
-- You MUST record the planned tasks in your notebook with expected inputs/outputs
-- You MUST NOT dispatch more than `ORCHESTRATOR_MAX_TOTAL_AGENTS` sub-agents
-
-### 3. Dispatch Sub-Agents
+- You MUST use `use_agent` for each package sub-agent
+- Each sub-agent gets:
+ - **system_prompt**: Tailored to the specific package analysis
+ - **prompt**: The list of PRs/changes to analyze for that package
+ - **tools**: `["shell", "http_request"]` (sub-agents only need read access)
+- You MUST give each sub-agent a clear, focused task:
+ 1. Summarize the changes (features, fixes, refactors)
+ 2. Run adversarial analysis (edge cases, breaking changes, security concerns)
+ 3. Generate draft release notes for that package
+ 4. Identify documentation gaps
+- You SHOULD NOT give sub-agents write tools — they analyze and report, you (the orchestrator) write
+
+**Example sub-agent call:**
+```
+use_agent(
+ system_prompt="You are a package release analyst for strands-agents/sdk-python. Analyze the changes since the last release. For each merged PR, identify: 1) What changed 2) Potential edge cases or breaking changes 3) Documentation gaps 4) Draft release note entry. Be thorough and adversarial — look for things that could go wrong.",
+ prompt="Analyze these merged PRs in strands-agents/sdk-python since tag v1.2.0:\n- PR #456: Add streaming support\n- PR #457: Fix memory leak in session manager\n- PR #458: Update bedrock model config\n\nFor each PR, clone the repo, read the actual diff, and provide:\n1. Summary of changes\n2. Adversarial findings (edge cases, breaking changes, security issues)\n3. Documentation gaps\n4. Draft release note entry",
+ tools=["shell", "http_request"]
+)
+```
-Dispatch sub-agents for parallel execution.
+### 3. Spawn Additional Sub-Agents (Optional)
-**Constraints:**
-- You MUST use the orchestrator module (`orchestrator.py`) to dispatch sub-agents to their **dedicated workflows** (each runs in isolation)
-- You MUST call the `dispatch_agent` function for each planned task
-- You MUST respect the concurrent agent limit — wait for slots before dispatching
-- You MUST wait the minimum cooldown between dispatches
-- You MUST dispatch each sub-agent to its dedicated workflow:
- - **Adversarial tester** → `strands-adversarial-test.yml`
- - **Release notes** → `strands-release-notes-agent.yml`
- - **Docs gap** → `strands-docs-gap.yml`
-- You MUST pass appropriate inputs to each sub-agent:
- - **Adversarial tester**: PR numbers, branch references
- - **Release notes**: Base tag, head reference, repository
- - **Docs gap**: PR numbers with API changes, repository docs structure
-- You MUST record each dispatch in the notebook:
- - Agent type
- - Dispatch time
- - Workflow run ID (if available)
- - Status (dispatched/failed/timed-out)
-- You MUST handle dispatch failures gracefully — log the error and continue with other tasks
-- If dispatch fails for ALL sub-agents, proceed to Step 5 with what information you gathered in Step 1
-
-### 4. Collect Results
-
-Wait for sub-agents to complete and gather their outputs.
+For cross-cutting concerns, spawn additional focused sub-agents.
**Constraints:**
-- You MUST poll for sub-agent completion using the orchestrator module
-- You MUST enforce the per-agent timeout — if a sub-agent exceeds its timeout, mark it as timed out
-- You MUST collect results from completed sub-agents:
- - Check for new issues or comments created by the sub-agent
- - Check for gists created by the sub-agent
- - Check workflow run logs for output artifacts
-- You MUST handle partial results — if some agents succeed and others fail, use what's available
-- You MUST record collection status in the notebook for each sub-agent
-- You SHOULD wait for all agents to complete before synthesizing, up to the timeout limit
+- You MAY spawn a **Docs Gap Analyzer** sub-agent if multiple packages have API changes
+- You MAY spawn a **Breaking Changes** sub-agent to cross-reference changes across packages
+- Total sub-agents (including per-package) SHOULD NOT exceed 6
+- Each additional sub-agent MUST have a clearly distinct purpose from the per-package ones
-### 5. Synthesize Release Digest
+### 4. Collect and Synthesize Results
-Compile all results into a comprehensive digest.
+Compile all sub-agent results into a consolidated digest.
**Constraints:**
-- You MUST create a single consolidated digest with the following sections:
+- You MUST wait for each `use_agent` call to return (they are synchronous)
+- You MUST handle sub-agent failures gracefully — if one returns an error, note it and continue
+- You MUST compile results into a single markdown digest following this structure:
```markdown
# 📦 Weekly Release Digest — [Date]
-**Period**: [Last Release Tag] → [Current HEAD]
-**PRs Merged**: [count]
-**Contributors**: [list]
+**Period**: [Date range]
+**Packages Analyzed**: [list]
---
-## 🔍 Changes Overview
+## 📊 Overview
-[Summary of what changed: features, fixes, refactors, docs]
+| Package | PRs Merged | Key Changes | Issues Found |
+|---------|-----------|-------------|-------------|
+| SDK Python | X | ... | Y |
+| SDK TypeScript | X | ... | Y |
+| Tools | X | ... | Y |
+| Evals | X | ... | Y |
---
-## 🔴 Adversarial Testing Findings
+## 🐍 SDK Python (`strands-agents/sdk-python`)
-[Results from adversarial tester sub-agent]
+### Changes
+[Sub-agent results]
-| PR | Category | Severity | Finding |
-|----|----------|----------|---------|
-| #123 | Bug | Critical | [description] |
+### Adversarial Findings
+[Sub-agent results]
-[Or: "All changes passed adversarial testing. No issues found."]
+### Draft Release Notes
+[Sub-agent results]
----
-
-## 📝 Release Notes (Draft)
+### Documentation Gaps
+[Sub-agent results]
-[Results from release notes sub-agent]
+---
-### Major Features
-[Features with code examples]
+## 📘 SDK TypeScript (`strands-agents/sdk-typescript`)
-### Major Bug Fixes
-[Bug fixes with impact descriptions]
+[Same structure]
---
-## 📚 Documentation Gaps
+## 🔧 Tools (`strands-agents/tools`)
-[Results from docs gap sub-agent]
+[Same structure]
-| PR | API Change | Missing Documentation |
-|----|------------|----------------------|
-| #456 | New `Agent.stream()` method | No docstring, no usage example |
+---
+
+## 📏 Evals (`strands-agents/evals`)
-[Or: "All API changes have adequate documentation."]
+[Same structure]
---
## ⚠️ Action Items
-- [ ] [Critical issue from adversarial testing that needs fixing]
-- [ ] [Missing docs that should be added before release]
+- [ ] [Critical issues that need fixing before release]
+- [ ] [Missing docs that should be added]
+- [ ] [Breaking changes that need migration guides]
- [ ] [Release notes need review/approval]
---
-## 📊 Orchestration Report
+## 📋 Orchestration Report
-| Sub-Agent | Status | Duration | Output |
-|-----------|--------|----------|--------|
-| Adversarial Tester | ✅ Complete | 15m | [link] |
-| Release Notes | ✅ Complete | 8m | [link] |
-| Docs Gap | ⏱️ Timed Out | 30m | Partial results |
+| Sub-Agent | Package | Status | Duration |
+|-----------|---------|--------|----------|
+| SDK Python Analyzer | sdk-python | ✅ Complete | ~Xm |
+| SDK TS Analyzer | sdk-typescript | ✅ Complete | ~Xm |
+| ... | ... | ... | ... |
```
-- You MUST include results from ALL sub-agents that completed (even partially)
-- You MUST clearly mark which sections had sub-agent failures
-- You MUST list concrete action items for the team
-- You MUST include the orchestration report showing sub-agent status
-
-### 6. Publish Digest
+### 5. Publish Digest
Create the digest as a GitHub issue.
**Constraints:**
-- You MUST create a new GitHub issue with the digest content
+- You MUST create a new GitHub issue with the digest content using `create_issue`
- You MUST use the title format: `📦 Release Digest — [YYYY-MM-DD]`
-- You MUST add appropriate labels (e.g., `release-digest`, `automated`)
+- You MUST add appropriate labels if available (e.g., `release-digest`, `automated`)
- You MUST include a link to the workflow run for audit trail
-- If issue creation is deferred, continue and note the deferred status
-- You MAY also create a GitHub Gist with the full digest for easier sharing
-- You MUST record the created issue/gist URL in your notebook
-
-## Configuration
-
-### Environment Variables
-
-| Variable | Default | Description |
-|----------|---------|-------------|
-| `ORCHESTRATOR_MAX_CONCURRENT` | `3` | Max sub-agents running simultaneously |
-| `ORCHESTRATOR_AGENT_TIMEOUT_MINUTES` | `30` | Per-agent timeout |
-| `ORCHESTRATOR_MAX_TOTAL_AGENTS` | `5` | Max total sub-agents per orchestration run |
-| `ORCHESTRATOR_AGENT_MAX_TOKENS` | `32000` | Token budget per sub-agent |
-| `ORCHESTRATOR_COOLDOWN_SECONDS` | `10` | Minimum time between dispatches |
-
-### Workflow Variables
-
-The orchestrator reads scheduling configuration from the `AGENT_SCHEDULES` repository variable. Example schedule entry:
-
-```json
-{
- "jobs": {
- "weekly_release_digest": {
- "enabled": true,
- "cron": "0 10 * * 3",
- "prompt": "Run the weekly release digest. Find all changes since the last release, dispatch adversarial testing and release notes sub-agents, and compile a comprehensive digest issue.",
- "system_prompt": "You are a Release Digest Orchestrator following the task-release-digest SOP.",
- "workflow": "strands-autonomous.yml" // orchestrator only,
- "tools": ""
- }
- }
-}
-```
-
-## Troubleshooting
-
-### No Changes Found
-If there are no changes since the last release:
-- Create a minimal digest noting "No changes since last release"
-- Skip all sub-agent dispatches
-- Post the minimal digest and exit
-
-### All Sub-Agents Failed
-If all sub-agent dispatches fail:
-- Create the digest with information gathered in Step 1
-- Include the error details in the orchestration report
-- List the failures as action items
-- Mark the digest as "Partial — sub-agent failures"
-
-### Rate Limiting
-If GitHub API rate limits are hit:
-- Log the rate limit status
-- Retry with exponential backoff (1min, 2min, 4min)
-- If still rate limited after 3 retries, proceed with what's available
-
-### Deferred Operations
-When GitHub tools are deferred (GITHUB_WRITE=false):
-- Continue with the workflow as if the operation succeeded
-- Note the deferred status in your progress tracking
-- The operations will be executed after agent completion
+- If some packages had no changes, note them briefly: "No changes since last release"
## Desired Outcome
* A single, comprehensive release digest issue containing:
- * Overview of all changes since last release
- * Adversarial testing findings (or clean bill of health)
- * Draft release notes with code examples
+ * Per-package analysis from dedicated sub-agents
+ * Adversarial testing findings per package
+ * Draft release notes per package
* Documentation gap analysis
* Concrete action items for the team
-* All sub-agent results properly collected and synthesized
-* Clear audit trail of orchestration decisions and outcomes
+* Clean orchestration — one agent, in-process sub-agents, no workflow dispatch complexity
diff --git a/strands-command/scripts/python/agent_runner.py b/strands-command/scripts/python/agent_runner.py
index b373ac7..c121767 100644
--- a/strands-command/scripts/python/agent_runner.py
+++ b/strands-command/scripts/python/agent_runner.py
@@ -19,7 +19,7 @@
from strands.models.bedrock import BedrockModel
from botocore.config import Config
-from strands_tools import http_request, shell
+from strands_tools import http_request, shell, use_agent
# Import local GitHub tools we need
from github_tools import (
@@ -41,14 +41,6 @@
# Import local tools we need
from handoff_to_user import handoff_to_user
-
-# Import orchestrator tools for agent-to-agent coordination
-try:
- from orchestrator import dispatch_agent, check_agents_status, wait_for_agents, get_orchestrator_config
- _ORCHESTRATOR_AVAILABLE = True
-except ImportError:
- _ORCHESTRATOR_AVAILABLE = False
- print("ℹ️ Orchestrator tools not available (orchestrator.py not found)")
from notebook import notebook
from str_replace_based_edit_tool import str_replace_based_edit_tool
@@ -186,12 +178,13 @@ def _get_all_tools() -> list[Any]:
# Agent tools
notebook,
handoff_to_user,
+
+ # Sub-agent creation — enables orchestrator pattern
+ # The parent agent can spawn specialized sub-agents for parallel tasks
+ # Each sub-agent runs in-process with its own system prompt and tools
+ use_agent,
]
- # Add orchestrator tools if available
- if _ORCHESTRATOR_AVAILABLE:
- tools.extend([dispatch_agent, check_agents_status, wait_for_agents, get_orchestrator_config])
-
return tools
diff --git a/strands-command/scripts/python/orchestrator.py b/strands-command/scripts/python/orchestrator.py
deleted file mode 100644
index f5f6db4..0000000
--- a/strands-command/scripts/python/orchestrator.py
+++ /dev/null
@@ -1,595 +0,0 @@
-"""Agent Orchestrator for Strands Command.
-
-Enables agents to dispatch and coordinate sub-agents via GitHub Actions
-workflow_dispatch events. Provides security limits, rate limiting, and
-result collection for multi-agent orchestration.
-
-Key Features:
-1. Sub-agent dispatch via GitHub workflow_dispatch API
-2. Configurable security limits (concurrency, token budgets, timeouts)
-3. Rate limiting with cooldown between dispatches
-4. Result collection via workflow run polling
-5. Graceful failure handling for partial results
-
-Usage:
- from orchestrator import AgentOrchestrator
-
- orch = AgentOrchestrator(repo="owner/repo")
- run_id = orch.dispatch_agent(
- agent_type="adversarial-test",
- prompt="Test PR #123 for edge cases",
- system_prompt="You are an adversarial tester...",
- )
- result = orch.wait_for_completion(run_id, timeout_minutes=30)
-
-Security:
- - Max concurrent agents: ORCHESTRATOR_MAX_CONCURRENT (default: 3)
- - Max total agents per run: ORCHESTRATOR_MAX_TOTAL_AGENTS (default: 5)
- - Per-agent timeout: ORCHESTRATOR_AGENT_TIMEOUT_MINUTES (default: 30)
- - Cooldown between dispatches: ORCHESTRATOR_COOLDOWN_SECONDS (default: 10)
- - Token budget: ORCHESTRATOR_AGENT_MAX_TOKENS (default: 32000)
-"""
-
-import json
-import os
-import time
-from dataclasses import dataclass, field
-from datetime import datetime, timezone
-from enum import Enum
-from typing import Any
-
-import requests
-from strands import tool
-
-
-class AgentStatus(str, Enum):
- """Status of a dispatched sub-agent."""
- PENDING = "pending"
- DISPATCHED = "dispatched"
- RUNNING = "running"
- COMPLETED = "completed"
- FAILED = "failed"
- TIMED_OUT = "timed_out"
-
-
-@dataclass
-class AgentTask:
- """Represents a dispatched sub-agent task."""
- agent_type: str
- prompt: str
- system_prompt: str = ""
- status: AgentStatus = AgentStatus.PENDING
- run_id: str | None = None
- dispatch_time: datetime | None = None
- completion_time: datetime | None = None
- result: str | None = None
- error: str | None = None
- workflow: str = ""
-
-
-@dataclass
-class OrchestratorConfig:
- """Configuration for the agent orchestrator."""
- max_concurrent: int = 3
- max_total_agents: int = 5
- agent_timeout_minutes: int = 30
- agent_max_tokens: int = 32000
- cooldown_seconds: int = 10
- poll_interval_seconds: int = 30
-
- @classmethod
- def from_env(cls) -> "OrchestratorConfig":
- """Create config from environment variables."""
- return cls(
- max_concurrent=int(os.getenv("ORCHESTRATOR_MAX_CONCURRENT", "3")),
- max_total_agents=int(os.getenv("ORCHESTRATOR_MAX_TOTAL_AGENTS", "5")),
- agent_timeout_minutes=int(os.getenv("ORCHESTRATOR_AGENT_TIMEOUT_MINUTES", "30")),
- agent_max_tokens=int(os.getenv("ORCHESTRATOR_AGENT_MAX_TOKENS", "32000")),
- cooldown_seconds=int(os.getenv("ORCHESTRATOR_COOLDOWN_SECONDS", "10")),
- poll_interval_seconds=int(os.getenv("ORCHESTRATOR_POLL_INTERVAL_SECONDS", "30")),
- )
-
-
-class AgentOrchestrator:
- """Orchestrates sub-agent dispatch and result collection.
-
- Provides security-limited agent-to-agent coordination via
- GitHub Actions workflow_dispatch events.
- """
-
- def __init__(
- self,
- repo: str | None = None,
- config: OrchestratorConfig | None = None,
- ):
- self.repo = repo or os.getenv("GITHUB_REPOSITORY", "")
- self.config = config or OrchestratorConfig.from_env()
- self.token = os.getenv("PAT_TOKEN", os.getenv("GITHUB_TOKEN", ""))
- self.tasks: list[AgentTask] = []
- self._last_dispatch_time: float = 0
- self._total_dispatched: int = 0
-
- if not self.repo:
- raise ValueError("Repository not specified and GITHUB_REPOSITORY not set")
- if not self.token:
- raise ValueError("No GitHub token available (PAT_TOKEN or GITHUB_TOKEN)")
-
- @property
- def _headers(self) -> dict[str, str]:
- return {
- "Authorization": f"Bearer {self.token}",
- "Accept": "application/vnd.github+json",
- "X-GitHub-Api-Version": "2022-11-28",
- }
-
- def _check_rate_limit(self) -> bool:
- """Check if we're within GitHub API rate limits."""
- try:
- resp = requests.get(
- "https://api.github.com/rate_limit",
- headers=self._headers,
- timeout=10,
- )
- resp.raise_for_status()
- data = resp.json()
- remaining = data.get("resources", {}).get("core", {}).get("remaining", 0)
- return remaining > 10 # Keep a buffer
- except Exception:
- return True # Optimistic on failure
-
- def _enforce_cooldown(self) -> None:
- """Enforce minimum cooldown between dispatches."""
- elapsed = time.time() - self._last_dispatch_time
- if elapsed < self.config.cooldown_seconds:
- wait_time = self.config.cooldown_seconds - elapsed
- print(f"⏳ Cooldown: waiting {wait_time:.1f}s before next dispatch")
- time.sleep(wait_time)
-
- def _get_active_count(self) -> int:
- """Count currently active (dispatched or running) agents."""
- return sum(
- 1 for t in self.tasks
- if t.status in (AgentStatus.DISPATCHED, AgentStatus.RUNNING)
- )
-
- def can_dispatch(self) -> bool:
- """Check if we can dispatch another agent."""
- if self._total_dispatched >= self.config.max_total_agents:
- print(f"⚠️ Total agent limit reached ({self.config.max_total_agents})")
- return False
- if self._get_active_count() >= self.config.max_concurrent:
- print(f"⚠️ Concurrent agent limit reached ({self.config.max_concurrent})")
- return False
- if not self._check_rate_limit():
- print("⚠️ GitHub API rate limit approaching")
- return False
- return True
-
- def dispatch_agent(
- self,
- agent_type: str,
- prompt: str,
- system_prompt: str = "",
- workflow: str = "",
- extra_inputs: dict[str, str] | None = None,
- ) -> AgentTask:
- """Dispatch a sub-agent via workflow_dispatch.
-
- Args:
- agent_type: Type of agent (e.g., "adversarial-test", "release-notes")
- prompt: Task prompt for the sub-agent
- system_prompt: System prompt override for the sub-agent
- workflow: Target workflow file. Each sub-agent has a dedicated workflow:
- - strands-adversarial-test.yml
- - strands-release-notes-agent.yml
- - strands-docs-gap.yml
- extra_inputs: Additional workflow inputs
-
- Returns:
- AgentTask with dispatch status
-
- Raises:
- RuntimeError: If dispatch limits are exceeded
- """
- task = AgentTask(
- agent_type=agent_type,
- prompt=prompt,
- system_prompt=system_prompt,
- workflow=workflow,
- )
-
- # Security checks
- if not self.can_dispatch():
- task.status = AgentStatus.FAILED
- task.error = "Dispatch limit exceeded"
- self.tasks.append(task)
- return task
-
- # Enforce cooldown
- self._enforce_cooldown()
-
- # Parse workflow target (same-repo or cross-repo)
- if workflow.count("/") >= 2:
- # Cross-repo: "owner/repo/workflow.yml"
- parts = workflow.split("/", 2)
- dispatch_repo = f"{parts[0]}/{parts[1]}"
- dispatch_workflow = parts[2]
- else:
- dispatch_repo = self.repo
- dispatch_workflow = workflow
-
- # Build inputs
- inputs: dict[str, str] = {
- "command": prompt,
- }
- if system_prompt:
- inputs["system_prompt"] = system_prompt
-
- if extra_inputs:
- inputs.update(extra_inputs)
-
- # Dispatch via GitHub API
- url = f"https://api.github.com/repos/{dispatch_repo}/actions/workflows/{dispatch_workflow}/dispatches"
- payload = {
- "ref": "main",
- "inputs": inputs,
- }
-
- try:
- print(f"🚀 Dispatching {agent_type} agent to {dispatch_repo}/{dispatch_workflow}")
- resp = requests.post(
- url,
- headers=self._headers,
- json=payload,
- timeout=30,
- )
-
- if resp.status_code == 204:
- task.status = AgentStatus.DISPATCHED
- task.dispatch_time = datetime.now(timezone.utc)
- self._last_dispatch_time = time.time()
- self._total_dispatched += 1
- print(f"✅ Dispatched {agent_type} agent successfully")
-
- # Try to find the run ID (poll recent runs)
- time.sleep(2) # Brief wait for GitHub to register the run
- run_id = self._find_recent_run(dispatch_repo, dispatch_workflow)
- if run_id:
- task.run_id = run_id
- print(f" Run ID: {run_id}")
- else:
- task.status = AgentStatus.FAILED
- task.error = f"HTTP {resp.status_code}: {resp.text}"
- print(f"❌ Dispatch failed: {task.error}")
-
- except Exception as e:
- task.status = AgentStatus.FAILED
- task.error = str(e)
- print(f"❌ Dispatch error: {e}")
-
- self.tasks.append(task)
- return task
-
- def _find_recent_run(self, repo: str, workflow: str) -> str | None:
- """Find the most recent workflow run (just dispatched)."""
- try:
- url = f"https://api.github.com/repos/{repo}/actions/workflows/{workflow}/runs"
- resp = requests.get(
- url,
- headers=self._headers,
- params={"per_page": 1, "status": "queued"},
- timeout=10,
- )
- resp.raise_for_status()
- runs = resp.json().get("workflow_runs", [])
- if runs:
- return str(runs[0]["id"])
- except Exception:
- pass
- return None
-
- def check_run_status(self, task: AgentTask) -> AgentStatus:
- """Check the status of a dispatched agent's workflow run.
-
- Args:
- task: The agent task to check
-
- Returns:
- Updated AgentStatus
- """
- if not task.run_id:
- return task.status
-
- try:
- url = f"https://api.github.com/repos/{self.repo}/actions/runs/{task.run_id}"
- resp = requests.get(url, headers=self._headers, timeout=10)
- resp.raise_for_status()
- run_data = resp.json()
-
- status = run_data.get("status", "")
- conclusion = run_data.get("conclusion", "")
-
- if status == "completed":
- if conclusion == "success":
- task.status = AgentStatus.COMPLETED
- task.completion_time = datetime.now(timezone.utc)
- else:
- task.status = AgentStatus.FAILED
- task.error = f"Workflow concluded: {conclusion}"
- task.completion_time = datetime.now(timezone.utc)
- elif status in ("queued", "in_progress"):
- task.status = AgentStatus.RUNNING
-
- # Check timeout
- if task.dispatch_time:
- elapsed = (datetime.now(timezone.utc) - task.dispatch_time).total_seconds()
- if elapsed > self.config.agent_timeout_minutes * 60:
- task.status = AgentStatus.TIMED_OUT
- task.error = f"Exceeded {self.config.agent_timeout_minutes}m timeout"
- task.completion_time = datetime.now(timezone.utc)
-
- except Exception as e:
- print(f"⚠️ Status check failed for run {task.run_id}: {e}")
-
- return task.status
-
- def wait_for_all(self, timeout_minutes: int | None = None) -> list[AgentTask]:
- """Wait for all dispatched agents to complete.
-
- Args:
- timeout_minutes: Overall timeout (default: config.agent_timeout_minutes)
-
- Returns:
- List of all tasks with final statuses
- """
- timeout = (timeout_minutes or self.config.agent_timeout_minutes) * 60
- start_time = time.time()
-
- active_tasks = [
- t for t in self.tasks
- if t.status in (AgentStatus.DISPATCHED, AgentStatus.RUNNING)
- ]
-
- while active_tasks and (time.time() - start_time) < timeout:
- for task in active_tasks:
- self.check_run_status(task)
-
- active_tasks = [
- t for t in self.tasks
- if t.status in (AgentStatus.DISPATCHED, AgentStatus.RUNNING)
- ]
-
- if active_tasks:
- elapsed = int(time.time() - start_time)
- print(f"⏳ Waiting for {len(active_tasks)} agent(s)... ({elapsed}s elapsed)")
- time.sleep(self.config.poll_interval_seconds)
-
- # Mark remaining active tasks as timed out
- for task in active_tasks:
- if task.status in (AgentStatus.DISPATCHED, AgentStatus.RUNNING):
- task.status = AgentStatus.TIMED_OUT
- task.error = "Overall orchestration timeout exceeded"
- task.completion_time = datetime.now(timezone.utc)
-
- return self.tasks
-
- def get_summary(self) -> dict[str, Any]:
- """Get a summary of all orchestrated tasks.
-
- Returns:
- Dictionary with task counts and details
- """
- summary = {
- "total": len(self.tasks),
- "completed": sum(1 for t in self.tasks if t.status == AgentStatus.COMPLETED),
- "failed": sum(1 for t in self.tasks if t.status == AgentStatus.FAILED),
- "timed_out": sum(1 for t in self.tasks if t.status == AgentStatus.TIMED_OUT),
- "tasks": [],
- }
-
- for task in self.tasks:
- duration = None
- if task.dispatch_time and task.completion_time:
- duration = (task.completion_time - task.dispatch_time).total_seconds()
-
- summary["tasks"].append({
- "agent_type": task.agent_type,
- "status": task.status.value,
- "run_id": task.run_id,
- "duration_seconds": duration,
- "error": task.error,
- })
-
- return summary
-
- def format_report(self) -> str:
- """Format a markdown report of orchestration results.
-
- Returns:
- Markdown-formatted orchestration report
- """
- summary = self.get_summary()
- lines = [
- "## 📊 Orchestration Report",
- "",
- f"**Total agents**: {summary['total']} | "
- f"**Completed**: {summary['completed']} | "
- f"**Failed**: {summary['failed']} | "
- f"**Timed out**: {summary['timed_out']}",
- "",
- "| Sub-Agent | Status | Duration | Run ID | Error |",
- "|-----------|--------|----------|--------|-------|",
- ]
-
- status_icons = {
- "completed": "✅",
- "failed": "❌",
- "timed_out": "⏱️",
- "dispatched": "🚀",
- "running": "🔄",
- "pending": "⏳",
- }
-
- for task_info in summary["tasks"]:
- icon = status_icons.get(task_info["status"], "❓")
- duration = ""
- if task_info["duration_seconds"]:
- mins = int(task_info["duration_seconds"] // 60)
- secs = int(task_info["duration_seconds"] % 60)
- duration = f"{mins}m {secs}s"
-
- error = task_info["error"] or ""
- if len(error) > 40:
- error = error[:40] + "..."
-
- lines.append(
- f"| {task_info['agent_type']} | {icon} {task_info['status']} | "
- f"{duration} | {task_info['run_id'] or 'N/A'} | {error} |"
- )
-
- return "\n".join(lines)
-
-
-# =============================================================================
-# Tool interface for agent usage
-# =============================================================================
-
-# Module-level orchestrator instance (lazy-initialized)
-_orchestrator: AgentOrchestrator | None = None
-
-
-def _get_orchestrator() -> AgentOrchestrator:
- """Get or create the module-level orchestrator instance."""
- global _orchestrator
- if _orchestrator is None:
- _orchestrator = AgentOrchestrator()
- return _orchestrator
-
-
-@tool
-def dispatch_agent(
- agent_type: str,
- prompt: str,
- system_prompt: str = "",
- workflow: str = "",
-) -> str:
- """Dispatch a sub-agent via GitHub Actions workflow_dispatch.
-
- Security limits are enforced:
- - Max concurrent agents (default: 3)
- - Max total agents per run (default: 5)
- - Cooldown between dispatches (default: 10s)
- - Rate limit checking
-
- Args:
- agent_type: Type of agent (e.g., "adversarial-test", "release-notes", "docs-gap")
- prompt: Task prompt for the sub-agent
- system_prompt: System prompt override (optional)
- workflow: Target workflow file. Each sub-agent has a dedicated workflow:
- - strands-adversarial-test.yml
- - strands-release-notes-agent.yml
- - strands-docs-gap.yml.
- Use "owner/repo/workflow.yml" for cross-repo dispatch.
-
- Returns:
- Status message with dispatch result
- """
- try:
- orch = _get_orchestrator()
- task = orch.dispatch_agent(
- agent_type=agent_type,
- prompt=prompt,
- system_prompt=system_prompt,
- workflow=workflow,
- )
-
- if task.status == AgentStatus.DISPATCHED:
- return (
- f"✅ Agent dispatched: {agent_type}\n"
- f" Run ID: {task.run_id or 'pending'}\n"
- f" Workflow: {workflow}\n"
- f" Active agents: {orch._get_active_count()}/{orch.config.max_concurrent}\n"
- f" Total dispatched: {orch._total_dispatched}/{orch.config.max_total_agents}"
- )
- else:
- return f"❌ Dispatch failed: {task.error}"
-
- except Exception as e:
- return f"❌ Orchestrator error: {e}"
-
-
-@tool
-def check_agents_status() -> str:
- """Check the status of all dispatched sub-agents.
-
- Returns:
- Markdown-formatted status report
- """
- try:
- orch = _get_orchestrator()
-
- if not orch.tasks:
- return "No sub-agents have been dispatched yet."
-
- # Update statuses
- for task in orch.tasks:
- if task.status in (AgentStatus.DISPATCHED, AgentStatus.RUNNING):
- orch.check_run_status(task)
-
- return orch.format_report()
-
- except Exception as e:
- return f"❌ Status check error: {e}"
-
-
-@tool
-def wait_for_agents(timeout_minutes: int = 30) -> str:
- """Wait for all dispatched sub-agents to complete.
-
- Polls GitHub Actions API at regular intervals until all agents
- complete, fail, or timeout.
-
- Args:
- timeout_minutes: Maximum time to wait (default: 30)
-
- Returns:
- Final orchestration report
- """
- try:
- orch = _get_orchestrator()
-
- if not orch.tasks:
- return "No sub-agents have been dispatched yet."
-
- active = [t for t in orch.tasks if t.status in (AgentStatus.DISPATCHED, AgentStatus.RUNNING)]
- if not active:
- return "All sub-agents have already completed.\n\n" + orch.format_report()
-
- print(f"⏳ Waiting for {len(active)} agent(s) with {timeout_minutes}m timeout...")
- orch.wait_for_all(timeout_minutes=timeout_minutes)
-
- return orch.format_report()
-
- except Exception as e:
- return f"❌ Wait error: {e}"
-
-
-@tool
-def get_orchestrator_config() -> str:
- """Get the current orchestrator security configuration.
-
- Returns:
- Current configuration values
- """
- config = OrchestratorConfig.from_env()
- return (
- f"## Orchestrator Configuration\n\n"
- f"| Setting | Value |\n"
- f"|---------|-------|\n"
- f"| Max concurrent agents | {config.max_concurrent} |\n"
- f"| Max total agents per run | {config.max_total_agents} |\n"
- f"| Agent timeout | {config.agent_timeout_minutes} minutes |\n"
- f"| Agent max tokens | {config.agent_max_tokens} |\n"
- f"| Cooldown between dispatches | {config.cooldown_seconds} seconds |\n"
- f"| Poll interval | {config.poll_interval_seconds} seconds |\n"
- )
diff --git a/strands-command/workflows/strands-adversarial-test.yml b/strands-command/workflows/strands-adversarial-test.yml
deleted file mode 100644
index ede1749..0000000
--- a/strands-command/workflows/strands-adversarial-test.yml
+++ /dev/null
@@ -1,138 +0,0 @@
-# Strands Adversarial Test Agent Workflow
-#
-# Dedicated workflow for adversarial testing — runs in isolation.
-# Triggered by the release digest orchestrator or manually via workflow_dispatch.
-#
-# Required Secrets:
-# AWS_ROLE_ARN: IAM role for AWS access
-# AWS_SECRETS_MANAGER_SECRET_ID: Secret containing agent config
-#
-# Usage:
-# - Dispatched by strands-autonomous.yml (release digest orchestrator)
-# - Manual: workflow_dispatch with issue_id or pr_number
-# - Comment: /strands adversarial-test on a PR
-
-name: Strands Adversarial Test
-
-on:
- workflow_dispatch:
- inputs:
- command:
- description: 'Task prompt for the adversarial tester'
- required: false
- type: string
- default: 'Run adversarial testing on recent changes since the last release.'
- issue_id:
- description: 'Issue or PR number to test'
- required: false
- type: string
- session_id:
- description: 'Session ID for resuming'
- required: false
- type: string
- default: ''
-
-permissions: write-all
-
-jobs:
- adversarial-test:
- runs-on: ubuntu-latest
- timeout-minutes: 45
- steps:
- - name: Checkout repository
- uses: actions/checkout@v4
-
- - name: Checkout devtools
- uses: actions/checkout@v5
- with:
- repository: strands-agents/devtools
- ref: main
- sparse-checkout: |
- strands-command/scripts
- strands-command/agent-sops
- path: devtools
-
- - name: Copy devtools to safe directory
- shell: bash
- run: |
- mkdir -p ${{ runner.temp }}/strands-agent-runner
- cp -r devtools/strands-command ${{ runner.temp }}/strands-agent-runner/
-
- - name: Set up Python
- uses: actions/setup-python@v4
- with:
- python-version: '3.13'
-
- - name: Install uv
- uses: astral-sh/setup-uv@v3
-
- - name: Install dependencies
- shell: bash
- run: |
- uv pip install --system -r ${{ runner.temp }}/strands-agent-runner/strands-command/scripts/python/requirements.txt --quiet
-
- - name: Configure Git
- shell: bash
- run: |
- git config --global user.name "Strands Agent"
- git config --global user.email "217235299+strands-agent@users.noreply.github.com"
- git config --global core.pager cat
-
- - name: Configure AWS credentials
- uses: aws-actions/configure-aws-credentials@v4
- with:
- role-to-assume: ${{ secrets.AWS_ROLE_ARN }}
- role-session-name: GitHubActions-AdversarialTest-${{ github.run_id }}
- aws-region: us-west-2
- mask-aws-account-id: true
-
- - name: Retrieve secrets
- id: secrets
- shell: bash
- run: |
- if [ -n "${{ secrets.AWS_SECRETS_MANAGER_SECRET_ID }}" ]; then
- SECRET_JSON=$(aws secretsmanager get-secret-value --secret-id "${{ secrets.AWS_SECRETS_MANAGER_SECRET_ID }}" --query SecretString --output text --region us-east-1)
- SESSIONS_BUCKET=$(echo $SECRET_JSON | jq -r '.AGENT_SESSIONS_BUCKET // empty')
- [ -n "$SESSIONS_BUCKET" ] && echo "::add-mask::$SESSIONS_BUCKET"
- echo "sessions_bucket=$SESSIONS_BUCKET" >> $GITHUB_OUTPUT
- fi
-
- - name: Build task prompt
- id: prompt
- shell: bash
- run: |
- COMMAND="${{ github.event.inputs.command }}"
- ISSUE_ID="${{ github.event.inputs.issue_id }}"
- SESSION_ID="${{ github.event.inputs.session_id }}"
-
- if [ -z "$SESSION_ID" ]; then
- SESSION_ID="adversarial-test-$(date +%s)"
- fi
- echo "session_id=$SESSION_ID" >> $GITHUB_OUTPUT
-
- if [ -n "$ISSUE_ID" ]; then
- TASK="Run adversarial testing on PR #${ISSUE_ID}. Check the code changes for edge cases, failure modes, and security issues."
- else
- TASK="${COMMAND:-Run adversarial testing on recent changes since the last release.}"
- fi
- echo "task=$TASK" >> $GITHUB_OUTPUT
-
- - name: Execute adversarial test agent
- shell: bash
- env:
- GITHUB_WRITE: 'true'
- GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- PAT_TOKEN: ${{ secrets.PAT_TOKEN }}
- S3_SESSION_BUCKET: ${{ steps.secrets.outputs.sessions_bucket }}
- SESSION_ID: ${{ steps.prompt.outputs.session_id }}
- INPUT_SYSTEM_PROMPT: |
- You are an Adversarial Tester following the task-adversarial-tester SOP.
- Your goal is to break code changes by actively finding bugs, edge cases,
- security holes, and failure modes. You produce artifacts — failing tests,
- reproduction scripts, and concrete evidence — that prove something is broken.
- INPUT_TASK: ${{ steps.prompt.outputs.task }}
- STRANDS_TOOL_CONSOLE_MODE: 'enabled'
- BYPASS_TOOL_CONSENT: 'true'
- run: |
- echo "🔴 Running adversarial test agent"
- uv run --no-project ${{ runner.temp }}/strands-agent-runner/strands-command/scripts/python/agent_runner.py "$INPUT_TASK"
diff --git a/strands-command/workflows/strands-autonomous.yml b/strands-command/workflows/strands-autonomous.yml
index ad9bbb0..fc7e393 100644
--- a/strands-command/workflows/strands-autonomous.yml
+++ b/strands-command/workflows/strands-autonomous.yml
@@ -1,19 +1,18 @@
-# Strands Release Digest Orchestrator Workflow
+# Strands Release Digest Workflow
#
-# This is the orchestrator that runs every Wednesday at 10am UTC.
-# It finds changes since the last release and dispatches sub-agents
-# to their dedicated workflows for parallel execution:
-# - strands-adversarial-test.yml (adversarial testing)
-# - strands-release-notes-agent.yml (release notes generation)
-# - strands-docs-gap.yml (documentation gap analysis)
+# Single workflow that runs one orchestrator agent.
+# The orchestrator uses `use_agent` from strands_tools to spawn
+# per-package sub-agents in-process — no workflow dispatch needed.
#
-# Each sub-agent workflow can also be triggered independently for
-# isolated testing via workflow_dispatch.
+# Packages analyzed:
+# - strands-agents/sdk-python
+# - strands-agents/sdk-typescript
+# - strands-agents/tools
+# - strands-agents/evals
#
# Required Secrets:
# AWS_ROLE_ARN: IAM role for AWS access
# AWS_SECRETS_MANAGER_SECRET_ID: Secret containing agent config
-# PAT_TOKEN: Personal access token for dispatching sub-agent workflows
name: Strands Release Digest
@@ -41,12 +40,7 @@ permissions: write-all
jobs:
release-digest:
runs-on: ubuntu-latest
- timeout-minutes: 60
- env:
- # Orchestrator security limits
- ORCHESTRATOR_MAX_CONCURRENT: ${{ vars.ORCHESTRATOR_MAX_CONCURRENT || '3' }}
- ORCHESTRATOR_AGENT_TIMEOUT_MINUTES: ${{ vars.ORCHESTRATOR_AGENT_TIMEOUT_MINUTES || '30' }}
- ORCHESTRATOR_MAX_TOTAL_AGENTS: ${{ vars.ORCHESTRATOR_MAX_TOTAL_AGENTS || '5' }}
+ timeout-minutes: 120 # 2 hours — sub-agents run in-process
steps:
- name: Checkout repository
uses: actions/checkout@v4
@@ -123,28 +117,34 @@ jobs:
env:
GITHUB_WRITE: 'true'
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- PAT_TOKEN: ${{ secrets.PAT_TOKEN }}
S3_SESSION_BUCKET: ${{ steps.secrets.outputs.sessions_bucket }}
SESSION_ID: ${{ steps.setup.outputs.session_id }}
INPUT_SYSTEM_PROMPT: |
You are a Release Digest Orchestrator following the task-release-digest SOP.
- Your goal is to produce a comprehensive weekly release digest by coordinating
- multiple parallel analysis tasks. You find all changes since the last release,
- dispatch sub-agents for adversarial testing and release notes generation,
- collect their results, and compile everything into a single consolidated digest issue.
- Sub-agent workflows (dispatch to these via the orchestrator):
- - strands-adversarial-test.yml — Adversarial testing
- - strands-release-notes-agent.yml — Release notes generation
- - strands-docs-gap.yml — Documentation gap analysis
+ Your goal: produce a comprehensive weekly release digest for the Strands Agents
+ ecosystem. You use `use_agent` to spawn per-package sub-agents that analyze
+ changes in parallel. Each sub-agent runs in-process with its own system prompt.
- Each sub-agent runs in its own isolated workflow and can be tested independently.
+ Packages to analyze:
+ - strands-agents/sdk-python
+ - strands-agents/sdk-typescript
+ - strands-agents/tools
+ - strands-agents/evals
+
+ For each package with changes since its last release:
+ 1. Spawn a sub-agent via `use_agent` with tools=["shell", "http_request"]
+ 2. The sub-agent analyzes PRs, runs adversarial testing, generates release notes
+ 3. Collect all results and compile into a single digest issue
+
+ You have `use_agent` available. Use it to delegate analysis to specialized sub-agents.
+ You have `create_issue` to publish the final digest.
STRANDS_TOOL_CONSOLE_MODE: 'enabled'
BYPASS_TOOL_CONSENT: 'true'
run: |
TASK="${{ github.event.inputs.command }}"
if [ -z "$TASK" ]; then
- TASK="Run the weekly release digest. Find all changes since the last release tag, analyze merged PRs, dispatch adversarial testing (strands-adversarial-test.yml), release notes (strands-release-notes-agent.yml), and docs gap (strands-docs-gap.yml) sub-agents to their dedicated workflows, and compile a comprehensive digest issue with findings, draft release notes, and action items."
+ TASK="Run the weekly release digest following the task-release-digest SOP. Check strands-agents/sdk-python, strands-agents/sdk-typescript, strands-agents/tools, and strands-agents/evals for changes since their last release. For each package with changes, spawn a sub-agent via use_agent to analyze the PRs, test for edge cases, and generate draft release notes. Compile all results into a single digest issue."
fi
echo "🚀 Running release digest orchestrator"
diff --git a/strands-command/workflows/strands-docs-gap.yml b/strands-command/workflows/strands-docs-gap.yml
deleted file mode 100644
index bf7f8f9..0000000
--- a/strands-command/workflows/strands-docs-gap.yml
+++ /dev/null
@@ -1,137 +0,0 @@
-# Strands Docs Gap Analysis Agent Workflow
-#
-# Dedicated workflow for documentation gap analysis — runs in isolation.
-# Triggered by the release digest orchestrator or manually via workflow_dispatch.
-#
-# Required Secrets:
-# AWS_ROLE_ARN: IAM role for AWS access
-# AWS_SECRETS_MANAGER_SECRET_ID: Secret containing agent config
-#
-# Usage:
-# - Dispatched by strands-autonomous.yml (release digest orchestrator)
-# - Manual: workflow_dispatch with pr_numbers
-
-name: Strands Docs Gap Analysis
-
-on:
- workflow_dispatch:
- inputs:
- command:
- description: 'Task prompt for the docs gap analyzer'
- required: false
- type: string
- default: 'Analyze recent changes for documentation gaps.'
- pr_numbers:
- description: 'Comma-separated PR numbers with API changes to analyze'
- required: false
- type: string
- session_id:
- description: 'Session ID for resuming'
- required: false
- type: string
- default: ''
-
-permissions: write-all
-
-jobs:
- docs-gap:
- runs-on: ubuntu-latest
- timeout-minutes: 30
- steps:
- - name: Checkout repository
- uses: actions/checkout@v4
-
- - name: Checkout devtools
- uses: actions/checkout@v5
- with:
- repository: strands-agents/devtools
- ref: main
- sparse-checkout: |
- strands-command/scripts
- strands-command/agent-sops
- path: devtools
-
- - name: Copy devtools to safe directory
- shell: bash
- run: |
- mkdir -p ${{ runner.temp }}/strands-agent-runner
- cp -r devtools/strands-command ${{ runner.temp }}/strands-agent-runner/
-
- - name: Set up Python
- uses: actions/setup-python@v4
- with:
- python-version: '3.13'
-
- - name: Install uv
- uses: astral-sh/setup-uv@v3
-
- - name: Install dependencies
- shell: bash
- run: |
- uv pip install --system -r ${{ runner.temp }}/strands-agent-runner/strands-command/scripts/python/requirements.txt --quiet
-
- - name: Configure Git
- shell: bash
- run: |
- git config --global user.name "Strands Agent"
- git config --global user.email "217235299+strands-agent@users.noreply.github.com"
- git config --global core.pager cat
-
- - name: Configure AWS credentials
- uses: aws-actions/configure-aws-credentials@v4
- with:
- role-to-assume: ${{ secrets.AWS_ROLE_ARN }}
- role-session-name: GitHubActions-DocsGap-${{ github.run_id }}
- aws-region: us-west-2
- mask-aws-account-id: true
-
- - name: Retrieve secrets
- id: secrets
- shell: bash
- run: |
- if [ -n "${{ secrets.AWS_SECRETS_MANAGER_SECRET_ID }}" ]; then
- SECRET_JSON=$(aws secretsmanager get-secret-value --secret-id "${{ secrets.AWS_SECRETS_MANAGER_SECRET_ID }}" --query SecretString --output text --region us-east-1)
- SESSIONS_BUCKET=$(echo $SECRET_JSON | jq -r '.AGENT_SESSIONS_BUCKET // empty')
- [ -n "$SESSIONS_BUCKET" ] && echo "::add-mask::$SESSIONS_BUCKET"
- echo "sessions_bucket=$SESSIONS_BUCKET" >> $GITHUB_OUTPUT
- fi
-
- - name: Build task prompt
- id: prompt
- shell: bash
- run: |
- COMMAND="${{ github.event.inputs.command }}"
- PR_NUMBERS="${{ github.event.inputs.pr_numbers }}"
- SESSION_ID="${{ github.event.inputs.session_id }}"
-
- if [ -z "$SESSION_ID" ]; then
- SESSION_ID="docs-gap-$(date +%s)"
- fi
- echo "session_id=$SESSION_ID" >> $GITHUB_OUTPUT
-
- TASK="$COMMAND"
- if [ -n "$PR_NUMBERS" ]; then
- TASK="$TASK Analyze PRs: $PR_NUMBERS for missing documentation, docstrings, and usage examples."
- fi
- echo "task=$TASK" >> $GITHUB_OUTPUT
-
- - name: Execute docs gap agent
- shell: bash
- env:
- GITHUB_WRITE: 'true'
- GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- PAT_TOKEN: ${{ secrets.PAT_TOKEN }}
- S3_SESSION_BUCKET: ${{ steps.secrets.outputs.sessions_bucket }}
- SESSION_ID: ${{ steps.prompt.outputs.session_id }}
- INPUT_SYSTEM_PROMPT: |
- You are a Documentation Gap Analyzer. Your goal is to find missing or
- inadequate documentation for new or changed APIs. You check for missing
- docstrings, missing usage examples, undocumented parameters, and gaps
- between the code and the published documentation. You produce a structured
- report of documentation gaps with specific recommendations.
- INPUT_TASK: ${{ steps.prompt.outputs.task }}
- STRANDS_TOOL_CONSOLE_MODE: 'enabled'
- BYPASS_TOOL_CONSENT: 'true'
- run: |
- echo "📚 Running docs gap analysis agent"
- uv run --no-project ${{ runner.temp }}/strands-agent-runner/strands-command/scripts/python/agent_runner.py "$INPUT_TASK"
diff --git a/strands-command/workflows/strands-release-notes-agent.yml b/strands-command/workflows/strands-release-notes-agent.yml
deleted file mode 100644
index 5886d09..0000000
--- a/strands-command/workflows/strands-release-notes-agent.yml
+++ /dev/null
@@ -1,145 +0,0 @@
-# Strands Release Notes Agent Workflow
-#
-# Dedicated workflow for release notes generation — runs in isolation.
-# Triggered by the release digest orchestrator or manually via workflow_dispatch.
-#
-# Required Secrets:
-# AWS_ROLE_ARN: IAM role for AWS access
-# AWS_SECRETS_MANAGER_SECRET_ID: Secret containing agent config
-#
-# Usage:
-# - Dispatched by strands-autonomous.yml (release digest orchestrator)
-# - Manual: workflow_dispatch with base_ref and head_ref
-# - Comment: /strands release-notes on an issue
-
-name: Strands Release Notes Agent
-
-on:
- workflow_dispatch:
- inputs:
- command:
- description: 'Task prompt for the release notes generator'
- required: false
- type: string
- default: 'Generate release notes for the latest unreleased changes.'
- base_ref:
- description: 'Base git reference (e.g., last release tag)'
- required: false
- type: string
- head_ref:
- description: 'Head git reference (default: HEAD)'
- required: false
- type: string
- default: 'HEAD'
- session_id:
- description: 'Session ID for resuming'
- required: false
- type: string
- default: ''
-
-permissions: write-all
-
-jobs:
- release-notes:
- runs-on: ubuntu-latest
- timeout-minutes: 30
- steps:
- - name: Checkout repository
- uses: actions/checkout@v4
- with:
- fetch-depth: 0 # Full history for tag discovery
-
- - name: Checkout devtools
- uses: actions/checkout@v5
- with:
- repository: strands-agents/devtools
- ref: main
- sparse-checkout: |
- strands-command/scripts
- strands-command/agent-sops
- path: devtools
-
- - name: Copy devtools to safe directory
- shell: bash
- run: |
- mkdir -p ${{ runner.temp }}/strands-agent-runner
- cp -r devtools/strands-command ${{ runner.temp }}/strands-agent-runner/
-
- - name: Set up Python
- uses: actions/setup-python@v4
- with:
- python-version: '3.13'
-
- - name: Install uv
- uses: astral-sh/setup-uv@v3
-
- - name: Install dependencies
- shell: bash
- run: |
- uv pip install --system -r ${{ runner.temp }}/strands-agent-runner/strands-command/scripts/python/requirements.txt --quiet
-
- - name: Configure Git
- shell: bash
- run: |
- git config --global user.name "Strands Agent"
- git config --global user.email "217235299+strands-agent@users.noreply.github.com"
- git config --global core.pager cat
-
- - name: Configure AWS credentials
- uses: aws-actions/configure-aws-credentials@v4
- with:
- role-to-assume: ${{ secrets.AWS_ROLE_ARN }}
- role-session-name: GitHubActions-ReleaseNotes-${{ github.run_id }}
- aws-region: us-west-2
- mask-aws-account-id: true
-
- - name: Retrieve secrets
- id: secrets
- shell: bash
- run: |
- if [ -n "${{ secrets.AWS_SECRETS_MANAGER_SECRET_ID }}" ]; then
- SECRET_JSON=$(aws secretsmanager get-secret-value --secret-id "${{ secrets.AWS_SECRETS_MANAGER_SECRET_ID }}" --query SecretString --output text --region us-east-1)
- SESSIONS_BUCKET=$(echo $SECRET_JSON | jq -r '.AGENT_SESSIONS_BUCKET // empty')
- [ -n "$SESSIONS_BUCKET" ] && echo "::add-mask::$SESSIONS_BUCKET"
- echo "sessions_bucket=$SESSIONS_BUCKET" >> $GITHUB_OUTPUT
- fi
-
- - name: Build task prompt
- id: prompt
- shell: bash
- run: |
- COMMAND="${{ github.event.inputs.command }}"
- BASE_REF="${{ github.event.inputs.base_ref }}"
- HEAD_REF="${{ github.event.inputs.head_ref }}"
- SESSION_ID="${{ github.event.inputs.session_id }}"
-
- if [ -z "$SESSION_ID" ]; then
- SESSION_ID="release-notes-$(date +%s)"
- fi
- echo "session_id=$SESSION_ID" >> $GITHUB_OUTPUT
-
- TASK="$COMMAND"
- if [ -n "$BASE_REF" ]; then
- TASK="$TASK Base reference: $BASE_REF. Head reference: ${HEAD_REF:-HEAD}."
- fi
- echo "task=$TASK" >> $GITHUB_OUTPUT
-
- - name: Execute release notes agent
- shell: bash
- env:
- GITHUB_WRITE: 'true'
- GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- PAT_TOKEN: ${{ secrets.PAT_TOKEN }}
- S3_SESSION_BUCKET: ${{ steps.secrets.outputs.sessions_bucket }}
- SESSION_ID: ${{ steps.prompt.outputs.session_id }}
- INPUT_SYSTEM_PROMPT: |
- You are a Release Notes Generator following the task-release-notes SOP.
- Your goal is to create high-quality release notes highlighting Major Features
- and Major Bug Fixes. Analyze merged PRs, extract code examples, validate them,
- and format into well-structured markdown.
- INPUT_TASK: ${{ steps.prompt.outputs.task }}
- STRANDS_TOOL_CONSOLE_MODE: 'enabled'
- BYPASS_TOOL_CONSENT: 'true'
- run: |
- echo "📝 Running release notes agent"
- uv run --no-project ${{ runner.temp }}/strands-agent-runner/strands-command/scripts/python/agent_runner.py "$INPUT_TASK"
From da949b6e542da9a72a2f48813db81bee789b3f98 Mon Sep 17 00:00:00 2001
From: agent-of-mkmeral
Date: Fri, 20 Mar 2026 17:57:21 +0000
Subject: [PATCH 4/6] chore: remove README updates per review feedback
---
strands-command/README.md | 47 ---------------------------------------
1 file changed, 47 deletions(-)
diff --git a/strands-command/README.md b/strands-command/README.md
index 8100471..ffa1412 100644
--- a/strands-command/README.md
+++ b/strands-command/README.md
@@ -477,50 +477,3 @@ Use workflow dispatch with:
---
**Note**: This system is designed for trusted environments. Always review security implications before deployment and implement appropriate guardrails for your use case.
-
----
-
-## 🤖 Autonomous Agents: Release Digest
-
-The strands-command system includes autonomous agent capabilities for automated release analysis.
-
-### Architecture
-
-A single orchestrator agent runs weekly (Wednesday 10am UTC) and uses `use_agent` from `strands_tools` to spawn per-package sub-agents **in-process**. No workflow dispatch, no PAT tokens, no self-trigger prevention needed.
-
-```
-strands-autonomous.yml (Wed 10am cron / manual dispatch)
-└── Release Digest Orchestrator Agent
- ├── use_agent → SDK Python Analyzer
- ├── use_agent → SDK TypeScript Analyzer
- ├── use_agent → Tools Analyzer
- ├── use_agent → Evals Analyzer
- └── Compiles results → Creates digest issue
-```
-
-### How It Works
-
-1. **Orchestrator** discovers changes across all packages since their last release tag
-2. **Per-package sub-agents** are spawned via `use_agent` with read-only tools (`shell`, `http_request`)
-3. Each sub-agent analyzes PRs, runs adversarial testing, generates draft release notes, and finds docs gaps
-4. **Orchestrator** collects all results and creates a single consolidated digest issue
-
-### Files
-
-| File | Purpose |
-|------|---------|
-| `workflows/strands-autonomous.yml` | Template workflow — cron + manual dispatch |
-| `agent-sops/task-release-digest.sop.md` | Orchestrator SOP (uses `use_agent`) |
-| `agent-sops/task-adversarial-tester.sop.md` | Adversarial testing SOP (used as sub-agent system prompt) |
-| `scripts/python/agent_runner.py` | Agent runner with `use_agent` tool |
-
-### Setup
-
-1. Copy `workflows/strands-autonomous.yml` to your repo's `.github/workflows/`
-2. Configure secrets: `AWS_ROLE_ARN`, `AWS_SECRETS_MANAGER_SECRET_ID`
-3. The workflow runs automatically every Wednesday at 10am UTC, or trigger manually via `workflow_dispatch`
-
-### Commands
-
-- `/strands adversarial-test` — Run adversarial testing on a PR
-- `/strands release-digest` — Trigger a release digest manually
From bca21606e616e3200de982ed64e5262f72dd6cde Mon Sep 17 00:00:00 2001
From: agent-of-mkmeral <265349452+agent-of-mkmeral@users.noreply.github.com>
Date: Fri, 20 Mar 2026 20:10:36 +0000
Subject: [PATCH 5/6] refactor: remove use_agent and autonomous workflow per
review feedback
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Addresses review feedback from @Unshure and @mkmeral:
- Remove use_agent tool from agent_runner.py (not yet quality-tested for all agents)
- Revert _get_all_tools() to direct return style
- Remove strands-autonomous.yml workflow
Keeps valid improvements:
- Model upgrade: Opus 4.5 → Opus 4.6
- Thinking mode: enabled+budget → adaptive
- SOPs and command additions remain (they're docs/config, not runtime)
---
.../scripts/python/agent_runner.py | 11 +-
.../workflows/strands-autonomous.yml | 151 ------------------
2 files changed, 2 insertions(+), 160 deletions(-)
delete mode 100644 strands-command/workflows/strands-autonomous.yml
diff --git a/strands-command/scripts/python/agent_runner.py b/strands-command/scripts/python/agent_runner.py
index c121767..e28f450 100644
--- a/strands-command/scripts/python/agent_runner.py
+++ b/strands-command/scripts/python/agent_runner.py
@@ -19,7 +19,7 @@
from strands.models.bedrock import BedrockModel
from botocore.config import Config
-from strands_tools import http_request, shell, use_agent
+from strands_tools import http_request, shell
# Import local GitHub tools we need
from github_tools import (
@@ -149,7 +149,7 @@ def _get_trace_attributes() -> dict:
}
def _get_all_tools() -> list[Any]:
- tools = [
+ return [
# File editing
str_replace_based_edit_tool,
@@ -178,14 +178,7 @@ def _get_all_tools() -> list[Any]:
# Agent tools
notebook,
handoff_to_user,
-
- # Sub-agent creation — enables orchestrator pattern
- # The parent agent can spawn specialized sub-agents for parallel tasks
- # Each sub-agent runs in-process with its own system prompt and tools
- use_agent,
]
-
- return tools
def run_agent(query: str):
diff --git a/strands-command/workflows/strands-autonomous.yml b/strands-command/workflows/strands-autonomous.yml
deleted file mode 100644
index fc7e393..0000000
--- a/strands-command/workflows/strands-autonomous.yml
+++ /dev/null
@@ -1,151 +0,0 @@
-# Strands Release Digest Workflow
-#
-# Single workflow that runs one orchestrator agent.
-# The orchestrator uses `use_agent` from strands_tools to spawn
-# per-package sub-agents in-process — no workflow dispatch needed.
-#
-# Packages analyzed:
-# - strands-agents/sdk-python
-# - strands-agents/sdk-typescript
-# - strands-agents/tools
-# - strands-agents/evals
-#
-# Required Secrets:
-# AWS_ROLE_ARN: IAM role for AWS access
-# AWS_SECRETS_MANAGER_SECRET_ID: Secret containing agent config
-
-name: Strands Release Digest
-
-on:
- # Weekly: Wednesdays at 10am UTC
- schedule:
- - cron: '0 10 * * 3'
-
- # Manual trigger
- workflow_dispatch:
- inputs:
- command:
- description: 'Override task prompt for the orchestrator'
- required: false
- type: string
- default: ''
- session_id:
- description: 'Session ID for resuming'
- required: false
- type: string
- default: ''
-
-permissions: write-all
-
-jobs:
- release-digest:
- runs-on: ubuntu-latest
- timeout-minutes: 120 # 2 hours — sub-agents run in-process
- steps:
- - name: Checkout repository
- uses: actions/checkout@v4
- with:
- fetch-depth: 0 # Full history for release tag discovery
-
- - name: Checkout devtools
- uses: actions/checkout@v5
- with:
- repository: strands-agents/devtools
- ref: main
- sparse-checkout: |
- strands-command/scripts
- strands-command/agent-sops
- path: devtools
-
- - name: Copy devtools to safe directory
- shell: bash
- run: |
- mkdir -p ${{ runner.temp }}/strands-agent-runner
- cp -r devtools/strands-command ${{ runner.temp }}/strands-agent-runner/
-
- - name: Set up Python
- uses: actions/setup-python@v4
- with:
- python-version: '3.13'
-
- - name: Install uv
- uses: astral-sh/setup-uv@v3
-
- - name: Install dependencies
- shell: bash
- run: |
- uv pip install --system -r ${{ runner.temp }}/strands-agent-runner/strands-command/scripts/python/requirements.txt --quiet
-
- - name: Configure Git
- shell: bash
- run: |
- git config --global user.name "Strands Agent"
- git config --global user.email "217235299+strands-agent@users.noreply.github.com"
- git config --global core.pager cat
-
- - name: Configure AWS credentials
- uses: aws-actions/configure-aws-credentials@v4
- with:
- role-to-assume: ${{ secrets.AWS_ROLE_ARN }}
- role-session-name: GitHubActions-ReleaseDigest-${{ github.run_id }}
- aws-region: us-west-2
- mask-aws-account-id: true
-
- - name: Retrieve secrets
- id: secrets
- shell: bash
- run: |
- if [ -n "${{ secrets.AWS_SECRETS_MANAGER_SECRET_ID }}" ]; then
- SECRET_JSON=$(aws secretsmanager get-secret-value --secret-id "${{ secrets.AWS_SECRETS_MANAGER_SECRET_ID }}" --query SecretString --output text --region us-east-1)
- SESSIONS_BUCKET=$(echo $SECRET_JSON | jq -r '.AGENT_SESSIONS_BUCKET // empty')
- [ -n "$SESSIONS_BUCKET" ] && echo "::add-mask::$SESSIONS_BUCKET"
- echo "sessions_bucket=$SESSIONS_BUCKET" >> $GITHUB_OUTPUT
- fi
-
- - name: Build session ID
- id: setup
- shell: bash
- run: |
- SESSION_ID="${{ github.event.inputs.session_id }}"
- if [ -z "$SESSION_ID" ]; then
- SESSION_ID="release-digest-$(date +%s)"
- fi
- echo "session_id=$SESSION_ID" >> $GITHUB_OUTPUT
-
- - name: Execute release digest orchestrator
- shell: bash
- env:
- GITHUB_WRITE: 'true'
- GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- S3_SESSION_BUCKET: ${{ steps.secrets.outputs.sessions_bucket }}
- SESSION_ID: ${{ steps.setup.outputs.session_id }}
- INPUT_SYSTEM_PROMPT: |
- You are a Release Digest Orchestrator following the task-release-digest SOP.
-
- Your goal: produce a comprehensive weekly release digest for the Strands Agents
- ecosystem. You use `use_agent` to spawn per-package sub-agents that analyze
- changes in parallel. Each sub-agent runs in-process with its own system prompt.
-
- Packages to analyze:
- - strands-agents/sdk-python
- - strands-agents/sdk-typescript
- - strands-agents/tools
- - strands-agents/evals
-
- For each package with changes since its last release:
- 1. Spawn a sub-agent via `use_agent` with tools=["shell", "http_request"]
- 2. The sub-agent analyzes PRs, runs adversarial testing, generates release notes
- 3. Collect all results and compile into a single digest issue
-
- You have `use_agent` available. Use it to delegate analysis to specialized sub-agents.
- You have `create_issue` to publish the final digest.
- STRANDS_TOOL_CONSOLE_MODE: 'enabled'
- BYPASS_TOOL_CONSENT: 'true'
- run: |
- TASK="${{ github.event.inputs.command }}"
- if [ -z "$TASK" ]; then
- TASK="Run the weekly release digest following the task-release-digest SOP. Check strands-agents/sdk-python, strands-agents/sdk-typescript, strands-agents/tools, and strands-agents/evals for changes since their last release. For each package with changes, spawn a sub-agent via use_agent to analyze the PRs, test for edge cases, and generate draft release notes. Compile all results into a single digest issue."
- fi
-
- echo "🚀 Running release digest orchestrator"
- uv run --no-project ${{ runner.temp }}/strands-agent-runner/strands-command/scripts/python/agent_runner.py "$TASK"
From de739b5151db840dfaeedb90ad02a1d98be33f4e Mon Sep 17 00:00:00 2001
From: agent-of-mkmeral
Date: Wed, 25 Mar 2026 14:46:49 +0000
Subject: [PATCH 6/6] fix: remove strands-coder reference from comment
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Address @zastrowm's review feedback — this agent runner is standalone
and shouldn't reference strands-coder.
---
strands-command/scripts/python/agent_runner.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/strands-command/scripts/python/agent_runner.py b/strands-command/scripts/python/agent_runner.py
index e28f450..b60b820 100644
--- a/strands-command/scripts/python/agent_runner.py
+++ b/strands-command/scripts/python/agent_runner.py
@@ -44,7 +44,7 @@
from notebook import notebook
from str_replace_based_edit_tool import str_replace_based_edit_tool
-# Strands configuration constants — matches strands-coder settings
+# Strands configuration constants
# Opus 4.6 with adaptive thinking and 1M context window
STRANDS_MODEL_ID = "global.anthropic.claude-opus-4-6-v1"
STRANDS_MAX_TOKENS = 128000