# Piceli

> Piceli is Kubernetes infrastructure as typed Python. Describe an application with a typed `App` (or templates, `kubernetes` client models or YAML/JSON), review a plan computed against the live cluster, and apply only that plan with an explicit approval of its hash; every run is journaled, resumable and can be rolled back. `piceli deploy MODULE:ATTR` takes a `Pipeline` (app, target, optional builds and image delivery, checks) from source to a running, checked release; `piceli release` does the same from a `release.toml` spec. `piceli status` and `piceli access` say whether the app is up and forward its ports to 127.0.0.1. Pre-alpha: every page states its maturity (stable, preview, experimental).

Rules for agents:

- Run `piceli help-json` for every command's options, side effects, approval rules and retry safety; do not guess flags.
- Plan before apply and never approve on the owner's behalf: show the owner the plan (`piceli deploy … --plan`, `piceli release plan`) and run `--approve <hash>` only after they approved that exact hash. Before the images exist, `deploy --plan` shows a release preview with placeholder images (`stages.plan.preview`, `approvable: false`): approve the `combined_hash`, never the `preview_hash`. Never add `--auto-approve` unless the owner said this is an unattended CI run for this exact target.
- An owner may declare an approval policy in reviewed code (`Pipeline(..., auto_approve=ApprovalPolicy(...))`, `[release] auto_approve = {...}`); then `piceli deploy … --approve-if-policy` and `piceli release apply … --approve-if-policy` run a plan only when every action is inside it, otherwise exit 3 with `approval-policy-exceeded` and the hash for the owner. `delete`, `replace` and `adopt` always need the hash. Never add or widen a policy yourself; no flag can pass one.
- Parse JSON from stdout; human text is on stderr. Exit codes: 0 success, 1 ran but did not succeed, 2 rejected before any change, 3 approval required.
- On a refusal read `reason` and run `piceli explain <reason> --json` (cause, fix, retry_safe). A model module that raises is a refusal too (`render-target-invalid`, `pipeline-load-failed`, `access-target-invalid`, `invalid-composition`; `message` names the exception, `PICELI_DEBUG=1` adds the traceback on stderr). A `deploy --plan` refused with `blocking` suggests `Pipeline(adopt=[…])`/`replace=[…]` (deploy has no `--adopt`): report it, the owner decides.
- A failed apply with `apply-crashloop` / `pipeline-apply-crashloop` carries `diagnosis` (per workload: container, reason, exit code, restarts, redacted log tail, events): report those causes; do not retry unchanged. `piceli release status --spec … --run <id>` shows them again later.
- Never read or print secret values; never rely on `~/.kube/config`, `KUBECONFIG` or the current context, and never pick or change a kube context, `allow_exec` or exec-plugin pins yourself: ask the owner. Never read or configure the credentials of external secret sources (Vault tokens, `~/.aws`, age/PGP keys, `~/.config/sops`).
- To learn what a deploy did or why it failed, read `<state_dir>/runs/<id>/summary.json` (named in the result as `summary.json`; `piceli runs MODULE:ATTR --json` lists them). `piceli cache status`, `cache prune --dry-run`, `doctor` and `runs` are read-only and never contact a cluster; run `cache prune` itself only when the owner asked or set `cache_budget=`.
- After an interruption, resume (`piceli deploy … --resume`, `piceli release resume`) instead of planning a new run; an interrupted re-apply or `release rollback` is not resumable: run the same command again. With shared state (`state="cluster"`) any runner can resume; `pipeline-locked`/`release-locked` means another deployer holds the release lock: wait, never delete `piceli-lock-*` Leases or `piceli-state-*` Secrets.
- To deploy committed code from a dirty or shared tree, plan with `--ref SOURCE=REV` and approve with the exact command `--plan` prints (it pins the resolved SHA); `--resume` reuses the run's commits and takes no `--ref`.

Minimal workflow: `piceli render app.py:pipeline` (no cluster; the target's namespace and nodes) → `piceli deploy app.py:pipeline --plan --json` → owner approves → `piceli deploy app.py:pipeline --approve <combined_hash> --json` → `piceli status app.py:pipeline --json`.

## Start here

- [Using Piceli from an agent](https://docs.pynenc.org/projects/piceli/en/stable/agents.html): commands safe to run without asking, commands that need approval, the output contract, errors, resume, secrets and cluster access
- [Command reference](https://docs.pynenc.org/projects/piceli/en/stable/reference/cli.html): every command with options and its output contract, which every command follows (generated from `piceli help-json`)
- [Error codes](https://docs.pynenc.org/projects/piceli/en/stable/reference/errors.html): every refusal code with cause, fix and whether a retry can succeed (same as `piceli explain`)
- [Agent skill](https://github.com/pynenc/piceli/tree/main/skills/piceli): `SKILL.md` plus scripts to install, describe an App and Pipeline, render, plan and ask the owner, deploy with the approved hash (or inside the owner's policy), check status, diagnose with `piceli explain`, resume and roll back; its walkthrough runs in CI against the built wheel
- [Getting started](https://docs.pynenc.org/projects/piceli/en/stable/getting_started/index.html): a typed app and `Pipeline` in one file, rendered, planned, approved, checked and reached on a disposable `kind` cluster

## Docs

- [When to use Piceli, and when not to](https://docs.pynenc.org/projects/piceli/en/latest/when_to_use.html): use it for typed Python apps with hash-approved plans, explicit cluster selection, adoption, resume and rollback; prefer Helm (vendor charts), Argo CD/Flux (GitOps), Pulumi (cloud resources beyond Kubernetes) or stable APIs elsewhere; every point links a test
- [The same app in Piceli, Helm, Kustomize, cdk8s and Pulumi](https://docs.pynenc.org/projects/piceli/en/latest/comparisons.html): `examples/comparisons/<tool>` (pinned versions) render identical objects for dev/staging/prod, checked in CI; files and lines, steps to deploy/change/roll back, safety features (plan-hash approval, server-side apply ownership, drift, adoption, secrets, rollback, typed validation) and where the others are better
- [Overview and architecture](https://docs.pynenc.org/projects/piceli/en/stable/overview.html): model, plan, execute, observe; the engine; design principles; glossary
- [Describe an app in typed Python](https://docs.pynenc.org/projects/piceli/en/stable/typed_apps.html): `piceli.App` deployments, stateful sets (`ClaimTemplate`, headless Service; claims are never pruned), daemon sets, jobs and cron jobs, services, config, secrets, volumes, pod defaults (`PodDefaults`, `Security`) on every pod kind, service accounts with typed RBAC (`Rule`; cluster-scoped objects are per namespace and flagged `cluster_scoped` in plans), label-selected network policies, autoscalers (the HPA owns `replicas`), disruption budgets, Ingress and Gateway API HTTPRoute (`Route`; needs the Gateway API CRDs), dependencies; a changed Job or StatefulSet immutable field refuses the plan with `immutable-field-changed` until `--replace Kind/name`; `piceli render`
- [Custom resources and any other kind](https://docs.pynenc.org/projects/piceli/en/latest/crds.html): `piceli codegen crd FILE | --from-cluster --kubeconfig F --context C --crd NAME` generates deterministic pydantic models from a CRD's schema; `app.resource(api_version, kind, name, spec)` declares any kind with a validated spec; cluster-scoped resources are managed per namespace (`piceli.io/namespace`); `public=` for sensitive-looking fields that hold no secret; `resource-scope-mismatch`
- [Environments](https://docs.pynenc.org/projects/piceli/en/latest/environments.html): `app.environment(name, replicas=…, autoscalers={name: Scaling(min_replicas=…, max_replicas=…)}, images=…, resources=…, config=…, hosts=…, node_selector=…, specs=…, enabled=…)` typed overrides of one App; `piceli render MODULE:ATTR --env NAME [--diff-env OTHER]` (typed diff, text or JSON); `Pipeline(app, {"dev": Target…, "prod": Target…})` and `piceli deploy|release … --env NAME` (own state per environment; the combined hash covers the environment's name and values)
- [A realistic app: dev, staging and prod](https://docs.pynenc.org/projects/piceli/en/latest/reference_app.html): `examples/reference/app.py`, one typed module with no dicts or overrides: web (Deployment, HPA, PDB), api (ServiceAccount + RBAC), Redis StatefulSet (`ClaimTemplate`), Job and CronJob, HTTPRoute (Ingress in dev), cert-manager `Certificate` from `piceli codegen crd`, release-wide NetworkPolicy, restricted pods, a SOPS-encrypted password (example-only age key) and checks; `render --env staging --diff-env prod`; deployed to kind in CI
- [Deploy an app from source](https://docs.pynenc.org/projects/piceli/en/stable/deploy.html): `piceli deploy MODULE:ATTR`, one journaled run of inputs, build, deliver, plan, apply and checks; `--plan` (with a placeholder-image release preview and blocking objects before any build), `--approve`, `--resume`, `--until`, `--json` events (`piceli.deploy-event.v1`); `piceli render MODULE:pipeline`; `--ref [SOURCE=]REV` builds a commit instead of the working tree (the hash pins the SHA); mirror third-party images by digest (`NodeLoopbackRegistry(mirror=[…])`) and take over a live node registry (`adopt=`/`replace=`, owner's choice, part of the approved plan); "If it fails" (a workload that cannot start fails the apply at once with per-workload causes); an owner-declared `auto_approve=ApprovalPolicy(...)` and `--approve-if-policy` for routine plans
- [Hand off to Flux or Argo CD](https://docs.pynenc.org/projects/piceli/en/latest/gitops.html): `piceli publish MODULE:ATTR --to oci://registry/repo:tag` pushes the rendered manifests as an OCI artifact in the `flux push artifact` layout (deterministic digest; exit 3 until `--approve <digest>`; credentials only from `--credentials FILE`), `piceli render --out DIR` writes them for Git; a Secret is refused unless `--secrets external` (SOPS, ExternalSecret); `OCIRepository` + `Kustomization` and Argo CD `Application` examples; what Piceli's plan, approval and journal no longer cover once a controller applies
- [Deploy from CI with an approval step](https://docs.pynenc.org/projects/piceli/en/stable/ci.html): GitHub Actions recipe: plan the pushed commit (`piceli deploy … --ref $GITHUB_SHA --plan --out deploy-plan.json --json`), approve through a protected environment, apply the plan file on any runner (`piceli deploy --apply deploy-plan.json --approve <combined_hash>`), resume anywhere; post the run's `summary.md` as the job summary (optionally a PR comment) and keep `summary.json` for agents; kubeconfig from a 0600 secret file, shared state, release lock, concurrency
- [Runner hygiene: disk, caches, doctor and run summaries](https://docs.pynenc.org/projects/piceli/en/latest/maintenance.html): every temporary directory (TLS, OCI, build staging, `--ref` worktrees) removed on exit, error and SIGTERM/SIGHUP; `piceli cache status [--json]` (per state directory and category) and `piceli cache prune [--keep-last N] [--budget 20GiB] [--dry-run]` (never the release state, secret store, approved plans or what a rollback needs); `Pipeline(cache_budget="20GiB")` enforced after each run; `piceli doctor [--json]` (free disk/memory vs the next build, docker/buildx/kubectl; exit 1 on a warning); run summaries `<state_dir>/runs/<id>/summary.{json,md}` (`piceli.run-summary.v1`: commits, digests, sizes, plan classes, changed fields, checks, failure code, timings; no secrets) and `piceli runs [--json]`
- [Share deployment state between runners](https://docs.pynenc.org/projects/piceli/en/latest/state.html): `Pipeline(state="cluster")` / `[release] state = "cluster"` keeps journal, catalog, secret store and receipts in the release namespace (Secrets) behind a release-scoped Lease lock with fencing and stale-owner takeover (`pipeline-locked` names the holder); portable plan files; `piceli state show|pull|export|import` (secrets excluded or encrypted; import needs `--approve <digest>`)
- [Releases from a spec](https://docs.pynenc.org/projects/piceli/en/stable/release_cli.html): `piceli release` plan, apply, adopt, replace, roll back, resume, stop, status (`--spec release.toml`, or `--spec module.py:pipeline` for a pipeline's release; `status --run ID` shows why a past execution failed); "If plan refuses"; an apply whose pods cannot start fails at once with `apply-crashloop` and a redacted `diagnosis`
- [Check a release and roll back automatically](https://docs.pynenc.org/projects/piceli/en/stable/checks.html): `[[checks]]` and `piceli.checks` (http, exec, metric, Python), `rollback_on_failed_checks`, `piceli release check`
- [How plans decide what changes](https://docs.pynenc.org/projects/piceli/en/stable/plans_and_diffs.html): no-op vs apply from server dry runs, field-level diffs, `piceli release diff`; recovering an interrupted apply or rollback; what a rollback restores and what it cannot (data, external side effects, what others own)
- [Compatibility and shared ownership](https://docs.pynenc.org/projects/piceli/en/latest/compatibility.html): supported Kubernetes versions (the last four minors, 1.34 to 1.37, tested on kind); autoscalers own `spec.replicas` (one rule for typed apps and plain manifests; `autoscaled` in the plan JSON: `initial`, `held`, `yielded`); readiness of kinds without a specific rule; operators, mutating webhooks, adoption and pruning with other field managers
- [Manage secrets](https://docs.pynenc.org/projects/piceli/en/stable/secrets.html): generate, template, import and rotate secret values; external sources read at every plan (SOPS files through `sops`, HashiCorp Vault KV v2, AWS Secrets Manager with `piceli[aws]`; a changed value becomes a new private version); private versions; `piceli release secret show`. Agents never read or configure source credentials (Vault tokens, AWS profiles, age/PGP keys)
- [Connect to managed clusters](https://docs.pynenc.org/projects/piceli/en/stable/managed_clusters.html): GKE, EKS, AKS and OIDC exec credential plugins with `[target] allow_exec`, pinning and refresh
- [Reach your services from your laptop](https://docs.pynenc.org/projects/piceli/en/stable/access.html): declare `app.access.forward`, then `piceli status` (is it up, which URLs; `piceli.status.v1`) and `piceli access` (supervised port forwards, port-conflict owners by pid; `piceli access stop --stale` stops Piceli's own leftover forwards and servers, with the owner's go-ahead)
- [From kubectl scripts to Piceli](https://docs.pynenc.org/projects/piceli/en/stable/migrate_from_kubectl.html): `piceli import live|yaml` generates a typed module from existing objects; adopt them with `--adopt-all-desired`; `App.override`
- [Test against a fake Kubernetes API](https://docs.pynenc.org/projects/piceli/en/stable/testing.html): `piceli.testing` for consumers' own tests, no cluster needed
- [Source identity](https://docs.pynenc.org/projects/piceli/en/stable/source_identity.html): `piceli inputs record/verify` and source drift
- [Containerized builds](https://docs.pynenc.org/projects/piceli/en/stable/containerized_builds.html): `build.toml`, pinned builders, build receipts, `piceli artifacts build-spec`; isolated per-image smoke checks with exit code, plain `env`, `entrypoint`, `expect_stdout`/`expect_stderr` regexes (`smoke-output-mismatch`)
- [Image delivery](https://docs.pynenc.org/projects/piceli/en/stable/node_delivery.html): `piceli artifacts deliver` to a registry (default) or a node, approved by config digest
- [Node-local registry](https://docs.pynenc.org/projects/piceli/en/stable/node_local_registry.html): a registry one node pulls from without configuration
- [Roadmap and feature status](https://docs.pynenc.org/projects/piceli/en/stable/roadmap.html): maturity of every feature
- [FAQ](https://docs.pynenc.org/projects/piceli/en/stable/faq.html): existing YAML, patch vs recreate, which cluster, deletion, CRDs, registries, web UI exposure

## Optional

- [Deployment planning](https://docs.pynenc.org/projects/piceli/en/stable/deployment_planning.html): discovery, pure plans, authorized execution and journals (Python API)
- [Artifact delivery](https://docs.pynenc.org/projects/piceli/en/stable/artifact_delivery.html): deterministic offline OCI layouts, tool grants, local-engine import
- [Operations lens](https://docs.pynenc.org/projects/piceli/en/stable/operations_lens.html): `piceli observe`, local UI, logs and port forwards
- [Operator workflow](https://docs.pynenc.org/projects/piceli/en/stable/operator_workflow.html): experimental inventory, catalog, approvals and backups
- [Kubernetes model](https://docs.pynenc.org/projects/piceli/en/stable/kubernetes_model/index.html): templates, client models and YAML/JSON definitions
- [API reference](https://docs.pynenc.org/projects/piceli/en/stable/apidocs/): generated from docstrings
- [Cross-model eval](https://docs.pynenc.org/projects/piceli/en/latest/contributing/evals.html): how well models install, implement, operate and recommend Piceli (`evals/`); safety violations (default kubeconfig, `--auto-approve`, `--allow-exec`, printed secrets) fail a task
- [Contributing](https://github.com/pynenc/piceli/blob/main/AGENTS.md): how to work on Piceli itself (setup, tests, invariants, code map)
- [Changelog](https://docs.pynenc.org/projects/piceli/en/stable/changelog.html): breaking changes and the 0.4.0 contract changes
