Skills DirectorySkills Directory
SkillsLearnSecurityCategoriesDocsCommunityBlog
Sign InSubmit Skill
Skills Directory

Security-tested agent skills for Claude, coding agents, and AI workflows.

Directory

  • Browse Skills
  • All Skills A–Z
  • Claude Skills
  • Claude Code Skills
  • Agent Skills
  • Categories
  • Submit a Skill

Learn

  • Learn Hub
  • Install Claude Skills
  • Write SKILL.md
  • Skills vs MCP
  • Directories Compared

Security

  • Security
  • Methodology
  • Secure Claude Skills
  • Security Badges

Company

  • About
  • Community
  • Blog
  • API Docs
  • Advertise

2026 Skills Directory. All rights reserved.

Back to skills

Code Strands

ASecurity

Best practices for the AWS Strands Agents SDK — structuring prompts, multi-agent patterns, structured I/O, and splitting monolithic agents into specialists. Use when designing or refactoring Strands-based agent systems.

8 stars
0 votes
0 copies
0 views
Added 9/20/2026
documentationtypescriptpythonrustnodeawsrefactoringgitapisecuritydocumentation

Works with

api

Security Analysis

A100/100

Scanned 9/20/2026

Install to Claude Code

$npx -y skills add tstapler/dotfiles --skill code-strands --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Code Strands?

Add the live security badge to your README — it updates automatically with every re-scan.

Security grade badge for Code Strands
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/tstapler-code-strands/badge)](https://www.skillsdirectory.com/skills/tstapler-code-strands)

More formats (shields.io, HTML) on the badges page.

Download Zip
Files
SKILL.md
---
name: code-strands
description: Best practices for the AWS Strands Agents SDK — structuring prompts, multi-agent patterns, structured I/O, and splitting monolithic agents into specialists. Use when designing or refactoring Strands-based agent systems.
---

# Strands Agents SDK Best Practices

> For prompt design for Strands system prompts and tool descriptions, apply the `meta-prompt-engineering` skill.

## Core Philosophy

Strands is **model-driven**: agents decide what to do, tools define what's possible. Keep system prompts focused on a single domain of expertise. Fat prompts become brittle; specialists compose cleanly.

## `@tool` Decorator — How It Works

Strands builds the LLM tool spec from your function signature automatically:

```python
from strands import tool

@tool
def analyze_incident(incident_key: str, severity: str, days_back: int = 30) -> str:
    """Analyze a BTS incident and return classification recommendations.

    Args:
        incident_key: Jira ticket ID (e.g. BTS-12345)
        severity: P1, P2, P3, or P4
        days_back: Days back for comparison window
    """
    ...
```

- **First docstring paragraph** → tool description shown to the LLM (make it precise — this is the routing signal)
- **`Args:` section** → per-parameter descriptions in the tool spec
- **Type annotations** → JSON Schema types
- **Default values** → optional parameters

Override name/description or provide a full custom schema (e.g. for enums):

```python
@tool(name="get_weather", description="Retrieves weather forecast")
def weather_forecast(...): ...

@tool(inputSchema={"json": {"type": "object", "properties": {"shape": {"type": "string", "enum": ["circle", "rectangle"]}}, "required": ["shape"]}})
def calculate_area(shape: str): ...
```

## Agent-as-Tool Pattern (Primary Decomposition Strategy)

Wrap specialist agents in `@tool` functions. The orchestrator routes to them; each specialist has a short, focused system prompt.

```python
from strands import Agent, tool

@tool
def field_classification_specialist(context_json: str) -> str:
    """Assess missing classification fields and return a comment section if action needed."""
    agent = Agent(
        system_prompt=FIELD_CLASSIFICATION_PROMPT,  # ~60 lines, single concern
        tools=[get_transcript_field_suggestions, get_datadog_service_catalog, update_jira_field],
        callback_handler=None,  # suppress intermediate noise from appearing in orchestrator output
    )
    return str(agent(context_json))

# Orchestrator uses specialists as tools
orchestrator = Agent(
    system_prompt=ORCHESTRATOR_PROMPT,  # routing + combining logic only
    tools=[field_classification_specialist, mitigated_closure_specialist, coe_specialist],
)
```

**Key rules:**
- `callback_handler=None` on sub-agents — prevents duplicate/noisy output in the orchestrator's stream
- Each specialist gets only the tools it needs; don't share tool lists
- The `@tool` docstring IS the routing description — make it unambiguous

> For typing inter-agent contracts with Pydantic models and parse-don't-validate, apply the `type-driven-design` skill.

## Structured Outputs Between Agents

Use Pydantic models for typed inter-agent contracts instead of string blobs:

```python
from pydantic import BaseModel

class WorkflowResult(BaseModel):
    should_act: bool
    comment_section: str | None      # Markdown section for the combined comment
    auto_updates: list[FieldUpdate]  # Fields to apply before commenting
    flag_for_human: str | None       # Reason string if human review needed

# Agent produces structured output
result = agent("...", structured_output_model=WorkflowResult)
workflow_result: WorkflowResult = result.structured_output
```

**⚠ Known bug**: `structured_output_model` + `tools=` has known issues (GitHub #872, #891, #1032) where tool calls may not fire when structured output is active. A revamp is in progress. **Workaround**: use `structured_output_model` only on agents that don't need to call tools, or serialize the result as JSON string and deserialize on the receiving side.

**⚠ `str(AgentResult)` loses structured output**: when returning from an agent-as-tool, use `.model_dump_json()` to serialize the Pydantic model and parse it back on the orchestrator side:

```python
@tool
def my_specialist(query: str) -> str:
    agent = Agent(system_prompt=PROMPT, tools=[...], callback_handler=None)
    result = agent(query, structured_output_model=WorkflowResult)
    return result.structured_output.model_dump_json()  # serialize explicitly

# In orchestrator tool handler or post-processing:
workflow_result = WorkflowResult.model_validate_json(specialist_return_value)
```

## Passing Metadata Without Polluting LLM Context

Use `invocation_state` for configuration and metadata that tools need but the LLM shouldn't see in its token budget:

```python
result = orchestrator(message, invocation_state={
    "issue_key": "BTS-12345",
    "dry_run": True,
    "jira_base_url": "https://betfanatics.atlassian.net/browse",
})

# Tools access it via ToolContext — never visible in the LLM prompt
@tool(context=True)
def add_jira_comment(body: str, tool_context: ToolContext) -> str:
    issue_key = tool_context.invocation_state["issue_key"]
    dry_run = tool_context.invocation_state.get("dry_run", False)
    ...
```

**⚠ `invocation_state` does NOT auto-propagate to sub-agents**: when a `@tool` spawns a sub-agent, the parent's `invocation_state` is not forwarded automatically. You must thread it explicitly:

```python
@tool(context=True)
def my_specialist(query: str, tool_context: ToolContext) -> str:
    """Run specialist agent."""
    sub_agent = Agent(system_prompt=PROMPT, tools=[...], callback_handler=None)
    # Manually forward needed state into the prompt or invocation_state
    return str(sub_agent(query, invocation_state=tool_context.invocation_state))
```

## `Agent.__call__` Signature

```python
result: AgentResult = agent(
    prompt,                              # str | list[ContentBlock] | list[Message] | None
    invocation_state=None,               # dict — context for tools, invisible to LLM
    structured_output_model=None,        # per-call override of agent-level default
)

# AgentResult fields:
result.stop_reason          # why the agent stopped
result.message              # final message
result.metrics              # token counts, cycle durations, tool stats
result.structured_output    # populated if structured_output_model was set
```

**Note**: `prompt` must be `str`, `ContentBlock` list, `Message` list, or `None` — not a raw dict or dataclass. Structured context must be serialized into the string or passed via `invocation_state`.

## Multi-Agent Patterns (When to Use Each)

| Pattern | Use When | How Context Flows |
|---------|----------|-------------------|
| **Agent-as-Tool** | Orchestrator delegates to specialists; results combine | Orchestrator collects returns, aggregates |
| **Graph** | Conditional routing with LLM-decided paths, cycles OK | Full conversation transcript shared across nodes |
| **Swarm** | Agents hand off to peers; exploration/multidisciplinary | Shared context with prior agent knowledge |
| **Workflow (DAG)** | Repeatable pipeline, parallel steps, deterministic | Task-specific context from dependencies only |

For structured processes with one combined output (e.g. incident management): **Agent-as-Tool** is correct — specialists are called by an orchestrator that owns the final assembly.

## Execution Patterns: Streaming, Conversation Management, Instance Lifecycle

Sub-agent event streaming (`stream_async`), the three built-in conversation-management strategies (sliding window, summarizing, manual), and the singleton-vs-fresh-instance tradeoff for agent-as-tool are covered in [Execution Patterns](references/execution-patterns.md).

**Defaults for stateless, parallelized incident processing** (our use case): fresh `Agent` instances per call, `SlidingWindowConversationManager` with `per_turn=True`.

> For optimizing context windows, compaction, and sub-agent isolation, apply the `meta-context-engineering` skill.

## Prompt Sizing Guidelines

No SDK-imposed limit — the constraint is the model's context window. Practical guidance:
- **Orchestrator**: routing logic + cross-cutting rules only (~50-80 lines / ~200-400 tokens)
- **Specialist**: one workflow domain only (~40-80 lines / ~100-500 tokens)
- **Rule of thumb**: if a prompt has two `---` section separators for unrelated concerns, it should be two agents

> For selecting which Claude model to use for orchestrators vs. specialists, apply the `meta-model-selection` skill.

## When to Split a Monolithic Agent

Split when **any** of these are true:
1. System prompt exceeds ~2,000 tokens with clearly distinct domain sections
2. Toolbelt has 15+ tools and wrong-tool selection is a recurring problem
3. Context window overflows regularly on complex runs
4. Some sub-tasks can run concurrently (use async)
5. Different domains warrant different model capabilities or costs
6. Multiple teams need to independently maintain different capabilities

**Model optimization** — the orchestrator only needs to route; use a cheap/fast model there. Specialist sub-agents can use more capable models where their domain requires it:

```python
orchestrator = Agent(
    model=BedrockModel(model_id="amazon.nova-lite-v1:0"),  # cheap router
    tools=[field_classification_specialist, mitigated_closure_specialist],
)
# Each specialist uses its own model (defaulting to Sonnet)
```

## Splitting a Monolithic Prompt

1. Identify independent "workflows" or "concerns" in the prompt
2. Each concern becomes a specialist with its own system prompt + minimal tool set
3. Cross-cutting rules (comment formatting, unassigned handling, section ordering) stay in the orchestrator
4. Define a `WorkflowResult` Pydantic model as the contract; serialize with `.model_dump_json()` across the agent-as-tool boundary
5. Add `context=True` to specialist `@tool` functions so they can forward `invocation_state`
6. Orchestrator collects results, applies auto-updates, assembles and posts one combined output

## Known Limitations (as of 2026-02)

| Issue | Impact | Workaround |
|-------|--------|------------|
| `structured_output_model` + `tools` conflicts (GH #872, #891, #1032) | Tool calls may not fire when structured output active | Separate output-producing agents from tool-calling agents; serialize via JSON string |
| `invocation_state` not auto-propagated to sub-agents | Sub-agent tools can't see parent state | Pass `tool_context.invocation_state` explicitly to sub-agent `invocation_state=` |
| `str(AgentResult)` drops `structured_output` | Pydantic models lost across agent-as-tool boundary | Use `.model_dump_json()` / `model_validate_json()` explicitly |
| Structured output is Python-only | No TypeScript structured output | N/A |

---

## Related Skills

| Skill | When to apply |
|-------|--------------|
| `meta-prompt-engineering` | Crafting focused system prompts and tool descriptions for Strands agents |
| `meta-context-engineering` | Optimizing context windows, compaction, and sub-agent isolation |
| `meta-model-selection` | Choosing the right Claude model for orchestrators vs. specialists |
| `type-driven-design` | Typing inter-agent Pydantic contracts; parse-don't-validate at boundaries |
| `python-development` | Python standards (uv, Pydantic, async) for Strands tool implementations |
| `security-review` | Auditing tool permissions, invocation_state exposure, and agent trust boundaries |

## Reference

- [Execution Patterns](references/execution-patterns.md) — async streaming, conversation management, singleton vs fresh-instance
- [Agents as Tools](https://strandsagents.com/latest/documentation/docs/user-guide/concepts/multi-agent/agents-as-tools/)
- [Multi-Agent Patterns](https://strandsagents.com/latest/documentation/docs/user-guide/concepts/multi-agent/multi-agent-patterns/)
- [Structured Output](https://strandsagents.com/latest/documentation/docs/user-guide/concepts/agents/structured-output/)
- [Custom Tools](https://strandsagents.com/latest/documentation/docs/user-guide/concepts/tools/custom-tools/)
- [Callback Handlers / Streaming](https://strandsagents.com/latest/documentation/docs/user-guide/concepts/streaming/callback-handlers/)
- [Agent API Reference](https://strandsagents.com/latest/documentation/docs/api-reference/python/agent/agent/)
- [strands-agents/samples](https://github.com/strands-agents/samples)
- [Deep Agents pattern (community)](https://www.pierreange.ai/blog/deep-agents-using-strands)

Attribution

tstaplertstapler
View sourceMore from tstapler →
SSkills DirectorySkills Directory

Your tool, in front of Claude Code builders.

3 founder slots · $299/mo · GSC-verified traffic · sponsors can never buy grades.

See placements

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments (0)

No comments yet. Be the first to comment!

SSkills DirectorySkills Directory

Your tool, in front of Claude Code builders.

3 founder slots · $299/mo · GSC-verified traffic · sponsors can never buy grades.

See placements

Related Skills

Context Fundamentals

Understand the components, mechanics, and constraints of context in agent systems. Use when designing agent architectures, debugging context-related failures, or optimizing context usage.

179001 votes

release-notes

Draft release notes and changelog entries from git history or merged PRs between two refs (tags/SHAs/branches), including breaking changes, migrations, and upgrade steps. Use when the user asks for release notes, changelog updates, or a GitHub Release draft.

1301 votes

docs-style-guide

Documentation style guide enforcer by @planetabhi. Applies and reviews the writing style guide when authoring or editing product documentation and tutorials. Use to check prose for voice, tense, word choice, inclusive language, formatting, code block, UI, Markdown, and number/date conventions.

11 votes

Caveman Help

Quick-reference card for all caveman modes, skills, and commands. One-shot display, not a persistent mode. Trigger: /caveman-help, "caveman help", "what caveman commands", "how do I use caveman".

1023330 votes

How It Works

Explain how claude-mem captures observations, when memory injection kicks in, and where data lives. Use when the user asks "how does claude-mem work?" or "what is this thing doing?".

942310 votes
View all in documentation →