# Purpose > See [README#Purpose](README.md#purpose). ## Normative Language The key words MUST, MUST NOT, REQUIRED, SHALL, SHALL NOT, SHOULD, SHOULD NOT, RECOMMENDED, MAY, and OPTIONAL in this document are to be interpreted as described in RFC 2119. Sub-folder SEEDs in this tree inherit the RFC 2119 declaration. They MUST NOT re-declare it. ## Dependencies This is a SEED per the [openseed convention](https://github.com/plow-pbc/openseed) — this repo *is* the convention. An agent installing it without the `seed-install` skill MUST first read this `SEED.md` end-to-end and follow its installer contract (the [convention prerequisite](SPEC.md#dependencies), satisfied here by reading on). This SEED installs the OpenSeed tooling — the three agent skills described under [Installed skills](#installed-skills) — by running the bootstrap installer from the repo root. The block is `tier-1` per [Tier](SPEC.md#execution-and-trust): display, announce in one line, execute. ```bash bash install.sh ``` [`install.sh`](#installsh) ensures the prerequisites exist (a C toolchain, Node >= 18, an agent CLI — auto-installed on macOS where it can; any link it cannot auto-install fails loud with a one-line manual remediation), detects the agent hosts present on the machine, and installs the `seed-create`, `seed-install`, and `seed-audit` skills into each. The step-by-step contract is [Tooling is installed](#tooling-is-installed). ## Objects The named entities of the OpenSeed tooling — what exists on a machine after `## Dependencies` is satisfied — plus this repo's reference artifacts and the operational protocols the installed skills implement. The normative format grammar these contracts build on (folder structure, the [canonical H2 grammar](SPEC.md#structure), section modes, the tier model, the `ref/` convention) is defined in [SPEC.md](SPEC.md). ### install.sh - The bootstrap installer at the repo root — the artifact this SEED's `## Dependencies` runs. - Skills-only mode (`bash install.sh`): ensure prerequisites, then install the three [Installed skills](#installed-skills) into every detected agent host. Bootstrap mode (`bash install.sh `): additionally offer to launch an installed agent CLI pre-seeded with `/seed-install `; the URL is subject to [clone URL hygiene](#clone-url-hygiene) before it reaches agent context. - Curl-pipe-safe: `curl -fsSL https://raw.githubusercontent.com/plow-pbc/openseed/main/install.sh | bash` is the equivalent invocation on a machine without a clone. - Headless mode for orchestrators: the single env var `SEED_INSTALL_LAUNCH=none|claude|codex|agy|auto` pre-answers bootstrap mode's prompts without `/dev/tty` — `none` installs the skills, prints the `/seed-install ` snippet, and exits 0; a named/`auto` launch implies the agent's permission-bypass flag (an unattended launch stalled on a permission prompt never finishes). Unrecognized values fail loud before any work; unset, the script prompts interactively as before. - On macOS it auto-installs missing prerequisites where it can: Xcode Command Line Tools (headlessly), Node >= 18 via Homebrew — but only when Homebrew is already present; a Mac without Homebrew fails loud with a manual Node-install line — and Claude Code (via `npm install -g`) when none of `claude` / `codex` / `agy` is found. On other OSes nothing is auto-installed. Any prerequisite the script cannot auto-install fails loud with a one-line manual remediation. Agent login is never automated — the script only checks and instructs. ### Installed skills - The three agent skills (below) that the installer installs into each detected agent host's global skills directory. `install.sh` fetches them with the skills CLI (`npx skills add`) from this repo's published `main` on GitHub — not from the local clone — so a re-run always lands the latest published versions; their source in this tree is `ref/skills/`, per the [`ref/` convention](SPEC.md#conventions-were-trying). - Detected hosts: Claude Code, OpenClaw, and Hermes always; Codex, Gemini CLI, opencode, Cursor, GitHub Copilot, and Junie when their home directory exists. Antigravity (Agy) has no installer target and self-installs the skill at launch in bootstrap mode. #### ref/skills/seed-create/ - The agent-skill folder providing the reference implementation of [SEED authoring](#seed-is-authored). Targets any agent host that loads skills (Claude Code, Codex, Hermes, OpenClaw, Agy, etc.); the folder is itself a sub-SEED — see [ref/skills/seed-create/SEED#Purpose](ref/skills/seed-create/SEED.md#purpose). - Contains `SKILL.md` (the agent entry point), a local `README.md` (the skill's purpose), a local `SEED.md` (the skill's structural declaration), and any supporting files. #### ref/skills/seed-install/ - The agent-skill folder providing the reference implementation of [SEED installation](#seed-is-installed). Targets any agent host that loads skills; the folder is itself a sub-SEED — see [ref/skills/seed-install/SEED#Purpose](ref/skills/seed-install/SEED.md#purpose). - Contains `SKILL.md`, which delegates to the natural-language contract in `## Actions > SEED is installed` rather than restating it; a local `README.md` and `SEED.md` per the sub-SEED pattern; and `prepare-template.sh`, the **non-load-bearing** how-to template the installer adapts into a concrete prepare-script (secret collection + interactive logins + sudo-last — never non-secret values or validation; see [Preflight is rendered](#preflight-is-rendered) step 4). #### ref/skills/seed-audit/ - The agent-skill folder providing the reference implementation of [SEED auditing](#seed-is-audited). Targets any agent host that loads skills; the folder is itself a sub-SEED — see [ref/skills/seed-audit/SEED#Purpose](ref/skills/seed-audit/SEED.md#purpose). - Contains `SKILL.md`, a local `README.md` and `SEED.md` per the sub-SEED pattern, and the security checklist the skill enumerates: `audit-malicious.md` (the four malicious-intent checks). ### ref/knightwatch/ - Reviewer-posture reference artifacts (`review-priority.md`, `product-context.md`) for the knightwatch-reviewer bot. The canonical source for the `.knightwatch/` copies stamped into each new SEED by the [authoring flow](#seed-is-authored) and committed in published SEEDs. The contrast table inside is the PR-relevant distillation of the audit checklists under [ref/skills/seed-audit/](#refskillsseed-audit); edit the checklists first, re-distill here. ### ref/verify.sh - A deterministic bash implementation of the [structural conformance](SPEC.md#conformance) prompts. CI and non-AI callers get an exit-code answer without an agent. - Run from the repo root: `bash ref/verify.sh`. Accepts an optional target directory for verifying external SEED trees (used by the [authoring flow](#seed-is-authored)). ### ref/test_verify.sh - Acceptance test for `ref/verify.sh`'s target-dir mode. Uses the convention repo itself as the canonical conforming fixture and builds malformed siblings ad-hoc. ### ref/test_skill_links.sh - Acceptance test for cross-file markdown links and heading anchors across the convention's markdown surfaces (`SEED.md`, `SPEC.md`, `README.md`, every `ref/skills//{README,SEED,SKILL}.md`). Each `[text](path#anchor)` with a relative path MUST resolve to an existing file, and every `#anchor` — same-file, cross-file, or in a `https://raw.githubusercontent.com/plow-pbc/openseed/main/...` URL (mapped back to the repo-local file; other absolute URLs are skipped) — MUST resolve to a real heading in its target; it also enforces the skill-directory linking rule owned by [Cross-references](SPEC.md#conformance). Prefer the raw-URL form for OpenSeed content so agents can fetch raw markdown. ### ref/test_installer_manifest.sh - Acceptance test for the installer manifest — every `ref/skills//README.md` that advertises `bash install.sh` as its install path MUST have its skill dir name in `install.sh`'s `SKILLS_TO_INSTALL` array, so a new skill folder can't ship with the installer silently missing it. ### ref/skills/seed-install/requirements.sh - Deterministic implementation of the [Preflight is rendered](#preflight-is-rendered) action's table tier. Ships inside the seed-install skill bundle (alongside its `SKILL.md`) so `npx skills add` deploys it to the install host and the deterministic preflight tier actually runs. Traverses a SEED tree leaves-first, extracts each SEED's [Requirements table](SPEC.md#conventions-were-trying) into normalized records, dedups by `(kind, label)`, probes per-requirement status, and renders the preflight report — including the operator-touchpoint count against the [budget](#preflight-is-rendered). Also emits an env template (`--prompt`) naming the required vars — a CI/inspection aid; the runnable replay artifact is the [inputs file](#inputs-file). Modes: `--records`, `--status`, `--report`, `--prompt `. - It covers only the well-formed table tier; the lenient prose-inference tier is the [install action](#seed-is-installed)'s (agent's) job, so an untabled SEED contributes nothing here rather than erroring. ### ref/test_requirements.sh - Acceptance test for `ref/skills/seed-install/requirements.sh` — table extraction, leaves-first traversal + dedup, status probing, report rendering, env template, and prose-only tolerance. ### Install state machine The states an install attempt passes through. The agent SHOULD track its current state for diagnostics. The exit requirement (emit one terminal reason) lives in [SEED installation](#seed-is-installed) step 6. ```mermaid stateDiagram-v2 [*] --> received received --> cloning: clone mode received --> walking_deps: local / cwd mode received --> terminal: aborted cloning --> walking_deps: clone OK cloning --> terminal: failure cloning --> terminal: aborted walking_deps --> running_command: next shell block walking_deps --> walking_deps: sub-SEED / external SEED URL (recurse) walking_deps --> verifying: deps done walking_deps --> terminal: failure walking_deps --> terminal: aborted running_command --> walking_deps: command OK running_command --> terminal: failure running_command --> terminal: aborted verifying --> verifying: next prompt verifying --> terminal: failure verifying --> terminal: aborted verifying --> terminal: success terminal --> [*] ``` | State | Description | |---|---| | `received` | Target parsed; input mode determined (`clone` / `local` / `cwd`) per [input modes](#input-modes). | | `cloning` | Clone-mode only: `git clone` in progress. Skipped in local / cwd modes. | | `walking_deps` | Iterating `## Dependencies` entries; may recurse into sub-SEEDs and external SEED URLs. | | `running_command` | Displaying a `## Dependencies` shell block (or external non-SEED clone) and announcing it as a one-line summary, then executing. Returns to `walking_deps` on success. | | `verifying` | Running through `## Verification` prompts top-down. | | `terminal` | Install attempt complete. The terminal reason is one of the [terminal reasons](#terminal-reasons). Feedback dispatch (per [feedback reporting](#feedback-is-reported)) is a post-terminal side effect — it observes the terminal reason but does not extend the state machine. | ### Install report A JSON file written at `$REPO_ROOT/install-report.json` by [SEED installation](#seed-is-installed) — the canonical local record of one install attempt. It is a flat, five-field object and nothing else: ```json { "success": true, "duration_seconds": 137, "platform": "darwin/arm64", "os_version": "macOS 15.3", "hardware": "Apple MacBook Air (M3, 2024)" } ``` - **`success`** — boolean; `true` iff the install completed successfully end-to-end (the `terminal` reason was `success`), `false` on `failure` or `aborted`. - **`duration_seconds`** — integer; wall-clock seconds from the install **start** to the `terminal` state. The start differs by input mode (per the [install state machine](#install-state-machine)): in **clone mode** it is the `git clone` command itself — which runs *before* preflight, so clone-mode duration includes preflight; in **local/CWD mode** (no clone) it is the first Phase-1 `## Dependencies` action *after* preflight — or Phase 2's first root block if the SEED declares no SEED dependencies — so local/CWD duration excludes preflight. It exists only when a start timestamp was captured AND the attempt reached a writable `$REPO_ROOT`; a pre-start abort (local/CWD, before its first action), or a clone-mode failure before `$REPO_ROOT` exists, has no measured duration and writes no report (terminal reason only). - **`platform`** — string; `/`, lowercase (`uname -s` lowercased + `uname -m`), e.g. `darwin/arm64`, `linux/x86_64`. Always determinable. - **`os_version`** — string; human OS version. macOS → `macOS `; Linux → `PRETTY_NAME` from `/etc/os-release`; fallback `uname -sr`. Always set. - **`hardware`** — string or `null`; best-effort machine model. macOS → `system_profiler SPHardwareDataType` (`Model Name` + `Chip`), fallback `sysctl -n hw.model`; Linux → `/sys/class/dmi/id/sys_vendor` + `/sys/class/dmi/id/product_name`. `null` when undeterminable. The three environment fields are sniffed **once**, best-effort, alongside the start-timestamp capture (the environment is static during an install). Sniffing NEVER fails or aborts the install: an empty sniff degrades the field (`os_version` falls back to `uname -sr`; `hardware` becomes `null`). Properties: - **Single terminal write, symlink-safe.** The file is written once, on reaching the `terminal` state — for every outcome that reached a writable `$REPO_ROOT` with a captured start timestamp (success, failure, post-start abort). The five-field JSON is written to an **exclusive-created temp file with an unpredictable name** (`mktemp "$REPO_ROOT/.install-report.json.XXXXXX"` — O_EXCL, never following or racing a pre-existing path) and then **atomically `rename`d** over `install-report.json`; the existing report path is **never opened for writing**, so a malicious SEED that commits `install-report.json` as a symlink — or pre-creates the temp path — cannot redirect the write: the symlink is *replaced* by the rename, not followed to overwrite an arbitrary user-writable file. There is no per-step ledger and no per-step items; the outcome and duration are knowable only at the end (the environment fields are captured earlier, at install start, and held). An attempt that never reached a writable `$REPO_ROOT` — a pre-start abort, or a clone-mode failure before `$REPO_ROOT` exists — writes no report; it emits only its [terminal reason](#terminal-reasons). Once `$REPO_ROOT` exists on disk, and before the run could write anything there, any pre-existing `install-report.json` is **unlinked** (`rm -f`, which removes a symlink without following it) — so a rerun that crashes after the start timestamp but before this terminal write never leaves a stale prior-run report behind (a fresh clone has none to clear; this matters for local/CWD reruns). - **Single source of truth.** The post-terminal user-facing summary (per [install reporting](#install-is-reported)) reports the same fields; the feedback dispatch (per [feedback reporting](#feedback-is-reported)) uploads the file verbatim — alongside a separate sanitized `clone_url` field for attribution — on per-run user consent. - **Coarse by construction.** The report carries no paths, usernames, secrets, or per-step detail — only the five flat fields, of which `platform`/`os_version`/`hardware` are host-class descriptors (machine model and OS, not user-identifying). It is safe to paste or upload as-is; the control on transmission is the explicit per-run consent (see [feedback reporting](#feedback-is-reported)), not field-level stripping. The report stays flat — there is no nested `environment` object. - **SHOULD be gitignored** by SEED authors (`install-report.json` under `.gitignore`) so reruns don't accumulate report churn in the SEED's own git history. The operational mechanics — timing and environment capture, the single terminal write, and the feedback-upload rules — are owned by [ref/skills/seed-install/SKILL.md > Install report](ref/skills/seed-install/SKILL.md#install-report). ### Inputs file - The per-seed operator-inputs file at `${XDG_CONFIG_HOME:-$HOME/.config}/seed/.env`, where `` is `$REPO_ROOT`'s basename — the same identity the install itself uses, so a collision-suffixed install dir (`2` per the [default install location](#default-install-location)) gets its own inputs file, never another seed's — mode 600, inside a mode-700 `seed/` directory, OUTSIDE the SEED tree (secrets are never persisted in-tree). - Written by **two** producers, both using the same replace-by-key writer ([Preflight is rendered](#preflight-is-rendered) steps 3–4): the **prepare-script** for routed secrets, interactive logins, and sudo; the **agent** for non-secret values it collected through the question interface. Consumed by the install, which `source`s it so the declared env vars are present for every shell block. - Body is one `KEY=value` line per requirement, the value shell-quoted at write time (`printf '%s=%q\n'`) so sourcing the file is safe for arbitrary operator input, and any prior line for the same key is replaced rather than appended (the reference `put()` in `prepare-template.sh`); keys are exactly the env-var names the [Requirements table](SPEC.md#conventions-were-trying) declares — `satisfy`, plus env-shaped `bypass` tokens when the operator elects a bypass — the table is the schema; there is no separate inputs spec. The one non-table key is the `SEED_PREPARE_OK=` completion stamp the prepare-script's final statement appends (per [Preflight is rendered](#preflight-is-rendered)) — a **reserved** name: a table that declares `SEED_PREPARE_OK` as a `satisfy`/`bypass` token is malformed, and an installer assembling a prepare-script from it MUST refuse (a value row could otherwise shadow the stamp the installer reads as success). - Values never appear on argv and are never echoed. `rm ${XDG_CONFIG_HOME:-$HOME/.config}/seed/.env` forgets one seed's inputs; the next install re-collects. - Replay contract: `set -a; . ${XDG_CONFIG_HOME:-$HOME/.config}/seed/.env; set +a; ` is the fully-unattended re-run. ### Terminal reasons The three legal terminal reasons for an install attempt, emitted at [install](#seed-is-installed) exit. The install report's `success` boolean derives from the reason — `success` → `true`, `failure`/`aborted` → `false`. | Reason | When | |---|---| | `success` | All shell blocks under `## Dependencies` displayed, announced, and executed without error (agent-run, or operator-run via the [prepare-script](#preflight-is-rendered)); all `## Verification` prompts returned the expected answer. Surfaced external system requirements (which the agent does not run) are orthogonal — missing ones surface as `## Verification` failures, not as an absent execution. | | `failure` | An invoked command exited non-zero: a shell block or external non-SEED clone under `## Dependencies`, `git clone` in clone mode, a `## Verification` prompt that returned an unexpected answer, or a recursively-installed sub-SEED that terminated with `failure`. A **clone-mode failure before `$REPO_ROOT` exists** (the `git clone` itself fails, so there is no writable repo root) is a **no-report terminal failure** — it emits only this terminal reason; no five-field `install-report.json` is written (the on-disk write is scoped to attempts that reached a writable `$REPO_ROOT` with a start timestamp). | | `aborted` | The install stopped without a command's exit code: the target was invalid or `SEED.md` was unreadable, a required tool was missing so the agent could not attempt the step, the user interrupted the run, or a recursively-installed sub-SEED terminated with `aborted`. A **pre-start abort** — one that happens before the install-start timestamp is captured (e.g. local/CWD mode with no readable `SEED.md`) — emits only this terminal reason; **no five-field `install-report.json` is written** (there is no start to measure `duration_seconds` from, and the on-disk write is scoped to attempts that reached `$REPO_ROOT` with a start timestamp). | ## Actions The verbs performed BY the Objects above. Each entry's shape — definition, normative checklist or table — is defined in the [Actions section](SPEC.md#conventions-were-trying). [Tooling is installed](#tooling-is-installed) is this SEED's own install action; the remaining Actions are the convention-level contracts the installed skills implement. ### Tooling is installed Running `bash install.sh` from [`$REPO_ROOT`](SPEC.md#execution-and-trust) installs the OpenSeed tooling — this is what satisfying this SEED's `## Dependencies` does. 1. The operator (or the installing agent, `tier-1` per [Tier](SPEC.md#execution-and-trust)) runs `bash install.sh` from the repo root. 2. The script ensures the prerequisites: a C toolchain, Node >= 18, and an agent CLI. On macOS it auto-installs what it can — Xcode Command Line Tools headlessly, Node via Homebrew only when Homebrew is already present, Claude Code via `npm install -g` when no agent CLI is found; on other OSes, and for any link it cannot auto-install (e.g. Node on a Mac without Homebrew), it fails loud with a one-line manual remediation. Agent login is checked and instructed, never automated. 3. It installs the three [Installed skills](#installed-skills) into every detected agent host, overwriting any same-name skill (refresh-on-rerun semantics: a re-run picks up the latest skills). 4. In bootstrap mode (a `` argument), it offers to launch a detected agent CLI pre-seeded with `/seed-install ` — after the URL passes [clone URL hygiene](#clone-url-hygiene). 5. The install is confirmed by answering `## Verification`. ### Folder is read An agent (human or AI) absorbs a SEED-participating folder by reading its `SEED.md` top-down and recursing leaves-first through `## Dependencies`. 1. Open `/SEED.md`. 2. Resolve the `# Purpose` link and read the target `README#Purpose`. 3. For each **SEED dependency** in `## Dependencies` (sub-SEED links and external SEED URLs per [external SEED URLs](SPEC.md#dependencies)), recurse before continuing (leaves-first). Non-SEED entries — shell blocks, system requirements, external non-SEED clones — are read but not recursed into here; they belong to [installation](#seed-is-installed) Phase 2. 4. Read `## Objects` (named entities) and `## Actions` (verbs). ### SEED is authored An agent authors a new SEED by interviewing the user one question at a time, drafting both files in memory, getting explicit approval, then writing-verifying-committing in the new tree. 1. Interview the user one question at a time, applying the tier model at [Tier](SPEC.md#execution-and-trust): purpose, hardware/API/software dependencies, named objects, observable actions, what "verified" looks like. 2. Inspect the live system read-only to corroborate user answers. See [reconnaissance probes](#reconnaissance-probes) for rules. 3. Draft `SEED.md` and `README.md` with the canonical structure (one `# Purpose` H1 plus the [canonical H2 grammar](SPEC.md#structure)). Present the draft for user approval before writing. 4. On approval, `mkdir` the target path, run `git init`, write the files, then run the convention's structural conformance prompts (from [SPEC.md > Structural conformance](SPEC.md#conformance)) against the new tree — **before** the initial commit, so a verify failure does not leave a non-conforming commit in the new SEED's history. MAY shell out to `bash /ref/verify.sh ` as the deterministic implementation (without the explicit target arg, `ref/verify.sh` verifies the convention repo itself, not the new tree). The written tree also includes `.knightwatch/{review-priority.md,product-context.md}` stamped from [ref/knightwatch/](#refknightwatch), with `product-context.md`'s payload line auto-filled from reconnaissance (`tier-1`). 5. Once verify passes, create the initial commit. The agent MUST NOT push or create a remote repo; distribution is the user's choice. 6. NEVER include literal secret values in the drafted SEED. See [secret redaction](#secret-redaction) for the full ban list. A SEED authored this way is structurally indistinguishable from one written by hand. #### Reconnaissance probes The authoring flow uses its own confirmation gate (not the install-time display gate in [trust boundaries](#seed-is-trusted)): batch-confirm the full probe list once at `ref/skills/seed-create/SKILL.md > Step 2 — Reconnaissance sweep`, then execute sequentially; any later probe added outside the batch is confirmed individually. Inspection probes MUST NOT dump raw secret values into the agent's tool output — once a secret enters the conversation context, no later redaction step can recall it. Forbidden examples: `env` / `printenv` without a specific var name, `cat` of credential files (`~/.ssh/*`, `~/.aws/credentials`, `~/.netrc`), `docker compose config` (resolves env values), `git remote -v` / `git config --get remote.*.url` (HTTPS remotes often carry `user:token@` userinfo), auth-token-print commands (`gh auth token`, `aws sts get-session-token`, `gcloud auth print-access-token`). Use presence/name-only probes instead — `printenv VAR >/dev/null && echo set`, `test -f && echo present`, `env | awk -F= '{print $1}'`, `git remote` (without `-v`). #### Secret redaction Env vars matching `*_KEY`, `*_TOKEN`, `*_SECRET`, `*_PASSWORD`, `*_URL`, `*_URI`, `*_CONNECTION_STRING`, `*_DSN`; URI userinfo (`scheme://user:password@host/...`); paths under `~/.ssh/`, `~/.aws/credentials`, `~/.config/gh/hosts.yml`, `~/.netrc`; anything matching `sk-...`, `ghp_...`, `xox[abp]-...`, AWS `AKIA.../ASIA...`, JWTs. The agent MAY describe the requirement ("requires `OPENAI_API_KEY` in env") but never the value; if a probe result contains a secret despite the [reconnaissance probes](#reconnaissance-probes) rule, redact it (last 3 chars: `sk-...xY7`) before presenting. ### Preflight is rendered Before the [install action](#seed-is-installed) executes any `## Dependencies` block, the installer SHOULD render a **preflight requirements report** so the operator sees everything they must supply once, up front — the front-of-UX gate that makes an otherwise-interactive install a single shot. 1. Resolve the whole tree onto disk FIRST so no transitive requirement is missed: follow local sub-SEED links (`[Purpose](/SEED.md#purpose)` and bare relative paths) and shallow-clone every [external SEED URL](SPEC.md#dependencies) dependency into the cache. (The deterministic [`ref/skills/seed-install/requirements.sh`](#refskillsseed-installrequirementssh) only reads SEEDs already on disk; ensuring the tree is present is the installer's job — and it reuses these clones for the leaves-first install that follows. Exception: for a [remote-host dep](#remote-host-seed-dependency) the local clone is **discovery-only** — it feeds this requirements walk, while Phase 1 clones and installs that dep on its declared host.) Then traverse leaves-first and extract each SEED's requirements: from a [Requirements table](SPEC.md#conventions-were-trying) (or `### Inputs` table) when present — the deterministic tier — otherwise inferred best-effort from the `## Dependencies` prose. A SEED that yields nothing is skipped; the report degrades gracefully and never aborts on an imperfect SEED. 2. Dedup across the tree by `(kind, label)`, then render a grouped report (`hardware`, `account`/`auth`, `input`, `system`, `tool`, and a `DURING INSTALL` section for `in-flow` items) with `✓ have / ⚠ need / ⏳ during-install / ⚙ derived` status, a satisfy hint, provenance, and a hands-on estimate. The whole dependency graph SHOULD cost the operator **at most 3 touchpoints** — typed values, interactive logins, and pre-announced `in-flow` grants all count. The count is mechanical, a deliberate approximation: [`ref/skills/seed-install/requirements.sh`](#refskillsseed-installrequirementssh) `--report` totals the `need` + `in-flow` rows and flags overage — an upper bound on the `need` side (an agent-resolvable `need` row, e.g. a missing tool the install itself adds, still counts) and optimistic on `⚙` rows (one whose derivation and override both fail at step 3 becomes a real touchpoint); a pre-set env-shaped `bypass` on an `in-flow` row counts it `have` — the install will skip that grant — so graph growth surfaces in review rather than at an outside operator's first install. 3. Partition the unsatisfied `preflight` items, honoring pre-set env vars (never re-collecting a satisfied one). Items the installer can resolve itself (derivable values, defaults, probes) are resolved without asking the operator. A `derive` row's `satisfy` cell NAMES the fact to derive — prose, like any satisfy hint, never SEED-authored shell to execute: the installer resolves it with its own read-only probes (read a setting, list accounts, query the system), and repo-supplied shell runs only inside displayed `## Dependencies` / `## Verification` blocks — preserving the display-gate invariant during a preflight that is read-only-plus-collection. **Derive before collect:** a collected row is legitimate only when no probe on the install host can resolve the value. A host-derivable fact (the account's full name, a configured-accounts list, a signed-in handle, the system timezone) MUST be declared `phase: derive` — the fact to derive named in `satisfy`, an env-var override as `bypass` — never as an operator input. This partition is where rows are validated: for each `derive` row a pre-set override wins (never re-probing a satisfied row), otherwise the installer derives the named fact, and a row routes onward (into the classification/routing below) only when it is a true unknown — a derivable fact authored as an input is an authoring defect, surfaced by the touchpoint count in step 2. Each remaining **value-bearing row** (any row whose `satisfy` names an operator-supplied env var — whatever its `kind` — plus any row the operator elects to satisfy via its env-shaped `bypass`, the bypass vars being the collected values) is then **classified secret or non-secret by agent judgment** (no schema field — the installer reads `kind`, the `satisfy`/`bypass` env-var **name**, and `label`). A row is **secret** when ANY of: `kind` is `auth`/`account`; the env-var name matches the [Secret redaction](#secret-redaction) set (`*_KEY`, `*_TOKEN`, `*_SECRET`, `*_PASSWORD`, `*_URL`, `*_URI`, `*_CONNECTION_STRING`, `*_DSN`); or the `label` names a credential or a *private* resource (token, key, password, secret, a private URL/feed). The env-var-name test is load-bearing: a generic `label` like `ICS` on a `satisfy: LD_ICAL_URL` row still routes to the prepare-script on the `*_URL` match alone, so a private-feed URL never reaches the agent transcript. This name test deliberately **over-routes** — a genuinely public `*_URL`/`*_URI` (e.g. a public `APP_BASE_URL`) lands in the prepare-script too; that safety-over-UX default is intentional (the name alone can't tell public from private) and MUST NOT be relaxed with label-based exceptions, which would re-open the leak path the rule guards. **When classification is ambiguous** — a value that could be sensitive but matches none of the three tests (e.g. `satisfy: API_CREDENTIAL` / `LICENSE` / `WEBHOOK` with a `kind: input` and a generic `label`) — the agent MUST default to **secret** and route it to the prepare-script: a non-secret wrongly routed costs the operator one terminal entry, but a secret wrongly sent to the question interface leaks into the transcript irreversibly. The conservative default covers misses outside the suffix set, not just the URL family. Routing follows the classification: - **Secret rows — and every `auth`/`account` interactive login — are never collected in-conversation** (a secret typed at the agent lands in its transcript, violating the never-echo discipline): they route to the prepare-script in step 4. - **Non-secret rows are collected by the agent up front** through the host's question interface (`tier-3` per [Tier](SPEC.md#execution-and-trust) — an operator-supplied value is open prose, so it is `tier-3`/`open-prompt`, not a closed `tier-2` choice; AskUserQuestion on Claude Code, the equivalent on other hosts), within the ≤3-touchpoint budget. The agent validates each answer **inline** against the consuming SEED's [`## Verification`](#seed-is-verified) checks (it holds the value), re-asks on failure, then writes the validated value to the [inputs file](#inputs-file) itself with the same replace-by-key writer the prepare-script uses (drop any prior `KEY=` line, then append `printf '%s=%q\n'`). A non-secret value belongs nowhere near the prepare-script. Pre-announce the remaining `in-flow` items (with their `bypass`); do NOT collect them now. 4. Generate the **prepare-script** — the user-run half of the two-artifact preflight. The installer ASSEMBLES (never invents) a script — adapting the non-load-bearing reference at [`ref/skills/seed-install/`](#refskillsseed-install)'s `prepare-template.sh` (a how-to pattern, not a sourced library) — from already-committed pieces: one silent `read -s` prompt per routed **secret** row (writing each to the [inputs file](#inputs-file) under its declared env-var name — `satisfy`, or the elected `bypass` tokens — with validation kept to non-empty only — `label` is human-readable text, not schema to parse; **non-secret value rows never appear in the script** — the agent collected them in step 3 via the question interface), each interactive-login `satisfy` command verbatim, and — verbatim from `## Dependencies`, in the same leaves-first order the install itself would run them — only those shell blocks that require **interactive-password `sudo`**. The minimality rule is normative: the prepare-script contains ONLY what cannot run without the operator's shell — secret/key collection, interactive logins, interactive-password `sudo`. Interactive-password `sudo` is prepare-script work whether the privileged step runs locally OR on a [remote-host dep](#remote-host-seed-dependency): when an up-front probe finds a required system package missing on a remote-host dep AND that host grants no passwordless sudo, the prepare-script performs the install over `ssh -t sudo …` — one interactive remote-sudo entry, in the same up-front run that does the dep's `ssh-copy-id` — so the later dependency walk finds the prereq satisfied and runs unattended, NOT a mid-install hand-back of a manual root command. Passwordless `sudo -n` and every unprivileged step belong to the agent-run install, never to the prepare-script. The assembled script MUST be generated fail-loud (`set -euo pipefail`), MUST be displayed in full before the operator runs it (the display gate, applied to the bundle), MUST run as the operator with internal targeted `sudo` (never wholesale under `sudo` — the inputs file must stay user-owned), and on re-run MUST skip value keys already present in the inputs file. Blocks the prepare-script ran MUST NOT be re-executed in Phase 2 — but only after the installer confirms the script exited 0, via the completion stamp (below); inputs-file value keys alone are not success. On a failed run the install stops rather than silently re-running privileged steps. **Validation is the agent's, and runs before any `sudo`.** After the operator runs the collection half, the agent validates each collected secret against the consuming SEED's [`## Verification`](#seed-is-verified) checks via a **non-echoing probe** — source the inputs file in a subshell, run the check, print only OK/FAIL (suppress the command's stderr, e.g. `curl … 2>/dev/null`, which can itself name a secret URL), never the value; on FAIL clear that key and re-hand-off. Interactive-password `sudo` runs only after validation passes: when a run has *both* a validatable secret *and* an interactive-`sudo` block, split the prepare-script into a **collection half** (secrets) and a **privileged half** (sudo), handing off the privileged half only once the probe passes. The script itself does NOT validate format or reachability — those deterministic checks (a single-line regex, a `curl` reachability probe) accreted here and broke; keep them out, they are the agent's job above. 5. From the resolved answers, the installer MAY emit a **single-shot prompt** — `source` the [inputs file](#inputs-file), then the install invocation — so the operator (or an agent) can replay the whole install unattended. The prompt itself carries no values; real ones live only in the operator-controlled inputs file, never echoed or written into the SEED tree. In addition to step 4's assembly rules, the assembled prepare-script and its hand-back MUST satisfy: - It MUST initialize the inputs surface before any body block: create the mode-700 `seed/` config directory and the mode-600 [inputs file](#inputs-file) up front (under `umask 077` so no wider-mode window ever exists; idempotent, re-asserting `chmod 700`/`600` on a pre-existing surface so a wider-mode leftover is repaired), so a script with NO routed secret rows — interactive logins or `sudo` blocks only — still has the file its final stamp append writes to, with the right modes. It MUST begin `#!/usr/bin/env bash` and prompt using only the bash `read` builtin, silent for **every** routed **secret** row (`read -r -s -p`, per step 4): an agent-inline shell's output lands in the transcript, so a terminal-echoed secret would leak there. (Non-secret rows are not in this script at all — the agent collects them via the question interface per step 3 — so there is no non-secret prompt to silence here.) The installer assembles from this vocabulary; it never invents other-dialect syntax (zsh `read "var?prompt"` under bash is the canonical failure). - It MUST open with a fail-loud TTY guard, e.g. `[ -t 0 ] || { echo "This shell has no TTY — run this script in a regular terminal window." >&2; exit 1; }` — a silent `read -s` with no controlling terminal otherwise dies with a cryptic ENXIO ("device is not configured"). - The hand-back MUST name the exact host the script runs on — the resolved **install host** (where the agent/install runs and the mode-600 [inputs file](#inputs-file) lives, and from which any SSH to a remote-host dep originates) — and MUST direct the operator to run the script in a real terminal **on that install host**; a [remote-host dep](#remote-host-seed-dependency)'s privileged blocks are not run there but transported over `ssh -t sudo …` from it (per step 4). The script never runs on a remote-host dep's machine — the operator there could not supply the install's secrets into the local inputs file. The agent's own shell has no TTY (`[ -t 0 ]` is false there, so a `read -s` reads zero characters), so it cannot host the script's `read -s` secret prompts or interactive-password `sudo` itself — an agent-inline `!`-prefix shell (e.g. Claude Code's `! bash `) MUST NOT be relied on for secret/sudo input; the TTY guard is the arbiter, and on the agent-inline path it trips, sending the operator to a regular terminal window. - For each routed **secret** row, the script MUST print the row's acquisition guidance (when the SEED declares any) before prompting, and after each entry MUST confirm receipt non-silently without echoing the value (e.g. `received, 42 chars`) and MUST reject an empty entry (re-prompt or exit non-zero), so a failed paste fails loudly instead of installing a blank secret. - Its final statement — reached only on success under the mandated `set -euo pipefail` — MUST append the completion stamp `SEED_PREPARE_OK=` to the [inputs file](#inputs-file): the nonce is minted by the installer at hand-off — MUST be unique per hand-off and alphanumeric (e.g. hex from `/dev/urandom`), so it can neither collide with a prior hand-off's nor break the file's source-safety as the one line not written via `%q` — and baked into the script (a re-run is a fresh hand-off: new nonce, new script; the installer reads the LAST stamp line — the same value `source` yields). The stamp is the machine-checkable exit-0 evidence — distinct from value keys a partial run may have left — and the ONE completion contract for both hand-off paths, inline `!`-prefix shell and a separate terminal window. The agent detects completion by **polling** the [inputs file](#inputs-file) for a line matching `^SEED_PREPARE_OK=$` (this hand-off's nonce) and auto-continues the moment it appears — never the operator relaying success: the script's closing line is informational (`DONE — the agent will pick this up`), not a relay instruction. The script always runs on the install host (above), so the stamp lands in the local inputs file and the poll reads it directly — including for a [remote-host dep](#remote-host-seed-dependency), whose privileged blocks the script transports over SSH but whose stamp is still written locally. Polling the one-line stamp detects success in about a second without reading any collected value. No stamp matching this hand-off's nonce — however the run is reported — is the failed-run case above: stop, regenerate the prepare-script, and hand off afresh. `SEED_PREPARE_OK` is reserved ([inputs file](#inputs-file)): an installer assembling a prepare-script MUST refuse a tree whose Requirements rows claim it (the no-rows path assembles no script and reads no stamp, so nothing detonates there). The preflight is read-only-plus-collection — it never installs (the prepare-script's privileged blocks are the seed's own front-loaded `## Dependencies` steps, run by the operator). It precedes Phase 1 of the [install action](#seed-is-installed). ### SEED is installed An agent installs a SEED at `` by traversing the [install state machine](#install-state-machine) — resolving the target to `$REPO_ROOT`, walking `## Dependencies` recursively leaves-first with display-and-announce per-block execution, then answering `## Verification`. On exit, the agent MUST emit exactly one [terminal reason](#terminal-reasons). 1. Resolve `` to a `$REPO_ROOT` on disk (see [input modes](#input-modes) below). 2. Read `/SEED.md`. 3. **Phase 1 — recurse into every SEED dependency** under `## Dependencies` (sub-SEED links and external SEED URLs per [external SEED URLs](SPEC.md#dependencies)). Install each one first by repeating this procedure against it. Leaves-first: all transitive SEED deps complete before any root-level shell runs. A dependency MAY declare a remote install host — see [remote-host SEED dependency](#remote-host-seed-dependency). 4. **Phase 2 — execute every remaining `## Dependencies` entry** (shell blocks, external non-SEED clones, system requirements; blocks the [prepare-script](#preflight-is-rendered) already ran are not among them). Each shell block AND each external non-SEED clone command MUST be displayed in full and announced as a one-line summary (`tier-1` per [Tier](SPEC.md#execution-and-trust)) before execution. The user's invocation of this action is the single up-front consent — no per-block confirmation is requested. System requirements are surfaced to the user as text (the SEED MAY provide commands but does not auto-run system-wide installs). 5. Run [verification](#seed-is-verified) against the root SEED. 6. Reach the `terminal` state (see [install state machine](#install-state-machine)); emit a [terminal reason](#terminal-reasons). Then emit the install summary per [install reporting](#install-is-reported) — the install-report fields (`success`, `duration_seconds`, `platform`, `os_version`, `hardware`) for a post-start attempt (a pre-start or pre-`$REPO_ROOT` outcome shows only the terminal reason); on `success`, a suggested next action (auto-run-if-safe) follows as a separate post-success step. **After** the summary, dispatch the feedback report per [feedback reporting](#feedback-is-reported) — that section owns the firing rules (root-only, clone-mode-only, per-run consent, and the upload payload — the flat report plus a sanitized `clone_url` field alongside it). The agent captures a start timestamp — and sniffs the three environment fields (`platform`, `os_version`, `hardware`) — at the install **start**: in clone mode the `git clone` command itself (which runs *before* preflight), in local/CWD mode the first Phase-1 `## Dependencies` action *after* preflight (or Phase 2's first root block if the SEED declares no SEED dependencies). Once `$REPO_ROOT` exists on disk and before anything could be written there, it **unlinks any pre-existing `install-report.json`** (`rm -f` — removes a symlink without following it), so a rerun that crashes before the terminal write cannot leave a stale prior-run report in place. On reaching the `terminal` state — **provided the attempt reached a writable `$REPO_ROOT` on disk AND captured that start timestamp** — it writes [`install-report.json`](#install-report) **once**: the flat five-field record (`success`, `duration_seconds`, `platform`, `os_version`, `hardware`) is emitted to an **exclusive-created temp file with an unpredictable name** (`mktemp`, O_EXCL) in `$REPO_ROOT` and **atomically `rename`d** over the report path (never opening an existing path at either the temp or the destination, so a committed `install-report.json` symlink is replaced, not followed). An attempt that did not reach a writable `$REPO_ROOT` — a pre-start abort, or a clone-mode failure before `$REPO_ROOT` exists — writes no on-disk report; it emits only its [terminal reason](#terminal-reasons). There is no per-step ledger. The post-terminal feedback dispatch and the user-facing summary in step 6 both derive from this terminal write. Order: leaves-first, root-last (Phase 1 fully completes before Phase 2 starts). `ref/skills/seed-install/` is the reference agent-skill implementation of this action. #### Remote-host SEED dependency A SEED dependency MAY declare that its install host is a **different machine reachable over SSH** — a `### Requirements` `hardware` row (or `### Hardware` prose) naming the host, with the target satisfied by an operator-supplied `user@host` env var. Remote-host deps are [external SEED URLs](SPEC.md#dependencies) only (a sub-SEED link has no clone URL to materialize remotely). The Phase-1 recursion for such a dep is a normal SEED install in every respect except *where the bytes execute*: - Every SSH invocation below — the reachability probe included — carries `-o StrictHostKeyChecking=accept-new`: a never-seen host key is trusted on first contact (TOFU) so an unattended run doesn't stall; a *changed* key still hard-fails. - The installer MUST validate the target against the `user@host` allowlist (`[A-Za-z0-9_.-]` only, neither side leading `-` — closes ssh option-injection), verify reachability first (`ssh -o BatchMode=yes -o ConnectTimeout=10 -o StrictHostKeyChecking=accept-new -- true`), and fail fast if unreachable. - The dep is cloned **on the remote host** at the cache location from [default install location](#default-install-location); that clone is the dep's `$REPO_ROOT`, and its [`install-report.json`](#install-report) is written there (the report lives with the install). The parent does NOT recover the child's outcome from that file — the five-field report is success-only and cannot distinguish `failure` from `aborted`. Instead the parent observes the child's **emitted [terminal reason](#terminal-reasons) over the SSH install transport** (the child install's exit signal on the wire), preserving the full `success`/`failure`/`aborted` distinction for the leaves-first round-trip. - Every shell block — including shell run to answer `## Verification` prompts (the prompts themselves are read and answered by the local agent as usual) — executes over `ssh -- bash -l -s` — a login shell, so the session sees the remote user's full profile environment; the block body arrives on stdin. The [display-and-announce gate](#seed-is-trusted) applies **locally, before transport** — SSH is just the wire. - The dep's declared env inputs are projected as an `export`-assignment preamble (`printf %q`-quoted) prepended to each block's stdin — never on argv, which is world-readable via `/proc//cmdline` and `ps` on either machine. The preamble's first line is `set +x`, so xtrace enabled by the remote login profile cannot echo the assignments. The display gate shows the **SEED-authored block body only**; the preamble is displayed redacted — env-var names, never values — per the [secret redaction](#secret-redaction) discipline. - The remote host's privileged system-package setup (a missing prerequisite needing root, e.g. `apt install nodejs`, when the host grants no passwordless sudo) is folded into the [prepare-script](#preflight-is-rendered) over `ssh -t … sudo …` — run in the same up-front interactive moment as the dep's `ssh-copy-id` — not surfaced as a mid-install hand-back. - A standalone install of such a SEED — run on the declared host itself — treats the requirement as already met and executes locally; the remote transport applies only when a parent SEED recursion supplies the target. Worked example: [`seed-life-dashboard`](https://github.com/plow-pbc/seed-life-dashboard) declares its Raspberry Pi kiosk SEED (`seed-life-dashboard-viewer`) as a dep whose host is satisfied by `LD_PI_SSH_TARGET`. #### Input modes The agent accepts `` in one of three input modes: - **Clone mode** — a git URL (`https://...` or `git@host:...`). The agent clones to `$REPO_ROOT` at the [default install location](#default-install-location) (`${SEED_HOME:-$HOME/seeds}/`), announcing the target and proceeding without a prompt — no "where to clone?" question. The operator overrides the parent dir per-run via `$SEED_HOME`, or interrupts (Ctrl-C) to stop and re-run with a different target. See [clone URL hygiene](#clone-url-hygiene) for URL validation rules. - **Local mode** — an existing path containing a `SEED.md`. No clone; the agent `cd`s into the path and treats it as `$REPO_ROOT`. - **CWD mode** — empty target or `.`. The agent treats the current working directory as `$REPO_ROOT`. #### Default install location The SEED never prescribes where it lands, but the *installer* SHOULD default to a predictable, overridable layout so `seed install ` is reproducible and scriptable rather than a per-run guess: - **Root SEED — the durable store** → `${SEED_HOME:-$HOME/seeds}/`. An installed SEED is worked-in, not an opaque artifact — it carries `## Verification`, `## Feedback`, and an optional `## Open Items` the user returns to — so the store is *visible* (`~/seeds`), not hidden under `~/.local/share`. `$SEED_HOME` overrides the parent dir; on a path collision the installer suffixes `2`, `3`, …. - **Transitive external-SEED clones + scratch — the cache** → `${XDG_CACHE_HOME:-$HOME/.cache}/seed///`, keyed on the full [external SEED URL](SPEC.md#dependencies) slug so two SEEDs that share a `` but differ by host or org don't collide on one cached tree. Disposable and reconstructible; this is exactly the `$HOME/.cache//`-style dep-owned path the [`$REPO_ROOT`](SPEC.md#execution-and-trust) rule already blesses. - **Software a SEED actually installs** (a `brew` / `npm` / build step) lands in that tool's own canonical home — the installer neither owns nor relocates it. This is an installer convenience, not a SEED-level prescription: "where to clone?" is **not a prompt** — the installer announces the resolved default target and proceeds (`tier-1` per [Tier](SPEC.md#execution-and-trust): display + announce, no confirmation), so clone mode is as unattended as local/CWD mode. The operator overrides the parent dir per-run via `$SEED_HOME`, or interrupts (Ctrl-C) to stop and re-run with a different target — the same safe-default-not-a-stop posture the rest of the install already follows. #### Clone URL hygiene Only a plain `https://host/org/repo[.git]` or SSH (`git@host:org/repo.git`) URL may reach a clone — `git clone ` puts the whole URL into process argv (visible via `/proc//cmdline` and shell history), and userinfo, query, and fragment are the canonical carriers of credentials and session-scoped identifiers. A URL carrying userinfo (`user:token@host/...`) MUST be rejected: the agent asks for the plain form and relies on the user's git credential helper for auth. Query (`?...`) and fragment (`#...`) components are stripped before the URL reaches argv or agent context — the installer normalizes to the clean repo URL and clones that. A code-host web view of a file or directory (`https://host/org/repo/blob//`, `/tree/`, `/raw/`, or GitLab's `/-/blob/` form) names the repo unambiguously and is normalized the same way: truncated to the repo root before validation — only for the single-level `org/repo` shape the rule above accepts; GitLab subgroup paths remain rejected. (Rejecting query/fragment-bearing URLs outright instead of normalizing is future hardening, deferred.) The same hygiene applies to external non-SEED clone commands under `## Dependencies` (Phase 2): `argv` exposure is identical, so the agent MUST reject a userinfo-bearing URL and strip query/fragment the same way. #### Deploy safety A `## Dependencies` step that attaches to or deploys over a named **remote, shared** resource — a hosting project, a database, a DNS record — can silently clobber an unrelated production system that happens to share the name (observed near-miss: a fresh install's `vercel link --yes --project ` auto-attached to a pre-existing production project of the same name; the next step would have deployed the test install over it). The contract is safe-by-construction and fully autonomous — the install never pauses for it: - **List before creating.** Before any link/attach/deploy to a named remote resource, the agent MUST determine whether the target already exists (e.g. the provider's project list before `vercel link`) and whether *this install* created it. This is a runtime safety decision; the flat five-field [install report](#install-report) does not record it. - **Never attach to a resource this install did not create and was not given.** A pre-existing target this install did not itself create MUST NOT be attached to or deployed over — and the agent MUST NOT pass `--yes` / `--force` / `--confirm` to do so. Instead the agent creates a NEW, uniquely-named resource (e.g. `-`), preferring a personal scope over a shared team scope, announces the substitution like any other block (`tier-1` per [Tier](SPEC.md#execution-and-trust) — display + announce, no prompt), and continues — the runtime list-before-create decision, which the flat five-field report does not record. - **Targeting an existing resource is a preflight decision, never a runtime one.** A SEED whose install legitimately deploys into an existing resource MUST surface the target as a [Requirements table](SPEC.md#conventions-were-trying) `input` row with `phase: preflight` (an env var, e.g. `SEED_DEPLOY_TARGET`), collected up-front per [Preflight is rendered](#preflight-is-rendered) — never `in-flow`, which would reintroduce the mid-run interaction this contract bans (a `confirmed-existing` outcome). Absent that input, the create-new rule above applies unconditionally — including on a rerun of a partially-completed install, which simply creates fresh uniquely-named resources (there is no resume from a partial install). The guard sits before the link/attach — not after the publish that mutates a live target (`deploy --prod`), which is the irreversible step. Safety comes from the safe default, not from a prompt — the one-shot unattended path is preserved by construction. #### Harness prompts The SEED's no-prompt contract binds the SEED and the installer — the **agent harness may still raise its own prompts** (permission requests, plugin-install hints, skill-trust confirmations) depending on its permission mode. A fully unattended run therefore needs the harness's permission-bypass posture — the same posture a named or `auto` `SEED_INSTALL_LAUNCH` launch already implies for the bootstrap launch (see [install.sh](#installsh)). In interactive modes, harness prompts pause the install rather than break it: the install resumes across them (Claude Code's plugin-install hints in particular self-dismiss after 30 seconds, at most once per session). ### SEED is verified An agent answers every natural-language prompt under `## Verification`; all MUST return the expected answer for the SEED to be considered installed. 1. Read each prompt under `## Verification` top-down. 2. For any prompt that asks the agent to run shell, display the shell in full and announce it as a one-line summary before executing (`tier-1` per [Tier](SPEC.md#execution-and-trust)). Same pattern as `## Dependencies` — Verification is normatively read-only as an authoring contract, but the agent cannot prove that from the source. 3. Answer each prompt; all MUST return the expected answer. 4. For CI or non-AI callers, the SEED MAY ship `ref/verify.sh` (see [ref/](SPEC.md#conventions-were-trying)) as a deterministic bash implementation of the same prompts. ### SEED is audited An agent runs a security-only audit of a SEED-conforming repo against the four malicious-intent checks and writes a single flat JSON report to the audited repo's root. 1. Read `audit-malicious.md` end-to-end (under [ref/skills/seed-audit/](#refskillsseed-audit)). 2. Run each of the four checks against the working tree (read-only), preferring `grep` / `find` / `awk` over eyeball-reading. 3. Set each check's boolean: `true` if no violation was found, `false` if at least one violation is present. 4. Write `audit-report.json` at the audited repo's root **symlink-safely** (write to an exclusive-created temp with an unpredictable name — `mktemp "/.audit-report.json.XXXXXX"`, O_EXCL — then `mv -f` it over the report path; the existing path is **never opened for writing**, so a committed `audit-report.json` symlink is replaced by the rename, not followed to clobber an arbitrary user-writable file) — a flat object with exactly the four boolean keys `no_secret_exfiltration`, `no_prompt_injection`, `no_destructive_operations`, `no_hidden_execution` (where `true` means clean). No header, per-item objects, or remediation text. Validate it parses as JSON before exit. 5. Print the four booleans to stdout. `ref/skills/seed-audit/` is the reference agent-skill implementation of this action. The audit is normatively read-only on the audited tree (the report file is the only write). ### SEED is trusted The agent enforces trust boundaries section-by-section. Repo-supplied shell is high-trust (`tier-1` per [Tier](SPEC.md#execution-and-trust)); descriptive sections are low-trust. The user's invocation of [install](#seed-is-installed) is the single up-front consent for all shell blocks the SEED ships — the agent displays each block and a one-line summary, then runs (blocks routed to the [prepare-script](#preflight-is-rendered) keep the display but run as the operator). | Section | Trust | Agent obligation | |---|---|---| | `## Dependencies` shell | high | Display each block in full, print a one-line summary (10–20 words: what runs and why), execute (`tier-1` per [Tier](SPEC.md#execution-and-trust)). No per-block confirmation. Interactive-password `sudo` blocks routed to the [prepare-script](#preflight-is-rendered) keep the display; the operator executes. | | `## Verification` shell | high | Same display + announce + execute pattern as `## Dependencies`. The read-only contract is an authoring obligation, not a runtime check. | | `## Objects` | low | Descriptive only; never executed. | | `## Actions` | low | Descriptive only; never executed. | The display of each block is the user-facing review surface — a malicious or mistaken SEED author's shell appears verbatim on the user's terminal before it runs. The user's act of invoking [install](#seed-is-installed) is the single up-front consent; the display gate is the visibility invariant. ### Feedback is reported The agent uploads at most one feedback report per install attempt. The feedback dispatch IS the [install report](#install-report) upload: the finished `install-report.json` is POSTed in full to the endpoint, alongside a separate sanitized `clone_url` field for attribution (see [Payload](#payload)) — there is no separate report *document*, only the flat file plus that one identity field. #### Trigger - Offers exactly once per install attempt that reaches the `terminal` state (see [install state machine](#install-state-machine)); the report's `success` boolean carries the outcome. - Offers only for the **root** SEED of the install — the one the user passed to `Install `. Transitively-installed sub-SEEDs are silent. - Offers only in **clone mode** (see [input modes](#input-modes)) — the upload is scoped to installs of remote SEEDs. Local mode and CWD mode skip the upload entirely; a local tree is the user's own working state, not a remote SEED's install record. - The agent MUST NOT offer if the root SEED's `## Feedback` section is absent or its body is `(none)`. #### Body resolution Reading the root SEED's `## Feedback` body (whitespace-trimmed): 1. Body is `(none)` → no offer. 2. Body is `(default)` → agent offers to upload to plow's default endpoint (`https://seeds.plow.co/api/v1/upload-install-report` until otherwise specified). 3. Any other body → no offer. The agent SHOULD log a one-line warning to stderr. #### Consent — opt-in per-run offer - After the install reaches `terminal`, the agent offers the upload once per eligible run, naming the destination and stating that the five-field `install-report.json` — plus the sanitized clone URL alongside it, for attribution — will be sent. The upload happens ONLY on an explicit yes (`tier-2` per [Tier](SPEC.md#execution-and-trust)); no answer, no, or a non-interactive run = no upload. - The offer is suppressed entirely — not even asked — by any of: - Env var `SEED_FEEDBACK=off` for the current shell session. - `~/.config/seed/feedback.json` containing `{"enabled": false}` — suppresses globally for this machine. - The root SEED's `## Feedback` body being `(none)` or absent — author-side / privacy-default opt-out. - There is no auto-send and no remembered "always yes": consent is asked fresh each eligible run — the report leaves the machine only on an explicit yes, however small it is. #### Payload - The payload is the install report PLUS a sanitized seed-identity field alongside it: [`install-report.json`](#install-report) as written on disk at `$REPO_ROOT`, POSTed verbatim as a multipart file field, accompanied by a **separate `clone_url` form field** carrying the sanitized canonical clone URL (clone-URL hygiene applied — no userinfo, query, or fragment). The report file itself is unchanged and carries no identity; the identity travels *alongside* it so the backend can attribute the upload to the producing SEED. - The reference upload is `curl -X POST -F "install-report.json=@install-report.json" -F "clone_url="` run from `$REPO_ROOT`, where `` resolves per [body resolution](#body-resolution). - The report is five flat fields (`success`, `duration_seconds`, `platform`, `os_version`, `hardware`) carrying no paths, usernames, secrets, or per-step detail — only coarse host-class descriptors (machine model and OS). Seed identity is **not** in the report; it rides alongside as the `clone_url` field (the `upload-install-report` endpoint resolves the producing SEED from the request's url / owner+name). There is nothing to redact field-by-field; the control on what leaves the machine is the explicit per-run consent above. #### Failure modes - Feedback failures (network, 4xx/5xx, timeout, malformed body) MUST be silently dropped. - Reporting failures MUST NOT propagate to the install outcome. A user whose install succeeded but whose report failed to transmit MUST see a successful install. - No retry queue or offline buffering. Lost reports are lost. ### Install is reported After the install reaches `terminal` (see [install state machine](#install-state-machine)), the agent emits a brief summary to the user. For a **post-start terminal attempt** (one that reached a writable `$REPO_ROOT` and wrote [`install-report.json`](#install-report)), the summary reflects the report's fields (`success`, `duration_seconds`, `platform`, `os_version`, `hardware`). An attempt that wrote **no report** — a pre-start abort, or a clone-mode failure before `$REPO_ROOT` exists — summarizes only its terminal reason (no duration, environment, report path, or upload). There is no per-step action summary — the report is a single terminal write, not a per-step ledger. The shape otherwise depends on the [terminal reason](#terminal-reasons). #### On `success` 1. **Outcome.** State `success: true`, the `duration_seconds`, and the captured environment (`platform` / `os_version` / `hardware`) — e.g. "Installed successfully in 137s on macOS 15.3 (darwin/arm64), Apple MacBook Air (M3, 2024)". 2. **Suggested next action.** Suggest one concrete command the user can run in their agent to confirm the SEED is functioning end-to-end. Derive from, in priority order: 1. An explicit post-install hook in the root SEED's `## Actions` — e.g., an Action whose body says "after `## Verification` completes during a fresh install, offer to run this". 2. A `## Hello world`, `## Quickstart`, or `## Demo` block in the root `README.md`. 3. The most representative Action in `## Actions` — the one that exercises the SEED's primary capability end-to-end. 3. **Auto-run if safe.** If the suggested command requires NO user-only step — no manual login, no fresh terminal, no system restart, no human-in-the-loop decision (e.g., editing a config file the agent can't write, completing an OAuth flow in a browser, entering a password into a TUI prompt) — the agent SHOULD run it itself immediately and report the result inline. Otherwise print the command for the user to run and stop. #### On `failure` or `aborted` A **post-start** `failure` or `aborted` states the same five fields — `success: false`, the `duration_seconds`, and the captured environment (`platform` / `os_version` / `hardware`) — plus, as the outcome-specific addition, the terminal reason reached. A **pre-start abort** or a **clone-mode failure before `$REPO_ROOT` exists** wrote no report (see [Install report](#install-report)): it states only the terminal reason (`aborted` / `failure`) — no `duration_seconds`, no environment, no `install-report.json` path, and no upload. Either way: no suggested next action, no auto-run. The user decides whether to retry or investigate. #### Failure modes (of the reporter itself) - A reporter failure (e.g., the agent crashes after `terminal` but before emitting the report) MUST NOT change the terminal reason that was emitted. The user's takeaway about whether the install succeeded is the terminal reason, not whether the report was rendered. ## Verification Verification is a sequence of natural-language prompts the agent reads and answers — assertional and read-only per the [Verification section](SPEC.md#verification). Here they verify the install: that the tooling this SEED's `## Dependencies` installs is present and usable. (Structural conformance of SEED trees is specified separately at [SPEC.md > Structural conformance](SPEC.md#conformance); its deterministic implementation is [`ref/verify.sh`](ref/verify.sh).) 1. **Skills are on disk.** For each agent host `install.sh` reported installing to, list its global skills directory (e.g. `~/.claude/skills/` for Claude Code). Do `seed-create/`, `seed-install/`, and `seed-audit/` directories exist, each containing a `SKILL.md`? Expected: yes. 2. **Skills are intact.** Does each installed `SKILL.md` open with frontmatter declaring the skill's `name` and `description` (what an agent host needs to load and trigger it)? Expected: yes. 3. **Skills are invocable.** In at least one detected agent host, do the three skills appear in the agent's skill listing (e.g., `/seed-create`, `/seed-install`, and `/seed-audit` are offered)? Expected: yes. ## Feedback (default) ## Open Items - Demo video has not been recorded. - No CI. macOS/BSD awk portability of `ref/verify.sh` is exercised only on the author's dev machine; a `.github/workflows/test.yml` running `just test` on `ubuntu-latest` and `macos-latest` is the planned remedy and tracked separately.