{"slug": "piper-devops-copilot-where-the-llm-picks-typed-actions-not-shell", "title": "Piper – DevOps copilot where the LLM picks typed actions, not shell", "summary": "Piper, a new DevOps copilot, operates with a safety-first architecture where a large language model never directly executes commands — it only selects typed actions from a fixed catalog, which are then validated by deterministic code and run locally on the user's machine. The tool uses a conversational terminal interface to drive existing tools like SSH, kubectl, and Docker, but gates any mutating operations behind explicit human approval, preventing the LLM from reaching infrastructure without consent. By separating the LLM's planning role from command execution, Piper aims to provide a secure, auditable alternative to traditional AI-powered command-line tools that generate arbitrary shell strings.", "body_md": "**DevOps at the speed of thought.**\n\nA terminal-first, LLM-driven DevOps copilot that is *safe by construction* —\nthe LLM proposes, deterministic code validates, the human approves anything that mutates.\n\n[ Why](#why-this-exists) ·\n\n[·](#quick-start)\n\n**Quick start**[·](#the-deterministic-gate)\n\n**The gate**[·](#action-catalog)\n\n**Catalog**[·](#knowledge-base--rag)\n\n**Knowledge base**\n\n**Security** Important\n\n**The LLM never executes anything. It only picks an action from a fixed\ncatalog.** PIPER then validates the choice and runs the command **on your\nown machine** through a single audited executor. The LLM is a planner, not a\nshell. This is the entire product.\n\nPIPER pulls the relevant runbook from its knowledge base, runs read-only diagnostics over SSH, finds the planted issues, proposes fixes — and refuses to apply them, because M1 is read-only. The LLM proposes; the deterministic gate validates; the human stays in the loop.\n\nPIPER drives the tools you already trust (`ssh`\n\n, `kubectl`\n\n, `docker`\n\n, `gh`\n\n,\n`aws`\n\n, `gcloud`\n\n, `journalctl`\n\n, ...) from a conversational terminal UI — but\nevery command runs **locally**, picked from a **typed action catalog**,\nvalidated by a path denylist + secret scrubber, and (for anything that\nmutates) gated behind an explicit human approval. The LLM can hallucinate\nfreely; it cannot reach your infrastructure unless a real human says yes.\n\n```\n›  uptime, memory and disk on staging — tail nginx logs if anything looks off\n\nPIPER planning…  (3 actions chosen from the catalog)\n  1. system.uptime\n  2. system.memory\n  3. system.disk_usage\n   ✓ system.uptime         (520ms, ran locally)\n   ✓ system.memory         (340ms, ran locally)\n   ✓ system.disk_usage     (410ms, ran locally)\n\n▌ Y(◉ ◉)Y\n▌ Staging has been up for 14 days with a 0.43 load average [ev-1]. Memory\n▌ has plenty of headroom — 12 GB free out of 16 GB total [ev-2] — and the\n▌ root volume sits at 38% [ev-3]. Nothing worth flagging on the resource\n▌ side, no need to dig into the nginx logs right now.\n```\n\nEvery `[ev-N]`\n\nis a link back to the exact command output that produced the\nclaim. PIPER cannot make claims without evidence — the verifier rejects\nungrounded synthesis and retries.\n\nThis is the heart of the product. Read it twice.\n\n| What most LLM CLIs do | What PIPER does | |\n|---|---|---|\nWho composes the command |\nThe LLM writes a shell string (`tail -f …` , `kubectl get …` ) |\nThe LLM picks an action name + typed args from a closed catalog |\nWho runs it |\nAn execution layer that runs whatever the LLM wrote | PIPER's local executor runs a fixed command template bound to that action |\nWhat if the LLM hallucinates |\nA bogus command might run on your infrastructure | The catalog has no entry for a bogus action → the executor refuses |\nWhat you can audit |\nPrompts + arbitrary shell history | A typed list of actions in source — `src/actions/builtin/` — plus the verbatim local exec in `audit_log` |\nWhere the command runs |\nSometimes a remote sandbox, sometimes your machine | Always your machine. Local subprocess, optionally SSH'ing into an allowlisted host you registered |\n\nConcretely: when the LLM wants to check disk space, it does **not** emit\n`\"df -h /\"`\n\n. It emits a typed tool call —\n\n```\n{ \"name\": \"system.disk_usage\", \"args\": { \"host\": \"staging\", \"path\": \"/\" } }\n```\n\n— and PIPER's executor (only `src/exec/executor.ts`\n\nruns anything) translates\nthat into `df -h /`\n\nand spawns a local subprocess. The shell string is built\n**in PIPER's source code**, not by the LLM. The args are validated by Zod\nbefore the spawn. Secrets are stripped on the way out, before going to the\naudit log and before going back to the LLM.\n\nThe LLM can ask for `system.disk_usage`\n\n. It cannot ask for\n`system.evil_undocumented_thing`\n\n. That's the safety property.\n\nWe built PIPER for two people, both real:\n\n**The lone developer with no DevOps support.** You shipped an app, you need to keep it running, and there's no one to call when the staging container won't come up at 11pm. Today your fallback is pasting logs into ChatGPT and hoping.**The DevOps engineer doing the same diagnostic dance fifty times a day.** Tail the logs on that node. Check why the deploy is stuck. Verify the cron. You don't need a tutor — you need an editor for infrastructure with audit trail and rollback wired in.\n\nBoth meet on the same contract: **PIPER never silently mutates anything, and you\ncan always see exactly what it is about to do.**\n\n**Not** an autonomous agent. PIPER does not act on`mutate`\n\n/`destructive`\n\nactions without approval, and never will.**Not** a chat product. The TUI is a working surface, not a conversation.**Not** a Kubernetes admin panel, not a CI replacement, not a monitoring tool. PIPER drives the CLIs you already trust and adds the safety + grounding layer.**Not** a black box. Every action, prompt, approval rule and audit log entry is readable in source.\n\n| Milestone | What | State |\n|---|---|---|\nM0 |\nSpike — Bun `--compile` + Ink + PGlite WASM |\n✅ shipped |\nM1 |\nRead-only diagnostics: SSH, logs, health, container/pod status, deterministic gate |\n✅ shipped |\nM1.5 |\nRAG/memory layer, 3 embedding backends, sessions + resume, auto-compaction, interactive `/model` & `/memory` , HUMAN/YOLO modes, 40+ read actions |\n✅ shipped |\nM2 |\nMutations behind HITL — docker deploy, env updates, migrations, rollback | ⏳ next |\nM3 |\nScale — Kubernetes deploys, continuous monitor loop, repo suggestions | ⏳ |\nM4 |\nOn-prem / regulated — local-model-only path, encrypted audit, runbook ingestion at install | ⏳ |\n\nNo `mutate`\n\nor `destructive`\n\ntier actions exist in the catalog yet, and the\nrunner explicitly refuses them. **M1.5 is fully diagnostic by design.**\n\nDownload the binary for your platform from the\n[latest release](https://github.com/antoniociccia/piper/releases/latest) — no\nBun, no `node_modules`\n\n, single ~76 MB file.\n\n```\n# macOS (Apple Silicon)\ncurl -fsSLO https://github.com/antoniociccia/piper/releases/latest/download/piper-darwin-arm64\nchmod +x piper-darwin-arm64 && mv piper-darwin-arm64 /usr/local/bin/piper\npiper\n\n# Linux x64\ncurl -fsSLO https://github.com/antoniociccia/piper/releases/latest/download/piper-linux-x64\nchmod +x piper-linux-x64 && sudo mv piper-linux-x64 /usr/local/bin/piper\npiper\n```\n\nA `.sha256`\n\nis published alongside each binary — verify the download before\nrunning.\n\nNeed [Bun](https://bun.sh) ≥ 1.2 (one-line install: `curl -fsSL https://bun.sh/install | bash`\n\n).\n\n```\ngit clone https://github.com/antoniociccia/piper\ncd piper\nbun install\nbun dev\n```\n\nOn first launch PIPER detects that `~/.piper/credentials.json`\n\ndoesn't exist and\nruns an interactive wizard:\n\n**Backend**— probes for any local LLM server running (Ollama`:11434`\n\n, LM Studio`:1234`\n\n, llama.cpp`:8080`\n\n, vLLM`:8000`\n\n), or asks for an OpenRouter API key.**Model**— pick a tier (Featherweight ~$0.10/M, Economy ~$0.44/M, Balanced ~$3/M, Premium $30+/M) or a local model from the listed catalog.**Embedding backend**—`wasm`\n\n(default, in-process, offline after first run),`http`\n\n(local OpenAI-compatible endpoint),`openrouter`\n\n(cloud, paid), or`none`\n\n(disable RAG).**Budget**— per-session USD cap (default $0.50; hard stop, not a warning).** SSH environment**— optionally add a first host PIPER will be able to reach.\n\nThe wizard writes `~/.piper/credentials.json`\n\nwith mode `0600`\n\n. From there:\n\n```\n›  check uptime and disk usage on staging\n```\n\nTo resume a previous session at startup:\n\n```\nbun dev -- --resume      # opens a picker over recent sessions\nbun run build      # ./dist/piper, ~76 MB\n./dist/piper       # runs without Bun, without node_modules\n```\n\nThe binary embeds PostgreSQL WASM (~13 MB) and Yoga layout. The embedding\nmodel is **not** bundled — it lazy-fetches on first RAG use (~120 MB, one\ntime) and caches at `~/.piper/cache/models/`\n\n.\n\nYou cannot get \"no hallucination\" from an LLM. **Don't try.** Instead, make\nbeing wrong *safe*. PIPER's LLM lives inside a deterministic cage:\n\n```\n       ┌────────────┐  proposes actions      ┌────────────────┐\n       │    LLM     │ ─────tool_calls──────► │  Action catalog│\n       │ (any model)│                        │  (read|mutate| │\n       │            │                        │   destructive) │\n       └────────────┘                        └───────┬────────┘\n              ▲                                      │ validate\n              │ scrubbed                             │ args (Zod)\n              │ messages                             ▼\n              │                              ┌────────────────┐\n              │                              │   Executor     │\n              │                              │   (the ONLY    │\n              │                              │   side-effect  │\n              │                              │   surface)     │\n              │                              └───────┬────────┘\n              │                                      │\n              │           scrub stdout/stderr        │ spawns kubectl /\n              └──────────────────────────────────────┤ docker / ssh /\n                                                     │ nc / gh / ...\n                                                     ▼\n                                           ┌──────────────────┐\n                                           │ PGlite + pgvector│\n                                           │ audit_log,       │\n                                           │ evidence,        │\n                                           │ knowledge        │\n                                           └──────────────────┘\n```\n\n**Three permission tiers** with no overrides:\n\n| Tier | Examples | Approval |\n|---|---|---|\n`read` |\n`uptime` , `docker.ps` , `kubectl get` |\nNone. Executes directly. Safe by definition. |\n`mutate` |\n(M2) `docker deploy` , env update |\nPer-env approval prompt; remembered |\n`destructive` |\n(M2) `delete` , `drop` , `prune` , force-push |\nFresh prompt every time. Never remembered. Ever. |\n\nFive overlapping defenses, applied at every layer:\n\n**Architectural**— SSH keys never leave the OS`ssh`\n\nbinary; API keys never enter`messages[].content`\n\n. Single-module discipline + CI rule.**Path denylist**—`~/.ssh/id_*`\n\n,`~/.aws/credentials`\n\n,`~/.kube/config`\n\n,`~/.gnupg/`\n\n,`~/.docker/config.json`\n\n,`~/.netrc`\n\n,`~/.piper/`\n\n,`.env*`\n\n. Non-disablable. User config can*extend*the list, never weaken it.**Two-pass scrubbing**— write-time (every Executor output → audit log) and pre-LLM (every message body → HTTP call). Defense in depth.** Args refuse**— if the LLM tries to embed a recognisable secret (`AKIA…`\n\n,`sk-or-…`\n\n, JWTs, PEM blocks,`Bearer …`\n\n) in an action's args, the Executor**refuses the action**— it does not redact. Redaction would mutate semantics.** Provider-level privacy**— OpenRouter requests set`body.provider.data_collection = 'deny'`\n\n. Local mode routes inference through Ollama / llama.cpp / LM Studio / vLLM — network egress for inference is zero.\n\nFull design rationale in [ docs/architecture.md](/antoniociccia/piper/blob/main/docs/architecture.md) and\n\n[.](/antoniociccia/piper/blob/main/docs/decisions/ADR-001-deterministic-gate.md)\n\n`docs/decisions/ADR-001-deterministic-gate.md`\n\n40+ read-tier actions across the major DevOps surfaces. Every action is a\ntyped object registered in `src/actions/builtin/`\n\n, validated by Zod, executed\nonly through `src/exec/executor.ts`\n\n. **Free-form shell from the LLM is not\nrepresentable in the type system.**\n\n**Click to expand the full catalog**\n\n| Category | Action | What it does |\n|---|---|---|\nSystem |\n`system.uptime` |\n`uptime` (load average + time up) |\n`system.os_info` |\n`uname -a` + `/etc/os-release` |\n|\n`system.memory` |\n`free -h` |\n|\n`system.disk_usage` |\n`df -h [path?]` |\n|\n`system.process_list` |\n`ps -eo pid,user,pcpu,pmem,args -ww` |\n|\n`system.list_dir` |\n`ls -la <path>` (deny-list enforced) |\n|\n`system.file_stat` |\n`stat <path>` |\n|\n`system.cpu_info` |\n`lscpu` / `/proc/cpuinfo` |\n|\n`system.dmesg` |\nKernel ring buffer tail | |\n`system.package_list` |\nInstalled packages (`dpkg -l` / `rpm -qa` ) |\n|\n`system.cron_list` |\nUser + system crontabs | |\n`system.systemctl_list` |\n`systemctl list-units --type=service` |\n|\n`system.iptables_list` |\n`iptables -L -n -v` |\n|\nNetwork |\n`network.connections` |\n`ss -tunap` |\n`network.port_check` |\n`nc -zv` (open / refused / timeout / closed) |\n|\n`network.ping` |\n`ping -c N -W T` |\n|\n`network.dns_lookup` |\n`dig` / `host` lookup |\n|\n`ssh.connect` |\nProbe SSH reachability against an allowlisted host | |\nLogs |\n`logs.tail` |\n`tail -n N <path>` with optional grep |\nServices |\n`service.status` |\n`systemctl status <unit>` |\n`service.journal` |\n`journalctl -u <unit> -n N` |\n|\nDocker |\n`docker.ps` |\nContainer list (JSON) |\n`docker.logs` |\nContainer log tail | |\n`docker.inspect` |\nContainer inspect (summarised) | |\n`docker.compose_ps` |\n`docker compose ps` for a project |\n|\nKubernetes |\n`kubernetes.get` |\n`kubectl get <kind>` (pods, deploys, services…) |\n`kubernetes.logs` |\n`kubectl logs <pod>` (with `-c` , `--previous` , tail-N) |\n|\n`kubernetes.describe` |\n`kubectl describe <kind>/<name>` |\n|\n`kubernetes.top_pod` |\n`kubectl top pod` |\n|\n`kubernetes.events` |\n`kubectl get events --sort-by=.lastTimestamp` |\n|\n`kubernetes.context_current` |\n`kubectl config current-context` |\n|\nGit |\n`git.status` |\n`git status --porcelain=v1` |\n`git.log` |\n`git log -n N --oneline --decorate` |\n|\nGitHub |\n`github.pr_list` |\n`gh pr list` |\n`github.pr_view` |\n`gh pr view <number>` |\n|\n`github.run_list` |\n`gh run list` (Actions) |\n|\n`github.run_view` |\n`gh run view <id>` (logs, conclusion) |\n|\n`github.issue_list` |\n`gh issue list` |\n|\nAWS |\n`aws.s3_ls` |\n`aws s3 ls` |\n`aws.ec2_describe` |\n`aws ec2 describe-instances` |\n|\n`aws.cloudwatch_tail` |\n`aws logs tail` (CloudWatch) |\n|\n`aws.rds_describe` |\n`aws rds describe-db-instances` |\n|\nGCP |\n`gcp.compute_list` |\n`gcloud compute instances list` |\n`gcp.logging_read` |\n`gcloud logging read` |\n|\nAzure |\n`azure.vm_list` |\n`az vm list` |\nDatabase |\n`postgres.pg_isready` |\n`pg_isready` against host:port |\nMemory |\n`memory.search` |\nIn-process semantic search over the local knowledge base |\n\nPIPER ships a `memory.search`\n\naction. It is *not* a shell action — it's\nin-process semantic search over a local PGlite + pgvector store of:\n\n— markdown under`runbook`\n\n`docs/runbooks/`\n\n— architecture decision records under`adr`\n\n`docs/decisions/`\n\n— produced by`session-summary`\n\n`/session-report`\n\n— distilled incident notes (annex format, opt-in)`solved-case`\n\n— free-form knowledge you add yourself`note`\n\nThe planner is instructed to call `memory.search`\n\n**first** when the user's\nprompt looks like a known incident pattern, a deploy procedure, or references\na host that has prior session notes. The agent stays grounded in *your*\nrunbooks instead of the model's training data.\n\n| Backend | Model | Dim | Cost | Notes |\n|---|---|---|---|---|\n`wasm` (default) |\n`Xenova/multilingual-e5-small` |\n384 | free | In-process via `@huggingface/transformers` . 94 languages. ~120 MB downloaded once, then fully offline. Cached at `~/.piper/cache/models/` . |\n`http` |\nOpenAI-compatible local endpoint | varies | free | Ollama (`nomic-embed-text` , 768-dim), LM Studio, llama.cpp, vLLM. |\n`openrouter` |\nCloud paid embedding model | varies | paid | Only offered if an API key is configured. |\n`none` |\n— | — | — | Disables RAG. `memory.search` returns empty. |\n\nThe schema auto-recreates if the dimension mismatches — switching e.g. from Ollama 768-dim to WASM 384-dim drops the old vectors and rebuilds from source. Zero manual migration.\n\nToggle modes with **Shift+Tab**:\n\n**HUMAN**(default) — PIPER asks for approval per planned step. Verbatim command is shown before any run.** YOLO**— read-tier actions execute without per-step approval.`mutate`\n\nand`destructive`\n\nactions**still always ask, every time**, by design.\n\n**Slash commands**\n\n```\n/model               interactive model picker (Local / OpenRouter tabs, paging, filter)\n/memory              knowledge-base viewer (Overview + Sources, delete with d)\n/mem, /rag           aliases for /memory\n/resume              pick a recent session and reload its history into scrollback\n/env add <name> <user@host[:port]> [--key <path>] [--desc \"...\"] [--tag a,b]\n/env list\n/env remove <name>\n/session-report      summarise the current session into the knowledge base\n/debug               toggle verbose agent events (costs, synth status, RAG hits, LLM trace)\n/help                show context-sensitive help\n/save [file.md]      export the last report to a file\n/quit                exit PIPER (Ctrl+C also works)\n```\n\n**Keyboard**\n\n| Keys | Effect |\n|---|---|\n`Enter` |\nSend |\n`Shift+Enter` |\nNew line (multi-line input) |\n`Shift+Tab` |\nToggle HUMAN ↔ YOLO |\n`Ctrl+O` |\nCollapse reasoning — hide agent-event lines from future turns |\n`?` |\nContext-sensitive help |\n`Esc` |\nClear current input |\n`Ctrl+C` |\nQuit |\n\nThe bottom strip of the TUI shows everything at a glance:\n\n```\nY(◉ ◉)Y  diagnosing staging      $0.0123 | google/gemini-pro-1.5 | OR $4.32 left | 12.4k/128k (10%) ███▒▒▒▒▒▒▒  HUMAN\n```\n\n**Alien mascot**— color-cycles while PIPER thinks; idle when waiting on you.** Session title**— auto-generated from the first user prompt by a tiny LLM call.** Cost**— running session cost in USD, real provider pricing.** Model id**— the model currently driving the planner (`/model`\n\nto switch).**OpenRouter remaining credit**— live-fetched every 60s on paid backends.** Token meter**—`N/limit (%)`\n\nagainst the model's`maxContextTokens`\n\n(minus 4k reserved for output), measured with real`gpt-tokenizer`\n\ncl100k_base.**Mode badge**— HUMAN (green) or YOLO (red).\n\n**Persistent by default.** PGlite stores sessions at`~/.piper/data/pglite/`\n\n. Override with`PIPER_DATA_DIR=/path`\n\n. Force in-memory (ephemeral) with`PIPER_EPHEMERAL=1`\n\n.**Auto-titled.** Small LLM call on the first user prompt names the session.**Auto-saved reports.** Every`done`\n\nwrites the final answer to`~/.piper/data/reports/{sessionId}/run-{ts}.md`\n\n.**Resume.**`bun dev -- --resume`\n\nat startup, or`/resume`\n\nmid-session.**Auto-compaction.** When the planner's context exceeds 70% of the model's`maxContextTokens`\n\n, older turns are rolled into a single summary message.**Grounded synthesis.** Every claim cites`[ev-N]`\n\n. A run passes the verifier if ≥75% of substantive lines are cited; ungrounded answers retry.History stays in the terminal's native scrollback — append-only, no redraw, no flicker, no loss when you scroll up.`<Static>`\n\nscrollback persistence.\n\n| Concern | Choice |\n|---|---|\n| Runtime | Bun ≥ 1.2 (single-binary via `bun build --compile` ) |\n| Language | TypeScript strict (`noUncheckedIndexedAccess` , `exactOptionalPropertyTypes` , no `any` ) |\n| Terminal UI | Ink (React for the terminal) |\n| Persistence | PGlite (PostgreSQL in WASM — single embedded DB) |\n| Vectors | pgvector inside the same PGlite DB (HNSW index) |\n| Embeddings (default) | `@huggingface/transformers` + `Xenova/multilingual-e5-small` (WASM) |\n| Tokenizer | `gpt-tokenizer` (cl100k_base) |\n| Schema validation | Zod |\n| Model API | OpenAI-compatible `/v1/chat/completions` |\n\nWhy these choices: see [ docs/decisions/](/antoniociccia/piper/blob/main/docs/decisions).\n\n`~/.piper/credentials.json`\n\n(created by the wizard, mode 0600)\n\n`~/.piper/credentials.json`\n\n(created by the wizard, mode 0600)\n\n```\n{\n  \"openrouter_api_key\": \"sk-or-v1-...\",\n  \"default_provider\": \"openrouter\",\n  \"default_model\": \"deepseek/deepseek-v4-pro\",\n  \"embedding_backend\": \"wasm\",\n  \"max_session_cost_usd\": 0.50,\n  \"max_followup_iterations\": 1,\n  \"compaction_keep_recent\": 6,\n  \"compaction_trigger_pct\": 0.70,\n  \"environments\": {\n    \"prod-web\": {\n      \"host\": \"192.0.2.10\",\n      \"ssh_user\": \"deploy\",\n      \"port\": 22,\n      \"identity_file\": \"/Users/me/.ssh/id_ed25519\",\n      \"description\": \"production web tier\",\n      \"tags\": [\"prod\", \"web\"]\n    }\n  }\n}\n```\n\n**Environment variables (override the file — useful in CI)**\n\n| Variable | Purpose |\n|---|---|\n`PIPER_PROVIDER` |\n`openrouter | ollama | lmstudio | llamacpp | vllm | custom` |\n`PIPER_BASE_URL` |\nEndpoint override |\n`PIPER_API_KEY` / `OPENROUTER_API_KEY` |\nAPI key |\n`PIPER_MODEL` |\nModel id |\n`PIPER_EMBEDDING_BACKEND` |\n`wasm | http | openrouter | none` |\n`PIPER_MAX_SESSION_COST_USD` |\nHard budget cap |\n`PIPER_DATA_DIR` |\nPersistent storage (default: `~/.piper/data/pglite/` ) |\n`PIPER_EPHEMERAL` |\nSet to `1` for in-memory storage (loses sessions at exit) |\n\nIf an env var doesn't look like a valid API key (e.g. a leftover `test`\n\nvalue), PIPER ignores it with a warning and falls back to the file.\n\n```\nbun test                       # 386 unit + gate tests (no Docker, no network)\nbun run e2e                    # Docker sshd fixture, E2E tests, teardown\nbun run typecheck              # tsc --noEmit, strict\n```\n\nCoverage focuses on the security-critical layers: catalog gate, path denylist, secret scrubber, audit log persistence, verifier, embedding-dim migration.\n\nCI runs `license-checker`\n\nand **rejects any GPL transitive dependency**.\n\nPIPER is built around a deterministic safety gate. Vulnerability disclosure\nprocess: see [ SECURITY.md](/antoniociccia/piper/blob/main/SECURITY.md). Coordinated disclosure, 90-day\ndefault. Particular care for:\n\n- Prompt-injection that smuggles a command into the gate\n- Any code path that runs shell outside the\n`Executor`\n\n- Any code path that logs or sends unredacted secrets\n- Any code path that lets a remembered rule auto-approve a\n`destructive`\n\naction - Any code path that bypasses the SSH host allowlist\n\nThe full architecture + threat model is at\n[ docs/architecture.md](/antoniociccia/piper/blob/main/docs/architecture.md).\n\n**Apache-2.0.** See [ LICENSE](/antoniociccia/piper/blob/main/LICENSE) and\n\n[— the NOTICE file discloses the Apache-2.0 transitive deps and the LGPL transitive disclosure for](/antoniociccia/piper/blob/main/NOTICE)\n\n`NOTICE`\n\n`@img/sharp-libvips`\n\n(pulled in by the embedding pipeline).Contributions welcome. See [ CONTRIBUTING.md](/antoniociccia/piper/blob/main/CONTRIBUTING.md) for the flow.\n\n**Two-eyes rule** on anything touching\n\n`src/exec/`\n\n, `src/security/`\n\n, or\n`src/actions/`\n\n— the maintainer reviews these personally.Built with the conviction that making being wrong safe beats trying to make the LLM never wrong.", "url": "https://wpnews.pro/news/piper-devops-copilot-where-the-llm-picks-typed-actions-not-shell", "canonical_source": "https://github.com/antoniociccia/piper", "published_at": "2026-05-29 10:42:29+00:00", "updated_at": "2026-05-29 10:46:13.650755+00:00", "lang": "en", "topics": ["ai-tools", "ai-agents", "ai-safety", "mlops", "large-language-models"], "entities": ["PIPER", "DevOps", "LLM", "SSH", "kubectl", "docker", "gh", "aws"], "alternates": {"html": "https://wpnews.pro/news/piper-devops-copilot-where-the-llm-picks-typed-actions-not-shell", "markdown": "https://wpnews.pro/news/piper-devops-copilot-where-the-llm-picks-typed-actions-not-shell.md", "text": "https://wpnews.pro/news/piper-devops-copilot-where-the-llm-picks-typed-actions-not-shell.txt", "jsonld": "https://wpnews.pro/news/piper-devops-copilot-where-the-llm-picks-typed-actions-not-shell.jsonld"}}