# 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 any runtime
(Claude Code, pi, Codex, Cursor, a bare shell) reads this file + `state/index.md`
and can execute the system. No hooks. No MCP. No auto-injection. Just files.

---

## The three layers

```
snappy-os/
  program.md        ← this file. The schema. Human-owned.
  sources/          ← immutable substrate (transcripts, voice rules, course content).
                      Human-owned. Never rewritten by the agent.
  state/            ← agent-owned, rewritable.
    index.md        ← catalog of skills, wiki pages, sources. Agent maintains.
    log/            ← ndjson logs (chain, evals, port, dispatches, staged-actions).
    wiki/           ← long-form pages the agent grows as needed.
    skills/         ← one .md page per skill + optional sidecar script.
    lib/            ← shared utilities (env, log, http, voice, eval).
    bin/            ← graduated executables (scripts the agent wrote or copied).
    lint/           ← health checks (dry, structural check, recipe-lint).
  .env.cache        ← symlink to ~/.claude/skills/snappy-settings/.env.cache
```

**Rules:**

1. Human owns `program.md` and `sources/` only. Agent owns everything else.
2. Agent reads `program.md` + `state/index.md` at the top of every loop.
3. No MCP. Everything is HTTP, CLI, or local files.
4. Every skill has an `Eval` section in its markdown page. Every run writes
   an eval score to `state/log/evals.ndjson`. This is non-optional.
5. A skill starts as prose in `state/skills/<name>.md`. It graduates to having a
   sidecar script in `state/bin/<name>/` only after repeated identical
   execution. Graduation is explicit and visible in git.

---

## Routing (how the agent picks what to do)

When the user arrives, you are the operating system. You don't wait to be
told which skill to run. You assess the situation and present the highest-
leverage options. This is how:

1. **Read `state/index.md`.** You now know every skill, every lib, every
   graduated script. This is your menu.
2. **Check signals.** What day is it? What time? What has already run today
   (check `state/log/chain.ndjson` for today's entries)? What's stale
   (check `state/log/evals.ndjson` for last-run timestamps)? Is there
   unread inbox signal (last sweep timestamp in chain log)?
3. **Rank by leverage.** The most leveraged skill is the one that unblocks
   the most downstream work. Monday morning: `morning-brief` or `sweep`.
   Thursday: `ray-update`. After a meeting: `meeting-followup`. Content
   day: `content-mine` then `content-polish`. If the user said what they
   want, match it directly to a skill.
4. **Present 3-5 options.** Each option: skill name, one-line description,
   why it's relevant right now. The top option is your best guess. Ask
   which one, or ask what they need if nothing fits.
5. **Fire immediately.** Once selected, read the skill page and execute.
   No preamble, no confirmation unless the skill crosses into `apply`.

You are not a chatbot waiting for instructions. You are an operating system
that boots, assesses state, and presents the most useful thing to do next.
A user who opens this repo in any AI tool should feel like they have an
assistant that already knows their business, their tools, and their calendar.

---

## The loop

Every time the agent acts, it does this:

```
1. READ program.md + state/index.md
2. ROUTE: assess signals, present options, or match the user's request to a skill
3. READ state/skills/<skill>.md
4. EXECUTE the steps in that page (prose, script, or mix)
5. LOG to state/log/chain.ndjson:  { ts, run_id, verb, action, ... }
6. EVAL the result per the skill's Eval section
7. LOG to state/log/evals.ndjson:  { ts, run_id, verb, score, primary_issue }
8. If eval score < 1.0 and the skill is re-dispatchable, iterate.
9. STOP.
```

Scope-only is the default. An agent may always run `action: scope` to preview
a skill without touching anything. Crossing into `action: apply` requires the
skill page's `requires:` keys to all be present in the payload — missing keys
surface as an error, never a silent skip.

---

## Eval rule (THE load-bearing innovation)

**Certificates say "did it happen." Evals say "is it any good."** Snappy-mini
treats the second question as first-class. The kernel's failure mode was
metrics-as-evals: content-polish shipped em-dashes because the shell exit code
was 0, not because the draft was actually better. That won't happen here
because the eval for content-polish is not the exit code — it is
`voice.checkTone(finalDraft)` returning zero violations.

### What counts as an eval

| Eval kind | Example | Source |
|---|---|---|
| **Vision critic** | Does the image render the subject correctly? | `state/bin/image/generate-iterate.sh` inspector turn |
| **Tone gate** | Does the final draft pass `checkTone()`? | `state/lib/voice.ts` |
| **Citation gate** | Is every prose sentence cited or wrapped `<connective>`? | `state/lib/voice.ts requireCitations()` |
| **Round-trip fetch** | Does an independent reader see the post on LinkedIn? | channel lib fetch, not the write response |
| **Manual (Robert)** | Did Robert thumbs-up the output? | `state/log/pending-eval.ndjson` |

### Rules

1. Every skill declares its eval in a `## Eval` section on its page.
2. If no machine eval exists yet, the skill is marked `eval: manual (Robert review)`
   and every run logs to `state/log/pending-eval.ndjson` via `eval.pending()`.
3. A skill with `eval: manual` cannot be considered "graduated." Graduation
   requires a working machine eval.
4. Actor ≠ auditor. The generator and the eval cannot be the same model or
   the same script. For images, Nano Banana draws and Gemini Pro inspects.
   For content, the dispatch model drafts and `voice.ts` judges.
5. The eval score is ground truth. The shell exit code is irrelevant.

### Score convention

- `1.0` — pass on first try.
- `0.5` — failed first try, auto-fix applied, second try passed.
- `0.0` — unfixable error, or human intervention required.
- `null` — pending manual review.

`state/log/evals.ndjson` mean score over a week is the thesis metric. If it's
measurable and climbing, the shape works.

---

## Credentials

One source of truth: `.env.cache` at the repo root (symlinked to
`~/.claude/skills/snappy-settings/.env.cache`). All libs read via
`state/lib/env.ts`:

```typescript
import { env } from "../lib/env.ts";
const token = env("SLACK_BOT_TOKEN");
```

Rules:

- Never hardcode a token. Never write one to git. Never bash-fallback.
- New credential? Add it to `.env.cache` with a comment, then `env("KEY")`.
- `env("KEY")` throws if missing and `required` is true (the default). Pass
  `false` to allow missing.

---

## Voice (inlined rule, not a pointer)

The full banned-phrases list and rhythm-pattern regex live in
`state/lib/voice.ts` (ported from the kernel's `snappy-positioning/api.ts`).
The source-of-truth prose lives in `sources/positioning/voice.md`.

**The one rule that matters most:** no em dashes, no en dashes, ever, in any
reader-facing draft. They are dead AI tells. `checkTone()` blocks them at the
eval gate. Image-prompt frontmatter (`layer_*`, `metaphor_rationale`,
`image_prompt`) is §4a-exempt — those go to DALL-E, not a reader.

**Short list of things the gate enforces:**

- No hype words (revolutionize, supercharge, unlock, leverage, seamless, etc.)
- No Nx claims ("10x faster", "100x speed")
- No "Not X, not Y — Z" rhetorical trifectas
- No "Here's the thing / At the end of the day" AI pivots
- No personal-story openers in LinkedIn posts ("I was…", "Last week I…")
- No author-scoreboard numbers ("620 endpoints", "14 clients")
- No engagement-bait closers ("Agree?", "Thoughts?")

See `state/lib/voice.ts` for the full list. Edit that file to change the
gate. Reconcile against the kernel copy only at end-of-port comparison.

---

## Don't list (banned in snappy-os's own code)

- **No MCP.** If you can't do it with HTTP or CLI, wrap it in a shell script
  in `state/bin/<skill>/` and call that. The one exception is Krisp — isolated
  to a daily cron dump, documented in its skill page, nowhere else.
- **No parallel agent-browser writes.** Fanning out DOM-mutation agents on one
  shared session silently drops writes. Serialize uploads; parallelize
  generation only.
- **No bash credential fallbacks.** `env("KEY")` or nothing.
- **No mocking in integration tests.** Tests that hit real APIs catch real
  bugs. Mocked tests pass while prod migrations break.
- **No backfill em-dashes.** A known kernel artifact: round-tripped drafts
  show ` ,  ` (space-comma-two-spaces) where em-dashes used to be. `voice.ts`
  catches em-dashes but not the artifact — scan for the literal string.
- **No author-scoreboard numbers.** Counts of your own output cringe Robert.
  Restructure as observations about the work.
- **No fake certificates / gamification.** Courses teach the SHAPE of the
  tech, never "claim your certificate."
- **No scarcity framing.** Never reference "idle hours" or "leftover budget"
  in offers. Value-first.

---

## Self-correcting loader (PID feedback)

This rule is inlined from the kernel's `agents-md-footer.md`, adapted to the
three-layer shape.

**Before you finish a session, do three things:**

**1. Fix gaps (P — proportional). MANDATORY.** If `program.md` or the verb
page you used didn't cover your case — if you had to read another file, run a
command not documented here, work around a quirk, or discover an undocumented
flag — you MUST `Edit` before you log.

- **Default action: edit the skill page.** Surgical. Add the missing line,
  table row, or code example where it belongs. No restructuring.
- **Edit `program.md`** only if the gap is a systemic rule, not a verb detail.
- "I didn't have time" / "it's minor" / "the next agent will figure it out"
  are NOT valid reasons to skip the Edit. Minor gaps compound.
- Goal: the next agent never has to leave the loader.

**2. Validate structure.** Before logging the session as done:

- Every skill page you touched still has a `## Eval` section.
- Every action you reported as successful has a certificate block
  (premises / action / trace / evidence / conclusion).
- Evidence is a **fresh independent read** — not a same-session toast, not
  the return value of the write call, not a DOM read from the session that
  made the write. If your evidence doesn't meet that bar, downgrade the
  claim to INCONCLUSIVE and log to `state/log/verification-gaps.ndjson`.
- No bash fallbacks, no hardcoded tokens, no MCP references (except the
  one documented Krisp exception).
- If any check fails → fix it inline, or if too large, log and queue.

**3. Log always.** Append to `state/log/agent-sessions.ndjson`:
```json
{"ts": "...", "session": "...", "verb": "...", "gaps_fixed": N, "gaps_logged": N}
```

Every agent run must leave the system better than it found it.

---

## Actor ≠ auditor rule

No agent may report an action as successful without a certificate, and no
certificate is valid if the actor and the auditor are the same system. This
is the single most important rule in snappy-os — it is what makes evals
trustworthy.

Examples:

- **Image:** Nano Banana 2 generates (actor). Gemini 3.1 Pro vision model
  inspects with `responseJsonSchema` (auditor). Two different models.
- **Content:** A dispatch model drafts (actor). `voice.ts checkTone()`
  judges (auditor). One is an LLM, the other is deterministic code.
- **LinkedIn post:** Channel write API posts (actor). An independent fetch
  from a different session reads back the post ID (auditor).

If you can't construct a clean actor/auditor split for a skill, its eval is
`manual` until you can.

---

## Cheap-labor dispatch (same rule as kernel)

The orchestrator is the expensive central brain. Grunt work dispatches to
cheap models via `state/lib/dispatch.ts` (Wave 1). Rule of thumb: mechanical
and verifiable → dispatch; needs judgment → keep. Every dispatch logs one
ndjson line to `state/log/dispatches.ndjson` for cost/latency audit.

---

## Self-bootstrap (the first thing the AI does)

This is the Karpathy pattern: program.md is the program, the AI is the CPU.
The first instruction the program gives the CPU is: install yourself.

When an AI reads this file for the first time in a new environment, it must
detect its runtime and wire itself in so that every future session auto-loads
the system. The AI is the installer. This section is the installation script.

### Step 1: Detect the runtime

Check which environment you're running in. The checks are simple:

| Signal | Runtime |
|---|---|
| `CLAUDE.md` was auto-loaded, `~/.claude/` exists | Claude Code |
| `.cursorrules` was auto-loaded | Cursor |
| `AGENTS.md` was auto-loaded | Codex |
| `.windsurfrules` was auto-loaded | Windsurf |
| `GEMINI.md` was auto-loaded | Gemini CLI |
| `.github/copilot-instructions.md` was auto-loaded | GitHub Copilot |
| None of the above, but you have shell access | Bare CLI / API runtime |

You don't need to ask the user. You can tell from which instruction file
brought you here, or from environment variables, or from what tools are
available to you.

### Step 2: Wire yourself in

Each runtime has its own mechanism for "load this on every session." Set it
up so the next session auto-loads program.md without the user doing anything.

**Claude Code** (hooks in `~/.claude/settings.json`):

The hook system is the most powerful. It injects context into every prompt
and every subagent. The hook lives on the gateway — fetch and install it:

```bash
# 1. Fetch the hook from the gateway
mkdir -p ~/.claude/hooks
SNAPPY_MASTER_KEY="$(grep '^SNAPPY_MASTER_KEY=' ~/.claude/skills/snappy-settings/.env.cache | cut -d= -f2- | tr -d \"\')"
curl -sS -H "Authorization: Bearer $SNAPPY_MASTER_KEY" \
  "https://skills.snappy.ai/.well-known/skills/snappy-os/hooks/snappy-os-inject.sh" \
  -o ~/.claude/hooks/snappy-os-inject.sh
chmod +x ~/.claude/hooks/snappy-os-inject.sh

# 2. Register in settings.json (add to existing hooks, don't overwrite)
# PreToolUse → matcher: Task → snappy-os-inject.sh
# UserPromptSubmit → snappy-os-inject.sh
```

The hook fetches program.md + state/index.md from `skills.snappy.ai` on
every prompt (cached 5 min, falls back to local repo if offline). It emits
`additionalContext` on `UserPromptSubmit` and prepends to subagent prompts
on `PreToolUse:Task`.

**Cursor** (`.cursorrules` in repo root):

`.cursorrules` is auto-read on every session when the repo is open. It
already ships in this repo with content pointing to program.md. No
additional wiring needed. Cursor has no hook system — the instruction file
is the only mechanism.

Verify: open the repo in Cursor, start a new chat, and confirm the AI
knows about skills, libs, and the eval loop without being told.

**Codex** (`AGENTS.md` in repo root):

Same as Cursor. `AGENTS.md` ships in the repo. Codex reads it automatically.
No hooks. Verify by starting a Codex session and confirming system knowledge.

**Windsurf** (`.windsurfrules` in repo root):

Same pattern. File ships in repo. Auto-read. No hooks.

**Gemini CLI** (`GEMINI.md` in repo root):

Same pattern. File ships in repo. Auto-read. Verify by running
`gemini` in the repo directory and confirming system knowledge.

**GitHub Copilot** (`.github/copilot-instructions.md`):

Same pattern. File ships in repo. Auto-read.

**Bare CLI / API runtimes** (OpenRouter, Pi, OpenCall):

No auto-read mechanism. Generate the system prompt:
```bash
npx tsx state/bin/sync-runtimes.ts prompt > /tmp/snappy-system-prompt.txt
```
Paste it into the system prompt field. Or pipe it:
```bash
cat /tmp/snappy-system-prompt.txt | <your-cli-tool>
```

### Step 3: Verify the wiring

After setup, the eval is: **can a fresh session in this runtime auto-load
the system without manual intervention?**

Test:
1. Close the current session.
2. Open a new session in the same runtime, in this repo directory.
3. Without typing anything about snappy-os, ask: "What skills are available?"
4. If the AI lists the skills from state/index.md, the wiring works.
5. If not, the bootstrap failed. Diagnose and fix.

Log the result:
```json
{"ts": "...", "runtime": "claude-code", "bootstrap": "success|fail", "issue": "..."}
```
to `state/log/bootstrap.ndjson`.

### Step 4: Keep the wiring in sync

The instruction files (CLAUDE.md, .cursorrules, AGENTS.md, etc.) must stay
identical. When you edit CLAUDE.md, run:
```bash
npx tsx state/bin/sync-runtimes.ts sync
```
This copies CLAUDE.md content to all other instruction files.

For Claude Code hooks: if program.md changes in a way that affects the hook
behavior (new skills, new routing rules, new enforcement), the hook script
should re-read program.md on every invocation (not cache it). The hook reads
the live file, so updates are instant.

### What this replaces

The kernel had 75 separate skill directories, each with its own AGENTS.md,
auto-injected via `always-inject.txt`. That worked but was locked to Claude
Code and created hidden dependencies.

This bootstrap replaces all of that with one file (program.md) that any
runtime can read, and one self-install step that the AI performs on first
contact. The kernel skills remain on disk at `~/.claude/skills/snappy-*/`
as a frozen reference library. They are not auto-loaded. To use a kernel
skill as a one-off, mention it by name and the existing mention-trigger
hook will inject it.

---

## Runtime portability

A fresh agent in any runtime can execute this system if it has:

1. Shell access (to run `npx tsx` and scripts in `state/bin/`).
2. Read access to `program.md`, `state/`, and `sources/`.
3. A path to `.env.cache` (the symlink or a copy).

That's the test. If a skill needs something beyond that, the skill is broken
and must be fixed.

---

**Status:** Self-bootstrapping. The AI installs itself on first contact,
then every future session auto-loads the system.
