Territory Grounder is an open-source, self-hosted SRE platform: an LLM agent ingests alerts, chat messages, and tickets, triages incidents across Kubernetes, hypervisors, network, and storage, and (from Phase 2 on) executes remediations inside a mechanical governance envelope. It is the ground-up rewrite of Agentic ChatOps as a distributable product: Go and Temporal instead of Python, n8n, and cron. Same control logic, none of the code.
GitHub: territory-grounder/grounder · Product site: territorygrounder.com
Overview
| Aspect | Details |
|---|---|
| Function | Alert/chat/ticket ingest, LLM triage, governed remediation (predict, act, verify) |
| Control plane | Go 1.25, Temporal workflows, PostgreSQL + pgvector |
| Model access | Bundled LiteLLM gateway: one OpenAI-compatible endpoint, multi-provider fallback ladder |
| Integrations | ~31 connector modules across 7 adapter surfaces (ingest, tracker, notifier, CMDB, actuation, model provider, observability) |
| Deployment | Single docker-compose stack; no SaaS dependency, no phone-home |
| Status | Phase 1 live, read-only; mutation code merged, globally disabled |
| License | Apache-2.0 |
| Repository | github.com/territory-grounder/grounder |
Control Loop
No mutating action runs without a consequence prediction committed first, keyed to action_id = SHA-256(canonicalJSON(Action)). After execution, a pure Go function compares observed state against the committed prediction and writes the verdict: match, partial, or deviation. The model that proposed the action has no write path to the verdict columns. A deviation disables auto-resolve for that action class.
Predictions are additionally scored against a degree-preserving shuffled-graph negative control. The real prediction must be at least twice as precise as the shuffle (control_ratio <= 0.5), otherwise the gate is treated as uninformative.
alert / chat / ticket ──▶ adapter ──▶ Temporal workflow
│
predict ──▶ commit prediction (the gate)
│
act (bounded to its territory)
│
independent verify ──▶ verdict ──▶ hash-chained ledger
│
band: AUTO · AUTO_NOTICE · POLL_PAUSE
The human is a circuit-breaker, not a per-action approver. Three autonomy bands set the interrupt level per session: AUTO (execute, log), AUTO_NOTICE (execute, notify), POLL_PAUSE (wait for a vote; never proceeds on timeout, pinned by a DB CHECK).
Enforcement by Construction
Safety properties live in the type system, the schema, and boot checks, not in review discipline:
| Failure class | Enforcement |
|---|---|
| Approve action X, execute action Y | action_id is asserted at classification, prediction, approval, execution, and verification. Any change to the action mints a new id and voids prior approvals |
| Approval poll without a prediction | BuildApprovalPoll accepts only a GatedProposal, a type constructible only by the prediction-gate activity. The bypass does not compile |
| Error path defaults permissive | Zero values are the restrictive option: unset band is POLL_PAUSE, unset reversibility is Irreversible |
| Shell injection | Actuation is fixed argv vectors via exec.Command / x/crypto/ssh with pinned host keys. sh -c is not expressible; CI greps for it |
| SQL injection | pgx/sqlc generated queries only; string-built SQL fails CI |
| Unauthenticated route | Handlers come from a factory that requires the authenticated principal; a route declared auth=none fails to register at boot |
| Silently unwired control | A startup self-test fails boot if the actuation interceptor chain is not in place |
| Ledger tampering | Append-only SHA-256 hash chain with no UPDATE/DELETE grants; a verifier walks the chain and alerts on break or staleness |
A non-configurable never-auto floor covers mkfs, dropdb, zpool destroy, tofu destroy, kubectl delete/drain, credential revocation, and reboot/halt. No flag or org policy lifts it. An unrecognized mutation classifies as never-auto.
Operator Console

15 live-wired surfaces: sessions, approvals, ledger, estate graph, governance controls, and the Grounding scorecard (verifier match rate, blast-radius precision/recall, falsifiability signal against the shuffled control). The estate causal graph is discovered from NetBox, Proxmox, and LibreNMS with per-source confidence tiers (1.00 for declared tunnels down to a 0.75 cap on learned co-occurrence edges). Screenshots show fixture data.
Build Process
Most of the Go is written by coding agents under a spec-driven workflow: EARS-format requirements, godog acceptance oracles in CI, and a content-hash lockstep that binds each safety-critical file to its owning spec. Current gate: 1,926 spec checks across 74 lockstep-bound files, -race clean. The -race sweep fixed 7 latent concurrency bugs, one of them a ledger writer racing itself and corrupting its own hash chain.
Status
| Phase | Scope | Status |
|---|---|---|
| 0 · Secure foundation | Mandatory-auth router, argv-only actuation, DML-only runtime DB role, fail-closed mutation gate | Done |
| 1 · Typed spine | Ingest, risk classifier, agent loop, prediction gate, verdict engine, ledger, console, estate graph | Live, read-only |
| 2 · Governed autonomy | Actuation interceptor chain, human approval loop, mutation enable | Merged, gated OFF |
| 3 · Anti-drift | Reconciliation, drift correction, safe decommission | Planned |
| 4 · Adversarial assurance | Boundary-coverage gate, sealed-holdout evals, incident-replay benchmark | Planned |
Running read-only against my own estate: authentication, the console, the estate graph, the prediction/verdict/ledger spine, the scorecard. Not implemented: the vote-consuming half of the approval loop, the incident-replay benchmark, and mutation itself, which stays globally disabled (mutation_enabled=false, asserted at boot preflight) until the Phase 2 self-tests pass.
Successor to Agentic ChatOps . Product site: territorygrounder.com . Design rationale: The Map Is Not the Territory .
