Deployment

What it takes to run Odonat in your VPC.

Four steps from an empty namespace to Odonat correlating a real alert in your own production traffic. Everything below happens inside your perimeter, and the first stage changes nothing.

The chart below is real and reviewable, and it is what a security reviewer should read before approving anything. The full API reference and the incident runbooks go out with each deployment while we onboard customers one at a time. Ask for access.
Step 1

Install the agent in your cluster

A Helm chart deploys the agent into a dedicated odonat-system namespace on your own EKS, GKE or AKS cluster. It runs non-root on a read-only root filesystem, opens no ports, and reaches everything it needs by outbound connection. Nothing in your environment ever dials in to it.

Step 2

Connect source control

You register a GitHub or GitLab app scoped to read repository contents and checks, and to write pull requests. It cannot force push and it cannot alter branch protection. That scope is what lets Odonat see a diff before it merges, and draft a revert after one lands.

Step 3

Point it at your Alertmanager

You point the agent at your existing Alertmanager and it polls outbound every 15 seconds. Nothing dials in to it, which is why its NetworkPolicy can deny all ingress and it can run with no Service. No agent goes on your app servers and nothing enters your request path.

Step 4

Set the safety plane, then start in shadow mode

You choose which workload kinds are eligible for autonomous action, which actions are permitted at all, and which are correlated and recorded but never acted on. Every deployment starts in shadow mode: Odonat watches, drafts what it would have done, and changes nothing until you have read enough of those drafts to trust the judgement.

Metrics & telemetry

The agent can be scraped by your Prometheus, and is not by default

The agent accepts no inbound traffic at all out of the box. Its NetworkPolicy denies every ingress, and it has no Service. That is the default because a reliability tool should not widen your attack surface to report on itself.

If you want the numbers in your own Prometheus, you opt in explicitly. This opens exactly one cluster-internal port, 8080, reachable only from inside the cluster. Nothing is exposed to the public internet in either configuration, because a namespaceSelector cannot match an external client and the chart renders no Service, Ingress or LoadBalancer for one to route to.

helm upgrade odonat ./deploy/helm/odonat --set metrics.enabled=true

Two details that are usually glossed over, and should not be. The ingress rule permits any pod in any namespace, not Prometheus specifically: NetworkPolicy selects pods by label, Prometheus lives somewhere different on every install, and a rule narrow enough to name it would silently match nothing on most clusters. A cluster-wide rule that works was chosen over a scoped rule that quietly does not.

And 8080 is one listener, not three. /metrics, /healthz and /status share it, so opening it for scraping also exposes /status, which carries the running policy and poll health. There is nothing in it a pod on your cluster could not learn from the pod it is scraping, but it is more than metrics, and metrics.enabled=false restores the original posture exactly.

Four counters, all cumulative. They are the same numbers behind the decision funnel, so a dashboard you build from these agrees with the audit log by construction.

odonat_decisions_total{verdict="..."}

Every decision, labelled by verdict: actionable, ambiguous, no_symptom_start, no_match, unlabelled, chronic_flapper, unknown. All seven are initialised to zero at startup, so a verdict that has never occurred reads 0 rather than showing no data.

odonat_reverts_opened_total

Revert pull requests opened. Never merges, so this counts proposals a human then accepted or rejected.

odonat_propose_only_total

Correct attributions the per-kind policy declined to act on. StatefulSets are propose-only by default, so this is the safety plane working rather than a fault.

odonat_eliminated_candidates_total

Candidates the causal filter ruled out because they changed after the symptom started. The clearest evidence the engine is doing more than correlating on time.

Artifacts

What you actually apply, and what we actually ingest.

Both blocks below are generated from the chart in our repository rather than written for this page, so what you read here is what your cluster gets.

Artifact 1

Kubernetes deployment

Deploying the Odonat Agent via Helm requires a simple values override to connect to your local cluster.

runAsNonRoot, readOnlyRootFilesystem, allowPrivilegeEscalation: false and capabilities.drop: ["ALL"] are set in the Deployment template itself, not in values. There is no override that turns them off.

values.override.yamlyaml
# values.override.yaml# helm install odonat ./odonat -n odonat-system -f values.override.yamlimage:  repository: ghcr.io/odonatagents/odonat-agent  # Defaults to latest. Pin a commit SHA in production: latest moves on every  # merge, including during an incident somebody is trying to reason about.  tag: latest# The safety plane. What you set is a ceiling, never a way to widen what the# binary permits: an action absent from the compiled allowlist stays refused# however this is configured.safetyPlane:  allowedActions:    - git_revert# How a tie is broken. Two changes closer together than this look like one# coordinated release, so the agent refuses rather than guesses. Whole minutes,# 1 to 60; outside that range the agent refuses to start.causality:  tieBreakSeparationMinutes: 5# Outbound model traffic egresses through this sidecar. failClosed drops the# request rather than sending it when tokenization fails.egressProxy:  enabled: true  failClosed: true# Denies all inbound. The agent needs no ingress.networkPolicy:  enabled: trueresources:  requests:    cpu: 200m    memory: 256Mi
Artifact 2

Incident ingestion schema

Odonat polls your existing Alertmanager outbound, every 15 seconds, and reads the alerts it is already producing. There is no webhook to configure and no payload to adapt: the shape below is what Alertmanager returns, unchanged.

The labels are what the correlation engine filters on. An alert carrying no namespace and no service or app cannot be narrowed by anything, so it is capped below the action threshold and can never be acted on.

alertmanager-alert.jsonjson
{  "labels": {    "alertname": "HighLatencyCheckout",    "namespace": "prod",    "service": "checkout-api",    "severity": "critical"  },  "annotations": {    "summary": "p99 latency above 2s"  },  "startsAt": "2026-08-12T14:30:00Z",  "status": { "state": "active" },  "fingerprint": "a1b2c3d4e5f60718"}
Artifact 3

Autonomous revert PR output

When an incident crosses the 85% causal confidence threshold, Odonat autonomously opens a structured revert PR containing telemetry traces and root cause analysis.

Sample output, not a customer incident. It renders the same incident as the console on the home page, so the two can be read against each other. Below the threshold this PR is still written, but it is posted for approval instead of merged.

revert-pr-4022.md · SAMPLEmarkdown
## [ODONAT-AUTO-REVERT] Mitigation for P0 Incident #PT4KHLK**Status:** Autonomous Action Executed (Confidence: 90%)**Trigger:** Alertmanager `HighLatencyCheckout` on `prod/checkout-api`**Culprit Commit:** `8f2a1b3` (PR #4021 by @dev_user)### Root Cause Analysis (RCA)Reverted commit introduced a connection pool bottleneck (`pool_size` reduced from 100 to 5).Thread pool exhaustion detected in DB connection manager.### Automated Mitigation Actions1. Opened and merged Revert PR #4022.2. Verified `PaymentService` p99 latency returned to nominal baseline (< 120ms) in 8.4s.
What the chart creates

Four objects. Read them before you apply them.

The chart is plain YAML with no templating tricks, and it is short enough to review in one sitting. Your platform team should read it rather than take our word for the permission boundary.

ServiceAccount
Identity for the agent. Nothing else uses it.
ClusterRole and binding
The permission boundary. get, list and watch on workloads, and no mutating verb of any kind. It cannot change an image, a command, an environment variable, or a replica count. The only thing Odonat ever writes to is a pull request in your own source control.
NetworkPolicy
The ingress rule set is empty, which denies all inbound. Egress is DNS plus outbound TLS.
Deployment
The agent, plus the egress proxy that tokenizes credentials before any prompt leaves your VPC.