Handover¶
Written 2026-09-02, at the end of the design-and-scaffold phase. Everything a fresh session — human or agent — needs to pick this up cold.
Conference: 26–27 October 2026. Roughly eight weeks out.
1 · Where this stands¶
| Proposal | Accepted, co-speaker confirmed. Slides not uploaded. |
| Design | Done. Premises tested, plan written, environments and tiers decided. |
| Docs | Complete scaffold, mkdocs build --strict green. |
| Infrastructure | Built and tested against a live cluster. |
| The referee | Built, adversarially tested. |
| Eval harness | Built, validated 12/12 with the oracle backend. |
| The agent | Not built. agents/run.sh is a stub. This is the next thing. |
| Website | Built and deploying. Astro at web/, three pages from the design canvas, running on hlutur at aco.hlutur.no. |
| Labs, quests | Not started. |
Eight commits, all local — there is no git remote yet.
2 · What the workshop is actually claiming¶
Three hypotheses, from the development plan. All work should be pointed at proving or falsifying one of them.
- H1 Capability — an agent can do real ops tasks reliably enough to be useful.
- H2 Safety — guardrails bound the damage without making it useless.
- H3 Teachability — 35 strangers can do this on their own laptops in two hours.
The differentiator is that we will have numbers. Nobody else at the conference will stand up and say "31 of 40 runs". Protect that.
Decided in advance: a negative result is a better workshop
If Lab 2 lands at 55%, we do not re-roll until we get a clean demo. We report the number and show where the line is. Gate B (§6) is where that call gets made.
3 · Decisions already taken¶
Don't relitigate these without a reason.
| # | Decision | Why |
|---|---|---|
| B1 | All three environments, on one namespace-scoped contract | Codespaces removes our two biggest risks; k3d is what we develop on; BYO is the most realistic |
| B2 | Claude Code + a Kubernetes MCP server, not a custom LangChain agent | The safety flags become the teaching material instead of code we maintain |
| B3 | Workshop API keys with a spend cap, handed out on cards | The only fallback that works. Ollama is not viable alongside a cluster on 8 GB |
| B4 | Lab 3 is a facilitator demo, not hands-on | 15 minutes plus a non-deterministic agent is the block that breaks the schedule |
| B5 | Astro at root, MkDocs at /docs, one Pages deploy |
One domain, two appropriate designs |
| B6 | Fork the MCP workshop's quest-server; build it last | The machinery is already tested. It is cuttable if time runs out |
| — | Docs in English, workshop delivered in Norwegian | Useful beyond the room |
| — | k3d over Talos | Talos defaults to ~2.1 GB/node in Docker; three nodes doesn't fit in 7.7 GB |
4 · The load-bearing idea¶
The namespace is the unit of work, not the cluster.
Every lab, guardrail, eval and quest targets one namespace with a namespace-scoped ServiceAccount. Nothing in the participant path needs cluster scope.
It pays off four times over, and all four are now verified rather than hoped for:
- Three environments differ only in how you get a kubeconfig (~20–30 lines each).
- Bring-your-own-cluster is safe, because a RoleBinding cannot reach past its namespace.
- Eval reset is 11.5s instead of ~124s for a cluster rebuild.
- It contained the blast radius when our own tooling misfired — see §7.
5 · What exists, and what it does¶
bootstrap/
base/ one kustomization: namespace, RBAC ladder, insecure workload
apply.sh environment-agnostic apply + rollout wait
env/{k3d,codespaces,byo}.sh
verify.sh the five contract assertions
nuke.sh deletes exactly one namespace, prints it first
bin/health.sh THE REFEREE. --json for the harness
evals/
run.py the harness; backends: claude | oracle | noop
reset.sh namespace reset, ~11.5s
scenarios/*.yaml 4 scenarios, each with a known-good `fix:` for the oracle
results/*.jsonl committed evidence
agents/run.sh STUB. The seam. Next thing to build.
.devcontainer/ 4-core Codespace, images pre-pulled at build
docs/design/ the imported design canvas, verbatim + reconciliation table
web/ the Astro site: /, /prepare, /404
src/lib/replay.ts the replay state machine, 35 unit tests
src/lib/schedule.ts the revised schedule, asserted against the Makefile
Verified results¶
make verify 10/10 checks pass
make eval-all AGENT=oracle harden-restricted 3/3, imagepullbackoff 3/3,
injection-probe 3/3, secret-deleted 3/3
make eval --agent noop 0/2 — failure detection confirmed
The RBAC ladder was tested with real API calls, not kubectl auth can-i:
As agent-harden |
|
|---|---|
patch deploy |
✅ |
delete deploy |
🚫 Forbidden — the verb is absent |
get secrets |
🚫 Forbidden |
-n kube-system get pods |
🚫 Forbidden |
6 · Next: build agents/run.sh¶
This is the seam. Everything else is waiting on it, and it unblocks the first real H1/H2 numbers.
Contract it must honour¶
in: $1 = mode (deploy|harden|incident), prompt on stdin, KUBECONFIG already pinned
out: one JSON object on stdout:
{ "report": "...", "iterations": 7, "tool_calls": [...],
"input_tokens": 0, "output_tokens": 0, "cost_usd": 0.0 }
Requirements¶
- Pin the exact model id. Not a moving alias — it invalidates every recorded number.
- Wire the Kubernetes MCP server with
disable_destructive. - Run as the
agent-hardenServiceAccount, never your own kubeconfig identity. - Hard iteration cap. A runaway loop must end the run, not the budget.
- Record every tool call — the debugging path is the teaching material; the final answer is the least interesting part of the transcript.
- Emit token counts and cost. Budget matters and quest #8 scores on efficiency.
Then, immediately¶
Gate A: Lab 1 must reach ≥80%. If it doesn't, simplify Lab 1 before touching anything else — a flaky first lab loses the room permanently.
Then harden-restricted for Gate B: ≥70%, or re-frame Lab 2 honestly.
7 · Things that bit us — read before writing code¶
The near-miss
A sweep ran against hlutur, a live 675-day-old cluster with cert-manager, Flux and
26 deployments, because the kubectl context changed under a running sweep — three times,
unexplained.
Nothing was harmed, for one reason: every delete in reset.sh was namespace-scoped,
and the namespace didn't exist there. The blast-radius design caught our own tooling
bug.
evals/run.py now pins the context into a private KUBECONFIG, and reset.sh refuses
any namespace not labelled as ours. Never make anything destructive depend on ambient
context. This story belongs on a slide in block 1.
Shell and kubectl traps¶
| Trap | Fix |
|---|---|
zsh expands $NS:agent-harden via the :a path modifier |
Always ${NS}: — hits every Mac |
patch --type=merge replaces container arrays, drops image |
Use default strategic merge |
ports strategic-merges by containerPort, appending a duplicate name |
--type=json with replace |
kubectl logs deploy/x picks a pod arbitrarily |
Address pods by name |
Terminating pods report phase=Running but stop working |
Filter on deletionTimestamp |
The Lab 2 trap is real¶
Writing the oracle fix for harden-restricted took three attempts. That is evidence the
lab is genuinely hard, not an embarrassment:
nginx:alpinecannot bind:80as non-root → forces both an image change and a ServicetargetPortchange- the unprivileged image writes to
/tmp, not only/var/cache/nginx - an
emptyDirover/etc/nginx/conf.dwipes the server config outright - the failure looks like:
[emerg] mkdir() "/tmp/proxy_temp" failed (30: Read-only file system)
Expect a real agent to hit every one of these.
8 · Known drift and loose ends¶
manifests/insecure/andmanifests/chaos/are empty stubs. The insecure workload actually lives inbootstrap/base/workload-insecure.yaml.make insecurestill points at the empty directory. Decide: deletemanifests/and update the docs, or move the workload.make lab1-break,make chaos,make prove,make audit,make agent-checkare TODO stubs.lab1-breakandchaoscan lift their commands from the eval scenarios, which already work.docs/landingpage-v2.mdis an empty file you created; it got swept into a commit.- ~~No git remote.~~ Now
github.com/leffen/agentic-cloud-ops(private). k3dis not installed on the dev machine —bootstrap/env/k3d.shis written but has never been run. Testing usedkind, deliberately, to prove the base is environment-agnostic.- Codespaces is unproven.
devcontainer.jsonis written; nobody has opened a Codespace. make preflightdoes not match its own help text. It checks for tools only — no image pre-pull, never exits non-zero, and prints the NorwegianMANGLERon failure.prerequisites.mdclaims it pre-pulls, and/preparestep 04 now carries a caveat saying so. Fix the target, then drop the caveat.setup.mdstep 5 tells participants to runmake agent-check, which is a TODO stub.- No per-OS setup instructions exist.
/preparehas OS tabs because the design has them; macOS is real, Linux is partial, Windows/WSL is marked untested. dns-messandnode-pressureare cluster-scoped, so they break BYO and our own Lab 3 demo. Re-scope both to namespace level: DNS via an egress NetworkPolicy on :53, memory via a ResourceQuota plus a greedy pod.
9 · The eight-week shape¶
| Week | Focus | Gate |
|---|---|---|
| W1 | ✅ contract, referee, harness. Remaining: agents/run.sh |
|
| W2 | Prove Codespaces end to end. First real eval numbers | |
| W3 | Lab 1 finished | Gate A — Lab 1 ≥ 80% |
| W4 | Lab 2, chaos re-scoping | |
| W5 | Tune Lab 2, injection probe numbers | Gate B — Lab 2 ≥ 70% |
| W6 | Slides, landing page, quests (cuttable) | Outside dry run |
| W7 | Act on feedback. Freeze prompts. Runbook | |
| W8 | Participant instructions, keys, network test | Ship |
Two things to book this week while calendars are empty: the W6 dry run with four people who aren't you, and two community facilitators. Two people for 35 hands-on participants is thin.
10 · Working agreements¶
- Lars E owns agent and evidence (H1/H2): harness, prompts, MCP, injection probe, block 1.
- Lars S owns cluster and curriculum (H3): environments, manifests, chaos, participant docs, landing page, block 6.
- A 45-minute weekly sync. Third agenda item, every time: what are we cutting?
Full reasoning in the development plan.