Implementing a PMCR-O Chatbot: From Text to Autonomous Conversations
Traditional chatbots react to user input. PMCR-O chatbots evolve through conversation. They maintain cognitive trails, reflect on interactions, and improve their responses over time.
This tutorial shows you how to build a production-ready chatbot that uses the full PMCR-O cycle: Plan responses, Make them contextually relevant, Check for accuracy, Reflect on effectiveness, and Orchestrate multi-turn conversations.
🤖 Actual PMCR-O Chatbot Architecture
SignalR Hub
REST Gateway
Planner/Maker/Checker
RAG retrieval
Based on your actual PMCR-O source architecture
Prerequisites
Complete the PMCR-O Quickstart tutorial first, or ensure you have:
- .NET 10 SDK with Aspire
- Ollama running with
qwen2.5-coder:7bandnomic-embed-textmodels - PostgreSQL with pgvector extension
- SignalR for real-time communication
Step 1: Create the Chatbot Service
Step 1 of 5Create a new ASP.NET Core Web API project for the chatbot:
# Add to your PMCR-O solution
dotnet new webapi -n PmcroQuickstart.ChatbotService
dotnet sln add PmcroQuickstart.ChatbotService/PmcroQuickstart.ChatbotService.csproj
# Add SignalR for real-time chat
dotnet add PmcroQuickstart.ChatbotService package Microsoft.AspNetCore.SignalR
Step 2: Build the Chat Hub (Real-time Communication)
Step 2 of 5Create a SignalR hub that calls your Orchestration API. Add ChatHub.cs:
using Microsoft.AspNetCore.SignalR;
using PmcroQuickstart.Shared.Grpc;
namespace PmcroQuickstart.ChatbotService;
public class ChatHub : Hub
{
private readonly ILogger<ChatHub> _logger;
private readonly IHttpClientFactory _httpClientFactory;
public ChatHub(ILogger<ChatHub> logger, IHttpClientFactory httpClientFactory)
{
_logger = logger;
_httpClientFactory = httpClientFactory;
}
public async Task SendMessage(string message, string conversationId)
{
var connectionId = Context.ConnectionId;
try
{
// Send typing indicator
await Clients.Caller.SendAsync("TypingIndicator", true);
// Process through PMCR-O agents
var response = await ProcessWithPmcroAgents(message, conversationId);
// Send response
await Clients.Caller.SendAsync("ReceiveMessage", new
{
content = response.Content,
agent = response.AgentRole,
timestamp = DateTime.UtcNow,
conversationId = conversationId
});
// Stop typing indicator
await Clients.Caller.SendAsync("TypingIndicator", false);
}
catch (Exception ex)
{
_logger.LogError(ex, "Chat processing failed");
await Clients.Caller.SendAsync("ReceiveMessage", new
{
content = "I apologize, but I'm experiencing technical difficulties. Please try again.",
agent = "system",
timestamp = DateTime.UtcNow,
conversationId = conversationId
});
await Clients.Caller.SendAsync("TypingIndicator", false);
}
}
private async Task<ChatResponse> ProcessWithPmcroAgents(string message, string conversationId)
{
var httpClient = _httpClientFactory.CreateClient();
// Call your Orchestration API (REST gateway to gRPC services)
// Based on your actual architecture, this calls the WorkflowController
var workflowRequest = new
{
intent = message,
conversationId = conversationId,
context = new
{
maxTokens = 1000,
temperature = 0.7,
includeKnowledge = true
}
};
var response = await httpClient.PostAsJsonAsync(
"http://localhost:5201/api/workflow/execute", // Your Orchestration API endpoint
workflowRequest);
var workflowResult = await response.Content.ReadFromJsonAsync<WorkflowResponse>();
return new ChatResponse
{
Content = workflowResult?.Content ?? "I'm processing your request...",
AgentRole = workflowResult?.AgentRole ?? "orchestrator",
ConversationId = conversationId
};
}
public override async Task OnConnectedAsync()
{
_logger.LogInformation("Client connected: {ConnectionId}", Context.ConnectionId);
await base.OnConnectedAsync();
}
public override async Task OnDisconnectedAsync(Exception? exception)
{
_logger.LogInformation("Client disconnected: {ConnectionId}", Context.ConnectionId);
await base.OnDisconnectedAsync(exception);
}
}
// Data models
public record ChatResponse(string Content, string AgentRole, string ConversationId);
public record WorkflowResponse(string Content, string AgentRole, string ConversationId, bool Success);
Step 3: Configure the Chatbot Service
Step 3 of 5Update Program.cs to configure SignalR and CORS:
var builder = WebApplication.CreateBuilder(args);
// Add service defaults & Aspire components
builder.AddServiceDefaults();
// Add SignalR
builder.Services.AddSignalR();
// Add CORS for web client
builder.Services.AddCors(options =>
{
options.AddPolicy("AllowAll", policy =>
{
policy.AllowAnyOrigin()
.AllowAnyMethod()
.AllowAnyHeader();
});
});
// Add HTTP client for inter-service communication
builder.Services.AddHttpClient();
var app = builder.Build();
// Configure the HTTP request pipeline
if (app.Environment.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseCors("AllowAll");
app.MapDefaultEndpoints();
// Map SignalR hub
app.MapHub<ChatHub>("/chatHub");
// Basic health check
app.MapGet("/health", () => "Chatbot service is healthy");
app.Run();
Step 4: Create the Chat Interface
Step 4 of 5Create a simple HTML chat interface. Add chat.html to your wwwroot:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>PMCR-O Chatbot</title>
<script src="https://cdnjs.cloudflare.com/ajax/libs/microsoft-signalr/8.0.0/signalr.min.js"></script>
<style>
body { font-family: Arial, sans-serif; margin: 0; padding: 20px; background: #0a0a0a; color: #ffffff; }
.chat-container { max-width: 800px; margin: 0 auto; }
.chat-messages { height: 400px; overflow-y: auto; border: 1px solid #2c2c2c; padding: 10px; margin-bottom: 10px; background: #1a1a1a; }
.message { margin-bottom: 10px; padding: 8px; border-radius: 5px; }
.user { background: #d4a574; color: #0a0a0a; margin-left: 100px; }
.agent { background: #2c2c2c; margin-right: 100px; }
.typing { font-style: italic; color: #888; }
.input-container { display: flex; gap: 10px; }
#messageInput { flex: 1; padding: 10px; border: 1px solid #2c2c2c; background: #1a1a1a; color: #ffffff; }
button { padding: 10px 20px; background: #d4a574; color: #0a0a0a; border: none; cursor: pointer; }
button:hover { background: #e6c89f; }
</style>
</head>
<body>
<div class="chat-container">
<h1>🤖 PMCR-O Chatbot</h1>
<div id="messages" class="chat-messages"></div>
<div class="input-container">
<input type="text" id="messageInput" placeholder="Ask me anything..." />
<button onclick="sendMessage()">Send</button>
</div>
</div>
<script>
const connection = new signalR.HubConnectionBuilder()
.withUrl("http://localhost:5005/chatHub")
.configureLogging(signalR.LogLevel.Information)
.build();
const messagesDiv = document.getElementById('messages');
const messageInput = document.getElementById('messageInput');
let conversationId = 'chat_' + Date.now();
connection.on("ReceiveMessage", (message) => {
addMessage(message.content, 'agent', message.agent);
});
connection.on("TypingIndicator", (isTyping) => {
const typingDiv = document.getElementById('typing');
if (isTyping) {
if (!typingDiv) {
const div = document.createElement('div');
div.id = 'typing';
div.className = 'message typing';
div.textContent = '🤖 PMCR-O is thinking...';
messagesDiv.appendChild(div);
messagesDiv.scrollTop = messagesDiv.scrollHeight;
}
} else {
if (typingDiv) {
typingDiv.remove();
}
}
});
async function startConnection() {
try {
await connection.start();
addMessage("Hello! I'm a PMCR-O autonomous agent. How can I help you today?", 'agent', 'system');
} catch (err) {
console.error(err);
addMessage("Connection failed. Please check if the chatbot service is running.", 'agent', 'system');
}
}
function sendMessage() {
const message = messageInput.value.trim();
if (!message) return;
addMessage(message, 'user');
connection.invoke("SendMessage", message, conversationId).catch(err => {
console.error(err);
addMessage("Failed to send message. Please try again.", 'agent', 'system');
});
messageInput.value = '';
}
function addMessage(content, type, agent = '') {
const div = document.createElement('div');
div.className = `message ${type}`;
div.textContent = agent ? `[${agent}] ${content}` : content;
messagesDiv.appendChild(div);
messagesDiv.scrollTop = messagesDiv.scrollHeight;
}
messageInput.addEventListener('keypress', (e) => {
if (e.key === 'Enter') {
sendMessage();
}
});
startConnection();
</script>
</body>
</html>
Step 5: Update AppHost Configuration
Step 5 of 5Add the chatbot service to your existing PMCR-O AppHost. Based on your source architecture:
// In AppHost/Program.cs (add to your existing configuration)
var chatbotService = builder.AddProject<Projects.ProjectName_ChatbotService>("chatbot-service")
.WithReference(orchestrationApi) // Connect to your Orchestration API
.WaitFor(orchestrationApi);
Your chatbot service now integrates with your actual PMCR-O architecture:
- Chatbot Service → Orchestration API (REST calls)
- Orchestration API → gRPC Services (Planner, Maker, etc.)
- gRPC Services → Knowledge Vault (RAG queries)
🚀 Test Your Autonomous Chatbot
🎉 What You've Built
Your PMCR-O chatbot is now a learning system that:
- Queries the knowledge vault for relevant context
- Plans responses using structured reasoning
- Makes contextually appropriate replies
- Checks responses for accuracy
- Reflects on conversation effectiveness
- Maintains cognitive trails for future improvement
Advanced Features to Add
Once your basic chatbot works, extend it with:
- Multi-agent conversations - Let agents debate responses
- Memory consolidation - Periodically summarize conversation history
- Tool integration - Allow agents to call external APIs
- Personality evolution - Agents adapt based on user feedback
- Multi-modal input - Handle images, documents, voice
Production Deployment
For production use:
- Authentication - Add user sessions and API keys
- Rate limiting - Prevent abuse and manage costs
- Monitoring - Track conversation quality and agent performance
- Backup/Recovery - Ensure cognitive trails persist
- Scaling - Deploy agents across multiple instances
🔗 Related Resources:
- PMCR-O Quickstart - Build the foundation agents
- Structured Output - Reliable agent responses
- Prompt Library - Copy production-ready BIP prompts
- PMCR-O Codex - Technical deep-dive