PlanResult JSON deep dive: fields, risk, jq recipes, and what is never stored
Schema-focused companion to CI plan gates: apiVersion, plan.actions, risk.denied, result payloads, RouteResult / MultiContextResult, history vs CI artifacts, and hard rules on manifests and API keys.
The intent compiler bet only works if the artifact is boring and stable. PlanResult is that artifact: one JSON document on stdout when you pass --output json, human UI on stderr, no manifests, no API keys. This post is the field guide to the schema — companion to the pipeline patterns in Kubernetes in CI/CD: gating cluster changes with plan JSON before apply and the CI / JSON docs.
Emit it
stdout = PlanResult; stderr = human noise
kprompt "scale api to 10" -n prod --output json
kprompt "scale api to 10" -n prod -o json > plan.jsonEnvelope fields
| Field | Type | Notes |
|---|---|---|
| apiVersion | string | Always kprompt.io/v1 |
| kind | string | PlanResult (or RouteResult / MultiContextResult — see below) |
| schemaVersion | string | "1" — bump only on breaking changes |
| prompt | string | Original natural-language prompt |
| cluster_context | string? | Resolved kubeconfig context when known |
| plan | object | Intent, summary, actions, requiresApproval |
| risk | object | level, denied, message |
| applied | bool | Whether a mutation actually ran |
| result | object? | Read/tool payload (get, explain, optimize, …) |
Minimal mutate plan (illustrative)
{
"apiVersion": "kprompt.io/v1",
"kind": "PlanResult",
"schemaVersion": "1",
"prompt": "scale api to 10",
"cluster_context": "kind-staging",
"plan": {
"intent": "scale",
"summary": "Scale Deployment/api to 10 replicas",
"requiresApproval": true,
"namespace": "prod",
"actions": [
{
"op": "scale",
"backend": "kubernetes",
"kind": "Deployment",
"name": "api",
"namespace": "prod",
"replicas": 10
}
]
},
"risk": { "level": "medium", "denied": false, "message": "Mutation requires approval" },
"applied": false
}plan.actions — what CI should inspect
- op — scale, deploy, delete, patch, rollback, …
- backend — kubernetes, helm, …
- kind / name / namespace — target object
- cluster_context — set on multi-context paths
- replicas / revision — when relevant
- diff — optional live before→after text (still not a full manifest dump)
Actions are intentionally thin. Policy engines gate on op + kind + namespace, not on guessing YAML from an LLM.
risk — the gate that matters
| Field | Meaning |
|---|---|
| level | low | medium | high | denied |
| denied | true when hard-deny fired (wipe-class, etc.) |
| message | Human reason — log it; do not parse prose for policy |
jq recipes
# Hard deny must fail the job
jq -e '.risk.denied == false'
# Block deletes
jq -e '[.plan.actions[].op] | index("delete") | not'
# Allow only scale
jq -e '.plan.intent == "scale"'
# Reject high risk
jq -e '.risk.level != "high" and .risk.level != "denied"'
# Namespace allow-list
jq -e '.plan.namespace == "staging"'result — read and report payloads
For get/list/explain/logs/optimize/graph and similar reads, result holds a structured payload (shape depends on intent). Mutating plans that only print a plan leave result empty or omitted. Optimize attaches idle / rightsizing / findings under result — see optimize my cluster.
Related kinds: RouteResult and MultiContextResult
- RouteResult — multi-tool chain: steps[] of PlanResult, aggregate risk, stoppedAt / stopReason
- MultiContextResult — read fan-out across contexts: contexts[], steps[], optional fleetSummary for optimize
Gate RouteResult by inspecting .risk and each .steps[].risk.denied. Never treat a parent --approve as consent for every step without reading the aggregate plan.
History vs CI artifacts
| Store | What | What not |
|---|---|---|
| ~/.kprompt/history.jsonl | Local prompt + plan summary for rerun | Full manifests, API keys, kubeconfig |
| CI plan.json artifact | Full PlanResult for audit / PR comment | Secrets — they were never in the document |
| Team audit (when enrolled) | Summaries pushed by policy | Cluster credentials in the control plane |
What is never stored
- LLM API keys
- Full Kubernetes manifests / Secret data
- kubeconfig files or tokens
- Raw provider request dumps in PlanResult
Anti-patterns
- One CI step: -o json --approve on production
- Parsing risk.message with regex instead of risk.denied / level
- Assuming schemaVersion will stay "1" forever without checking
- Committing plan.json that you manually edited to bypass gates
Wire it
Plan → gate → separate approve
#!/usr/bin/env bash
set -euo pipefail
json="$(kprompt "scale api to 3" -n staging -o json)"
echo "$json" | jq -e '.risk.denied == false' >/dev/null
echo "$json" | jq -e '.plan.intent == "scale"' >/dev/null
echo "$json" > plan.json
# Human or second job:
# kprompt "scale api to 3" -n staging --approve --waitFull GitHub Actions patterns live in the CI gates post. For why this artifact beats a chat transcript, see the intent compiler note. Schema reference stays mirrored on CI / JSON docs.
Related posts
Building AI SRE in Public #3: PlanResult
PlanResult is the IR of AI SRE: one typed document for humans and CI. Why JSON, what applied means vs verify, how blastRadius attaches, what never gets stored, and how investigate/why must extend the same artifact.
Read articleKubernetes in CI/CD: gating cluster changes with plan JSON before apply
How to use kprompt PlanResult JSON in CI/CD pipelines to review Kubernetes scale, deploy, and rollback plans before apply — with jq gates, GitHub Actions patterns, and production safety rules.
Read articleBuilding AI SRE in Public #4: Safety Engine
Policy is code, not LLM vibes. How kprompt’s safety engine hard-denies wipe-class intents, scores risk, forces approval, and why fail-closed is the load-bearing wall of AI SRE.
Read article