AI Orchestrator
The AI Orchestrator is the intelligent middleware layer that coordinates between your frontend application, AI models, and INTU's blockchain infrastructure. It manages conversation flow, session state, and ensures secure execution of AI-initiated actions.
What is the AI Orchestrator?
The Orchestrator acts as a smart proxy that:
- Coordinates AI Providers: Routes requests to OpenAI, Ollama, Anthropic, or other LLM services
- Manages INTU Integration: Handles vault operations and transaction coordination
- Maintains Context: Preserves conversation history and user session state
- Ensures Security: Implements approval workflows for AI-initiated blockchain actions
Architecture Role
Frontend (React/Vue/Angular)
↓ HTTP/REST
┌─────────────────┐
│ AI Orchestrator │ ← You are here
│ • Session Mgmt │
│ • AI Routing │
│ • INTU Bridge │
│ • Security │
└─────────────────┘
↓ MCP Protocol
┌─────────────────┐
│ MCP Server │
│ • AI Models │
│ • Data Layer │
│ • Blockchain │
└─────────────────┘
Quick Setup
1. Install Dependencies
npm install @intuweb3/orchestrator
# or clone from demo
git clone https://github.com/intu-labs/intu-persona-demo.git
cd intu-persona-demo/orchestrator
npm install
2. Configure Environment
# Create .env file
cp env.example .env
# Required configuration
MCP_SERVER_URL=http://localhost:3000
OPENAI_API_KEY=your_openai_key_here
INTU_NETWORK=arbitrum-sepolia
3. Start the Orchestrator
# Development mode
npm run dev
# Production mode
npm run build && npm start
The orchestrator will be available at http://localhost:3005
Integration with INTU SDK
Basic Setup
// orchestrator/src/intu-integration.js
import { ethers } from "ethers";
import { getVaults, createIntuAccount } from '@intuweb3/web-kit';
export class IntuIntegration {
constructor() {
this.provider = null;
this.userVaults = new Map();
}
async initializeUser(userAddress, provider) {
this.provider = provider;
// Load user's INTU vaults
const vaults = await getVaults(userAddress, provider);
this.userVaults.set(userAddress, vaults);
return {
address: userAddress,
vaults: vaults.length,
hasVaults: vaults.length > 0
};
}
async getUserVaults(userAddress) {
return this.userVaults.get(userAddress) || [];
}
async createAIVault(userAddress, signer, vaultConfig) {
const newVault = await createIntuAccount(
vaultConfig.participants || [userAddress],
vaultConfig.name || "AI Assistant Vault",
vaultConfig.rotateThreshold || 1,
vaultConfig.txThreshold || 1,
vaultConfig.adminThreshold || 1,
signer
);
// Update cached vaults
const vaults = this.userVaults.get(userAddress) || [];
vaults.push(newVault);
this.userVaults.set(userAddress, vaults);
return newVault;
}
}
AI-INTU Bridge
// orchestrator/src/ai-intu-bridge.js
import { IntuIntegration } from './intu-integration.js';
import { MCPClient } from './mcp-client.js';
export class AIIntuBridge {
constructor(mcpServerUrl, intuIntegration) {
this.mcpClient = new MCPClient(mcpServerUrl);
this.intuIntegration = intuIntegration;
this.pendingActions = new Map();
}
async processAIRequest(userAddress, message, context = {}) {
try {
// Get user's INTU context
const userVaults = await this.intuIntegration.getUserVaults(userAddress);
// Enhanced context with INTU data
const enhancedContext = {
...context,
userAddress,
vaults: userVaults.map(v => ({
address: v.address,
name: v.name,
participants: v.participants,
thresholds: v.thresholds
})),
hasVaults: userVaults.length > 0
};
// Send to MCP server for AI processing
const aiResponse = await this.mcpClient.sendMessage({
sessionId: userAddress,
message,
context: enhancedContext
});
// Process any blockchain actions suggested by AI
if (aiResponse.actions) {
const processedActions = await this.processAIActions(
userAddress,
aiResponse.actions
);
aiResponse.processedActions = processedActions;
}
return aiResponse;
} catch (error) {
console.error('Error processing AI request:', error);
throw error;
}
}
async processAIActions(userAddress, actions) {
const processedActions = [];
for (const action of actions) {
try {
switch (action.type) {
case 'create_vault':
const vaultAction = await this.handleCreateVault(userAddress, action.params);
processedActions.push(vaultAction);
break;
case 'propose_transaction':
const txAction = await this.handleProposeTransaction(userAddress, action.params);
processedActions.push(txAction);
break;
case 'generate_persona':
const personaAction = await this.handleGeneratePersona(userAddress, action.params);
processedActions.push(personaAction);
break;
default:
console.warn(`Unknown action type: ${action.type}`);
}
} catch (error) {
processedActions.push({
type: action.type,
status: 'error',
error: error.message
});
}
}
return processedActions;
}
async handleCreateVault(userAddress, params) {
// Create pending action that requires user approval
const actionId = `vault_${Date.now()}`;
this.pendingActions.set(actionId, {
type: 'create_vault',
userAddress,
params,
status: 'pending_approval',
createdAt: new Date()
});
return {
type: 'create_vault',
actionId,
status: 'pending_approval',
params,
approvalRequired: true
};
}
async handleProposeTransaction(userAddress, params) {
const actionId = `tx_${Date.now()}`;
// Check if user has required vault
const userVaults = await this.intuIntegration.getUserVaults(userAddress);
const targetVault = userVaults.find(v => v.address === params.vaultAddress);
if (!targetVault) {
throw new Error('Vault not found or not accessible');
}
// Simulate transaction to check validity
const simulation = await this.simulateTransaction(params);
this.pendingActions.set(actionId, {
type: 'propose_transaction',
userAddress,
params,
simulation,
vault: targetVault,
status: 'pending_approval',
createdAt: new Date()
});
return {
type: 'propose_transaction',
actionId,
status: 'pending_approval',
params,
simulation,
vault: targetVault,
approvalRequired: true
};
}
async simulateTransaction(params) {
// Implement transaction simulation logic
return {
estimatedGas: '21000',
gasPrice: '20000000000',
estimatedCost: '0.00042',
success: true,
effects: [
`Send ${params.value} ETH to ${params.to}`,
`Estimated fee: 0.00042 ETH`
]
};
}
async approveAction(actionId, userApproval) {
const action = this.pendingActions.get(actionId);
if (!action) {
throw new Error('Action not found');
}
if (!userApproval.approved) {
action.status = 'rejected';
return { status: 'rejected', reason: userApproval.reason };
}
try {
switch (action.type) {
case 'create_vault':
const vault = await this.intuIntegration.createAIVault(
action.userAddress,
userApproval.signer,
action.params
);
action.status = 'completed';
action.result = vault;
return { status: 'completed', vault };
case 'propose_transaction':
// Execute transaction through INTU vault
const txResult = await this.executeVaultTransaction(
action.vault,
action.params,
userApproval.signer
);
action.status = 'completed';
action.result = txResult;
return { status: 'completed', transaction: txResult };
default:
throw new Error(`Unknown action type: ${action.type}`);
}
} catch (error) {
action.status = 'failed';
action.error = error.message;
throw error;
}
}
async executeVaultTransaction(vault, params, signer) {
// Implement INTU vault transaction execution
// This would integrate with INTU's transaction API
return {
hash: '0x...',
status: 'pending',
vault: vault.address,
value: params.value,
to: params.to
};
}
}
API Endpoints
Core Endpoints
// orchestrator/src/routes/ai.js
import express from 'express';
import { AIIntuBridge } from '../ai-intu-bridge.js';
const router = express.Router();
// Send message to AI with INTU context
router.post('/message', async (req, res) => {
try {
const { sessionId, message, context } = req.body;
const response = await aiBridge.processAIRequest(
sessionId,
message,
context
);
res.json({
success: true,
response: response.content,
actions: response.processedActions,
sessionId
});
} catch (error) {
res.status(500).json({
success: false,
error: error.message
});
}
});
// Approve AI-proposed action
router.post('/approve-action', async (req, res) => {
try {
const { actionId, approved, signer, reason } = req.body;
const result = await aiBridge.approveAction(actionId, {
approved,
signer,
reason
});
res.json({
success: true,
result
});
} catch (error) {
res.status(500).json({
success: false,
error: error.message
});
}
});
// Get user's INTU context
router.get('/user/:address/context', async (req, res) => {
try {
const { address } = req.params;
const vaults = await intuIntegration.getUserVaults(address);
res.json({
success: true,
context: {
address,
vaults: vaults.length,
hasVaults: vaults.length > 0,
vaultDetails: vaults.map(v => ({
address: v.address,
name: v.name,
participants: v.participants.length
}))
}
});
} catch (error) {
res.status(500).json({
success: false,
error: error.message
});
}
});
export default router;
Streaming Support
// orchestrator/src/routes/streaming.js
import express from 'express';
const router = express.Router();
// Streaming AI responses
router.get('/message/stream', async (req, res) => {
const { sessionId, message } = req.query;
res.writeHead(200, {
'Content-Type': 'text/event-stream',
'Cache-Control': 'no-cache',
'Connection': 'keep-alive',
'Access-Control-Allow-Origin': '*'
});
try {
// Stream AI response
const stream = await aiBridge.streamAIResponse(sessionId, message);
stream.on('data', (chunk) => {
res.write(`data: ${JSON.stringify({ content: chunk })}\n\n`);
});
stream.on('end', () => {
res.write(`data: ${JSON.stringify({ done: true })}\n\n`);
res.end();
});
stream.on('error', (error) => {
res.write(`data: ${JSON.stringify({ error: error.message })}\n\n`);
res.end();
});
} catch (error) {
res.write(`data: ${JSON.stringify({ error: error.message })}\n\n`);
res.end();
}
});
export default router;
Advanced Features
Session Management
// orchestrator/src/session-manager.js
export class SessionManager {
constructor() {
this.sessions = new Map();
this.sessionTimeout = 30 * 60 * 1000; // 30 minutes
}
createSession(userAddress, context = {}) {
const sessionId = userAddress; // Use address as session ID
const session = {
id: sessionId,
userAddress,
context,
messages: [],
createdAt: new Date(),
lastActivity: new Date(),
aiPersona: null,
preferences: {}
};
this.sessions.set(sessionId, session);
this.scheduleCleanup(sessionId);
return session;
}
getSession(sessionId) {
const session = this.sessions.get(sessionId);
if (session) {
session.lastActivity = new Date();
return session;
}
return null;
}
addMessage(sessionId, message) {
const session = this.getSession(sessionId);
if (session) {
session.messages.push({
id: Date.now(),
...message,
timestamp: new Date()
});
// Keep only last 50 messages for memory management
if (session.messages.length > 50) {
session.messages = session.messages.slice(-50);
}
}
}
updatePersona(sessionId, persona) {
const session = this.getSession(sessionId);
if (session) {
session.aiPersona = persona;
}
}
scheduleCleanup(sessionId) {
setTimeout(() => {
const session = this.sessions.get(sessionId);
if (session && Date.now() - session.lastActivity > this.sessionTimeout) {
this.sessions.delete(sessionId);
}
}, this.sessionTimeout);
}
}
Multi-LLM Provider Support
// orchestrator/src/llm-providers.js
export class LLMProviderManager {
constructor() {
this.providers = new Map();
this.defaultProvider = 'openai';
}
registerProvider(name, provider) {
this.providers.set(name, provider);
}
async route(message, context, preferences = {}) {
let providerName = preferences.provider || this.defaultProvider;
// Smart routing based on context
if (context.requiresImageGeneration) {
providerName = 'runpod';
} else if (context.localOnly) {
providerName = 'ollama';
} else if (context.complexReasoning) {
providerName = 'anthropic';
}
const provider = this.providers.get(providerName);
if (!provider) {
throw new Error(`Provider ${providerName} not available`);
}
return await provider.generate(message, context);
}
}
// Example provider implementations
export class OpenAIProvider {
constructor(apiKey, model = 'gpt-4') {
this.apiKey = apiKey;
this.model = model;
}
async generate(message, context) {
// OpenAI API implementation
const response = await fetch('https://api.openai.com/v1/chat/completions', {
method: 'POST',
headers: {
'Authorization': `Bearer ${this.apiKey}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
model: this.model,
messages: [
{ role: 'system', content: this.buildSystemPrompt(context) },
{ role: 'user', content: message }
]
})
});
const data = await response.json();
return data.choices[0].message.content;
}
buildSystemPrompt(context) {
let prompt = "You are an AI assistant for Web3 and INTU applications.";
if (context.hasVaults) {
prompt += ` The user has ${context.vaults.length} INTU vault(s) available.`;
} else {
prompt += " The user doesn't have any INTU vaults yet.";
}
if (context.aiPersona) {
prompt += ` You should embody the persona: ${context.aiPersona.name} - ${context.aiPersona.description}`;
}
return prompt;
}
}
Production Deployment
Docker Configuration
# Dockerfile
FROM node:20-alpine
WORKDIR /app
# Copy package files
COPY package*.json ./
RUN npm ci --only=production
# Copy source code
COPY . .
# Build the application
RUN npm run build
# Expose port
EXPOSE 3005
# Health check
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
CMD curl -f http://localhost:3005/health || exit 1
# Start the application
CMD ["npm", "start"]
Environment Configuration
# Production .env
NODE_ENV=production
PORT=3005
HOST=0.0.0.0
# MCP Server
MCP_SERVER_URL=http://mcp-server:3000
# AI Providers
OPENAI_API_KEY=prod_openai_key
ANTHROPIC_API_KEY=prod_anthropic_key
OLLAMA_HOST=http://ollama:11434
# INTU Configuration
INTU_NETWORK=arbitrum-one
INTU_RPC_URL=https://arb1.arbitrum.io/rpc
# Security
CORS_ORIGIN=https://your-app.com
RATE_LIMIT_WINDOW=900000
RATE_LIMIT_MAX=100
# Monitoring
LOG_LEVEL=info
SENTRY_DSN=your_sentry_dsn
Kubernetes Deployment
# k8s-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: intu-orchestrator
spec:
replicas: 3
selector:
matchLabels:
app: intu-orchestrator
template:
metadata:
labels:
app: intu-orchestrator
spec:
containers:
- name: orchestrator
image: intu/orchestrator:latest
ports:
- containerPort: 3005
env:
- name: MCP_SERVER_URL
value: "http://mcp-server:3000"
- name: OPENAI_API_KEY
valueFrom:
secretKeyRef:
name: intu-secrets
key: openai-api-key
livenessProbe:
httpGet:
path: /health
port: 3005
initialDelaySeconds: 30
periodSeconds: 10
readinessProbe:
httpGet:
path: /health
port: 3005
initialDelaySeconds: 5
periodSeconds: 5
---
apiVersion: v1
kind: Service
metadata:
name: orchestrator-service
spec:
selector:
app: intu-orchestrator
ports:
- port: 3005
targetPort: 3005
type: LoadBalancer
Security Considerations
Action Approval Workflow
// orchestrator/src/security/approval-workflow.js
export class ApprovalWorkflow {
constructor() {
this.riskLevels = {
low: { maxValue: '0.01', autoApprove: false },
medium: { maxValue: '1.0', autoApprove: false },
high: { maxValue: 'unlimited', autoApprove: false }
};
}
async assessRisk(action, vault) {
const assessment = {
level: 'medium',
factors: [],
requiresApproval: true,
requiredApprovals: 1
};
// Check transaction value
if (action.type === 'propose_transaction') {
const value = parseFloat(action.params.value || '0');
if (value > 1.0) {
assessment.level = 'high';
assessment.factors.push('High value transaction');
}
}
// Check vault thresholds
if (vault.thresholds.txThreshold > 1) {
assessment.requiredApprovals = vault.thresholds.txThreshold;
assessment.factors.push('Multi-signature vault');
}
// Check action frequency
const recentActions = await this.getRecentActions(vault.address);
if (recentActions.length > 10) {
assessment.level = 'high';
assessment.factors.push('High activity frequency');
}
return assessment;
}
async requiresHumanApproval(action, vault, userContext) {
const risk = await this.assessRisk(action, vault);
// Always require approval for high-risk actions
if (risk.level === 'high') return true;
// Check user preferences
if (userContext.alwaysApprove) return true;
// Check if AI persona has permission for this action type
if (userContext.aiPersona?.permissions?.includes(action.type)) {
return false; // AI has permission
}
return true; // Default to requiring approval
}
}
Rate Limiting
// orchestrator/src/middleware/rate-limit.js
import rateLimit from 'express-rate-limit';
export const createRateLimiter = (windowMs = 15 * 60 * 1000, max = 100) => {
return rateLimit({
windowMs,
max,
message: {
error: 'Too many requests from this IP, please try again later.'
},
standardHeaders: true,
legacyHeaders: false,
keyGenerator: (req) => {
// Use user address if available, otherwise IP
return req.body?.sessionId || req.ip;
}
});
};
Monitoring & Observability
Health Checks
// orchestrator/src/health.js
export class HealthChecker {
constructor(dependencies) {
this.dependencies = dependencies;
}
async checkHealth() {
const checks = await Promise.allSettled([
this.checkMCPConnection(),
this.checkAIProviders(),
this.checkMemoryUsage(),
this.checkActiveConnections()
]);
const health = {
status: 'healthy',
timestamp: new Date().toISOString(),
checks: {},
uptime: process.uptime()
};
checks.forEach((result, index) => {
const checkNames = ['mcp', 'ai_providers', 'memory', 'connections'];
const checkName = checkNames[index];
if (result.status === 'fulfilled') {
health.checks[checkName] = result.value;
} else {
health.checks[checkName] = {
status: 'unhealthy',
error: result.reason.message
};
health.status = 'degraded';
}
});
return health;
}
async checkMCPConnection() {
try {
const response = await fetch(`${process.env.MCP_SERVER_URL}/health`);
return {
status: response.ok ? 'healthy' : 'unhealthy',
responseTime: Date.now() - start
};
} catch (error) {
return {
status: 'unhealthy',
error: error.message
};
}
}
}
Next Steps
- Integrate Frontend - Connect your UI to the orchestrator
Ready to orchestrate intelligent Web3 experiences with INTU.