A cross-platform .NET 8 endpoint agent that runs as a Windows Service or Linux systemd service. It implements a custom Agent-to-Agent (A2A) protocol, registering with a central orchestrator and accepting task delegation for server management and security scanning capabilities.
- Architecture
- Capabilities
- Sequence Diagrams
- Slack Integration
- Incident Flow β Slack to Agent
- Configuration
- Deployment
- HTTPS / TLS
The solution contains two assemblies:
| Project | Role |
|---|---|
AIOpsAgent.Core |
Platform abstraction, A2A protocol models, JWT auth, HTTP client, capability handlers, security detectors |
AIOpsAgent |
ASP.NET Core minimal-API host, DI wiring, HTTP endpoints, lifecycle and task services |
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β AIOps Orchestrator β
β POST /api/a2a/register βββ agent registers on startup β
β POST /api/a2a/delegate βββΊ orchestrator sends tasks β
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β
A2A over HTTP
β
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β AIOps Platform Agent (this repo) β
β β
β ASP.NET Core Host β
β βββ /api/a2a/delegate β receive task β
β βββ /api/a2a/tasks/{id} β poll status β
β βββ /api/a2a/webhook β receive completion callback β
β βββ /health β liveness probe β
β βββ /ready β readiness probe β
β β
β Services β
β βββ AgentLifecycleService (startup register / deregister) β
β βββ TaskExecutorService (dispatch, track, timeout) β
β β
β Core Library β
β βββ IPlatformProvider β
β β βββ LinuxPlatformProvider (/proc, systemctl, df) β
β β βββ WindowsPlatformProvider (WMI, sc.exe, DriveInfo) β
β βββ ICapabilityHandler implementations β
β β βββ SystemInfoHandler β
β β βββ ListProcessesHandler β
β β βββ KillProcessHandler β
β β βββ ListServicesHandler β
β β βββ ManageServiceHandler β
β β βββ RunCommandHandler β
β βββ ISecurityDetector implementations β
β βββ LinuxAuditDetector (auth.log, audit.log) β
β βββ WindowsEventLogDetector (Security, System logs) β
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
| Capability string | What it does |
|---|---|
server.system_info |
OS info, CPU, memory, disk, network interfaces |
server.list_processes |
Running processes sorted by memory or CPU |
server.kill_process |
Terminate a process by PID |
server.list_services |
All systemd / Windows services with status |
server.manage_service |
Start, stop, or restart a named service |
server.run_command |
Run an allowlisted diagnostic command |
security.scan |
Scan OS logs for auth failures, brute force, privilege escalation |
sequenceDiagram
participant Svc as systemd / Windows SCM
participant Agent as AIOps Agent
participant Orch as Orchestrator
Svc->>Agent: Start service
Agent->>Agent: Bootstrap Serilog
Agent->>Agent: Validate AgentOptions (ApiKey, JwtSecret required)
Agent->>Agent: Detect platform (Linux / Windows)
Agent->>Agent: Start ASP.NET Core host
activate Agent
Agent->>Orch: POST /api/a2a/register\n{agentId, hostname, capabilities, apiKey}
alt Orchestrator available
Orch-->>Agent: 200 OK {orchestratorId}
Agent->>Agent: Log registration success
else Orchestrator unavailable
Orch-->>Agent: timeout / error
Agent->>Agent: Log warning, continue in standalone mode
end
Note over Agent: Agent is now ready to accept tasks
Agent-->>Svc: Service running (ACTIVE)
deactivate Agent
sequenceDiagram
participant Orch as Orchestrator
participant MW as AgentAuthMiddleware
participant Exec as TaskExecutorService
participant Handler as ICapabilityHandler
participant OS as OS / Platform
Orch->>+Agent: POST /api/a2a/delegate\nAuthorization: Bearer {JWT}\n{capability, params, mode: sync, timeoutMs}
Agent->>+MW: ValidateToken(jwt)
MW-->>-Agent: agentId (or 401)
Agent->>+Exec: ExecuteAsync(request, ct)
Exec->>Exec: Generate taskId, store Pending
Exec->>+Handler: ExecuteAsync(params, ct)
Handler->>+OS: platform.GetSystemInfoAsync() / etc.
OS-->>-Handler: result
Handler-->>-Exec: CapabilityResult
Exec->>Exec: Store Completed
Exec-->>-Agent: TaskResult {taskId, status: completed, output}
Agent-->>-Orch: 200 OK {taskId, status, output}
sequenceDiagram
participant Orch as Orchestrator
participant Agent as AIOps Agent
participant Exec as TaskExecutorService
participant Handler as ICapabilityHandler
Orch->>+Agent: POST /api/a2a/delegate\n{capability, mode: async, callbackUrl, timeoutMs}
Agent->>Agent: ValidateToken(jwt)
Agent->>+Exec: ExecuteAsync(request, ct)
Exec->>Exec: Generate taskId, store Pending
Exec->>Exec: Fire-and-forget RunAsync(taskId, handler, request)
Exec-->>-Agent: TaskResult {taskId, status: pending}
Agent-->>-Orch: 202 Accepted {taskId, status: pending}
Note over Exec,Handler: Background execution
activate Exec
Exec->>+Handler: ExecuteAsync(params, CancellationToken)
Handler-->>-Exec: CapabilityResult
Exec->>Exec: Store Completed
deactivate Exec
Note over Orch,Agent: Caller must poll for result\n(outbound webhook NOT yet implemented)
Orch->>+Agent: GET /api/a2a/tasks/{taskId}
Agent-->>-Orch: 200 OK {taskId, status: completed, output}
sequenceDiagram
participant Orch as Orchestrator
participant Agent as AIOps Agent
participant Scan as SecurityScanCapability
participant Linux as LinuxAuditDetector
participant Win as WindowsEventLogDetector
Orch->>+Agent: POST /api/a2a/delegate\n{capability: security.scan, params: {minSeverity}}
Agent->>+Scan: ExecuteAsync(params, ct)
loop For each registered ISecurityDetector
Scan->>+Linux: DetectAsync(ct)
Linux->>Linux: ReadAllLines(/var/log/auth.log)
Linux->>Linux: ReadAllLines(/var/log/audit/audit.log)
Linux->>Linux: Parse SSH failures, sudo, user creation
Linux->>Linux: Correlate brute-force (>= 5 failures)
Linux-->>-Scan: List<SecurityEvent>
Scan->>+Win: DetectAsync(ct)
Win->>Win: Query Windows Security Event Log\n(4624, 4625, 4648, 4672, 4720, 4740)
Win->>Win: Query Windows System Event Log\n(7040, 7045)
Win->>Win: Correlate brute-force
Win-->>-Scan: List<SecurityEvent>
end
Scan->>Scan: Filter by minSeverity
Scan->>Scan: Order by Timestamp desc
Scan-->>-Agent: SecurityScanResult {events, summary}
Agent-->>-Orch: 200 OK {output}
sequenceDiagram
participant Svc as systemd / SCM
participant Agent as AIOps Agent
participant Orch as Orchestrator
Svc->>Agent: SIGTERM / Stop command
Agent->>Agent: IHostedService.StopAsync triggered
Agent->>Orch: Best-effort deregister
Orch-->>Agent: Response (or timeout)
Agent->>Agent: Dispose HttpClient, flush logs
Agent-->>Svc: Service stopped
The AIOps Orchestrator exposes a Slack bot that teams use as the primary control plane. Users interact with the orchestrator directly from Slack; the orchestrator then delegates work to registered platform agents (this repo) over A2A.
User in Slack
β @mention or DM (e.g. "scan prod-server-01 for security issues")
βΌ
Slack Events API ββPOST /api/webhook/slackβββΊ AIOps Orchestrator
β
MessageHandler parses intent
β
TaskDelegator selects agent
(AgentRegistry capability match)
β
A2A POST /api/a2a/delegate βββΊ AIOps Platform Agent
β
ICapabilityHandler executes
β
A2A POST /api/a2a/webhook βββ result / webhook
β
Orchestrator formats response
β
Slack Web API chat.postMessage
βΌ
User receives answer in Slack
Commands can be prefixed with / or ! (Slack intercepts bare / commands, so ! is the Slack-friendly alias).
| Command | What it does |
|---|---|
!help |
Show all available commands |
!k8s pods [namespace] |
List pods |
!k8s fix [namespace] |
Auto-remediate CrashLoop / OOMKilled / Error pods |
!k8s scale <name> <n> [namespace] |
Scale a deployment |
!k8s logs <pod> [namespace] |
Tail pod logs |
!k8s nodes |
List cluster nodes |
!incident list |
List open incidents |
!alert list |
List recent alerts |
!approval list |
List pending human-approval requests |
approve <id> |
Approve a pending remediation action |
reject <id> |
Reject a pending remediation action |
@agent-name <task> |
Delegate a task directly to a named platform agent |
| Natural language | Any message not matching a command is handled by the AI router |
High-risk actions (node drain, service stop, process kill) require explicit Slack approval before the orchestrator executes them. The approval request is posted to the same Slack channel with a short ID:
π΄ Approval Required [HIGH]
Action: Stop service nginx on prod-server-01
Tool: `server.manage_service`
Parameters: `{"name": "nginx", "action": "stop"}`
Reply with approve a1b2c3d4 to proceed or reject a1b2c3d4 to cancel.
This request expires in 15 minutes.
Risk classification:
- LOW β execute immediately, notify after
- MEDIUM β post approval request, user must confirm
- HIGH β post approval request with explicit risk warning; all actions are audit-logged to PostgreSQL
See the full guide at docs/slack-setup.md in the orchestrator repo. Quick summary:
- Go to api.slack.com/apps β Create New App β From a manifest
- Paste
slack_manifest.ymlfrom the orchestrator repo (updaterequest_urlto your deployment URL) - Install to workspace, copy Bot User OAuth Token (
xoxb-...) and Signing Secret - Set in the orchestrator's
.env:SLACK_BOT_TOKEN=xoxb-your-token SLACK_SIGNING_SECRET=your-signing-secret
- Invite the bot to channels:
/invite @aiops-orchestrator
sequenceDiagram
actor User as Slack User
participant Slack as Slack
participant Orch as AIOps Orchestrator
participant Reg as AgentRegistry (Redis+PG)
participant Agent as AIOps Platform Agent
User->>Slack: "scan prod-server-01 for security issues"
Slack->>Orch: POST /api/webhook/slack\n{event: app_mention, text: ...}
Orch->>Orch: SlackAdapter.parse_message()
Orch->>Orch: MessageHandler detects capability:\nsecurity.scan
Orch->>Reg: find_agents_by_capability("security.scan")
Reg-->>Orch: [aiops-agent-prod-01, score=1.0]
Orch->>Agent: POST /api/a2a/delegate\nBearer JWT\n{capability: "security.scan", params: {min_severity: "Medium"}}
Agent->>Agent: LinuxAuditDetector + WindowsEventLogDetector run
Agent-->>Orch: 200 OK {taskId, status: completed, result: {...events}}
Orch->>Orch: Format security scan results
Orch->>Slack: chat.postMessage β summary of events
Slack-->>User: Security scan results posted in channel
sequenceDiagram
actor User as Slack User
participant Slack as Slack
participant Orch as AIOps Orchestrator
participant Approval as ApprovalManager (Redis)
participant Agent as AIOps Platform Agent
User->>Slack: "stop nginx on prod-server-01"
Slack->>Orch: POST /api/webhook/slack
Orch->>Orch: Detect intent: server.manage_service\nRisk level: HIGH
Orch->>Approval: request_approval(tool="server.manage_service",\nrisk=HIGH, ttl=15min)
Approval->>Slack: chat.postMessage\n"π΄ Approval Required β reply approve a1b2c3d4"
Slack-->>User: Approval request posted
Note over User,Slack: User reviews and decides
User->>Slack: "approve a1b2c3d4"
Slack->>Orch: POST /api/webhook/slack
Orch->>Approval: process_response("approve a1b2c3d4", user_id)
Approval->>Approval: Lookup in Redis, verify pending
Approval->>Agent: POST /api/a2a/delegate\n{capability: "server.manage_service",\nparams: {name: "nginx", action: "stop"}}
Agent->>Agent: ManageServiceHandler.ExecuteAsync()
Agent-->>Orch: 200 OK {status: completed}
Approval->>Approval: Write audit log to PostgreSQL
Orch->>Slack: chat.postMessage "β
nginx stopped on prod-server-01"
Slack-->>User: Confirmation posted
sequenceDiagram
participant Watch as Orchestrator WatchLoop
participant Orch as AIOps Orchestrator
participant Agent as AIOps Platform Agent
participant Slack as Slack
Note over Watch: Periodic health check cycle
Watch->>Agent: GET /health
Agent-->>Watch: 200 {status: online, activeTasks: 0}
Note over Watch: Anomaly detected via monitoring
Watch->>Orch: Alert: high CPU on prod-server-01
Orch->>Agent: POST /api/a2a/delegate\n{capability: "server.list_processes",\nparams: {sort_by: "cpu", limit: 5}}
Agent-->>Orch: Top 5 CPU processes
Orch->>Orch: RCA Engine: identify runaway process
Orch->>Slack: chat.postMessage\n"β οΈ Alert: prod-server-01 CPU 94%\nTop process: java PID 4521 (87%)\nApproval required to kill"
Note over Slack: On-call engineer responds
Slack-->>Orch: "approve <id>"
Orch->>Agent: POST /api/a2a/delegate\n{capability: "server.kill_process", params: {pid: 4521}}
Agent-->>Orch: {status: completed, killed: true}
Orch->>Slack: "β
PID 4521 killed. CPU normalising."
Configuration is layered in priority order:
appsettings.json
βββ appsettings.{ASPNETCORE_ENVIRONMENT}.json
βββ Environment variables (prefix: AIOPS_)
βββ Command-line args
| Key | Required | Default | Notes |
|---|---|---|---|
AgentId |
No | hostname | Unique identifier for this agent |
ApiKey |
Yes | β | Min 32 chars. Set via env var AIOPS_Agent__ApiKey |
JwtSecret |
Yes | β | Min 32 chars. Set via env var AIOPS_Agent__JwtSecret |
OrchestratorUrl |
No | "" |
Leave empty for standalone mode |
ListenUrl |
No | http://0.0.0.0:8090 |
HTTP listen address |
AllowedCapabilities |
No | ["*"] |
Allowlist of capability strings |
LookbackHours |
No | 1 |
Security scan lookback window |
HealthCheckIntervalSeconds |
No | 60 |
Heartbeat interval to orchestrator |
EnableJsonLogging |
No | false |
Enable JSON-format log output |
Set via /etc/aiops-agent/environment (mode 0600, loaded by systemd EnvironmentFile):
AIOPS_Agent__ApiKey=<min-32-char-random-string>
AIOPS_Agent__JwtSecret=<min-32-char-random-string>Set via System Environment Variables after installation:
[System.Environment]::SetEnvironmentVariable("AIOPS_Agent__ApiKey", "<key>", "Machine")
[System.Environment]::SetEnvironmentVariable("AIOPS_Agent__JwtSecret", "<secret>", "Machine")- .NET 8 Runtime (self-contained publish includes runtime β no separate install needed)
- Linux: systemd, audit group membership for
/var/log/audit/audit.logaccess - Windows: Administrator rights for service installation
# Build self-contained binary
make publish-linux-x64
# Run installer (requires root)
sudo bash installer/linux/install.sh
# Verify
systemctl status aiops-agent
curl http://localhost:5000/ready# Build MSI
make msi
# Silent install
.\installer\windows\silent-install.ps1 `
-MsiPath ".\publish\AIOpsAgent-1.0.0-win-x64.msi" `
-ApiKey "<key>" `
-JwtSecret "<secret>" `
-ListenPort 5000make build # Debug build
make test # Run unit tests
make publish-all # Publish all 4 RIDs (linux-x64, linux-arm64, win-x64, win-x86)
make msi # Build Windows MSI (requires WiX toolset)
make clean # Remove bin/obj/publishBy default the agent listens on plain HTTP. All A2A traffic β including the Bearer JWT β is sent in cleartext unless TLS is in place. Two supported approaches:
Place nginx, Caddy, or an IIS ARR proxy in front of the agent. The agent binds http://127.0.0.1:8090 (loopback only) and the proxy terminates TLS externally.
Linux (nginx example):
server {
listen 443 ssl;
server_name agent.example.internal;
ssl_certificate /etc/ssl/agent.crt;
ssl_certificate_key /etc/ssl/agent.key;
location / {
proxy_pass http://127.0.0.1:8090;
proxy_set_header Host $host;
}
}Change ListenUrl to http://127.0.0.1:8090 so the agent only accepts local connections:
"Agent": { "ListenUrl": "http://127.0.0.1:8090" }Configure Kestrel directly in appsettings.json. Requires a PFX certificate (self-signed or CA-issued):
"Kestrel": {
"Endpoints": {
"Https": {
"Url": "https://0.0.0.0:8443",
"Certificate": {
"Path": "/etc/aiops-agent/agent.pfx",
"Password": "<pfx-password>"
}
}
}
}Update Agent:ListenUrl to match so the agent registers its correct address with the orchestrator:
"Agent": { "ListenUrl": "https://<hostname>:8443" }The orchestrator and any caller must then use https:// URLs. The HttpClient in A2AClient inherits the system trust store; add your CA to the OS trust store if using a private CA.
MIT License β see LICENSE.
Copyright (c) 2026 AIOps Orchestrator Contributors