# snappy-os — program.md

The schema. This file + `sources/` are the only things a human owns. Everything under `state/` is agent-owned and rewritable. A fresh agent in a supported runtime reads this file + `state/index.md` and executes the system. One inject hook, no MCP, no daemons.

## 1. What snappy-os is

An agent operating system — a per-turn context layer that makes any agentic runtime (Claude Code, Codex CLI, Gemini CLI, openclaw, Cursor, Windsurf) execute the same verbs the same way across every machine one person owns. Local-first, seed-not-service: `npx snappy-os init` is a one-time scaffolder, like `create-react-app` or `degit`. After init, the user owns their tree completely. The product is the **self-correcting PID loop** the files encode: a skill gets invoked → the agent hits a gap → the agent edits its own loader → the next agent inherits the sharpened loader.

## 2. The four parts

- **`program.md`** (this file) — the system contract. Invariants only; no implementation.
- **`state/skills/<name>.md` + `<name>.agents.md`** — prose reference + per-turn loader. The loader rides along when `<name>` appears in a prompt; the `.md` is read on demand.
- **`state/lib/<name>.ts`** — typed backing code imported by skills and sidecars. When present, its exported function surface IS the skill's action scope. Prose-only skills (coordination, meta) don't need a lib.
- **Runtime hooks** — the operational mechanism that enforces every rule here on every turn. Without them, program.md is just a document. Hook points: `SessionStart`, `UserPromptSubmit`, `PreToolUse(Task)`, `PostToolUse(Edit)`, `Stop`. Seed ships one hook body (`state/hooks/snappy-os-inject.sh`); `snappy init` scaffolds it, the user wires it into `~/.claude/settings.json` (or the runtime's equivalent) once.

## 3. The four scopes

Named consistently everywhere they appear (byline, Ops view, logs, docs):

- **THIS MACHINE** — the box you're on. `machine_id = sha256(scutil --get LocalHostName)[:12]`, cached at `state/log/.machine_id`.
- **YOUR TENANT** — machines that belong to you. Eventually consistent via git (minutes of lag).
- **SHARED STATE** — optional. `skills.snappy.ai` is the seed author's personal instance, doubling as the public catalog (browse-only). Self-host path: the storefront Worker ships as its own repo (`github.com/roboulos/snappy-skills`, access on request). Clone and deploy to your own Cloudflare account. A sanitized template extraction into the seed (`sources/frontend/`) is tracked as a 2.0 item. Nothing in the default loop requires a gateway.
- **GLOBALLY** — everyone who installed. They share the framework shape, never state.

Primitives: **git, gateway (optional), local filesystem, ssh**. No daemons, no pubsub, no websockets. Sub-second cross-machine control isn't in scope.

## 4. Scope-only default

`apply:false` is the floor. Nothing sends, posts, publishes, or mutates remote state unless explicitly confirmed. Dry-run is the default; `apply:true` is always an act of consent.

## 5. Actor ≠ auditor

The thing that generates output cannot be the thing that grades it. Every eval row carries `actor_session_id` and `auditor_session_id`. `state/lint/eval-row-mandatory.ts` refuses `npm publish` when they match or either is missing (cutoff: `2026-04-20T18:00:00Z`, see `state/lib/eval.ts:ACTOR_AUDITOR_REQUIRED_AFTER`).

## 6. Eval contract

**Write evals. Never schedule a reader.** Every skill run writes one row to `state/log/evals.ndjson`. The row is feedback for the next agent, not a dashboard. Schema: `state/lib/eval.ts`. No cron, launchd job, systemd timer, or daemon reads this file. Reads are attended: next-agent turn via the inject hook's `tailRecentEvals` surface, regen drain on Stop hook, or a user keystroke (`snappy status`, future). Anything else is misuse — it's the pattern that grew the 2026-04 scaffolding. Enforcement: `state/lint/eval-row-mandatory.ts`.

## 7. PID loop rule

Every `.agents.md` loader carries the self-correction footer at `state/regen/footer.md`. On every turn, an agent that hits a gap MUST either (a) edit the loader inline and log `[FIXED]`, or (b) log `[LOGGED]` to `state/log/agents-md-feedback.log` when the fix is >10 lines or spans files. The `PostToolUse(Edit)` hook enqueues regen for any loader that gets patched; the `Stop` hook drains. "I didn't have time" / "it's minor" are not valid reasons — minor gaps compound.

## 8. Two-class cron rule

**Class B only.** A cron line is valid iff it invokes a graduated skill's sidecar whose eval is deterministic, whose actor ≠ auditor, and whose runs land in `evals.ndjson`. Class A (scaffolding cron that polices other scaffolding, aggregates evals for a dashboard, auto-rewrites loaders) is banned. No scheduler reads evals. The current Class B roster (skill sidecars at `state/bin/<name>/`) lives in `state/index.md`; when it's empty, that's correct — no sidecars yet graduated.

## 9. Seed-owned vs user-owned

The seed is the loop mechanism itself. The user owns everything the loop operates on.

**Seed-owned** (refreshable via `snappy init --refresh-seed` or `npm update`):
`program.md`, `README.md`, `bin/snappy.js`, `bin/install.js`, `state/regen/footer.md`, `state/hooks/snappy-os-inject.sh`, `state/lint/check.ts`, `state/lint/eval-row-mandatory.ts`, `state/lib/{log,env,eval,seed-manifest}.ts`. Canonical list: `SEED_OWNED_FILES` in `state/lib/seed-manifest.ts`. sha256-per-file tracked in `.seed-manifest.json`.

**User-owned** (NEVER touched by refresh or `npm update`):
`state/skills/**`, `state/lib/<skill>.ts` (non-seed), `state/bin/**`, `state/log/**`, `.env.cache`, the user's git, crontab, and any deployed frontend instance.

`snappy init --refresh-seed` diffs user files vs `.seed-manifest.json`: no-change → fast-forward; user-modified → refuse and show 3 options (manual copy / `--force` with `.bak` / stay); missing-on-disk → copy. `snappy doctor` runs `check`, `eval-row-mandatory`, and `manifest-verify`; non-zero exit on any drift.

## 10. Where to look next

- `state/index.md` — the catalog. Skills, lib modules, log files, bin sidecars. The agent's first read after this file.
- `state/lib/eval.ts` — eval row schema and the actor ≠ auditor cutoff.
- `state/regen/footer.md` — the PID self-correction rule, DRY'd.
- `state/hooks/snappy-os-inject.sh` — the injector; seed-scaffolded, user-wired.
- `state/skills/snappy-<runtime>.md` — per-runtime wiring (Claude / Codex / Gemini / openclaw / Cursor / Windsurf / Pi). When you need to wire a new runtime or a new machine, read the relevant setup skill and run its Steps.

---

**Length target.** This file fits on one screen. If a new section seems to belong here, ask whether it's actually a system invariant (stays) or a skill (move to `state/skills/`).

<!-- kernel-ok: program.md documents seed-owned file paths. -->
