All posts
Muhtalip Dede profile photoMuhtalip Dede · Founder of kprompt

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.json

Envelope fields

FieldTypeNotes
apiVersionstringAlways kprompt.io/v1
kindstringPlanResult (or RouteResult / MultiContextResult — see below)
schemaVersionstring"1" — bump only on breaking changes
promptstringOriginal natural-language prompt
cluster_contextstring?Resolved kubeconfig context when known
planobjectIntent, summary, actions, requiresApproval
riskobjectlevel, denied, message
appliedboolWhether a mutation actually ran
resultobject?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

FieldMeaning
levellow | medium | high | denied
deniedtrue when hard-deny fired (wipe-class, etc.)
messageHuman 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

StoreWhatWhat not
~/.kprompt/history.jsonlLocal prompt + plan summary for rerunFull manifests, API keys, kubeconfig
CI plan.json artifactFull PlanResult for audit / PR commentSecrets — they were never in the document
Team audit (when enrolled)Summaries pushed by policyCluster 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 --wait

Full 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.