Skip to content

Architecture Overview

Komand is built on .NET 10 and Microsoft Orleans 9 — a virtual actor framework that provides fault-tolerant, distributed state management with single-threaded execution guarantees per grain. This architecture was chosen over Node.js (used by open-source agent frameworks) because grains provide natural primitives for session isolation, state persistence, concurrent agent management, and fault tolerance.

External Channels (Telegram, Slack, Discord, WhatsApp, Teams, WebChat, Signal, SMS)
│ webhooks / WebSocket
┌─────────────────────────────────────────────────────┐
│ ASP.NET Core Gateway (port 5000) │
│ CorrelationId · ApiResponse<T> · Input Validation │
│ CORS · SignalR Hub · Health Checks │
│ /api/webchat /api/agents /api/sessions │
│ /api/skills /health Swagger (dev only) │
│ ← SPA serves React frontend from wwwroot → │
└────────────────────────┬────────────────────────────┘
│ Orleans Client
┌────────────────────────▼────────────────────────────┐
│ Orleans Silo (ports 11111 / 30000) │
│ AgentGrain SessionGrain SkillRegistryGrain │
│ ToolGrain CronGrain │
│ (all persist state to PostgreSQL) │
└────────────────────────┬────────────────────────────┘
│ ADO.NET / Npgsql
┌────────────────────────▼────────────────────────────┐
│ PostgreSQL 17 │
│ Orleans clustering · Grain storage · Reminders │
│ Audit log │
└─────────────────────────────────────────────────────┘
│ structured logs
Seq (port 5341)
ComponentTechnologyRationale
Runtime.NET 10Performance, strong typing, mature ecosystem
Actor frameworkMicrosoft Orleans 9.1Virtual actors, automatic state management, clustering
DatabasePostgreSQL 17Reliable, open-source, rich extension ecosystem
API layerASP.NET CoreFirst-class Orleans integration, middleware pipeline
FrontendReact 19 + Vite 7 + TypeScriptComponent model, ecosystem, developer productivity
State managementZustand + TanStack QueryLightweight client state, server state caching
StylingTailwindCSS 4Utility-first, consistent design system
Real-timeSignalRBi-directional WebSocket communication
ObservabilitySerilog + Seq + OpenTelemetryStructured logging, distributed tracing
Container runtimeDocker ComposeSimple local development, production-ready

The server repository is organized as a .NET solution with six backend projects and a React frontend:

komand-server/
├── backend/
│ ├── Komand.Shared/ # Domain models, DTOs, enums, configuration
│ ├── Komand.Grains.Interfaces/ # Orleans grain contracts (interfaces only)
│ ├── Komand.Grains/ # Grain implementations
│ ├── Komand.Silo/ # Orleans silo host (console app)
│ ├── Komand.Gateway/ # ASP.NET Core API + SPA host
│ │ ├── Middleware/ # CorrelationId, CORS
│ │ ├── Validation/ # Endpoint input validation
│ │ └── Hubs/ # SignalR hubs
│ └── Komand.Tests/ # xUnit + FluentAssertions + Orleans TestingHost
├── frontend/
│ └── src/
│ ├── features/ # Page-level components
│ ├── components/ # Reusable UI (AppShell, ChatInput, etc.)
│ ├── api/ # API client (fetchApi wrapper)
│ ├── stores/ # Zustand stores (auth, chat, UI)
│ ├── hooks/ # Custom React hooks
│ ├── lib/ # Utilities (SignalR, formatting)
│ └── types/ # TypeScript interfaces
└── docker/
├── docker-compose.yml # Full stack definition
├── Dockerfile.silo # Orleans silo container
├── Dockerfile.gateway # Gateway + embedded frontend
└── init-db/ # PostgreSQL schema initialization
Komand.Shared (no dependencies — models, DTOs, config)
Komand.Grains.Interfaces (grain contracts only)
Komand.Grains (implementations + Orleans SDK)
Komand.Silo ←── Komand.Gateway (Orleans client)

Komand.Shared has no Orleans dependency, making it safe to reference from any project. Grain interfaces are separated from implementations so the Gateway only needs the contracts, not the full Orleans server SDK.

All API endpoints return a consistent ApiResponse<T> envelope:

{
"success": true,
"data": { ... },
"error": null,
"meta": {
"total": 50,
"page": 1,
"pageSize": 20
}
}

Error responses follow the same shape:

{
"success": false,
"data": null,
"error": "Agent 'unknown' not found"
}

The Gateway validates all input at the API boundary using EndpointValidation helpers:

  • ValidateRequired(value, paramName, maxLength) — rejects null, empty, or oversized strings
  • ValidateOptional(value, paramName, maxLength) — allows null, validates if present
  • ValidateRange(value, paramName, min, max) — numeric range checking
  • Route parameters have maxlength constraints

All grain calls from the Gateway use .WaitAsync(TimeSpan.FromSeconds(30)). If a grain doesn’t respond, the API returns 504 Gateway Timeout.

EnvironmentAllowed Origins
Developmentlocalhost only
ProductionConfigurable via Cors:AllowedOrigins in appsettings

Allowed methods: GET, POST, PUT, DELETE, OPTIONS. Required headers: Content-Type, Authorization, X-Request-Id. Preflight cache: 10 minutes.

Every request flows through CorrelationIdMiddleware that generates or propagates the X-Request-Id header into the Serilog LogContext. This enables end-to-end tracing from the API gateway through grain calls to the database.

  • Serilog with structured logging (template-based, not string interpolation)
  • Seq for search and analysis (UI on port 5341, ingestion on 5342)
  • Default level: Information; Microsoft/Orleans/System overridden to Warning
  • OpenTelemetry for distributed tracing across silo boundaries in production

Every significant action is recorded as an AuditLogEntry with a typed action enum:

ActionWhen
MessageReceivedInbound message arrives
MessageSentOutbound response sent
ToolExecutionStartedSkill execution begins
ToolExecutionCompletedSkill execution succeeds
ToolExecutionFailedSkill execution fails
SkillInstalledSkill added to agent
SkillUninstalledSkill removed from agent
AgentConfiguredAgent config changed
SessionCreatedNew session started
SessionEndedSession closed
PromptInjectionDetectedSuspicious input flagged
PermissionDeniedInsufficient permissions

Each entry captures: actor ID, agent ID, session ID, skill ID, details, and timestamp.

The Docker Compose stack runs four services:

ServicePortResource LimitsHealth Check
PostgreSQL 175432512MB / 1 CPUpg_isready
Seq5341 (UI), 5342 (ingest)256MB / 0.5 CPUHTTP endpoint
Orleans Silo11111, 300001GB / 2 CPUsTCP socket 11111
API Gateway5000512MB / 1 CPUcurl /health

Services start in dependency order: PostgreSQL → Silo → Gateway. Each waits for its dependency’s health check before starting.

  • Non-root user (komand) in all application containers
  • Resource limits (CPU and memory caps) on every service
  • Secrets managed via .env file (gitignored)
  • Multi-stage Docker builds (SDK → runtime only)
  • The Gateway Dockerfile builds the React frontend and embeds it in wwwroot/
ModeClusteringGrain StorageReminders
DevelopmentLocalhostIn-memoryIn-memory
ProductionPostgreSQL ADO.NETPostgreSQL ADO.NETPostgreSQL ADO.NET

The environment is controlled by DOTNET_ENVIRONMENT / ASPNETCORE_ENVIRONMENT. In development, no external services are required — everything runs in-process with in-memory state.

VariablePurpose
DOTNET_ENVIRONMENTDevelopment or Production
ConnectionStrings__OrleansPostgreSQL connection string
SEQ_URLSeq ingestion endpoint
Cors__AllowedOriginsComma-separated allowed origins
VITE_API_URLFrontend API base URL
PackageVersionPurpose
Microsoft.Orleans.Server9.1.2Silo hosting
Microsoft.Orleans.Client9.1.2Gateway → silo communication
Npgsql9.0.3PostgreSQL driver
Serilog9.0.0Structured logging
OpenTelemetry1.12.0Distributed tracing
xUnit2.9.3Test framework
FluentAssertions8.0.1Test assertions