GORT closes the feedback loop after a merge to main. It watches for GitHub push events,
polls Flux until reconciliation completes, and if anything goes wrong it uses a configurable
AI provider to analyze the failure and automatically opens a fix PR.
After a merge to main there is no automated feedback loop verifying that Flux
successfully reconciled declared resources, or that the result matches the intent described
in docs/ plan documents. Failed deployments require manual investigation of Flux status,
pod logs, and events — then manual authoring of a fix PR.
GitHub push to main
│
▼
GORT webhook handler (HMAC-validated)
│ reads GitOpsWatcher CRDs to find matching apps
▼
Flux status polling (in-cluster, read-only K8s API)
│ Kustomizations, HelmReleases, pods, logs, events
▼
├── Flux FAILURE ──► AI provider: analyze failure + plan docs
│ └──► GitHub API: open fix PR
│
└── Flux SUCCESS ──► Collect runtime state (pods, deployments, events)
└──► AI provider: validate intent vs plan docs
├── Intent MET: done
└── Intent NOT MET ──► GitHub API: open fix PR
| Interface | Default | Extensible To |
|---|---|---|
pkg/gitops.Client |
Flux CD | ArgoCD, Rancher Fleet |
pkg/vcs.Client |
GitHub | GitLab, Gitea |
pkg/ai.Client |
Claude (Anthropic), GitHub Models, Ollama | OpenAI, Gemini |
GORT uses a GitOpsWatcher CRD to define which apps to watch:
apiVersion: gitops.gort.io/v1alpha1
kind: GitOpsWatcher
metadata:
name: cluster-config
spec:
type: flux
appName: cluster-config # Flux Kustomization name
namespace: flux-system # Namespace where Flux resources live
targetRepo: clcollins/cluster-config # Watch pushes to this repo
fixRepo: clcollins/cluster-config # Open fix PRs on this repo
docsPaths:
- docs/plans/ # Where to find plan documents
reconcileTimeout: 10mGORT runs two HTTP servers so that Prometheus scrape traffic is independent of webhook ingress:
| Port | Purpose | Endpoints |
|---|---|---|
8080 |
Webhook (application traffic) | POST /webhook |
8081 |
Metrics + health probes | GET /metrics, GET /healthz, GET /readyz |
/metrics— Prometheus metrics (port8081); scraped via theServiceMonitorinconfig/prometheus//healthz— liveness probe (port8081)/readyz— readiness probe (port8081)- Alertmanager rules in
config/alerting/alerts.yaml:FluxReconcileFailed(critical)FluxReconcileTimeout(warning)ResourceDeploymentFailed(warning)FixPRCreationFailed(warning)IntentNotMet(warning)IntentValidationError(warning)
Every PR to this repository should include a plan document in docs/plans/.
CI checks that at least one plan document exists. Use descriptive filenames
(e.g., setup-documentation.md); numeric prefixes are optional.
GORT follows the same convention when opening fix PRs on target repos.
GORT needs a GitHub token to read repositories and create fix PRs.
- Go to GitHub → Settings → Developer settings → Personal access tokens → Fine-grained tokens
- Click Generate new token
- Set the Repository access to the repos GORT will manage
- Grant the permissions listed below
- Copy the token and set it as
GORT_GITHUB_TOKEN
Note: If you plan to use
GORT_AI_PROVIDER=github-modelsand wantGORT_GITHUB_TOKENto double as the models token, you must use a classic PAT instead (withrepoandmodels:readscopes), since fine-grained PATs do not support themodels:readscope. Alternatively, setGORT_GITHUB_MODELS_TOKENto a separate classic PAT.
| Permission | Access | Why |
|---|---|---|
| Contents | Read and write | Read repo files and plan docs; create fix branches and commits |
| Pull requests | Read and write | Open fix PRs |
GORT receives push events via a GitHub webhook with HMAC signature validation.
-
Generate a webhook secret:
openssl rand -hex 32
-
In your target repository, go to Settings → Webhooks → Add webhook
-
Configure the webhook:
Field Value Payload URL http://<your-gort-host>:8080/webhook(GORT does not terminate TLS; use a reverse proxy or Ingress for HTTPS)Content type application/jsonSecret The value generated in step 1 -
Under Which events would you like to trigger this webhook?, select Just the push event
-
Click Add webhook
-
Set the same secret value as
GORT_GITHUB_WEBHOOK_SECRET
GORT uses an AI provider to analyze failures and validate intent. Choose one:
- Create an API key at console.anthropic.com
- Set
GORT_CLAUDE_API_KEYto the key value - Optionally set
GORT_CLAUDE_MODEL(default:claude-sonnet-4-6)
- Set
GORT_AI_PROVIDER=github-models - Set
GORT_GITHUB_MODELS_TOKENto a GitHub token that can access GitHub Models (defaults toGORT_GITHUB_TOKENif not set — a classic PAT withmodels:readscope is required; fine-grained PATs do not support this scope) - Optionally set
GORT_GITHUB_MODELS_MODEL(default:openai/gpt-4.1)
- Set
GORT_AI_PROVIDER=ollama - Optionally set
GORT_OLLAMA_URL(default:http://localhost:11434) - Optionally set
GORT_OLLAMA_MODEL(default:llama3)
No API key required — Ollama runs without authentication by default.
- Go 1.25.1+
- Podman (or set
CONTAINER_SUBSYS=docker) markdownlint-cli2(for markdown linting)
# Run all checks
make all
# Run tests only
make test
# Build binary
make build
# Build container image (podman)
make image-build
# Build with docker instead
CONTAINER_SUBSYS=docker make image-build
# Generate CRD manifests
make manifests
# Generate DeepCopy methods
make generate| Variable | Required | Default | Description |
|---|---|---|---|
GORT_GITHUB_WEBHOOK_SECRET |
yes | — | GitHub webhook HMAC secret (GORT_WEBHOOK_SECRET also accepted, deprecated) |
GORT_GITHUB_TOKEN |
yes | — | GitHub personal access token (fine-grained with Contents + Pull requests permissions, or classic with repo scope) |
GORT_CLAUDE_API_KEY |
if GORT_AI_PROVIDER=claude |
— | Anthropic Claude API key |
GORT_CLAUDE_MODEL |
no | claude-sonnet-4-6 |
Claude model to use when GORT_AI_PROVIDER=claude |
GORT_AI_PROVIDER |
no | claude |
AI provider (claude, github-models, or ollama) |
GORT_GITHUB_MODELS_TOKEN |
no | GORT_GITHUB_TOKEN |
GitHub Models API token — either this or the fallback GORT_GITHUB_TOKEN must be a classic PAT with models:read scope |
GORT_GITHUB_MODELS_MODEL |
no | openai/gpt-4.1 |
Model to use when GORT_AI_PROVIDER=github-models |
GORT_OLLAMA_URL |
no | http://localhost:11434 |
Ollama server URL when GORT_AI_PROVIDER=ollama |
GORT_OLLAMA_MODEL |
no | llama3 |
Model to use when GORT_AI_PROVIDER=ollama |
GORT_LISTEN_ADDR |
no | :8080 |
Webhook server listen address (host:port, e.g. :8080 for all interfaces or 127.0.0.1:8080 for localhost only) |
GORT_METRICS_ADDR |
no | :8081 |
Metrics + health probe server listen address (host:port, e.g. :8081 for all interfaces or 127.0.0.1:8081 for localhost only) |
cmd/gort/ — main entrypoint
internal/
claudeai/ — Claude AI client (implements pkg/ai.Client)
ghmodels/ — GitHub Models AI client (implements pkg/ai.Client)
ollama/ — Ollama AI client (implements pkg/ai.Client)
flux/ — Flux GitOps client (implements pkg/gitops.Client)
github/ — GitHub VCS client (implements pkg/vcs.Client)
k8s/ — Kubernetes client wrapper
metrics/ — Prometheus metric definitions
reconciler/ — Core reconcile orchestration (only non-pure package)
webhook/ — HTTP webhook handler + pure parse functions
pkg/
ai/ — AI interface + types
gitops/ — GitOps interface + types
vcs/ — VCS interface + types
api/v1alpha1/ — GitOpsWatcher CRD Go types
config/
crd/ — Generated CRD manifests
rbac/ — ClusterRole + ClusterRoleBinding
alerting/ — PrometheusRule manifest
service/ — Kubernetes Service manifests (webhook + metrics)
prometheus/ — ServiceMonitor for Prometheus Operator
docs/plans/ — Plan documents (required per PR)
hack/ — Code generation scripts and headers
This project is licensed under the MIT License. Copyright © 2026 Christopher Collins.
Note on AI-Generated Content: This software was developed with the assistance of AI tools. To the extent that any AI-generated content incorporated in this software is protectable by copyright, the copyright holder asserts that such content is covered by, and licensed under, the MIT License.
The legal status of AI-generated content with respect to copyright is unsettled. This notice reflects the copyright holder's present intent and is subject to revision as law and legal interpretation develop.
