Skip to content
Blog

Genkit Agents API for Dart: Full-Stack Conversational AI in Flutter

How the Genkit Agents API brings full-stack conversational AI to Dart and Flutter — defining agents on the server and driving them from Flutter with a shared chat() interface.

Published on September 18, 2026

AI Assistant

Building a conversational AI feature — a support assistant that remembers context, a copilot that works across turns, a chatbot with tool calling — requires wiring up message history, the tool loop, streaming, persistence, and a client protocol by hand. That plumbing repeats on every project and has little to do with what makes your app distinct.

The Genkit Agents API, now available in Dart (announced July 2026), packages all of that behind one interface. You define an agent on the server, then drive it with the same chat() API whether it runs in-process or behind an HTTP endpoint.

What the Agents API Solves

A single generate() call handles one request-response. Real conversational AI needs:

  • Message history — Track multi-turn conversations
  • Tool execution loop — Let the model call tools and process results
  • Streaming — Deliver tokens as they’re generated
  • Persistence — Save and restore conversation state
  • Client protocol — Frontend and backend speak the same wire format

The Agents API handles all of this. You define the agent once, and it manages everything else.

Defining an Agent

An agent needs a name, a system prompt, and optionally tools and a session store:

import 'package:genkit/genkit.dart';
import 'package:genkit/io.dart';
import 'package:genkit_google_genai/genkit_google_genai.dart';

final ai = Genkit(
  plugins: [googleAI()],
  model: googleAI.gemini('gemini-flash-latest'),
);

final getWeather = ai.defineTool(
  name: 'getWeather',
  description: 'Get the current weather for a given location.',
  inputSchema: GetWeatherInput.$schema,
  outputSchema: GetWeatherOutput.$schema,
  fn: (input, _) async => GetWeatherOutput(
    weather: 'Sunny in ${input.location}',
    temperature: '71F',
  ),
);

final weatherAgent = ai.defineAgent(
  name: 'weatherAgent',
  system: 'You are an assistant helping with weather information. '
      'Use the getWeather tool.',
  tools: [getWeather],
  store: FileSessionStore('.sessions'),
);

The same agent object handles one-shot replies, streamed turns, paused tool calls, and multi-turn conversations.

Driving the Agent

In-Process (Server-Side)

Call the agent directly from Dart code:

final chat = weatherAgent.chat();
final turn = chat.sendStream(text: 'What is the weather in London?');

await for (final chunk in turn.stream) {
  stdout.write(chunk.text);
}

final res = await turn.response;

A chat carries its snapshotId and state forward across turns automatically. Multi-turn is just calling send again.

From Flutter via HTTP

Serve the agent over HTTP, then call it from your Flutter app:

// Server: serve agent routes
// agent.shelf.dart
import 'package:genkit/shelf.dart';

final router = weatherAgent.serveShelf();
// Client: Flutter app
import 'package:genkit/beta/client.dart';

const agent = remoteAgent<WeatherState>(
  url: 'http://localhost:8080/api/weatherAgent',
);

final chat = agent.chat();
final res = await chat.send('Weather in Tokyo?');

The Flutter client uses the same chat() interface whether the agent runs locally or over HTTP. And because the endpoint speaks a shared wire protocol, a Flutter app can talk to agents written in Dart, JavaScript, Go, or Python — without changing a line of client code.

Session Persistence

Add a store to make the agent server-managed. The server persists messages, custom state, and artifacts as snapshots. Clients continue by sending back a session ID.

final agent = ai.defineAgent(
  name: 'assistant',
  system: 'You are a helpful assistant.',
  store: FileSessionStore('.sessions'),
);

Available store implementations:

  • FileSessionStore — File-based persistence for development
  • MemorySessionStore — In-memory (no persistence)
  • Custom stores — Implement SessionStore for your database

Custom State

Tools can mutate typed session state through ai.currentSession(), and Genkit streams the changes to the client as customPatch chunks. This is useful for task lists, workflow status, or any structured value that drives the next turn:

final updateTask = ai.defineTool(
  name: 'updateTask',
  description: 'Mark a task as complete.',
  inputSchema: UpdateTaskInput.$schema,
  outputSchema: UpdateTaskOutput.$schema,
  fn: (input, context) async {
    final session = context.currentSession;
    session.state.tasks[input.taskId].completed = true;
    return UpdateTaskOutput(success: true);
  },
);

Human in the Loop

The Agents API supports interrupts — pausing execution when a tool call requires human approval before proceeding:

final approveOrder = ai.defineTool(
  name: 'approveOrder',
  description: 'Place an order. Requires human approval.',
  inputSchema: OrderInput.$schema,
  outputSchema: OrderOutput.$schema,
  interrupt: true, // Pauses for human approval
  fn: (input, _) async => placeOrder(input),
);

When the agent hits an interrupt, it pauses and returns the pending tool call to the client. The UI shows an approval dialog, and execution resumes when the user confirms.

Streaming

Stream agent responses token-by-token for responsive UIs:

final chat = weatherAgent.chat();
final turn = chat.sendStream(text: 'Tell me about the weather');

await for (final chunk in turn.stream) {
  if (chunk.text.isNotEmpty) {
    setState(() => _response += chunk.text);
  }
}

The stream delivers text chunks, state patches, and tool call events as they happen.

When to Use Agents vs Flows

ScenarioUse
Single request-responseFlow or generate()
Scheduled batch jobsFlow
Conversational chatAgent
Multi-turn refinementAgent
Approval workflowsAgent
Stateful copilotsAgent
Maximum architectural controlFlow

Agents are the right abstraction when your feature is naturally conversational or iterative. For everything else, flows and generate() give you more control.

Getting Started

Install the required packages:

dependencies:
  genkit: ^0.13.0
  genkit_google_genai: ^0.3.0

The Agents API is available from the genkit/beta import:

import 'package:genkit/beta.dart';

Define an agent, add tools and a store, and start chatting.

Conclusion

The Genkit Agents API turns the repeated plumbing of conversational, full-stack AI into something you configure rather than rebuild. Define an agent on the server, give it a store when you want persistence, and drive it from your Flutter app with the same chat() interface. Whether your agent runs locally, on a server, or behind an HTTP endpoint — and whether it’s written in Dart, JS, Go, or Python — the client code stays the same.

References: