Real OPA Real score-k8s Real MCP 2025-06-18 100% open source No cluster · No API key

Agentic platform engineering, with the platform still in charge.

A working, end-to-end demonstration built entirely on free, open-source tooling. One request — "a PCI-scope payments service with a Postgres database, EU residency, staging and prod" — routed two ways. Without a platform, a capable model returns Kubernetes manifests that apply cleanly and fail 42 real policy checks. Through the golden path they converge to zero in three iterations, inside budget, with a human still holding the production approval.

42
policy violations with no platform in the way
0
after the golden path, in 3 real iterations
11 d
the illustrative ticket trail this replaces, across 6 teams
$602
per month the FinOps gate caught, on one service
0
cluster mutations any agent is able to perform
A real recording — 20 seconds, four acts

One request, eight acts, every verdict computed live.

Every policy verdict below came from running the real conftest binary against the real Rego in policy/. Every manifest came from the real score-k8s binary and the provisioner set in platform/. Run it twice and the numbers are identical — that is what a control plane is supposed to give you.

Real terminal recording: 42 policy violations without a platform, converging to zero through the golden path
A real recording of ./run.sh demo --acts 2,3,5,7 --scorecard. The full eight-act run is gifs/wow.gif; every cast is in recordings/.
ACT I

The ticket trail

One sentence becomes six tickets across six teams. Eleven working days, about four hours of which is work. The rest is queue.

ACT II

Skip the platform

A capable model writes the YAML from the prompt. It parses, it applies, it would pass a distracted review. Conftest and kube-linter return 42 denials.

ACT III

The platform API

The agent connects over MCP as a principal and receives 12 of 14 tools. The two it does not receive are the entire point.

ACT IV

Render

Three lines of Score become a Crossplane composite with encryption, a private endpoint, sized backups and a cost-centre tag.

ACT V

The gate, and the loop

Real OPA denials, each carrying its own remediation. The agent changes the inputs, re-renders, re-evaluates. Zero in three iterations.

ACT VI

Money is policy

$960.82/mo against a $400 envelope. The gate reduced capacity — never the 30-day PCI backup window.

ACT VII

Propose, then stop

The agent opens a PR carrying the policy verdict and cost delta, then tries to approve its own production promotion. The platform declines.

ACT VIII

Day two

18 replicas where git says 6. The PCI backup window silently cut. The NetworkPolicy gone for fifteen days. Attributed, and proposed as a diff.

The stack

Intent in, auditable pull request out.

Two front doors onto one golden path: a portal for humans, MCP for agents, the same authorization policy behind both. Every box is a real open-source project or a file in this repository — nothing is a placeholder.

INTENT Developer "a PCI service…" Agent MCP client PLATFORM API Northwind control plane src/platform_mcp.py Software catalog Backstage-shaped Golden-path scaffolder Software Template Authorization 12 actions · filtered MCP 2025-06-18 · stdio + HTTP RENDER Score workload spec score.dev/v1b1 score-k8s generate + northwind provisioner set + platform defaults Rendered objects SQLInstance (Crossplane) Deployment · Service NetworkPolicy · PDB ServiceAccount · Secret GATES Score spec NW-SCORE-001..005 Kubernetes + PCI NW-K8S-* · NW-PCI-* FinOps envelope NW-FIN-001..006 kube-linter independent check conftest · OPA · Apache-2.0 a denial carries its remediation → the agent changes the INPUT and re-renders DELIVERY Pull request agent-authored Human review required Argo CD reconcile Kargo staging → prod

The split that makes it work. Platform defaults eliminate the entire class of structural mistakes deterministically — security context, probes, labels, service-account tokens. No model judgement is involved and none is wanted. What the platform cannot decide for you is judgement: how big, which region, how long to keep backups, how much to spend.

Those are exactly the calls an agent gets plausibly wrong, and exactly what the policy bundle is for. Defaults for the deterministic half, policy for the judgement half, and the agent operates in between.

Open Policy Agent · Conftest 0.69.0 · Apache-2.0

The policy gate, and the agent that closes it.

17 controls across three bundles — 34 deny/warn rule bodies — all real Rego. Every message carries a policy id and an explicit remediation after ->. That second half is what makes the loop agentic rather than merely a linter: the agent parses it, changes a Score parameter, and the whole artefact is regenerated.

Terminal recording of the policy gate: three iterations from denials to zero
Act V — three real conftest evaluations, converging to zero.
./run.sh act 5REAL OUTPUT
╭──────────────────────────────────────────────────────────────────────────────────────────────────╮
AGENTIC PLATFORM ENGINEERING — NORTHWIND RETAIL                                                 
one request · eight acts · every verdict from a real OPA evaluation                             
╰──────────────────────────────────────────────────────────────────────────────────────────────────╯

━━━ ACT V  The gate: what the platform cannot decide for you ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

  reasoner: deterministic reasoner (no model, fully reproducible)
  engine:   conftest 0.69.0  ·  real Rego, real evaluation

   ITERATION 1   $960.82/mo vs $400.00 budget
   1. NW-PCI-003  SQLInstance/nw-payments-ledger-prod: backupRetentionDays=7 is below the 30-day
                  PCI floor
      fix → set spec.parameters.backupRetentionDays>=30
   2. NW-FIN-001  payments-ledger/prod: estimated $960.82/mo exceeds the CC-4471 budget of
                  $400.00/mo by $560.82
      fix → right-size the largest line item or request a budget increase from the cost-center
            owner
   3. NW-FIN-003  payments-ledger/prod: resource "payments-ledger-postgres" is $820.67/mo — 85% of
                  the whole service budget
      fix → single line items over 70% need an explicit exception annotation from the cost-center
            owner

    agent → 2 input change(s), then re-render from source
      · db_backup_retention_days 7  30
        PCI requires a 30-day minimum recovery window
      · db_instance_class db.r6g.xlarge  db.t4g.large
        largest reducible line item; db.r6g.xlarge -> db.t4g.large stays inside the cost-centre
        envelope

   ITERATION 2   $463.15/mo vs $400.00 budget
   1. NW-FIN-001  payments-ledger/prod: estimated $463.15/mo exceeds the CC-4471 budget of
                  $400.00/mo by $63.15
      fix → right-size the largest line item or request a budget increase from the cost-center
            owner

    agent → 1 input change(s), then re-render from source
      · db_instance_class db.t4g.large  db.t4g.medium
        largest reducible line item; db.t4g.large -> db.t4g.medium stays inside the cost-centre
        envelope

   ITERATION 3   $359.15/mo vs $400.00 budget
    ✔ 0 denials across 4 gates


    PASSED — 0 denials    converged in 3 iterations


  Note what the agent never did: it never edited a rendered manifest
  to make a check go green. Every fix changed an input and the whole
  artefact was regenerated. Patch the output instead and the finding
  comes back silently on the next render — that is how automated
  remediation becomes worse than none.

Build the platform one control at a time

Start from what the model handed back and switch on the guardrails a platform team would encode once. Each verdict list is a genuine conftest + kube-linter run captured at build time by src/build_playground.py — the page replays recorded output, it does not approximate Rego in JavaScript.

42 denials

Switch every control on and one finding survives: no-anti-affinity — replicas that could all land on one node. It is the least memorable item on the list, which is exactly why hand-hardening leaves it behind and a rendered golden path does not. That is the case for rendering rather than reviewing.

The idea worth stealing

A prompt is a request. An absent tool is a fact.

Every tool this platform exposes declares a permission, and tools/list is filtered by the calling identity before the model ever sees it. An agent without delivery:approve is not instructed to avoid approving production. It is never told the capability exists.

Terminal recording of the MCP tool list being filtered by identity
Act III — the same server, two identities, two different worlds.
./run.sh tools platform-agentREAL OUTPUT
identity: agent:platform-agent  (12/14 tools visible)

    catalog.get_entity                 catalog:read
    catalog.query                      catalog:read
    catalog.refresh_entity             catalog:refresh
  ! catalog.register                   catalog:write
    platform.estimate_cost             finops:read
    platform.evaluate_policy           policy:evaluate
  ! platform.open_pull_request         delivery:propose
    platform.plan_promotion            delivery:plan
    platform.render_workload           platform:render
  ! scaffolder.execute                 scaffolder:execute
    scaffolder.get_template_parameters scaffolder:read
    scaffolder.list_templates          scaffolder:read

  withheld from this identity:
    x platform.approve_promotion       identity lacks delivery:approve
    x platform.detect_drift            identity lacks observability:read
platform/catalog/ai-resources.yaml
apiVersion: backstage.io/v1alpha1
kind: AiResource
metadata:
  name: platform-agent
spec:
  type: agent
  owner: group:default/platform-team
  identity: platform-agent
  # The blast radius: twelve entries,
  # and note which one is absent.
  allowedTools:
    - catalog.query
    - catalog.get_entity
    - catalog.refresh_entity
    - catalog.register
    - scaffolder.list_templates
    - scaffolder.get_template_parameters
    - scaffolder.execute
    - platform.render_workload
    - platform.evaluate_policy
    - platform.estimate_cost
    - platform.plan_promotion
    - platform.open_pull_request
    # platform.approve_promotion is not
    # here, and never will be.

Two layers, deliberately

  • Identity → actions. Twelve platform actions granted per principal, in src/catalog.py. platform-agent holds delivery:propose and never delivery:approve.
  • AiResource.spec.allowedTools. A git-tracked, owned, reviewable catalog entity that narrows an agent further. An agent's blast radius is a YAML file a human reviews and an auditor can diff.

Four identities, four different worlds

./run.sh tools platform-agent    # 12/14
./run.sh tools drift-agent       #  5/14
./run.sh tools cost-reviewer     #  3/14
./run.sh tools release-manager   # 14/14

Borrowed from OpenChoreo (Apache-2.0, CNCF Sandbox), whose control plane routes agent tool calls through the same policy decision point as human API access — as far as I found, the only OSS internal developer platform that does.

Score 0.16.0 · Crossplane v2 · Apache-2.0

Three lines in. A managed, encrypted, tagged database out.

A developer writes resources: {db: {type: postgres}} and stops thinking. The platform team decides, in one file, that "postgres" means a Crossplane composite with encryption, a private endpoint, sized backups and a cost-centre tag — not an unmanaged StatefulSet running a mirrored image. Swap that file and every service in the estate gets the new definition on its next render.

Terminal recording of score.yaml rendered through the Northwind provisioner set
Act IV — real score-k8s generate, real provisioner set.
score.yaml — the entire database request
resources:
  db:
    type: postgres

# That is the whole thing. The only
# infrastructure a product engineer
# at Northwind ever writes.
rendered by platform/northwind.provisioners.yaml REAL RENDER
apiVersion: platform.northwind.io/v1alpha1
kind: SQLInstance
metadata:
  annotations:
    k8s.score.dev/resource-guid: 6b7dbc6f-1b90-faba-e51d-03539eacd311
    k8s.score.dev/resource-uid: postgres.default#payments-ledger.db
    k8s.score.dev/source-workload: payments-ledger
    northwind.io/provisioner: template://northwind/postgres-crossplane
  labels:
    app.kubernetes.io/managed-by: northwind-platform
    app.kubernetes.io/name: payments-ledger
    backstage.io/component: payments-ledger
    northwind.io/cost-center: CC-4471
    northwind.io/data-classification: pci
    northwind.io/data-residency: eu
    northwind.io/environment: prod
    northwind.io/owner: payments
    app.kubernetes.io/part-of: payments-platform
  name: nw-payments-ledger-prod
  namespace: payments-prod
spec:
  crossplane:
    compositionRef:
      name: sqlinstance.postgres.northwind.io
  parameters:
    backupRetentionDays: 30
    costCenter: CC-4471
    databaseName: payments_ledger
    engine: postgres
    engineVersion: '17.4'
    instanceClass: db.t4g.medium
    masterUsername: nw_payments_ledger
    multiAz: true
    passwordSecretRef:
      key: password
      name: nw-db-6b7dbc6f-1b90-faba-e51d-03539eacd311
    publiclyAccessible: false
    region: eu-west-1
    storageEncrypted: true
    storageGb: 500

Plus, without being asked: hardened securityContext, dropped capabilities, read-only root filesystem, liveness and readiness probes, topology spread, a PodDisruptionBudget, a default-deny NetworkPolicy, no automounted service-account token, and ownership and cost-centre labels on every object.

Every Act II violation that had exactly one correct answer is now structurally impossible — and no model was consulted about any of it.

Day two

The part that actually kills platforms.

Golden paths get a service created. What kills platforms is month four, when the estate has quietly diverged from what git says is true and nobody knows which of the two is correct.

Terminal recording of drift detection with attribution
Act VIII — four drifted fields, attributed, with a proposed diff.
FieldIn gitIn clusterWho / howImpact
replicas6 18 k.mensah · kubectl scale at 02:14 12 unplanned replicas — $197/mo, never reconciled back
backupRetentionDays30 7 out-of-band Terraform from the pre-platform pipeline PCI 23 days of recovery window silently lost
networkPolicydefault-deny absent namespace recreated without reapplying it PCI reachable from every namespace for 15 days
automountServiceAccountTokenfalse true namespace recreation restored the default service-account token mounted into a PCI pod

The drift agent holds no tool that mutates a cluster. It could not "just fix it" even if you asked. Everything it believes becomes a diff a human merges — so when it is wrong, and it will be, the git history is still the truth.

Components

Nine pieces, every one replaceable.

Nothing is vendored or reimplemented. ./bin/setup.sh fetches official release artefacts at pinned versions. Where a project has no agentic story yet, this page says so rather than implying one.

ProjectRole hereLicense VersionAgentic surface today
Conftest / OPA Evaluates every policy verdict in this demo Apache-2.00.69.0 / 1.19.1 the gate itself
score-k8s / Score Developer-facing spec; renders the manifests Apache-2.00.16.0 small schema'd YAML an LLM gets right
kube-linter Independent second opinion on rendered output Apache-2.00.8.3 verification
Crossplane v2 The platform API the provisioner renders into Apache-2.0v2.4.0 no official MCP server
Backstage Catalog + Software Template shape; AiResource Apache-2.0v1.54.3 first-party MCP Actions backend
Argo CD Reconciles the merged change Apache-2.0v3.5.1 argoproj-labs/mcp-for-argocd
Kargo Promotion path staging → prod, with a human gate Apache-2.0v1.11.2 MCP proposal closed not-planned
OpenChoreo Source of the authz-gated MCP pattern used here Apache-2.0v1.2.3 3 MCP servers, 3 in-tree agents
Trivy / OpenTofu Optional scanners and IaC toolchain Apache-2.0 / MPL-2.00.74.0 / 1.12.6 optional

Versions verified against upstream release pages in August 2026. Deliberately excluded, with reasons in the README: Cyclops and Kusion (both dormant for ~13 months), and Port (closed-source core, archived MCP server, AI agents behind a paid tier).

Test results

Acceptance checks

Produced by src/build_report.py, which executes and times each check against the real binaries and writes outputs/verify-report.json. The same suite runs in GitHub Actions on every push, plus weekly — because the failure mode worth catching is an upstream release changing behaviour under a demo that claims to be reproducible.

#CheckEvidenceResultDuration

Quickstart

Four ways to run it. None of them need an account.

No cluster, no cloud credentials, no API key, no LLM. Python 3.10+ and about ninety seconds.

1 · GitHub Codespaces

The dev container installs the one Python dependency, fetches the pinned binaries, runs the unit tests and then the acceptance suite before handing you a prompt — so the first thing you see is 120 passing and 15/15.

Open in GitHub Codespaces →

# what the container does before you get a prompt
pip install -r requirements.txt
./bin/setup.sh --all      # pinned upstream binaries
./run.sh test             # 120 unit tests
./run.sh verify           # 15/15

2 · Local

git clone https://github.com/adventurewave-labs/\
agentic-platform-engineering-extravaganza
cd agentic-platform-engineering-extravaganza

./run.sh setup   # pinned upstream binaries
./run.sh demo    # eight acts and a scorecard
./run.sh verify  # 15 acceptance checks

3 · Docker

docker compose run --rm demo
docker compose run --rm verify
docker compose up site   # :8080
docker compose up mcp    # :8099

4 · Point your own agent at it

# stdio
claude mcp add northwind -- \
  python3 src/platform_mcp.py

# or streamable HTTP
./run.sh mcp 8099
claude mcp add --transport http \
  northwind http://127.0.0.1:8099/mcp

Then ask it for a service. It will list the golden paths, read the parameter schema, render, evaluate, remediate, and stop at the pull request — because that is the last thing its identity is permitted to do.

Reproducibility

How this was built — no smoke, no mirrors.

A demo that overstates itself is worse than no demo. Here is the line, drawn honestly, and the command that checks it rather than asking you to take this table's word for it.

ComponentStatusDetail
Policy evaluationreal The pinned conftest binary over the Rego in policy/. Every number on this page comes from it.
Manifest renderingreal The pinned score-k8s binary with the provisioner set in platform/.
kube-linterreal An independent linter nobody here tuned. It found four genuine defects in the platform defaults during development — missing containerPort, no anti-affinity, an unresolvable ServiceAccount reference, an unset unhealthyPodEvictionPolicy. All four were fixed in the platform, not worked around in the demo.
MCP serverreal MCP 2025-06-18 over stdio and streamable HTTP, stdlib only. Origin-validated against DNS rebinding. Point Claude Code or Cursor at it.
Authorizationreal Enforced in code at tools/list and tools/call, not by prompt instruction.
GIFs and castsreal Genuine PTY recordings. A pseudo-terminal is allocated, the command is typed into a real interactive shell, and every byte is timestamped as it arrives — what asciinema rec does, minus the human hand. Wall-clock timings, a real shell prompt, and the highlight reel is a real run of a real command rather than a full run with lines cut out of it. agg plays back faster than life and trims dead air over 1.5s; both are declared in src/build_casts.py and neither can change a character of what was recorded.
The agent's reasonerdeterministic by default Denials are parsed and mapped to Score parameter changes by rules, so the demo reproduces exactly with no API key. --backend llm swaps in a real model against any OpenAI-compatible endpoint (Ollama, vLLM, OpenRouter, Z.AI, OpenAI); the loop is unchanged. That it makes no difference to the outcome is the point.
Intent extractionregex Prose to a structured request. A model does this better on messy input and worse on reproducibility. One function, replaced by --backend.
Cost figuresstatic rate card by default A checked-in table in src/costing.py by default, so there is no account requirement and the artefacts stay byte-reproducible. The swap is wired rather than asserted: NORTHWIND_COST_SOURCE=infracost re-prices the database and load-balancer lines against real cloud rates via src/sources/infracost.py, and every estimate carries a costSource so a rate-card number is never mistaken for a priced one. The Rego does not change — that was the claim.
Drift observationfixture by default platform/observed-state.yaml by default. Detection, attribution and the proposed patch are real code over that shape. NORTHWIND_DRIFT_SOURCE=argocd swaps the fixture for src/sources/argocd.py, which reads Argo CD's managed-resources for the desired/observed pair and metadata.managedFields for attribution — and reports a field manager, not a person, because that is all a control plane actually knows.
Kubernetes clusternot in the demo; real in CI ./run.sh demo applies nothing anywhere. But “valid against a real cluster” is no longer left as an assertion: .github/workflows/cluster.yaml stands up a pinned kind cluster, installs the platform API as a real CRD, and puts the committed manifests through the actual API server — schema validation, admission, defaulting — then asserts the controllers acted. It also runs the negative control: the same API server accepts the unguided manifests too, all 42 violations of them.
Cloud provisioningnot present The SQLInstance is a real Crossplane-shaped composite; no Composition is installed and no cloud account is touched.
Northwind Retailfictional The company, the teams and the ticket numbers. The pain is not.

Do not trust this table — run the suite. ./run.sh verify executes all 15 checks against the real binaries in a few seconds, and GitHub Actions runs the same thing on a clean checkout on every push.