snappy-os
v1.0.10
Published
Agent operating system. `npx snappy-os init` scaffolds a self-correcting PID loop into your chosen directory. Per-turn context layer for Claude Code / Codex / Gemini. Local-first, seed-not-service.
Readme
snappy-os
An agent operating system: a per-turn context layer that makes any agentic runtime (Claude Code, Codex CLI, Gemini CLI, Cursor, Windsurf, OpenClaw) execute the same verbs the same way on every machine you own. Not a service, not a daemon, not an MCP. A tree of markdown files + one inject hook per runtime + a library of small Node/TypeScript utilities.
The product is the self-correcting PID loop those files encode: a skill gets invoked → the agent hits a gap → the agent edits its own loader → the next agent inherits the sharpened loader.
Install
npx snappy-os init # scaffolds ~/projects/snappy-os
npx snappy-os init /path/to/tree # scaffolds elsewhereOne command, no key required for the default local-first install. init is a one-time scaffolder — like create-react-app. After it runs, the package is not a runtime dependency: the tree on disk is yours, you commit it to your own git, you author your own skills.
Hooks are wired automatically: init writes ~/.claude/hooks/snappy-os-inject.sh (plus -stop.sh and -enqueue.sh) and patches ~/.claude/settings.json to register them on UserPromptSubmit, PreToolUse(Task), Stop, and PostToolUse(Edit|Write). Merge is idempotent — existing hooks are preserved.
Verify
cd ~/projects/snappy-os
node bin/snappy.js doctor # check.ts + eval-row-mandatory + manifest-verify
node bin/snappy.js doctor --verify-remote # compare local sha256 vs skills.snappy.ai
node bin/snappy.js help # full verb listVerbs
| Verb | What it does |
| --- | --- |
| init [target] | Scaffold a fresh tree. --force to overwrite a non-empty target. |
| init --refresh-seed | Pull latest seed-owned files into an existing tree; diff vs .seed-manifest.json; refuse on user-modified unless --force. |
| scaffold <name> | Create state/skills/<name>.md + AGENTS.md + stub state/lib/<name>.ts. Appends to state/index.md. |
| doctor | Run the three lints. Non-zero exit if any fail. |
| doctor --rebuild-manifest | Regenerate .seed-manifest.json from on-disk hashes. |
| doctor --verify-remote | Fetch each seed file from gateway; sha256-compare vs manifest. |
| sync | git pull --rebase then git push. Refuses on dirty tree unless --force. |
| help / --help / -h | Full command reference. |
Upgrade protocol
Seed-owned files (what a refresh can touch):
program.md,README.mdbin/snappy.js,bin/install.jsstate/regen/{footer.md,drain.sh,enqueue.sh}state/hooks/{snappy-os-inject.sh,snappy-os-stop.sh}state/lint/{check.ts,eval-row-mandatory.ts}state/lib/{log.ts,env.ts,eval.ts,seed-manifest.ts}.seed-manifest.json
User-owned files (NEVER touched by init or --refresh-seed):
state/skills/**,state/bin/**,state/log/**,state/index.mdafter first write.env.cache, yourpackage.jsonadditions, your git history, your crontab
To upgrade from a scaffolded tree:
cd ~/projects/snappy-os
npx snappy-os@latest init --refresh-seed # diffs + fast-forwards; refuses on edits
npx snappy-os@latest init --refresh-seed --force # overwrites (edits → <file>.bak)Refresh is opt-in. If you never re-refresh, nothing breaks — your installed version continues working. bin/snappy.js init --refresh-seed from your tree auto-hops to npx snappy-os@latest because the local bin IS the current seed and has nothing newer.
Inspect manifest prev-hash:
cat .seed-manifest.json | jq '.files'The manifest is seed-owned. Delete it and snappy doctor --rebuild-manifest rewrites it from on-disk hashes (useful after a manual edit).
Layout
program.md kernel contract — read first
README.md this file
.env.cache credentials — NEVER commit
.seed-manifest.json sha256 per seed-owned file
bin/
snappy.js verb dispatcher (init/scaffold/doctor/sync/help)
install.js one-time scaffolder called by `init`
state/
index.md skill catalog — read second
skills/<name>.md prose reference for each skill
skills/<name>/AGENTS.md per-turn loader injected by the hook
lib/<name>.ts backing code for skills and sidecars
lib/{log,env,eval,seed-manifest}.ts seed-owned utilities
lint/{check,eval-row-mandatory}.ts structural + eval-row lints
hooks/{snappy-os-inject,snappy-os-stop}.sh runtime wiring
regen/{footer.md,enqueue.sh,drain.sh} PID self-correction plumbing
bin/<skill-name>/ graduated sidecars (user-owned; one dir per skill)
log/evals.ndjson every verb run writes one row here
log/chain.ndjson verb-chain provenance
log/agents-md-feedback.log [FIXED]/[LOGGED] trail from the PID loopRules (from program.md)
- Scope-only by default. Nothing sends, posts, or publishes unless
apply: true. - Every skill run writes one row to
state/log/evals.ndjson. Non-optional. - The actor cannot be the auditor — generator and grader must be different sessions.
- No MCP. Everything is HTTP, CLI, or local files.
- Write evals; never schedule a reader. Evals are feedback for the next agent; every read is attended (agent turn or user keystroke).
- Class A crons (scaffolders policing scaffolders) are banned. Only Class B — graduated skill sidecars with deterministic eval — are allowed, and only when the user wires them.
Optional: bring your own gateway
The default install has zero cloud dependencies. If you want a web window onto your own tree (like skills.snappy.ai), deploy the Cloudflare Worker template in sources/frontend/ to your own account. Nothing in the default PID loop requires a gateway token.
Credentials
.env.cache at the repo root is the canonical credential file. snappy-os itself reads nothing from it — it's the user's credential store for skills that hit external APIs. If you install on a new machine, init will:
- Symlink the back-compat path at
~/.claude/skills/snappy-settings/.env.cacheif present, or - Write
.env.cache.templateand print copy-paste instructions.
Skills that hit external APIs (gmail, calendar, etc.) document their keys in their own state/skills/<name>.md pages.
