Demo solution based on agents, Agent-to-Agent (A2A) communication, and async messaging for IT support and purchasing workflows.
This system demonstrates a modern, agent-based approach to handling IT support tickets and purchasing requests. It combines human actors (employees, help desk staff, approvers) with intelligent agents (ticketing triage, purchasing policy enforcement, fulfillment) to create an efficient, scalable support and procurement system.
- Design Documentation - System architecture, components, and design decisions
- Ticketing System - Data model, APIs, and UI specifications
- Async Messaging Patterns - Event publishing and queue patterns
| Component | Technology |
|---|---|
| Runtime | .NET 10 |
| UI Apps | Blazor Web Apps (SSR + InteractiveServer) |
| Business Logic | CSLA .NET 10 |
| Database | SQL Server LocalDB (dev) / Azure SQL (prod) |
| Agent Hosting | Azure Functions (isolated worker) |
| Messaging | Azure Service Bus |
| AI Models | Azure OpenAI (GPT-4o) |
| Project | Type | Port (HTTPS) | Description |
|---|---|---|---|
Ticketing.Auth |
ASP.NET Core API | 7069 | Auth service (JWT tokens, JWKS, demo users, service accounts) |
Ticketing.Web |
Blazor Server + API | 7029 | Main app: Blazor UI, REST API, MCP server |
Ticketing.Chatbot |
Blazor Server | 7252 | AI chatbot UI that uses MCP tools |
Ticketing.TriageAgent |
Azure Functions | n/a | Automatically triages new tickets via Azure OpenAI |
Ticketing.Domain |
Class library | n/a | CSLA business objects and validation |
Ticketing.DataAccess |
Class library | n/a | EF Core implementation (SQL Server) |
Ticketing.DataAccess.Abstractions |
Class library | n/a | DAL interfaces and DTOs |
Ticketing.Messaging.Abstractions |
Class library | n/a | IEventPublisher, event envelope types |
Ticketing.Messaging.ServiceBus |
Class library | n/a | Azure Service Bus publisher implementation |
- .NET 10 SDK
- SQL Server LocalDB (included with Visual Studio or installable separately)
- Azure Functions Core Tools v4 (for running the TriageAgent locally)
- An Azure subscription with:
- Azure OpenAI resource with a
gpt-4odeployment - Azure Service Bus namespace (Standard tier or higher for topics)
- Azure OpenAI resource with a
git clone <repo-url>
cd agentic-demo
dotnet build src/Ticketing.slnxCreate these resources in your Azure Service Bus namespace:
| Resource | Name | Notes |
|---|---|---|
| Topic | tickets.events |
All ticket events are published here |
| Subscription | triage-agent-subscription |
Under the tickets.events topic |
| Subscription filter | SQL filter: Subject = 'ticket.created' |
So the triage agent only receives new-ticket events |
You can create these via the Azure portal or the Azure CLI:
# Replace with your Service Bus namespace
SB_NAMESPACE="your-namespace"
RESOURCE_GROUP="your-rg"
az servicebus topic create \
--namespace-name $SB_NAMESPACE \
--resource-group $RESOURCE_GROUP \
--name tickets.events
az servicebus topic subscription create \
--namespace-name $SB_NAMESPACE \
--resource-group $RESOURCE_GROUP \
--topic-name tickets.events \
--name triage-agent-subscription
# Remove the default "match all" rule and add the filter
az servicebus topic subscription rule delete \
--namespace-name $SB_NAMESPACE \
--resource-group $RESOURCE_GROUP \
--topic-name tickets.events \
--subscription-name triage-agent-subscription \
--name '$Default'
az servicebus topic subscription rule create \
--namespace-name $SB_NAMESPACE \
--resource-group $RESOURCE_GROUP \
--topic-name tickets.events \
--subscription-name triage-agent-subscription \
--name ticket-created-filter \
--filter-sql-expression "Subject = 'ticket.created'"Get the connection string for configuration:
az servicebus namespace authorization-rule keys list \
--namespace-name $SB_NAMESPACE \
--resource-group $RESOURCE_GROUP \
--name RootManageSharedAccessKey \
--query primaryConnectionString -o tsvEach project that needs secrets should use dotnet user-secrets to keep credentials out of source control.
cd src/Ticketing.Web
dotnet user-secrets init # only needed once
dotnet user-secrets set "ServiceBus:ConnectionString" "<your-service-bus-connection-string>"The database connection string and auth service URL have working defaults for local development in appsettings.json (LocalDB + https://localhost:7069).
The Chatbot project already has a UserSecretsId. Set the Azure OpenAI credentials:
cd src/Ticketing.Chatbot
dotnet user-secrets set "AzureOpenAI:Endpoint" "https://<resource>.openai.azure.com/"
dotnet user-secrets set "AzureOpenAI:ApiKey" "<your-api-key>"
dotnet user-secrets set "AzureOpenAI:DeploymentName" "gpt-4o"Azure Functions use local.settings.json for local development (this file is .gitignore-friendly). Edit src/Ticketing.TriageAgent/local.settings.json:
{
"IsEncrypted": false,
"Values": {
"FUNCTIONS_WORKER_RUNTIME": "dotnet-isolated",
"ServiceBusConnection": "<your-service-bus-connection-string>",
"ServiceBus:ConnectionString": "<your-service-bus-connection-string>",
"AuthService:Url": "https://localhost:7069",
"AuthService:ClientId": "triage-agent",
"AuthService:ClientSecret": "secret-for-triage",
"TicketingApi:BaseUrl": "https://localhost:7029",
"AzureOpenAI:Endpoint": "https://<resource>.openai.azure.com/",
"AzureOpenAI:ApiKey": "<your-api-key>",
"AzureOpenAI:DeploymentName": "gpt-4o"
}
}
ServiceBusConnectionis used by the Azure Functions Service Bus trigger binding.ServiceBus:ConnectionStringis used by theIEventPublisherfor publishing events. Both need the same value.
No secrets needed. Service accounts and auth settings are configured in appsettings.json with demo defaults:
| Service Account | Client ID | Client Secret | Roles |
|---|---|---|---|
| Triage Agent | triage-agent |
secret-for-triage |
Agent, HelpDesk |
| Fulfillment Agent | fulfillment-agent |
secret-for-fulfillment |
Agent, HelpDesk |
The services must be started in this order because of startup dependencies:
Step 1: Start the Auth service (other services depend on its JWKS endpoint)
dotnet run --project src/Ticketing.Auth --launch-profile httpsVerify: https://localhost:7069 should return a health-check JSON. The JWKS endpoint at https://localhost:7069/.well-known/jwks.json must be reachable before starting other services.
Step 2: Start the Web app (database auto-creates on first run)
dotnet run --project src/Ticketing.Web --launch-profile httpsOn first run, EF Core migrations run automatically and demo data is seeded. Verify:
- Blazor UI:
https://localhost:7029 - API docs (Scalar):
https://localhost:7029/scalar/v1 - MCP endpoint:
https://localhost:7029/mcp
Step 3 (optional): Start the Chatbot
dotnet run --project src/Ticketing.Chatbot --launch-profile httpsVerify: https://localhost:7252
Step 4 (optional): Start the Triage Agent
cd src/Ticketing.TriageAgent
func startOn startup, the agent will:
- Scan for any existing tickets with status
Newand triage them - Listen for
ticket.createdevents on Service Bus to triage new tickets in real-time
-
Get a token from the auth service:
curl -X POST https://localhost:7069/token \ -H "Content-Type: application/json" \ -d '{"email": "alice@example.com"}'
-
Create a ticket via the REST API:
curl -X POST https://localhost:7029/api/tickets \ -H "Content-Type: application/json" \ -H "Authorization: Bearer <token>" \ -d '{"title": "My laptop screen is cracked", "ticketType": "Support"}'
-
If the Triage Agent is running, it will automatically:
- Receive the
ticket.createdevent via Service Bus - Fetch the ticket details via REST API
- Analyze the ticket with Azure OpenAI
- Update the ticket: status ->
Triaged, assign queue, set priority/category, add triage notes
- Receive the
-
Verify triage by fetching the ticket again:
curl https://localhost:7029/api/tickets/<ticket-id> \ -H "Authorization: Bearer <token>"
# Add a new migration
dotnet ef migrations add <MigrationName> \
--project src/Ticketing.DataAccess \
--startup-project src/Ticketing.Web
# Apply migrations manually (auto-applied in Development mode)
dotnet ef database update \
--project src/Ticketing.DataAccess \
--startup-project src/Ticketing.Web
# Reset database (auto-recreated on next run)
dotnet ef database drop --force \
--project src/Ticketing.DataAccess \
--startup-project src/Ticketing.Web| Setting | Default | Description |
|---|---|---|
AuthSettings:Issuer |
https://auth.ticketing.local |
JWT issuer claim |
AuthSettings:Audience |
ticketing-api |
JWT audience claim |
AuthSettings:TokenLifetimeMinutes |
60 |
User token lifetime |
AuthSettings:ServiceAccountTokenLifetimeMinutes |
30 |
Service account token lifetime |
| Setting | Default | Description |
|---|---|---|
ConnectionStrings:TicketingDb |
LocalDB connection | SQL Server connection string |
JwtSettings:AuthServiceUrl |
https://localhost:7069 |
Auth service URL for JWKS |
JwtSettings:Issuer |
https://auth.ticketing.local |
Expected JWT issuer |
JwtSettings:Audience |
ticketing-api |
Expected JWT audience |
ServiceBus:ConnectionString |
(empty) | Azure Service Bus connection string |
| Setting | Default | Description |
|---|---|---|
ChatSettings:AuthServiceUrl |
https://localhost:7069 |
Auth service URL |
ChatSettings:McpEndpointUrl |
https://localhost:7029/mcp |
MCP server endpoint |
AzureOpenAI:Endpoint |
(none) | Azure OpenAI resource endpoint |
AzureOpenAI:ApiKey |
(none) | Azure OpenAI API key |
AzureOpenAI:DeploymentName |
gpt-4-turbo |
Chat model deployment name |
| Setting | Default | Description |
|---|---|---|
ServiceBusConnection |
(empty) | Service Bus connection (trigger binding) |
ServiceBus:ConnectionString |
(empty) | Service Bus connection (event publisher) |
AuthService:Url |
https://localhost:7069 |
Auth service URL |
AuthService:ClientId |
triage-agent |
Service account client ID |
AuthService:ClientSecret |
secret-for-triage |
Service account secret |
TicketingApi:BaseUrl |
https://localhost:7029 |
Web API base URL |
AzureOpenAI:Endpoint |
(none) | Azure OpenAI resource endpoint |
AzureOpenAI:ApiKey |
(none) | Azure OpenAI API key |
AzureOpenAI:DeploymentName |
gpt-4o |
Chat model deployment name |
(To be added in future phases)
See LICENSE file for details.