Flint AI
flint_ai is the standalone AI runtime for Flint. It gives Dart applications
agents, tools, workflows, chat providers, memory, and run tracing without
requiring a specific web framework.
Use it directly in any Dart app, or through package:flint_dart/ai.dart when
you want Flint Dart's app.ai and ctx.ai integration.
Features
- Agent runtime with plans, run state, traces, and structured output
- Safe-by-default tool execution with production policy support
- Role and capability-aware tool authorization
- Reusable workflow registry
- Chat provider contracts and implementations for OpenAI, Gemini, and Anthropic
- Image and embedding provider contracts
- In-memory memory and persistence stores for local development and tests
- Framework-neutral request context support
- Flint Dart database adapters available from
package:flint_dart/ai.dart
Architecture
flint_ai is split into small layers so applications can adopt only what they
need:
- Providers adapt external AI services into common Flint contracts. Chat
providers implement
ChatProvider; image and embedding providers have their own contracts. Built-in chat providers include OpenAI, Gemini, and Anthropic. - Agents receive an
AiGoal, create anAiPlan, and synthesize the final structured output from run state. - Tools perform controlled actions during a run. They are the boundary for side effects such as sending messages, writing data, calling internal APIs, or queueing business operations.
- Workflows are named reusable operations that can be invoked directly when you do not need a full agent plan.
- Memory and repository store thread messages, run events, traces, artifacts,
and durable run records. Standalone
flint_aidefaults to in-memory stores. - Flint Dart adapters connect the runtime to Flint apps through
app.aiandctx.ai, add database-backed stores, expose AI table definitions, and provide.envsetup helpers.
The usual request flow is:
AiGoal -> AiAgent.plan() -> AiPlan -> AiToolPolicy -> AiTool/AiWorkflow
-> AiRun.state -> AiAgent.synthesize() -> AiRunResult
Install
For standalone Dart projects:
dart pub add flint_ai
For Flint Dart apps, use the framework export instead:
import 'package:flint_dart/ai.dart';
Quick Start
import 'package:flint_ai/flint_ai.dart';
Future<void> main() async {
final ai = FlintAi();
final result = await ai.run(
agent: BasicTaskAgent(),
goal: const AiGoal(
task: 'Summarize an order',
input: {'orderId': 'ord_1'},
),
userId: 'user-1',
);
print(result.output);
}
Examples
Runnable examples live in example/:
basic_agent.dartruns a simple agent end to end.chat_provider.dartregisters an OpenAI-compatible provider with a fake HTTP client so it can run without a real API key.production_policy.dartdemonstrates a capability-gated tool usingProductionAiToolPolicy.
Run one with:
dart run example/basic_agent.dart
Agents
Agents turn a goal into a plan and synthesize the final output after the plan runs.
class SupportAgent extends AiAgent {
@override
String get name => 'support_agent';
@override
Future<AiPlan> plan(AiRunContext context) async {
return AiPlan(
steps: [
AiPlanStep(
id: 'capture',
type: 'record',
description: 'Capture the support request',
arguments: {
'task': context.goal.task,
'input': context.goal.input,
},
),
],
);
}
}
Tools
Tools are the boundary for actions with side effects. The default policy allows only tools that are explicitly enabled or allowed by policy.
class EchoTool extends AiTool {
@override
String get name => 'echo';
@override
String get description => 'Returns the message argument.';
@override
bool get enabledByDefault => true;
@override
Future<Map<String, dynamic>> execute(AiToolContext context) async {
return {
'message': context.arguments['message'],
'userId': context.userId,
};
}
}
final ai = FlintAi()..registerTool(EchoTool());
Security
AI agents should not be allowed to perform every registered action. Treat tools as privileged application operations and use policy checks before any side effect happens.
The default SafeDefaultAiToolPolicy is convenient for development. It allows
tools marked enabledByDefault and can also allow specific tools or
capabilities.
For production, prefer ProductionAiToolPolicy. It denies tool execution unless
the invocation has an authenticated user and one of these explicit permissions:
- the tool name is listed in
allowedTools - the current user has a role listed in
allowedRoles - the tool's
requiredCapabilitiesare satisfied by user metadata orallowedCapabilities
Destructive Tools
Destructive tools are tools that send emails, delete data, update billing, issue refunds, publish content, provision infrastructure, or make irreversible external calls. Keep them restricted:
- Do not set
enabledByDefaulton destructive tools. - Give every destructive tool a specific
requiredCapabilitiesvalue. - Pass the current user's role and capabilities in
FlintAi.run(metadata: ...). - Use
ProductionAiToolPolicyin deployed apps. - Prefer queueing or review workflows for high-risk operations instead of doing the destructive action immediately.
Roles And Capabilities
Tools can declare required capabilities, and runs can provide role/capability metadata for the current user.
class RefundTool extends AiTool {
@override
String get name => 'billing.refund';
@override
String get description => 'Issues a customer refund.';
@override
Set<String> get requiredCapabilities => const {'billing:refund'};
@override
Future<Map<String, dynamic>> execute(AiToolContext context) async {
return {'queued': true};
}
}
final ai = FlintAi.production(
toolPolicy: const ProductionAiToolPolicy(
allowedRoles: {'ADMIN'},
),
)..registerTool(RefundTool());
await ai.run(
agent: SupportAgent(),
goal: const AiGoal(task: 'Handle refund request'),
userId: 'user-1',
metadata: {
'role': 'EMPLOYEE',
'capabilities': ['billing:refund'],
},
);
If you are using Flint Dart, production policy allow-lists can come from .env:
app.ai.useProductionToolPolicyFromEnv();
Supported keys:
AI_ALLOWED_TOOLSAI_ALLOWED_CAPABILITIESAI_ALLOWED_ROLES
Chat Providers
Register a provider, then call ai.chat().
final ai = FlintAi()
..registerChatProvider(
OpenAiChatProvider(apiKey: 'openai-key'),
);
final result = await ai.chat(
providerId: 'openai',
request: const ChatRequest(
model: 'gpt-4o-mini',
messages: [
ChatMessage(role: 'user', content: 'Say hello from Flint'),
],
),
);
print(result.content);
Built-in chat providers:
OpenAiChatProviderGeminiChatProviderAnthropicChatProvider
Streaming Chat
Use streamChat() when the UI should render text as it arrives. Providers emit
chat.delta events for incremental text and a final chat.completed event with
the accumulated content.
import 'dart:io';
await for (final event in ai.streamChat(
providerId: 'openai',
request: const ChatRequest(
model: 'gpt-4o-mini',
messages: [
ChatMessage(role: 'user', content: 'Write a short welcome note'),
],
),
)) {
if (event.type == 'chat.delta') {
stdout.write(event.payload['content']);
}
if (event.type == 'chat.completed') {
print('\nDone');
}
}
OpenAiChatProvider, GeminiChatProvider, and AnthropicChatProvider support
native server-sent event streaming.
Workflows
Workflows are named reusable operations that can be called directly.
class SupportWorkflow extends AiWorkflow {
@override
String get name => 'support_followup';
@override
String get description => 'Creates a support follow-up payload.';
@override
Future<Map<String, dynamic>> run(AiWorkflowContext context) async {
return {
'status': 'queued',
'userId': context.userId,
'threadId': context.threadId,
};
}
}
final ai = FlintAi()..registerWorkflow(SupportWorkflow());
final result = await ai.runWorkflow(
'support_followup',
userId: 'user-1',
threadId: 'thread-1',
);
Memory And Persistence
By default, flint_ai uses in-memory stores. That keeps standalone scripts,
tests, and local development simple.
For Flint Dart apps, use package:flint_dart/ai.dart to get database-backed
adapters:
import 'package:flint_dart/ai.dart';
final ai = FlintAi.production(
memoryStore: FlintAutoAiMemoryStore(),
repository: FlintAutoAiRepository(),
);
Register ...flintAiTables in the consuming app's table_registry.dart and
run flint migrate before relying on durable AI persistence in production.
Flint Dart Env Setup
Flint Dart adds environment-backed helpers:
import 'package:flint_dart/flint_dart.dart';
import 'package:flint_dart/ai.dart';
final app = Flint();
app.ai.useOpenAiFromEnv();
app.ai.useGeminiFromEnv();
app.ai.useAnthropicFromEnv();
// Or register every provider that has credentials configured.
final providers = app.ai.useChatProvidersFromEnv();
Supported provider keys include:
OPENAI_API_KEY,OPENAI_BEARER_TOKEN,OPENAI_CHAT_ENDPOINTGEMINI_API_KEY,GEMINI_BEARER_TOKEN,GEMINI_CHAT_ENDPOINTANTHROPIC_API_KEY,ANTHROPIC_CHAT_ENDPOINT
License
MIT. See LICENSE.
Libraries
- flint_ai
- AI agents, tools, workflows, providers, memory, and runtime APIs for Flint.