agentic-football-coach/kiro-football-coach/.kiro/skills/strands-agents/SKILL.md
Activate when the user chooses Strands or asks about Strands SDK concepts
npx skillsauth add aws-samples/sample-ai-possibilities strands-agentsInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Comprehensive reference for building, enhancing, and deploying football agents with the Strands Agents SDK.
sample-agent-strands/
├── src/main.py # Main agent with Strands SDK
├── test_local.py # Local testing
├── requirements.txt # strands-agents dependency
└── .bedrock_agentcore.yaml # AgentCore config
What the sample does:
Agent(model=BedrockModel(...), system_prompt=...)response = agent(state_summary)Key code pattern:
from strands import Agent
from strands.models import BedrockModel
model = BedrockModel(model_id="us.amazon.nova-micro-v1:0")
agent = Agent(model=model, system_prompt=SYSTEM_PROMPT)
response = agent(state_summary)
commands = parse_commands(str(response), team_id)
Return validated, type-safe responses using Pydantic models instead of raw text.
from pydantic import BaseModel, Field
from strands import Agent
class FootballAction(BaseModel):
commandType: str = Field(description="MOVE_TO, SHOOT, PASS, etc.")
playerId: int
parameters: dict
duration: int = 0
agent = Agent(system_prompt="Analyze game state, return ONE action.")
result = agent("Ball at (10,5), I'm at (0,0).", structured_output_model=FootballAction)
action = result.structured_output # Type-safe FootballAction
Give your agent callable functions for tactical analysis:
from strands import Agent, tool
@tool
def should_shoot(player_x: float, goal_x: float) -> dict:
"""Determine if player should shoot based on distance to goal.
Args:
player_x: Player's x coordinate
goal_x: Goal's x coordinate (55 or -55)
"""
distance = abs(player_x - goal_x)
return {'should_shoot': distance < 30, 'power': max(0.5, min(1.0, 1.0 - distance/100))}
agent = Agent(tools=[should_shoot], system_prompt="Use tools to decide actions.")
Switch between models easily:
from strands.models import BedrockModel
model = BedrockModel(model_id="us.amazon.nova-micro-v1:0") # Fast, cheap
# model = BedrockModel(model_id="anthropic.claude-sonnet-4-20250514-v1:0") # More capable
agent = Agent(model=model)
Track tokens, latency, and tool usage:
result = agent("What should I do?")
summary = result.metrics.get_summary()
print(f"Tokens: {summary['accumulated_usage']['totalTokens']}")
print(f"Latency: {summary['accumulated_metrics']['latencyMs']}ms")
print(f"Tool calls: {summary['tool_usage']}")
Monitor and control agent behavior at lifecycle points:
from strands.hooks import HookProvider, HookRegistry, BeforeToolCallEvent, AfterToolCallEvent
class FootballMonitoringHook(HookProvider):
def register_hooks(self, registry: HookRegistry) -> None:
registry.add_callback(BeforeToolCallEvent, self.log_start)
registry.add_callback(AfterToolCallEvent, self.log_end)
def log_start(self, event: BeforeToolCallEvent) -> None:
print(f"Calling: {event.tool_use['name']}")
def log_end(self, event: AfterToolCallEvent) -> None:
print(f"Result: {event.result}")
agent = Agent(tools=[should_shoot], hooks=[FootballMonitoringHook()])
Advanced: limit tool calls per invocation with BeforeInvocationEvent to reset counters and BeforeToolCallEvent to check limits via event.cancel_tool.
Content filtering and safety via Bedrock Guardrails:
model = BedrockModel(
model_id="us.amazon.nova-micro-v1:0",
guardrail_id="your-guardrail-id", guardrail_version="1", guardrail_trace="enabled"
)
agent = Agent(model=model)
result = agent("Some prompt")
if result.stop_reason == "guardrail_intervened":
print("Blocked by guardrails")
Call tools programmatically without LLM reasoning:
distance = agent.tool.calculate_distance(pos1={"x": 0, "y": 0}, pos2={"x": 10, "y": 5})
Pre-built tools from strands-agents-tools:
from strands_tools import calculator, python_repl
agent = Agent(tools=[calculator, python_repl])
Useful for football: calculator (math), python_repl (complex calculations), current_time (match timing).
Control how message history is handled:
from strands.agent.conversation_manager import (
NullConversationManager, SlidingWindowConversationManager, SummarizingConversationManager
)
# Keep recent messages (default)
agent = Agent(conversation_manager=SlidingWindowConversationManager(window_size=10))
# No trimming
agent = Agent(conversation_manager=NullConversationManager())
# Summarize old messages
agent = Agent(conversation_manager=SummarizingConversationManager(
summary_ratio=0.3, preserve_recent_messages=10
))
For football: typically stateless per tick (fresh agent each time).
Key-value storage across invocations — not sent to the model:
from strands import tool, ToolContext
@tool(context=True)
def track_opponent(opponent_id: int, tendency: str, tool_context: ToolContext) -> str:
"""Record an opponent's tendency.
Args:
opponent_id: Opponent player ID
tendency: Observed tendency
"""
tendencies = tool_context.agent.state.get("opponent_tendencies") or {}
tendencies[str(opponent_id)] = tendency
tool_context.agent.state.set("opponent_tendencies", tendencies)
return f"Recorded: player {opponent_id} → {tendency}"
agent = Agent(tools=[track_opponent], state={"opponent_tendencies": {}})
Persist state and conversation across restarts:
from strands.session.file_session_manager import FileSessionManager
session_mgr = FileSessionManager(session_id="team-alpha", storage_dir="./sessions")
agent = Agent(session_manager=session_mgr, state={"patterns": {}})
# After redeployment, restore with same session_id
Use S3SessionManager for cloud deployments.
Handle model throttling during high-frequency ticks:
from strands import ModelRetryStrategy
agent = Agent(retry_strategy=ModelRetryStrategy(max_attempts=3, initial_delay=2, max_delay=60))
# Or disable: agent = Agent(retry_strategy=None)
Monitor decisions in real-time with async iterators:
async for event in agent.stream_async("Ball at (30,10), should I shoot?"):
if "data" in event:
print(event["data"], end="")
elif "current_tool_use" in event and event["current_tool_use"].get("name"):
print(f"Using tool: {event['current_tool_use']['name']}")
Share state across related tools using classes:
class MatchAnalytics:
def __init__(self):
self.shots_taken = 0
@tool
def record_shot(self, result: str) -> str:
"""Record a shot attempt. Args: result: 'goal', 'saved', or 'missed'"""
self.shots_taken += 1
return f"Shot #{self.shots_taken}: {result}"
analytics = MatchAnalytics()
agent = Agent(tools=[analytics.record_shot])
Connect to external tool servers via Model Context Protocol:
from strands.tools.mcp import MCPClient
from mcp.client.sse import sse_client
mcp_client = MCPClient(lambda: sse_client("http://localhost:8000/sse"))
with mcp_client:
agent = Agent(tools=mcp_client.list_tools_sync())
Wrap specialist agents as tools for an orchestrator:
@tool
def defensive_analyst(game_state: str) -> str:
"""Analyze defensive positioning.
Args: game_state: Current game state summary"""
return str(Agent(system_prompt="You are a defensive analyst.")(game_state))
@tool
def offensive_strategist(game_state: str) -> str:
"""Plan attacking moves.
Args: game_state: Current game state summary"""
return str(Agent(system_prompt="You are an attacking strategist.")(game_state))
coach = Agent(tools=[defensive_analyst, offensive_strategist],
system_prompt="Route to the right specialist based on game situation.")
Collaborative agents that autonomously hand off tasks:
from strands.multiagent import Swarm
scout = Agent(name="scout", system_prompt="Analyze opponent weaknesses.")
tactician = Agent(name="tactician", system_prompt="Design game plans.")
coach = Agent(name="coach", system_prompt="Finalize tactics.")
swarm = Swarm([scout, tactician, coach], entry_point=scout, max_handoffs=10)
result = swarm("Prepare a plan against a 4-4-2 high-pressing team")
Structured pipeline with defined execution order:
from strands.multiagent import GraphBuilder
analyzer = Agent(name="analyzer", system_prompt="Analyze game state.")
planner = Agent(name="planner", system_prompt="Create tactical plan.")
executor = Agent(name="executor", system_prompt="Convert plan to player actions.")
builder = GraphBuilder()
builder.add_node(analyzer, "analyze")
builder.add_node(planner, "plan")
builder.add_node(executor, "execute")
builder.add_edge("analyze", "plan")
builder.add_edge("plan", "execute")
builder.set_entry_point("analyze")
graph = builder.build()
result = graph("Possession in midfield, 1-1, 80th minute")
Pause execution for human input (coaching decisions):
from strands.hooks import BeforeToolCallEvent, HookProvider, HookRegistry
class SubstitutionApproval(HookProvider):
def register_hooks(self, registry: HookRegistry) -> None:
registry.add_callback(BeforeToolCallEvent, self.approve)
def approve(self, event: BeforeToolCallEvent) -> None:
if event.tool_use["name"] == "substitute_player":
approval = event.interrupt("coach-approval",
reason={"out": event.tool_use["input"]["player_out"],
"in": event.tool_use["input"]["player_in"]})
if approval.lower() != "y":
event.cancel_tool = "Coach rejected the substitution"
# Handle interrupt: check result.stop_reason == "interrupt", then respond
The core cycle: invoke model → check for tool call → execute tool → re-invoke model → repeat until final response.
Stop reasons: end_turn (normal), tool_use (loop continues), max_tokens (truncated), guardrail_intervened (blocked), interrupt (paused).
result = agent("Ball at (30,10), should I shoot?")
print(f"Stop reason: {result.stop_reason}")
print(f"Messages: {len(agent.messages)}")
Control concurrent vs sequential tool execution:
from strands.tools.executors import SequentialToolExecutor
agent = Agent(tools=[...]) # Default: concurrent (faster)
agent = Agent(tool_executor=SequentialToolExecutor(), tools=[...]) # Sequential
Send images/documents alongside text (useful for pre/post-match analysis):
with open("opponent_heatmap.png", "rb") as f:
image_bytes = f.read()
result = agent([
{"text": "Analyze this opponent heatmap. Where are the gaps?"},
{"image": {"format": "png", "source": {"bytes": image_bytes}}}
])
Note: During live gameplay your agent receives JSON game state, not images.
Enable detailed logs for diagnosing agent behavior:
import logging
logging.getLogger("strands").setLevel(logging.DEBUG)
logging.basicConfig(format="%(levelname)s | %(name)s | %(message)s",
handlers=[logging.StreamHandler()])
Apply these to the sample agent — each maps to a core concept above:
| Enhancement | How | Core Concept |
|-------------|-----|--------------|
| Structured Output | Pass structured_output_model=GameCommand (Pydantic) to agent() — replaces manual parsing | #1 |
| Custom Tools | Add @tool functions (e.g., shooting angle calculator) to Agent(tools=[...]) | #2 |
| Observability | Read result.metrics.get_summary() for tokens, latency, tool usage | #4 |
| Hooks | Create a HookProvider subclass, register callbacks for BeforeInvocationEvent | #5 |
| Model Switching | Swap BedrockModel(model_id=...) — Nova Micro for speed, Claude for complex tactics | #3 |
| Guardrails | Add guardrail_id and guardrail_version to BedrockModel(...) | #6 |
| State Tracking | Use tool_context.agent.state.get/set in @tool(context=True) functions | #10 |
| Retry Strategy | Add retry_strategy=ModelRetryStrategy(max_attempts=3, initial_delay=1) | #12 |
| Multi-Agent Pipeline | Use GraphBuilder to chain analyzer → planner → executor agents | #18 |
python test_local.py # Without LLM
python test_local.py --llm # With LLM
agentcore deploy
After deployment you receive a Runtime ARN — use it to register your agent for matches.
aws logs tail /aws/bedrock-agentcore/runtimes/<runtime-id>-DEFAULT \
--log-stream-name-prefix "$(date +%Y/%m/%d)/[runtime-logs" --follow
Use the Runtime ARN from the AgentCore console to register your agent. You can use one ARN for all 5 players or create separate agents per player for diversity.
content-reference/en/strands-guide.md for full detaildevelopment
Activate when the user chooses LangGraph or asks about LangChain/ReAct patterns
data-ai
Activate when discussing strategy, tactics, or agent behavior design
development
Activate when discussing game rules, player actions, scoring, fouls, cards, or match format
tools
Activate when discussing deployment, AgentCore CLI, or production infrastructure