Skip to main content

Architecture

Akmatori runs as a small set of containers inside your network. An alert comes in as a webhook, an agent investigates it with the LLM you choose, and every tool call goes through one gateway that holds the credentials. This page shows the parts, how one incident moves through them, and how agents find their instructions and tools.

Scroll sideways to see the whole diagram.
Akmatori architecture Alerts arrive as webhooks through the proxy to the API server. The API server hands the incident to the agent worker over a WebSocket. The agent calls the LLM provider and makes tool calls through the MCP Gateway, which loads credentials from PostgreSQL and reaches your systems. The answer goes back to the chat channel. Akmatori, self-hostedDocker Compose, inside your networkwebhookWebSocket:task, skills,allowlistHTTPStoolscredentialsper callSocket Mode,bot APIsSignals inAlert webhooksAlertmanagerZZabbixPagerDutyGrafanaDatadogChat channelsSSlackTelegramDiscordYour teamBrowser and REST APIsign in with a JWTLLM providerAnthropicGeminiOpenRouterOllamaProxynginx, port 8080Web UIReactAPI serverGowebhooks, incidents,Slack, cron jobs,memory passAgent workerNodeLLM loop, execute_scriptno database accessMCP GatewayGoruns every toolcall and checksthe run allowlistholds secretsPostgreSQLincidents, runbooks, memory, settings and credentialsYour systemsReached by connectorsover SSH, SQL, HTTPS and MCPLinux hosts over SSHKubernetesPostgreSQL, ClickHouseVictoriaMetricsZabbix, GrafanaPagerDuty, Jira, NetBoxHHTTP connectorsMRemote MCP servers1234567
Only the proxy is published, on port 8080 by default. Credentials stay in PostgreSQL and are read by the MCP Gateway at call time; the agent worker never sees them. The numbers follow one incident, step by step, as described below.

How one incident moves through the system

  1. An alert arrives. Alertmanager, Zabbix, PagerDuty, Grafana or Datadog posts to /webhook/alert/{instance_uuid}. The proxy passes it to the API server, which checks the per-instance secret and normalizes the alert.
  2. The API server opens an incident. It either attaches the alert to an open incident or creates a new one, writes AGENTS.md for the run, and posts the alert to Slack, Telegram or Discord.
  3. The agent worker gets the task. Over a WebSocket, the API server sends the incident, the skills, the LLM settings and the run's tool allowlist.
  4. The agent investigates. It runs the LLM loop against your provider. The worker is the only container that calls the LLM.
  5. Tools run in the gateway. Each gateway_call goes to the MCP Gateway, which checks the allowlist, loads the connector's credentials from PostgreSQL, and calls your system over SSH, SQL, HTTPS or MCP.
  6. The answer goes back. Output streams to the API server, which saves the run and posts the root cause in the incident's chat thread.
  7. Memory is written after close. When an alert incident closes, a post-incident memory pass reads the runs and saves at most five graded, redacted memories for the next investigation.

Containers

The Docker Compose stack starts these services. Only the proxy publishes a port: ${HTTP_PORT:-8080}.

ServiceWhat it does
akmatori-proxynginx edge. Routes /api/, /auth/ and /webhook/ to the API server and everything else to the web UI. The only published port.
akmatori-frontendnginx that serves the React web UI.
akmatori-apiGo API server: REST with JWT auth, alert webhooks, Slack Socket Mode, Telegram and Discord, cron jobs, alert correlation and the post-incident memory pass. Writes AGENTS.md and SKILL.md for each run.
akmatori-agentNode agent worker: runs the LLM loop and execute_script. Receives tasks over a WebSocket from the API server and has no database credentials.
akmatori-mcp-gatewayGo gateway that runs every tool call: checks the run allowlist, reads credentials from PostgreSQL at call time, and proxies remote MCP servers.
akmatori-postgresPostgreSQL 16: incidents, runbooks, memory, settings, LLM keys and connector credentials.
akmatori-initOne-shot setup: creates data directories and the database password file, then exits.
Connector credentials, LLM keys, Slack tokens and remote MCP auth are all stored in PostgreSQL. The gateway reads tool credentials for each call, and the agent worker has no database credentials, so an agent never sees a password, token or key.

Instructions and tools

Agents read their instructions from files that Akmatori generates for each run. Tools are not assigned to skills: every enabled connector is available to an incident run, and the generated connected-tools skill lists them with their parameters and gateway_call(...) examples.

AGENTS.md

Incident-level instructions, written at the workspace root for each run.

  • Defines the incident manager role and the investigation workflow
  • Requires a runbook search, then a memory search, then a check of recent change events, before infrastructure tools
  • Includes the global memory manifest and lists the uploaded context files

SKILL.md

A skill's prompt in the open Agent Skills format.

  • YAML frontmatter and your own instructions
  • The memory manifest for the skill's scope, most recently updated or used first
  • A generated pointer: read connected-tools/SKILL.md before the first tool call

connected-tools/SKILL.md

A generated system skill. You cannot edit it.

  • Every enabled tool instance with its logical name, parameters and examples
  • The functions of HTTP connectors and remote MCP servers, read from the gateway registry
  • Tells the agent to pass logical_name when a tool type has more than one instance

Gateway tool registry

The tools that agents can call.

  • Built-in types: SSH, Zabbix, VictoriaMetrics, Catchpoint, PostgreSQL, Grafana, PagerDuty, ClickHouse, NetBox, Kubernetes and Jira
  • Credentialless namespaces: events, incidents, proposals, runbooks and memory
  • HTTP connectors add declarative API tools
  • Remote MCP servers are proxied under a prefix such as ext.github
connected-tools/SKILL.md (excerpt)
## Production PostgreSQL (logical_name: "prod-db", type: postgresql)

Usage (via gateway_call):

```python
gateway_call("postgresql.list_tables", {}, "prod-db")
gateway_call("postgresql.describe_table", {"table_name": "users"}, "prod-db")
```

For multi-step work, use `execute_script` with built-in `gateway_call()`.

Tool authorization

A skill does not restrict tools. The API server builds each run's allowlist from the kind of run and sends it with the task, and the gateway refuses every tool outside it. To keep a connector away from agents, disable its tool instance.

Run kindConnector toolsBuilt-in tools
Incident (alert, Slack, API request)All enabledevents, incidents, runbooks, memory
Proposal chatNoneincidents, proposals, runbooks, memory (memory writes refused)
Cron job, connector tools onAll enabledThe job's builtin_tools
Cron job, connector tools offNoneThe job's builtin_tools

Remote MCP servers

  • Streamable HTTP only, with bearer, API-key header or OAuth 2.1 auth. OAuth tokens are stored in PostgreSQL.
  • You can switch individual tools off. Tool schemas are refreshed every five minutes.
  • Outbound connections refuse loopback, link-local and cloud-metadata addresses, and redirects are not followed. Set MCP_BLOCK_PRIVATE_NETWORKS to refuse private ranges too.
  • A reload reconnects only the servers whose URL, prefix, auth or disabled tools changed.

Investigation guardrails

The incident manager prompt makes context recall a fixed first step. For every incident, the agent searches runbooks, then cross-incident memory, then recent change events, and only then calls infrastructure tools.

AGENTS.md (excerpt)
1. Understand the problem.
2. Search runbooks first:
   gateway_call("runbooks.search", {"query": "<the original alert text>"})
   then gateway_call("runbooks.get", {"id": <id>}) for the best match.
3. Search cross-incident memory next:
   gateway_call("memory.search", {"query": "<the same query>", "scopes": ["<skill names relevant to this alert>", "global"]})
   then gateway_call("memory.get", {"id": <id>}) for a relevant memory.
4. Check recent changes:
   gateway_call("events.search", {"kind": "change", "since": "<60 min before the alert>", "host": "<host>"}, "events")
5. Read the relevant SKILL.md files and connected-tools/SKILL.md, then call infrastructure tools.
runbooks.search and memory.search use lexical (BM25) ranking with no LLM in the loop. The agent passes the original alert text as the query and may retry up to two times with a narrower query. A memory graded unverified is a lead to check against live data, not a fact.

Runbooks and memory

Runbooks and memories live in PostgreSQL. Operators write runbooks and can add, edit or delete any memory in the UI. Agents use them through the runbooks.* and memory.* gateway tools.

  • Alert investigations do not write memory. When an alert incident closes, the post-incident memory pass reads the runs, the alerts and the final answers, and saves at most five memories. A failed pass is retried up to three times with backoff, and you can run it again from the incident page.
  • Other work can write memory directly. Request-driven runs save memories with memory.write.
  • Evidence grades. Each memory is confirmed (only an operator can set it), observed (seen directly) or unverified. An agent's causal claim is always unverified.
  • Protected rows. An agent cannot change operator memories or context-file summaries.
  • Usage. Akmatori records which memories each run searched, read and cited. The Dreaming cron job can retire unverified agent memories that nobody used for 90 days.
  • Secrets. Private keys, tokens, URL passwords and similar values are redacted on a best-effort basis before a memory is saved.
  • From chat. In Slack, reply remember: … in a thread to save an operator memory. You can turn memory generation off in Settings → Memory; operator writes still work.

Scheduled agents

Cron jobs use the same execution path as incidents. A cron job posts to its configured channel or falls back to the workspace default. Two fields control its tools: tools_enabled (all enabled connector tools, or none) and builtin_tools (which of events, incidents, proposals, runbooks and memory it can call). New cron jobs default to connector tools on, with events, runbooks and memory.

The self-improvement evaluator is a system cron job, off by default. It runs without connector tools and with the events, runbooks, memory, incidents and proposals tools, so it can review recent evidence and draft runbook and skill updates as proposals.

The backend seeds system cron jobs for platform maintenance. You can disable them, but the API rejects deletion with 409 Conflict, so maintenance schedules survive routine cleanup.

Design principles

Credentials stay in the gateway

Secrets live in PostgreSQL and are read by the MCP Gateway at call time. They never appear in prompts, skill files or agent output.

One path for every tool call

Agents call tools with gateway_call and execute_script, so authorization, credentials and auditing stay in one place.

Tool docs come from the running system

connected-tools/SKILL.md is built from the enabled tool instances, so the examples change when the tools change.

Recall before exploring

Runbooks, memory and recent changes come first, which removes avoidable exploration and leaves time for targeted debugging.

How the tool docs are generated

  1. You add, enable or disable a connector in the UI.
  2. The backend regenerates connected-tools/SKILL.md from every enabled tool instance, with its logical name and usage examples for its tool type. It also re-renders the file on a timer.
  3. Every other SKILL.md keeps its own instructions and ends with its memory manifest and a pointer to connected-tools.
  4. HTTP connector and MCP server changes trigger a gateway reload, so new tools appear without a rebuild.
  5. When an incident starts, Akmatori writes AGENTS.md with the incident workflow, the global memory manifest and the list of context files.

Contributor checklist

Adding a built-in tool type

Register the tool in the gateway, add a usage example in generateToolUsageExample(), and add the type to hasHandWrittenToolExample(), so connected-tools/SKILL.md gets useful examples.

Changing how skill files are built

Keep the generated section markers and the stripping logic aligned, so your own prompt text is kept and the generated blocks stay current.

Extending integrations

Decide whether the new capability belongs in a built-in tool type, a declarative HTTP connector, or a proxied remote MCP server, and document that path.

Troubleshooting

The tool list looks wrong

Check which tool instances are enabled on the Connectors page; connected-tools/SKILL.md is rebuilt from them. At startup, Akmatori logs a warning for any skill, runbook, memory or cron prompt that still names the retired runbook or memory subagents. Rewrite those to use the gateway tools.

A connector or MCP server is missing

Check that the connector exists and is enabled, that a cron job that needs it has connector tools on, that its prefix does not clash with another, and that the connector page shows it as connected. Use Test connection to see the server's answer.

Agents explore too much

Name the right hosts and services in skill instructions, write runbooks for repeated alerts, and check that the generated examples are specific enough to run as they are.