Tuesday, May 26, 2026Tech HubAboutContactAdvertiseNewsletter
Back to Home
I Built an AI Agent That Tells Me When to Go Wing Foiling

I Built an AI Agent That Tells Me When to Go Wing Foiling

The Problem: Should I Go Foiling Today? I'm obsessed with wing foiling. If you don't know what that is — imagine holding an inflatable wing, standing on a hydrofoil board, and literally flying above the water. It's incredible. But every morning I'd check 3-4 different apps — windy.com, and...

B
Blizine Admin
·8 min read·0 views

I Built an AI Agent That Tells Me When to Go Wing Foiling

The Problem: Should I Go Foiling Today?

I'm obsessed with wing foiling. If you don't know what that is — imagine holding an inflatable wing, standing on a hydrofoil board, and literally flying above the water. It's incredible.

But every morning I'd check 3-4 different apps — windy.com, and OpenWeatherMap for wind, tide charts, swells and also the weather in general, then mentally calculate what gear to bring, if you surf you may have different boards, wetsuite, leashes but wing foiling you have much more gears to deal with: front foil, mast size, wing size, board, wetsuite and much more! I thought: why not build an AI agent that does all of this for me and just tells me "GO" or "NO-GO"?

So I built one. With Strands Agents, MQTT, and DynamoDB. Let me walk you through it.

The Architecture

MQTT Request → mqtt_bridge.py → Strands Agent → Tools (tide, wind, gear, risk) → MQTT Response

The idea is simple: I publish a message to an MQTT topic like "Should I go wing foiling at Crissy Field, San Francisco? I weigh 85kg." and the agent responds with a full breakdown — wind data, tide predictions, gear recommendations, and a risk assessment.

Why MQTT? Because I want this to eventually run on IoT devices. Imagine a little screen at your local beach spot that shows real-time go/no-go conditions. MQTT is lightweight, pub/sub, and perfect for this.

Setting Up the Strands Agent

Strands Agents is an open-source SDK from AWS that makes building tool-using AI agents dead simple. You define tools as Python functions with the @tool decorator, give the agent a system prompt, and it figures out when and how to call your tools.

Here's my agent setup:

from strands import Agent from tools import get_tide_info, get_wind_and_weather, recommend_gear, assess_risks

SYSTEM_PROMPT = """You are an expert wing foiling advisor. When a user asks about wing foiling \ at a location, you MUST use your tools to gather real data before making recommendations.

Your workflow: 1. Use get_wind_and_weather to check wind/weather conditions 2. Use get_tide_info to check tide conditions 3. Use recommend_gear with the actual wind speed and rider weight 4. Use assess_risks with all gathered data

Always provide a structured response with: - Go / No-Go decision with clear reasoning - Best time window based on tide and wind - Gear recommendation (wing size, foil size, board) - Risk assessment with specific warnings - Tips specific to the conditions"""

def create_agent() -> Agent: return Agent( model="us.anthropic.claude-sonnet-4-5-20250929-v1:0", tools=[get_tide_info, get_wind_and_weather, recommend_gear, assess_risks], system_prompt=SYSTEM_PROMPT, callback_handler=None, )

That's it. The agent uses Claude via Amazon Bedrock, and Strands handles all the tool orchestration. I don't need to write any "if wind > X then call Y" logic — the LLM figures out the workflow from the system prompt and tool descriptions.

The Tools

I built 4 tools, each decorated with @tool so Strands knows how to use them:

1. get_wind_and_weather — OpenWeatherMap API

@tool def get_wind_and_weather(latitude: float, longitude: float) -> str: """Get current wind speed, direction, gusts, and weather for a location.""" # Calls OpenWeatherMap, returns formatted wind/weather data resp = requests.get( "https://api.openweathermap.org/data/2.5/weather", params={"lat": latitude, "lon": longitude, "appid": api_key, "units": "metric"}, ) # ... format and return

2. get_tide_info — Storm Glass API

@tool def get_tide_info(latitude: float, longitude: float) -> str: """Get current tide conditions and predictions for a location.""" # Calls Storm Glass for tide extremes (high/low predictions)

3. recommend_gear — Pure Logic

This one doesn't call any API. It's just wing foiling knowledge encoded as rules:

@tool def recommend_gear(wind_knots: float, rider_weight_kg: float = 80.0) -> str: """Recommend wing and foil size based on wind and rider weight.""" # Wind 15-20kts → 5m wing # Wind 20-25kts → 4m wing # Adjust for rider weight # Recommend foil area and board size

4. assess_risks — Safety First

@tool def assess_risks(wind_knots: float, gust_knots: float, conditions: str, tide_state: str) -> str: """Assess wing foiling risks based on current conditions.""" # Checks gust factor, extreme wind, thunderstorms, low tide, currents # Returns risk level: LOW / MEDIUM / HIGH

The beauty of Strands is that I just define these tools with good docstrings and type hints, and the agent knows when to call them and how to chain them together. It calls wind first, then tide, then passes the wind speed to gear recommendation, then feeds everything into risk assessment. All orchestrated by the LLM.

Adding MQTT: The IoT Bridge

I wanted this to be event-driven, not a CLI chatbot. MQTT was the natural choice:

import paho.mqtt.client as mqtt from agent import create_agent

class MqttBridge: def __init__(self, broker, port, topic_req, topic_resp): self.agent = create_agent() self.client = mqtt.Client(mqtt.CallbackAPIVersion.VERSION2) self.client.on_message = self._on_message self.client.connect(broker, port)

def _on_message(self, client, userdata, msg): payload = msg.payload.decode() result = self.agent(payload) self.client.publish(self.topic_resp, str(result))

def run(self): self.client.loop_forever()

Now I can trigger the agent from anywhere — a phone app, a Raspberry Pi at the beach, a Home Assistant automation, whatever speaks MQTT. The agent listens, thinks, and publishes back.

To use it:

# Ask the agent mosquitto_pub -t wingfoil/request -m "Can I wing foil at Tarifa, Spain (36.0143, -5.6044)? 75kg rider."

# Get the answer mosquitto_sub -t wingfoil/response

Adding DynamoDB: Caching API Calls

Here's a problem I hit quickly: the Storm Glass API has a very limited free tier (10 requests/day), and OpenWeatherMap isn't unlimited either. If I ask about the same spot multiple times in a day, I'm wasting API calls on data that hasn't changed.

Solution: a DynamoDB cache with a simple "fetched today?" check.

import boto3 import time from datetime import date, datetime

TABLE_NAME = "agent-external-data-cache"

def cache_get(source: str, lat: float, lng: float) -> str | None: """Return cached value if fetched today.""" key = f"{source}#{lat:.2f}#{lng:.2f}" resp = _get_table().get_item(Key={"cache_key": key}) item = resp.get("Item") if item: fetched_date = datetime.utcfromtimestamp(int(item["fetched_at"])).date() if fetched_date == date.today(): return item["data"] return None

def cache_put(source: str, lat: float, lng: float, data: str): """Store value in cache.""" key = f"{source}#{lat:.2f}#{lng:.2f}" _get_table().put_item(Item={ "cache_key": key, "data": data, "fetched_at": int(time.time()), "source": source, })

The cache key is source#lat#lng (rounded to 2 decimals so nearby queries hit the same cache). If the data was fetched today, we use it. Otherwise, we call the API fresh. I also enabled TTL on the table so old entries auto-delete.

The DynamoDB table is pay-per-request (on-demand), so it costs essentially nothing for my usage. And I created a simple setup script:

ddb.create_table( TableName="agent-external-data-cache", KeySchema=[{"AttributeName": "cache_key", "KeyType": "HASH"}], AttributeDefinitions=[{"AttributeName": "cache_key", "AttributeType": "S"}], BillingMode="PAY_PER_REQUEST", )

How It All Comes Together

When I send a message like:

"Should I go wing foiling at Crissy Field, San Francisco (37.8035, -122.4654)? I weigh 85kg."

Here's what happens:

MQTT bridge receives the message

Strands agent parses it, extracts the location and weight Agent calls get_wind_and_weather(37.8035, -122.4654) → checks DynamoDB cache → calls OpenWeatherMap if needed Agent calls get_tide_info(37.8035, -122.4654) → checks cache → calls Storm Glass if needed Agent calls recommend_gear(18, 85) with the actual wind speed it got Agent calls assess_risks(18, 24, "partly cloudy", "incoming") with all the data Agent synthesizes everything into a Go/No-Go recommendation

Response published back to MQTT

The whole thing takes about 5-10 seconds, and I get a response like:

GO ✅ — 18 knots steady from the SW, incoming mid-tide, perfect conditions. Use your 5m wing, medium foil (1400cm²), 5'2" board. Watch for gusts to 24kts near the bridge. Best window: next 3 hours before tide peaks.

What I Learned

Strands makes tool-use agents trivial. Seriously, the hardest part was writing the actual tool logic, not the agent orchestration. The @tool decorator + good docstrings is all you need.

MQTT + AI agents is a powerful combo. It decouples the "brain" from the "interface." I can build any frontend — mobile app, e-ink display, voice assistant — and they all just pub/sub to the same topics.

DynamoDB as a cache is overkill but convenient. I could've used Redis or even a local JSON file. But DynamoDB is serverless, always-on, and I'm already in the AWS ecosystem with Bedrock. Plus TTL handles cleanup for free.

Mock data fallbacks are essential. Both APIs have rate limits. Having mock data means the agent still works during development and demos without burning API calls.

What's Next

Multi-spot comparison — "Where should I foil today: Crissy Field, 3rd Ave, or Alameda?"

Forecast mode — Check conditions for the next 3 days and suggest the best window

Raspberry Pi deployment — A dedicated device at my local spot with an e-ink display

Alert system — "Notify me when conditions are good at my favorite spots"

Try It Yourself

The code is straightforward to adapt for any outdoor sport that depends on weather conditions — kiteboarding, surfing, sailing, paragliding. Just swap the tools and system prompt.

You need:

Python 3.10+ AWS credentials (for Bedrock + DynamoDB) An MQTT broker (Mosquitto works great locally) API keys for OpenWeatherMap and Storm Glass (optional — mock data works without them)

pip install strands-agents paho-mqtt boto3 requests

If you're into wing foiling (or want to be), hit me up. And if you build something similar for your sport, I'd love to see it! 🤙

📰Originally published at dev.to

Comments