Budget Analyst Agent — Strands
The Budget Analyst is a Strands agent that calculates event budget breakdowns. It's one of seven specialist agents in the Event Planning Team — a multi-agent system where each agent is built with a different framework and coordinates through Dapr pub/sub.
This agent also demonstrates DaprStateSessionManager — a Strands hook that persists conversation history across invocations using Dapr state management.
Event Planning Team
| Agent | Framework | Port | Pub/Sub Topics |
|---|---|---|---|
| Venue Scout | CrewAI | 8001 | venue.requests → venue.results |
| Catering Coordinator | OpenAI Agents | 8002 | catering.requests → catering.results |
| Entertainment Planner | Google ADK | 8003 | entertainment.requests → entertainment.results |
| Budget Analyst | Strands | 8004 | budget.requests → budget.results |
| Schedule Planner | LangGraph | 8005 | schedule.requests → schedule.results |
| Invitations Manager | Dapr Agents | 8006 | events.invitations.requests |
| Event Coordinator | Dapr Agents | 8007 | Orchestrator |
| Decoration Planner | Pydantic AI | 8008 | decorations.requests → decorations.results |
Prerequisites
- Python 3.11+
- Diagrid CLI installed and initialized (
diagrid dev init) - An OpenAI API key
Agent Code
The Budget Analyst uses Strands' Agent and @tool decorator with a DaprStateSessionManager for conversation history, wrapped in a DaprWorkflowAgentRunner for durable execution and pub/sub messaging.
import logging
import os
logging.basicConfig(level=logging.DEBUG)
from strands import Agent, tool
from diagrid.agent.strands import DaprWorkflowAgentRunner, DaprStateSessionManager
from diagrid.agent.core.state import DaprStateStore
@tool
def calculate_budget(items: str) -> str:
"""Calculate total budget from a comma-separated list of cost items."""
return (
"Budget breakdown:\n"
"- Venue: $3,500\n"
"- Catering: $2,250\n"
"- Entertainment: $1,500\n"
"- Decorations: $800\n"
"- Miscellaneous: $500\n"
"Total: $8,550\n"
"Recommended buffer (15%): $1,283\n"
"Grand Total: $9,833"
)
# State: persist conversation history across invocations
session_manager = DaprStateSessionManager(
store_name="agent-memory"
)
agent = Agent(
tools=[calculate_budget],
system_prompt="You are a budget analyst specializing in event planning. When asked to estimate costs, use the calculate_budget tool with a comma-separated list of cost items. Return the full budget breakdown with line items, totals, and recommended buffer. Always call the tool before responding.",
hooks=[session_manager],
)
runner = DaprWorkflowAgentRunner(
agent=agent,
state_store=DaprStateStore(store_name="agent-memory"),
)
# PubSub: subscribe for incoming tasks, publish results
runner.serve(
port=int(os.environ.get("APP_PORT", "8004")),
pubsub_name="agent-pubsub",
subscribe_topic="budget.requests",
publish_topic="budget.results",
)
What's happening
@tool— Strands decorator that registers a Python function as a tool the agent can callDaprStateSessionManager— Strands hook that persists conversation history to Dapr state management, so the agent remembers previous interactionsAgent(..., hooks=[session_manager])— Strands agent with tools and a session hook for persistent memoryDaprWorkflowAgentRunner— Wraps the agent in a durable workflow that survives crashes and restartsrunner.serve()— Starts an HTTP server that subscribes tobudget.requestsand publishes results tobudget.resultsvia Dapr pub/sub
Dapr Components
The agent uses shared Dapr components for pub/sub messaging, state persistence, and LLM access.
Pub/Sub (agent-pubsub)
apiVersion: dapr.io/v1alpha1
kind: Component
metadata:
name: agent-pubsub
spec:
type: pubsub.redis
version: v1
metadata:
- name: redisHost
value: localhost:6379
- name: redisPassword
value: ""
Routes messages between agents. Locally uses Redis; in production, swap for Kafka, RabbitMQ, or any Dapr pub/sub broker.
State Store (agent-memory)
apiVersion: dapr.io/v1alpha1
kind: Component
metadata:
name: agent-memory
spec:
type: state.redis
version: v1
metadata:
- name: redisHost
value: localhost:6379
- name: redisPassword
value: ""
- name: actorStateStore
value: "false"
Persists agent memory and conversation state across invocations.
LLM Provider
apiVersion: dapr.io/v1alpha1
kind: Component
metadata:
name: llm-provider
spec:
type: conversation.openai
version: v1
metadata:
- name: key
value: "{{OPENAI_API_KEY}}"
- name: model
value: gpt-4.1-2025-04-14
Provides LLM access through Dapr's conversation API. The API key is injected from the environment.
Run the Agent
Clone the quickstart
git clone https://github.com/diagridio/catalyst-quickstarts.git
cd catalyst-quickstarts/agents/strands
Set your API key
export OPENAI_API_KEY="your-key-here"
Install dependencies
pip install -r requirements.txt
Start the agent
diagrid dev run -f dev-python-strands.yaml
Test the agent
Send a budget calculation request:
curl -X POST http://localhost:8888/agent/run \
-H "Content-Type: application/json" \
-d '{"task": "Calculate the budget for a corporate event with venue, catering, and entertainment"}'
Run with the Full Team
To run all seven specialist agents together with the orchestrator, see the Event Planning Team overview.
Key Concepts
| Concept | Description |
|---|---|
| Durable Workflows | DaprWorkflowAgentRunner checkpoints every step — agent survives crashes and restarts |
| Session Persistence | DaprStateSessionManager saves conversation history so the agent remembers prior interactions |
| Pub/Sub Decoupling | Agents communicate through topics, not direct calls. Add or remove agents without code changes. |
| Portable Infrastructure | Swap message brokers and state stores by changing YAML — agent code stays the same |
Next Steps
- CLI Quickstart — Deploy a durable agent to Catalyst in minutes
- Session Management — Deep dive into Strands session persistence
- Event Planning Team — See all agents working together