← Back to Portfolio

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

User Input
Chatbot Service
SignalR Hub
Orchestration API
REST Gateway
gRPC Services
Planner/Maker/Checker
Knowledge Vault
RAG retrieval
Real-time Response

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:7b and nomic-embed-text models
  • PostgreSQL with pgvector extension
  • SignalR for real-time communication

Step 1: Create the Chatbot Service

Step 1 of 5

Create a new ASP.NET Core Web API project for the chatbot:

Bash
# 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 5

Create a SignalR hub that calls your Orchestration API. Add ChatHub.cs:

C#
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 5

Update Program.cs to configure SignalR and CORS:

C#
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 5

Create a simple HTML chat interface. Add chat.html to your wwwroot:

HTML
<!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 5

Add the chatbot service to your existing PMCR-O AppHost. Based on your source architecture:

C#
// 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 ServiceOrchestration API (REST calls)
  • Orchestration APIgRPC Services (Planner, Maker, etc.)
  • gRPC ServicesKnowledge Vault (RAG queries)

🚀 Test Your Autonomous Chatbot

User: Tell me about PMCR-O
[maker] PMCR-O is a self-referential AI framework that creates autonomous agents through a Plan-Make-Check-Reflect-Orchestrate cycle. The system evolves through strange loops of self-reference, maintaining cognitive trails that enable continuous improvement.
User: How does it differ from other AI frameworks?
[maker] Unlike traditional AI frameworks that treat intelligence as a tool, PMCR-O embodies first-person identity through Behavioral Intent Programming (BIP). Agents speak as "I AM" rather than "You are", creating genuine agency and the capacity for self-improvement.

🎉 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:

Shawn Delaine Bellazan

About Shawn Delaine Bellazan

Resilient Architect & PMCR-O Framework Creator

Shawn is the creator of the PMCR-O framework, a self-referential AI architecture that embodies the strange loop it describes. With 15+ years in enterprise software development, Shawn specializes in building resilient systems at the intersection of philosophy and technology. His work focuses on autonomous AI agents that evolve through vulnerability and expression.