|
|
A modern and flexible multi-agent, intent-driven conversational AI system |
Morgana is a modern conversational AI system designed to handle complex scenarios through a sophisticated multi-agent intent-driven architecture. Built on cutting-edge .NET 10 and leveraging the actor model via Akka.NET, Morgana orchestrates specialized AI agents that collaborate to understand, classify, and resolve customer inquiries with precision and context awareness.
The system is powered by Microsoft.Agents.AI framework, enabling seamless integration with Large Language Models (LLMs) while maintaining strict governance through guard rails and policy enforcement.
Traditional chatbot systems often struggle with complexity—they either become monolithic and unmaintainable, or they lack the contextual awareness needed for nuanced customer interactions. Morgana addresses these challenges through:
- Agent Specialization: Each agent has a single, well-defined responsibility with access to specific tools
- Actor-Based Concurrency: Akka.NET provides fault tolerance, message-driven architecture, and natural scalability
- Intelligent Routing: Requests are classified and routed to the most appropriate specialist agent
- Policy Enforcement: A dedicated guard actor ensures all interactions comply with business rules and brand guidelines
- Declarative Configuration: Prompts and agent behaviors are externalized as first-class project artifacts
- Automatic Discovery: Agents self-register through attributes, eliminating manual configuration
┌────────────────────────────────────────────────────────────────┐
│ User Request │
└──────────────────────────────┬─────────────────────────────────┘
│
▼
┌────────────────────────────────────────────────────────────────┐
│ ConversationManagerActor │
│ (Coordinates, routes and manages stateful conversational flow) │
└──────────────────────────────┬─────────────────────────────────┘
│
▼
┌────────────────────────────────────────────────────────────────┐
│ ConversationSupervisorActor │
│ (Orchestrates the entire multi-turn conversation lifecycle) │
└───┬───────────┬───────────────┬────────────────────────────────┘
│ │ │
▼ ▼ ▼
┌───────┐ ┌──────────┐ ┌───────────┐
│ Guard │ │Classifier│ │ Router │
│ Actor │ │ Actor │ │ Actor │
└───────┘ └──────────┘ └───────────┘
│ │ │
│ │ ▼
│ │ ┌───────────┐
│ │ │ Morgana │
│ │ │ Agent │
│ │ └───────────┘
│ │ └──────────────┬────────────┬─...fully extensible intent-based agents
│ │ │ │ │
│ │ ▼ ▼ ▼
│ │ ┌──────────┐ ┌───────────┐ ┌─────────────────┐ * Built-in example agents
│ │ │ Billing* │ │ Contract* │ │Troubleshooting* │
│ │ │ Agent │ │ Agent │ │ Agent │
│ │ │ │ │ │ │ │
│ │ └──────────┘ └───────────┘ └─────────────────┘
│ │ │ │ │
│ │ │ │ │
└─────┬─────┘ └───────────────┬────────────┘
│ │
▼ ▼
┌─────────────────────────────────────────────────┐
│ ILLMService │
│ (Guardrail, Intent Classification, Tooling) │
└─────────────────────────────────────────────────┘
Coordinates and owns the lifecycle of a single user conversation session.
Responsibilities:
- Creates and supervises the
ConversationSupervisorActorfor the associated conversation - Acts as the stable entry point for all user messages belonging to one conversation ID
- Forwards user messages to the supervisor and returns structured responses via SignalR
- Ensures that each conversation maintains isolation and state continuity across requests
- Terminates or resets session actors upon explicit user request or system shutdown
Key Characteristics:
- One
ConversationManagerActorper conversation/session - Persists for the entire life of the conversation unless explicitly terminated
- Prevents accidental re-creation of supervisors or cross-session contamination
The orchestrator that manages the entire conversation lifecycle. It coordinates all child agents and ensures proper message flow.
Responsibilities:
- Receives incoming user messages
- Coordinates guard checks before processing
- Routes classification requests
- Delegates to appropriate coordinator agents
- Triggers conversation archival
- Handles error recovery and timeout scenarios
A policy enforcement actor that validates every user message against business rules, brand guidelines and safety policies.
Capabilities:
- Profanity and inappropriate content detection
- Spam and phishing attempt identification
- Brand tone compliance verification
- Real-time intervention when violations occur
- LLM-powered contextual policy checks
Configuration-Driven:
Guard behavior is defined in prompts.json with customizable violation terms and responses, making policy updates deployment-independent.
An intelligent classifier actor that analyzes user intent and determines the appropriate handling path.
Intent Recognition:
The classifier identifies specific intents configured in prompts.json. Example built-in intents include:
billing: Fetch invoices or payment historytroubleshooting: Diagnose connectivity or device issuescontract: Handle service contracts and cancellationsother: General service inquiries- ...
Metadata Enrichment: Each classification includes confidence scores and contextual metadata that downstream agents can use for decision-making.
Coordinator actor that resolves mappings of intents to engage specialized executor agents. This actor works as a smart router, dynamically resolving the appropriate Morgana agent based on classified intent.
Specialized agents with domain-specific knowledge and tool access. The system includes three built-in example agents, but the architecture is fully extensible to support any domain-specific intent.
BillingAgent (example)
- Tools:
GetInvoices(),GetInvoiceDetails() - Purpose: Handle all billing inquiries, payment verification, and invoice retrieval
- Prompt: Defined in
prompts.jsonunder ID "Billing"
TroubleshootingAgent (example)
- Tools:
RunDiagnostics(),GetTroubleshootingGuide() - Purpose: Diagnose connectivity issues, provide step-by-step troubleshooting
- Prompt: Defined in
prompts.jsonunder ID "Troubleshooting"
ContractAgent (example)
- Tools:
GetContractDetails(),InitiateCancellation() - Purpose: Handle contract modifications and termination requests
- Prompt: Defined in
prompts.jsonunder ID "Contract"
Adding Custom Agents: To add a new agent for your domain:
- Define the intent in
prompts.json(Classifier section) - Create a prompt configuration for the agent behavior
- Implement a new class inheriting from
MorganaAgent - Decorate with
[HandlesIntent("your_intent")] - Define tools and inject via
AgentAdapter
The AgentRegistryService automatically discovers and validates all agents at startup.
Morgana treats prompts as first-class project artifacts, not hardcoded strings. The IPromptResolverService provides a flexible, maintainable approach to prompt engineering.
IPromptResolverService
- Centralizes prompt retrieval and resolution
- Supports structured prompt definitions with metadata
- Enables versioning and A/B testing of prompts
- Validates prompt consistency across agents
ConfigurationPromptResolverService
- Loads prompts from embedded
prompts.jsonresource - Parses structured prompt definitions including:
- System instructions
- Tool definitions
- Additional properties (guard terms, error messages)
- Language and versioning metadata
Prompt Structure
{
"ID": "billing",
"Type": "INTENT",
"SubType": "AGENT",
"Content": "Agent personality and role description",
"Instructions": "Behavioral rules and token conventions",
"AdditionalProperties": [
{
"Tools": [
{
"Name": "GetInvoices",
"Description": "Retrieves user invoices",
"Parameters": [...]
}
]
}
]
}- Separation of Concerns: Prompt engineering decoupled from application logic
- Rapid Iteration: Update agent behavior without recompiling
- Consistency: Single source of truth for agent instructions
- Auditability: Version-controlled prompt evolution
- Localization Ready: Multi-language support built-in
Morgana uses declarative intent mapping through the [HandlesIntent] attribute, enabling automatic agent discovery and validation.
1. Declarative Intent Handling
[HandlesIntent("billing")]
public class BillingAgent : MorganaAgent
{
// Agent implementation
}2. Automatic Discovery
The AgentRegistryService scans the assembly at startup to find all classes:
- Inheriting from
MorganaAgent - Decorated with
[HandlesIntent]
3. Bidirectional Validation The system enforces consistency between:
- Intents declared in agent classes
- Intents configured in the Classifier prompt
At startup, it verifies:
- Every classifier intent has a registered agent
- Every registered agent has a corresponding classifier intent
4. Runtime Resolution
Type? agentType = agentRegistryService.GetAgentType("billing");
// Returns typeof(BillingAgent)The system throws explicit exceptions if:
- A classifier intent has no corresponding agent
- An agent declares an intent not configured for classification
- Tool definitions mismatch between prompts and implementations
This fail-fast approach ensures configuration errors are caught at startup, not during customer interactions.
- .NET 10: Leveraging the latest C# features, performance improvements, and native AOT capabilities
- ASP.NET Core Web API: RESTful interface for client interactions
- Akka.NET 1.5: Actor-based concurrency model for resilient, distributed agent orchestration
- Microsoft.Extensions.AI: Unified abstraction over chat completions with
IChatClientinterface - Microsoft.Agents.AI: Declarative agent definition with built-in tool calling support
- Azure OpenAI Service: GPT-4 powered language understanding and generation
Tools are defined as C# delegates mapped to tool definitions from prompts. The ToolAdapter provides runtime validation and dynamic function creation:
// Define tool implementation
public async Task<string> GetInvoices(string userId, int count = 3)
{
// Implementation
}
// Register with adapter
toolAdapter.AddTool("GetInvoices", billingTool.GetInvoices, toolDefinition);
// Create AIFunction for agent
AIFunction function = toolAdapter.CreateFunction("GetInvoices");Validation:
- Parameter count and names must match between implementation and definition
- Required vs. optional parameters are validated
- Type mismatches are caught at registration time
The Agent Framework automatically:
- Exposes tool schemas to the LLM
- Handles parameter validation and type conversion
- Invokes the appropriate method
- Returns results to the LLM for natural language synthesis
Each MorganaAgent maintains an in-memory conversation history for multi-turn context:
protected readonly List<(string role, string text)> history = [];Interaction Token: #INT#
Agents use a special token #INT# to signal when additional user input is required:
- Present: Conversation continues, awaiting more information
- Absent: Conversation completed, user may close or start new request
This enables natural back-and-forth dialogues within a single intent execution.
Future Enhancement: The in-memory history is a temporary solution until the Agents Framework provides native memory support.
**Built with ❤️ using .NET 10, Akka.NET and Microsoft.Agents.AI