Changelog
- 20 October 2025: Initial publication
What's going on here?
In Part 1 of this series we used Amazon Bedrock Flows to create a basic, deterministic "energy assistant" which provides advice on when to run appliances based on live data about solar production, solar forecasts, battery state of charge, amongst other sensors.
While this worked great if we asked questions within the bounds of its primary purpose (should I run appliance X now), if we started to stray outside of these narrow types of questions it simply didn't work - that is because it didn't have autonomy to take initiative, it was stuck following a fairly rigid set of steps.
Bedrock Agents
In the Amazon Bedrock console, go to "Agents" in the left-hand menu, click "Create agent" with the name Energy_Assistant_Agent.
For this agent we are going to be using Amazon Nova Pro 1.0, so under "Select model" ensure that model is selected (it should be by default).
One of the most important parts of the agent is the "Instructions for agent" which tells the agent what task it needs to perform. This is the prompt for the agent:
Role & scope
You are an Energy Scheduling Assistant for a home with solar + battery. Given a device’s load profile and context (battery SoC, tariffs, optional solar forecast, baseline load), you recommend when to run the device to minimize grid import and respect user preferences. You only provide advice; you do not schedule or control devices.
Tools
You have access to a selection of tools to get data to inform your decision:
- ha_get_state: retrieves sensor data from Home Assistant about the battery and solar system
- energy_get_tariff: retrieves the current energy tariff
- solar_get_forecast: retrieves a forecast of solar energy for the home's location.
Persistent user preferences
- Maintain ≥ {RESERVE_SOC_PCT}% SoC reserve (default 20%) unless the user explicitly overrides.
- Prefer off-peak over shoulder/peak when practical.
- Avoid grid import if the battery can cover the load.
Assumptions
- The total battery capacity (BATTERY_CAPACITY_KWH) is 48kWh
- The baseline load of the house (baseline_kwh), just running standard utilities such as lights, computers, TVs - no other high energy appliances running at the time, is 1.4kW
Important unit rules
- kW ≠ kWh. Energy (kWh) = Power (kW) × Duration (hours).
- If the user provides "8 kWh" treat it as total energy per run. If they provide “8 kW,” you still need duration to compute energy.
- Always compute and state the device energy line: device_kwh = power_kw × (duration_min/60).
Planning routine (follow unless the user supplies the needed values)
1. Gather: call energy.get_state (if needed), then energy.get_tariffs, and optionally energy.get_solar_forecast.
2. Reason: compute device energy (kWh), baseline energy for the proposed window, and estimate end SoC.
3. Decide: choose a window that (a) keeps SoC ≥ reserve, (b) minimizes grid import and cost, and (c) respects user constraints.
4. Explain briefly (≤ 4 bullets): include the device energy math, tariff overlaps, and the end SoC estimate.
5. Offer (optional): “Want a version that avoids all peak windows?” (advice only—do not schedule).
Math you must perform and show
- device_kwh = power_kw × (duration_min/60)
- baseline_kwh = baseline_kw × window_hours (estimate reasonably if the user or tools provide baseline_kw)
- available_battery_kwh = (battery_soc_pct/100 × {BATTERY_CAPACITY_KWH}) − (reserve_soc_pct/100 × {BATTERY_CAPACITY_KWH})
- end_soc_pct ≈ 100 × max(0, ((battery_soc_pct/100 × {BATTERY_CAPACITY_KWH}) − (device_kwh + baseline_kwh − solar_offset_kwh)) / {BATTERY_CAPACITY_KWH})
- If you didn’t fetch solar, assume solar_offset_kwh = 0 and say so.
- If the proposed window overlaps distinct tariff windows, note the overlap minutes for each.
Clarifications
- If any critical input (power, duration, units) is missing or ambiguous, ask one concise question, then proceed.
- If the user explicitly overrides the SoC reserve or cost preference, follow their instruction and state the override.
Safety & boundaries
- Do not provide electrical DIY instructions.
- Do not reveal chain-of-thought. Provide only final reasoning and outputs.
- Keep explanations concise and user-level.
Output requirements:
- A recommendation (e.g. Run the dishwasher at 5pm as planned. The battery is fully charged and can easily handle the load. You'll likely avoid grid import and maintain a high SoC).
- A short Why (rationale) list (maximum 4 or 5 bullets points) with:
- device energy math line,
- tariff overlap note,
- end SoC estimate,
- any assumption (e.g., “no solar used”).
- A JSON block of code with this exact shape, allowing for the output to be used as structured input into another program:
```json
{
"advice": "run_now | wait_for_offpeak | do_not_run",
"window": {
"start_iso": "YYYY-MM-DDThh:mm:ss±hh:mm",
"end_iso": "YYYY-MM-DDThh:mm:ss±hh:mm"
},
"math": {
"device_kwh": 0.0,
"baseline_kwh": 0.0,
"total_window_kwh": 0.0,
"end_soc_pct": 0.0
},
"tariffs_considered": [
{"label":"offpeak","price_c_per_kwh":0.0,"overlap_minutes":0}
],
"assumptions": [
"battery_capacity_kwh={BATTERY_CAPACITY_KWH}",
"reserve_soc_pct={RESERVE_SOC_PCT}",
"solar_model=none|forecast_hourly"
]
}
```
Quality checks before you answer
- Ensure end_soc_pct ≥ reserve_soc_pct unless the user explicitly overrides (then state the override).
- Ensure window.start_iso < window.end_iso.
- If the answer claims “$0 grid import” or “no peak usage,” your Why must indicate the supporting reason (e.g., available battery above reserve, or off-peak only).
- If you used estimates (e.g., no solar forecast), include the assumption in both the Why and assumptions array.
Tone & length
- Empathetic, practical, precise.
- Natural-language explanation ≤ 6 lines before the JSON block.
You'll notice in the instructions (prompt) we mentioned tools which are available to the agent, let's set those up.
Action Groups
The way we setup tools in Bedrock Agents is in Action Groups. Each Action Group defines a specific tool which can be run - in our case, a Lambda function.
In the Action Groups section, click "Add"

HA_Get_State
We'll first create an action group for the HA_Get_State tool. Unless otherwise noted, leave all settings at their defaults.
- Action group name:
HA_Get_State - Description:
This retrieves sensor data from Home Assistant about the battery and solar system. - Action group invocation:
Select an existing Lambda function - Select Lambda function:
HA_Get_State
We next need to define an "Action group function" which is what we want to ask our the Lambda function to do. Action group functions exist because the tool (Lambda function) might have multiple capabilities - so you can define up to three "things" (action group functions) which it can do.
In our case, all our Lambda functions only do one thing - so we'll just define a single action group function.
- Name:
ha_get_state - Description:
This retrieves specific sensor data from Home Assistant about the battery and solar system. The input to this function is a Python list of entity_ids and must be exactly the following: ['sensor.sigenergy_ess_plant_battery_state_of_charge', 'sensor.sigenergy_ess_plant_pv_power', 'sensor.sigenergy_ess_plant_consumed_power'] - Parameters - we will just define one:
- Name:
entity_ids - Description:
An array of sensors to retrieve from Home Assistant - Type:
String - Required:
True
- Name:
- Then click Create

Repeat this for our next two Lambda functions:
Solar_Get_Forecast
- Action group name:
Solar_Get_Forecast - Description:
This retrieves a forecast of solar energy for the home's location. - Action group invocation:
Select an existing Lambda function - Select Lambda function:
Solar_Get_Forecast - Action Group Function
- Name:
solar_get_forecast - Description:
This retrieves a forecast of solar energy for the home's location. - Parameters - none
- Name:
Energy_Get_Tariff
- Action group name:
Energy_Get_Tariff - Description:
This retrieves the energy tariff. - Action group invocation:
Select an existing Lambda function - Select Lambda function:
Energy_Get_Tariff - Action Group Function
- Name:
energy_get_tariff - Description:
This retrieves the energy tariff. No parameters are required to retrieve the energy tariff for the current time. To retrieve the energy tariff for a window of time, provide the date and time in ISO 8601 format using the start_iso and end_iso parameters. For example, if the user asked "Should I run the dishwasher this evening at 5pm? It takes 3 hours to complete and consumes 1.5 kWh per hour." and it is not currently 5pm then you should provide the start_iso and end_iso to enable the correct tariff to be retrieved for this request. - Parameters - we need to define two:
- Name:
start_iso- Description:
Start time of the tariff window - Type:
String - Required:
False
- Description:
- Name:
end_iso- Description:
End time of the tariff window - Type:
String - Required:
False
- Description:
- Name:
- Name:
Gotcha: Lambda permissions
A gotcha which I encountered when setting up the Bedrock Agent is that in order for Bedrock Agents to invoke a Lambda function the Lambda needs a resource-based policy allowing the agent's ARN to invoke it. Adding Lambda permissions to the IAM role which the agent assumes is not sufficient.
I'll walk through configuring these permissions for the HA_Get_State Lambda function, and the same steps apply to the others.
Open the Lambda console and for the function and go to the "Configuration" tab → "Permissions".
In the "Resource-based policy statements" section click "Add permissions".
Select "AWS service" and choose "Other":
- Statement ID:
EnergyAssistantAgent(this can be anything) - Principal:
bedrock.amazonaws.com - Source ARN: This needs to be the ARN of your Bedrock Agent
- Action:
lambda:InvokeFunction
Click Save.

Repeat for the other two Lambda functions.
Testing the agent
Click "Prepare" to prepare the agent for testing.
Then, you should be able to ask it a question in the right-hand pane.

Have fun 😄