In the first two articles, we covered the fundamentals and how to build more reliable AI features using structured output, streaming, and proper error handling.
But there's a big limitation with single AI calls: they struggle with complex, multi-step tasks.
That's where AI Agents come in.
Today, we're going to build a simple but powerful multi-agent system in Flutter — specifically, a Planning Agent, a Generation Agent, and a Validation Agent.
By the end of this video, you'll understand how to design agentic workflows and implement them in real Flutter applications.
What Are AI Agents?
An AI Agent is more like giving someone a goal and letting them think, plan, and take multiple steps to achieve it, WHILE a regular LLM call is like asking a smart person one question and getting one answer.
We'll focus on a simple and practical multi-agent pattern:
- Planning Agent → Breaks the user request into clear steps
- Generation Agent → Executes the plan and creates the output
- Validation Agent → Reviews the result and checks for quality
This pattern is extremely useful for real applications.
Designing the Workflow
We'll build a simple Smart Feature Builder,
The user gives a high level request like: 'Create a clean Flutter logins creen with email and password validation.'
Then the system works like this:
- The Planning Agent creates a structured plan
- The Generation Agent turns that plan into actual code or a UI description
- The Validation Agent reviews the output and suggests improvements
Building the Planning Agent
The goal of this agent is to take a vague user request and turn it into a clear, structured plan.
- we define the role and system prompt for the Planning Agent
- we use structured output so we can get a clean list of steps
- then we create a Dart model for the plan
- finally, call the agent and display the result.
import 'dart:convert';
import 'package:googleai_dart/googleai_dart.dart';
import '../models/feature_plan.dart';
/// Stage 1: Planning Agent
/// Takes raw user feature requests and converts them into a structured architectural plan via Gemini API.
class PlanningAgent {
final String apiKey;
final String modelName;
PlanningAgent({
required this.apiKey,
this.modelName = 'gemini-3.1-flash-lite',
});
static const String _systemInstruction = '''
You are a Senior Flutter Software Architect.
Your role is to take a high-level mobile/web feature request and generate a clean, structured architectural plan.
You must respond ONLY with a valid JSON object matching this exact schema:
{
"title": "String - Concise title of the feature plan",
"overview": "String - Executive summary of the architectural strategy",
"targetPlatform": "String - Target platforms (e.g. Flutter Web, iOS & Android)",
"componentsToBuild": ["List of String - Individual UI components, widgets, or services to build"],
"implementationSteps": ["List of String - Sequential step-by-step engineering tasks"],
"riskConsiderations": ["List of String - Potential state management, security, performance or edge case risks"]
}
''';
Future<FeaturePlan> planFeature(String prompt) async {
if (apiKey.isEmpty) {
throw StateError('Gemini API Key is missing. Please configure your API key before running the pipeline.');
}
final config = GoogleAIConfig(authProvider: ApiKeyProvider(apiKey));
final client = GoogleAIClient(config: config);
final request = GenerateContentRequest(
contents: [Content.text('Feature Request:\n$prompt')],
systemInstruction: Content.text(_systemInstruction),
generationConfig: const GenerationConfig(
responseMimeType: 'application/json',
temperature: 0.2,
),
);
final response = await client.models.generateContent(
model: modelName,
request: request,
);
final text = response.text;
if (text == null || text.trim().isEmpty) {
throw Exception('Planning Agent returned empty response from Gemini API.');
}
final jsonMap = jsonDecode(text) as Map<String, dynamic>;
return FeaturePlan.fromJson(jsonMap);
}
}
Building the Generation Agent
This agent takes the structured plan and actually produces the output, in our case, Flutter-related code/content suggestions.
- first, we create the Generation Agent with a clear role
- then pass the plan from the previous agent
- generate the final output
- show how we keep the response structured and useful
import 'dart:convert';
import 'package:googleai_dart/googleai_dart.dart';
import '../models/feature_plan.dart';
import '../models/generated_feature_code.dart';
/// Stage 2: Generation Agent
/// Takes the architectural FeaturePlan and user request, then generates complete, production-ready Flutter code via Gemini API.
class GenerationAgent {
final String apiKey;
final String modelName;
GenerationAgent({
required this.apiKey,
this.modelName = 'gemini-3.1-flash-lite',
});
static const String _systemInstruction = '''
You are an expert Lead Flutter Engineer.
Your task is to take an Architectural Feature Plan and write production-ready, clean, well-formatted Flutter Dart code.
You must respond ONLY with a valid JSON object matching this exact schema:
{
"title": "String - Title of the generated feature code",
"explanation": "String - Clear explanation of key implementation patterns and Dart features used",
"flutterCode": "String - Complete, fully functioning, syntactically correct Flutter Widget Dart code",
"dependencies": ["List of String - Required packages in pubspec.yaml (e.g. google_fonts, flutter_animate)"]
}
''';
Future<GeneratedFeatureCode> generateCode({
required String userPrompt,
required FeaturePlan plan,
}) async {
if (apiKey.isEmpty) {
throw StateError('Gemini API Key is missing. Please configure your API key before running the pipeline.');
}
final config = GoogleAIConfig(authProvider: ApiKeyProvider(apiKey));
final client = GoogleAIClient(config: config);
final promptInput = '''
User Feature Request:
$userPrompt
Architectural Plan:
${plan.toFormattedJson()}
''';
final request = GenerateContentRequest(
contents: [Content.text(promptInput)],
systemInstruction: Content.text(_systemInstruction),
generationConfig: const GenerationConfig(
responseMimeType: 'application/json',
temperature: 0.3,
),
);
final response = await client.models.generateContent(
model: modelName,
request: request,
);
final text = response.text;
if (text == null || text.trim().isEmpty) {
throw Exception('Generation Agent returned empty response from Gemini API.');
}
final jsonMap = jsonDecode(text) as Map<String, dynamic>;
return GeneratedFeatureCode.fromJson(jsonMap);
}
}
Building the Validation Agent
This agent acts like a reviewer. It checks the output from the Generation Agent for issues, missing pieces, or areas for improvement.
- again, we create the Validation Agent
- feed it the original request + the generated output
- get structured feedback
- optionally show a simple improvement loop
import 'dart:convert';
import 'package:googleai_dart/googleai_dart.dart';
import '../models/feature_plan.dart';
import '../models/generated_feature_code.dart';
import '../models/validation_report.dart';
/// Stage 3: Validation Agent
/// Reviews generated code against the original user prompt and architectural plan via Gemini API.
class ValidationAgent {
final String apiKey;
final String modelName;
ValidationAgent({
required this.apiKey,
this.modelName = 'gemini-3.1-flash-lite',
});
static const String _systemInstruction = '''
You are a Lead Code Auditor and Security Reviewer for Flutter applications.
Your job is to audit generated Flutter code against the user request and architectural plan.
You must evaluate code quality, completeness, error handling, accessibility, and performance.
You must respond ONLY with a valid JSON object matching this exact schema:
{
"qualityScore": 92, // Integer between 0 and 100
"isPassed": true, // Boolean - true if qualityScore >= 75
"identifiedIssues": ["List of String - Specific flaws, missing validations, or anti-patterns"],
"improvementSuggestions": ["List of String - Actionable code quality or performance recommendations"],
"refinedCodeSnippet": "String or Null - Improved/corrected snippet fixing identified issues"
}
''';
Future<ValidationReport> validateOutput({
required String userPrompt,
required FeaturePlan plan,
required GeneratedFeatureCode generatedCode,
}) async {
if (apiKey.isEmpty) {
throw StateError('Gemini API Key is missing. Please configure your API key before running the pipeline.');
}
final config = GoogleAIConfig(authProvider: ApiKeyProvider(apiKey));
final client = GoogleAIClient(config: config);
final promptInput = '''
Original Request:
$userPrompt
Architectural Plan:
${plan.toFormattedJson()}
Generated Code to Audit:
${generatedCode.toFormattedJson()}
''';
final request = GenerateContentRequest(
contents: [Content.text(promptInput)],
systemInstruction: Content.text(_systemInstruction),
generationConfig: const GenerationConfig(
responseMimeType: 'application/json',
temperature: 0.1,
),
);
final response = await client.models.generateContent(
model: modelName,
request: request,
);
final text = response.text;
if (text == null || text.trim().isEmpty) {
throw Exception('Validation Agent returned empty response from Gemini API.');
}
final jsonMap = jsonDecode(text) as Map<String, dynamic>;
return ValidationReport.fromJson(jsonMap);
}
}
Putting It All Together
We need to connect everything into one workflow.
We will create a simple orchestrator that:
1. Calls the Planning Agent
2. Passes the plan to the Generation Agent
3. Sends the result to the Validation Agent
4. Displays the final output and feedback
/// Orchestrator Service
/// Coordinates the multi-agent pipeline: Planning -> Generation -> Validation
class AgentOrchestrationService extends ValueNotifier<AgentWorkflowState> {
...
Future<void> runWorkflow(String prompt, {required String apiKey}) async {
if (prompt.trim().isEmpty) return;
_addLog('Orchestrator', 'Workflow started for prompt: "$prompt"');
try {
// -----------------------------------------------------------------------
// STAGE 1: Planning Agent
// -----------------------------------------------------------------------
_addLog('Planning Agent', 'Analyzing request & composing architectural plan...');
final planningAgent = PlanningAgent(apiKey: apiKey);
final plan = await planningAgent.planFeature(prompt);
value = value.copyWith(
stage: AgentStage.generating,
plan: plan,
);
_addLog('Planning Agent', 'Plan generated successfully: "${plan.title}"', payload: plan.toFormattedJson());
// -----------------------------------------------------------------------
// STAGE 2: Generation Agent
// -----------------------------------------------------------------------
_addLog('Generation Agent', 'Writing Flutter code & module dependencies...');
final generationAgent = GenerationAgent(apiKey: apiKey);
final generatedCode = await generationAgent.generateCode(
userPrompt: prompt,
plan: plan,
);
value = value.copyWith(
stage: AgentStage.validating,
generatedCode: generatedCode,
);
_addLog('Generation Agent', 'Code generated successfully (${generatedCode.flutterCode.length} chars)', payload: generatedCode.toFormattedJson());
// -----------------------------------------------------------------------
// STAGE 3: Validation Agent
// -----------------------------------------------------------------------
_addLog('Validation Agent', 'Auditing code quality, security & accessibility...');
final validationAgent = ValidationAgent(apiKey: apiKey);
final validationReport = await validationAgent.validateOutput(
userPrompt: prompt,
plan: plan,
generatedCode: generatedCode,
);
_tickerTimer?.cancel();
value = value.copyWith(
stage: AgentStage.completed,
validationReport: validationReport,
);
_addLog('Validation Agent', 'Audit complete. Quality Score: ${validationReport.qualityScore}/100 (Passed: ${validationReport.isPassed})', payload: validationReport.toFormattedJson());
_addLog('Orchestrator', 'Multi-Agent pipeline executed successfully!');
} catch (e) {
_tickerTimer?.cancel();
_addLog('Orchestrator', 'Pipeline failed: $e');
value = value.copyWith(
stage: AgentStage.failed,
error: e,
);
}
}
}
The full source code is on GitHub: github.com/techwithsam/ai_engineer_for_flutter_devs/tree/video-3
Recap & Key Takeaways
Quick summary of what we covered:
- AI Agents allow us to handle multi-step tasks much better than single prompts
- The Planning → Generation → Validation pattern is simple but powerful
- Structured output is critical when agents need to communicate
- Keeping the architecture clean makes these systems maintainable
This is a foundational pattern you can expand in many directions.
If you haven't already, download the free AI Engineering Starter Pack - I've updated it with the code and patterns from this article.
Here: techwithsam.dev/ai-starter-kit-3
Just enter your email, and it will be sent to you instantly.
In the next and final video of this series, we'll focus on Production AI Engineering - covering performance, privacy, security, evaluation, and best practices for shipping AI features in real Flutter apps.
If you found this valuable, please like and drop a comment.
I'd love to know: What kind of multi-agent workflow would you like to see next?
Thank you for following. I'll see you in the final one.
Take care!
Top comments (0)