diff --git a/.cursor-plugin/marketplace.json b/.cursor-plugin/marketplace.json
index 824194e9a..e5cc0448d 100644
--- a/.cursor-plugin/marketplace.json
+++ b/.cursor-plugin/marketplace.json
@@ -532,6 +532,11 @@
"grokbot": "0.49.0",
"sand": "0.49.0"
}
+ },
+ {
+ "name": "onprem",
+ "source": "onprem",
+ "description": "On-prem model governance and operating doctrine for Cursor agent work: a closed-allowlist hard lock, a stack of named principles, an entry mode, setup, and guide."
}
]
}
diff --git a/README.md b/README.md
index 106f7973f..bd14c51ed 100644
--- a/README.md
+++ b/README.md
@@ -21,6 +21,7 @@ Official Cursor plugins for popular developer tools, frameworks, and SaaS produc
| `orchestrate` | [Orchestrate](orchestrate/) | Cursor | Developer Tools | Fan large tasks out across parallel cloud agents with planners, workers, verifiers, and structured handoffs. |
| `pstack` | [pstack](pstack/) | Lauren Tan | Developer Tools | if you want to go fast, go deep first. pstack helps you write less, but higher quality code. rigorous agent workflows you can parallelize with confidence. |
| `dyl-stack` | [dyl-stack](dyl-stack/) | Dylan Gattey | Developer Tools | Dylan's agent style on top of pstack: root causes over symptom patches, The Algorithm before design, terse verified delivery, a PR review that fits in a paste, and Figma-to-UI with a visual judge. |
+| `onprem` | [onprem](onprem/) | Jarrod Tran | Developer Tools | On-prem model governance and operating doctrine for agent work: a closed-allowlist hard lock, a stack of named principles, an entry mode, setup, and guide. |
| `advisor` | [Advisor](advisor/) | Cursor | Developer Tools | Consult a stronger model before major decisions, when stuck, and before declaring done. |
| `grok-voice` | [Grok Voice](grok-voice/) | Eric Zakariasson | Developer Tools | Add Grok voice to an app: realtime speech-to-speech, speech-to-text dictation, text-to-speech read-aloud, and a log-driven fix loop for voice sessions. |
| `gmail` | [Gmail](third_party/gmail/) | Cursor | Productivity | Search, read, draft, and manage email. |
diff --git a/onprem/.cursor-plugin/plugin.json b/onprem/.cursor-plugin/plugin.json
new file mode 100644
index 000000000..123880a35
--- /dev/null
+++ b/onprem/.cursor-plugin/plugin.json
@@ -0,0 +1,28 @@
+{
+ "name": "onprem",
+ "displayName": "onprem stack",
+ "version": "0.1.0",
+ "description": "on-prem model governance and operating doctrine for agent work: a closed allowlist hard lock, a stack of named principles, and an entry mode that applies them.",
+ "author": {
+ "name": "Jarrod Tran"
+ },
+ "license": "MIT",
+ "keywords": [
+ "onprem",
+ "on-prem",
+ "open-weight",
+ "model-governance",
+ "hard-lock",
+ "principles",
+ "agent-style"
+ ],
+ "category": "developer-tools",
+ "tags": [
+ "workflow",
+ "principles",
+ "review",
+ "model-governance"
+ ],
+ "skills": "./skills/",
+ "agents": "./agents/"
+}
diff --git a/onprem/.cursor/skills/verify-onprem/SKILL.md b/onprem/.cursor/skills/verify-onprem/SKILL.md
new file mode 100644
index 000000000..ffab7cd6a
--- /dev/null
+++ b/onprem/.cursor/skills/verify-onprem/SKILL.md
@@ -0,0 +1,75 @@
+---
+name: verify-onprem
+description: 'Drive the onprem plugin like its operator and prove its state with rerunnable checks. Use when asked to verify, audit, or health-check the onprem install (verify onprem, is my onprem lock intact, check the plugin after an edit, onprem doctor). Read-only; it never writes the plugin, the rule file, or the registration links.'
+---
+
+# Verify onprem
+
+The onprem plugin is configuration, not a running app. Its user surface is the `/onprem-*` skills in Cursor, the generated rule file `~/.cursor/rules/onprem-models.mdc`, and the registration links that make the plugin visible. This skill drives that surface with a deterministic check suite and proves it with captured evidence, per the deterministic-proof principle: a check that passes is a proof, a self-report is a claim.
+
+## Launch
+
+There is no server to start. Launch means locating the checkout and the rule:
+
+```bash
+python3 .cursor/skills/verify-onprem/verify.py --doctor
+```
+
+Ready means the doctor JSON reports `plugin_root: true`, `manifest: true`, and (on a configured machine) `rule_file: true`. The helper self-locates the plugin root by walking up to `.cursor-plugin/plugin.json`, so it runs from any checkout of the repo. Teardown is a no-op: nothing is started.
+
+## Doctor
+
+One read-only check that answers "is this instance worth driving?":
+
+- `plugin_root` and `manifest` false → not a checkout of onprem; stop and say so.
+- `rule_file` false → `/setup-onprem` has not run on this machine; the suite will skip `rule-audit` and say so in its evidence. That is a valid state for a fresh clone.
+- `git_origin` null → not a git checkout (a directory copy); fine for verification, wrong for a publish flow.
+
+## Drive
+
+Run the full suite:
+
+```bash
+python3 .cursor/skills/verify-onprem/verify.py
+```
+
+It prints a JSON summary and exits 0 on pass, 1 on any hard fail. The nine checks and what each proves:
+
+| check | proves |
+|---|---|
+| `manifest` | plugin.json parses, is named `onprem`, declares skills and agents |
+| `frontmatter` | every skill/agent frontmatter parses; skill `name` matches its directory |
+| `mode-dashes` | the mode file obeys its own no-long-dash rule |
+| `template-identity` | the 18 role lines are byte-identical between setup-onprem and role-mapping, all values in the closed set |
+| `closed-set` | the four real slugs are named in all nine core files; the exact-set surfaces (audit, template comment) include both `inherit-parent` and `auto` |
+| `banned-leak` | no banned-family name appears outside lock-statement files |
+| `links` | every relative markdown link resolves |
+| `rule-audit` | the rule file has `alwaysApply: true`, the `unlimited (max)` budget line, the full allowlist comment line, and 18 closed-set role lines |
+| `registration` | the 16 skill links and the agent link resolve on this machine (skips cleanly on a fresh clone) |
+
+For any failing check, quote the check name and its `detail` field, then open the named file and confirm the failure is real before acting. RoboDev-style review output and model self-reports are claims; this suite is the proof.
+
+## Evidence
+
+Capture the JSON and keep it. The named location is `~/.cursor/verify-onprem-evidence/`:
+
+```bash
+mkdir -p ~/.cursor/verify-onprem-evidence
+python3 .cursor/skills/verify-onprem/verify.py > ~/.cursor/verify-onprem-evidence/verify-$(date -u +%Y%m%dT%H%M%SZ).json
+echo "exit: $?"
+```
+
+A proof is the run's exit code 0 plus the JSON file on disk with `"status": "pass"` and every check `ok: true` (or an explicitly labeled `skip:` detail). Confirm the evidence file still exists after any cleanup; a cleanup that eats the proof fails this step.
+
+## Cleanup
+
+Read-only by construction: the suite writes nothing into the plugin, the rule file, or the registration links. The only artifact is the evidence file, which survives by design. There is nothing to kill; the doctor and suite start no processes.
+
+## Helpers
+
+- `verify.py` (this directory) is executable. Invocation: `python3 .cursor/skills/verify-onprem/verify.py [--doctor]`.
+- No other helpers. If a check needs to grow, extend `verify.py`; do not add one-off scripts that drift from the suite.
+
+## Maintenance
+
+Re-run the suite after every edit to the plugin or the rule file, and after any `/setup-onprem` re-run. onprem ships no separate maintain-verification skill; this suite plus the `/onprem-mode` audit playbook (which audits the rule file against the lock, including session detection that this script cannot see) is the maintenance pass.
diff --git a/onprem/.cursor/skills/verify-onprem/features/README.md b/onprem/.cursor/skills/verify-onprem/features/README.md
new file mode 100644
index 000000000..650ea7179
--- /dev/null
+++ b/onprem/.cursor/skills/verify-onprem/features/README.md
@@ -0,0 +1,11 @@
+# verify-onprem feature map
+
+One file per check suite the helper drives. A proof that exercises one convenient suite is incomplete when the map lists others.
+
+| feature | suites | proves |
+|---|---|---|
+| [lock-consistency](lock-consistency.md) | `closed-set`, `banned-leak` | the five-value closed set is stated identically everywhere; no banned family is ever presented as usable |
+| [template-identity](template-identity.md) | `template-identity` | the 18 role lines agree across both template surfaces |
+| [structure](structure.md) | `manifest`, `frontmatter`, `mode-dashes`, `links` | the plugin registers and renders: valid manifest, parsing frontmatter, dash-free mode file, resolving links |
+| [rule-audit](rule-audit.md) | `rule-audit` | the generated rule file is a valid lock surface |
+| [registration](registration.md) | `registration`, `--doctor` | the plugin is visible to sessions on this machine |
diff --git a/onprem/.cursor/skills/verify-onprem/features/lock-consistency.md b/onprem/.cursor/skills/verify-onprem/features/lock-consistency.md
new file mode 100644
index 000000000..8100d487d
--- /dev/null
+++ b/onprem/.cursor/skills/verify-onprem/features/lock-consistency.md
@@ -0,0 +1,16 @@
+# Lock consistency
+
+## Sub-features
+- `closed-set`: the four real slugs appear in all nine core files; the exact-set surfaces (audit playbook step 2, setup template comment line) name both `inherit-parent` and `auto`.
+- `banned-leak`: no banned-family name (claude, anthropic, fable, opus, sonnet, haiku, gemini, grok, xai, gpt, openai, sol) appears in any file that carries no lock language.
+
+## How to get to it (user POV)
+An operator who suspects the lock drifted after an edit asks "is my lock intact?" in a chat, or runs the suite after a hand-edited file. The suites answer the mechanical half of that question; the `/onprem-mode` audit playbook answers the session half (whether the machine actually has the slugs).
+
+## Driving it with verify.py
+`python3 .cursor/skills/verify-onprem/verify.py` runs both suites in one pass. Observable end state: `"ok": true` on `closed-set` and `banned-leak`, plus an evidence JSON on disk. A `closed-set` failure names the file and the missing slug; a `banned-leak` failure names file:line and the word.
+
+## Gotchas
+- The leak scan exempts any file containing lock language (never/banned/allowlist/closed). A banned family name inside a ban statement is correct content, not a leak.
+- `sol` as a substring is the banned-set's own token; the scan uses word boundaries, so `consolidated` never trips it.
+- These suites check text consistency, not endpoint liveness. A slug can be consistently named and still not served by the local deployment; the first real `/onprem-mode` task is that check.
diff --git a/onprem/.cursor/skills/verify-onprem/features/registration.md b/onprem/.cursor/skills/verify-onprem/features/registration.md
new file mode 100644
index 000000000..22a6fc6f3
--- /dev/null
+++ b/onprem/.cursor/skills/verify-onprem/features/registration.md
@@ -0,0 +1,15 @@
+# Registration
+
+## Sub-features
+- `registration`: the 16 skill links under `~/.claude/skills/` resolve to plugin skill directories with a readable SKILL.md, and `~/.cursor/agents/onprem-agent.md` resolves.
+- `--doctor`: the read-only readiness check (plugin root, manifest, rule file presence, git origin) that answers "is this machine worth driving at all?"
+
+## How to get to it (user POV)
+On the author's machine the plugin is wired by symlinks into the skill and agent roots so every new session sees it. A fresh clone is not wired: Cursor picks up `~/.cursor/plugins/local/` at startup, and a window reload may be needed. The suite tells the operator which of the two states they are in.
+
+## Driving it with verify.py
+`python3 .cursor/skills/verify-onprem/verify.py --doctor` for the readiness answer; the default pass for the link resolution. On a fresh clone the registration suite reports `skip:` with the missing names and exits 0.
+
+## Gotchas
+- Symlinks that resolve to a deleted plugin directory fail this suite; re-point them at the live checkout.
+- The skill-link list in verify.py must track the plugin's skill directories. Adding or renaming a skill leaf means updating SKILL_LINKS in the same commit.
diff --git a/onprem/.cursor/skills/verify-onprem/features/rule-audit.md b/onprem/.cursor/skills/verify-onprem/features/rule-audit.md
new file mode 100644
index 000000000..a427033c5
--- /dev/null
+++ b/onprem/.cursor/skills/verify-onprem/features/rule-audit.md
@@ -0,0 +1,14 @@
+# Rule audit
+
+## Sub-features
+- `rule-audit`: `~/.cursor/rules/onprem-models.mdc` (when present) has `alwaysApply: true`, a `# budget: unlimited (max)` line, a HARD LOCK comment line naming all four real slugs plus both aliases, and exactly 18 role lines whose values are all in the closed set.
+
+## How to get to it (user POV)
+The rule file is the configuration half of the lock. An operator who hand-edited it, or who re-ran `/setup-onprem` on a machine with different entitlements, wants a mechanical answer to "did the rule land right?" before the first real task.
+
+## Driving it with verify.py
+Part of the default pass. On a machine without the rule (fresh clone), the suite reports `skip:` and still exits 0; that is the correct, labeled state, not a pass.
+
+## Gotchas
+- This suite cannot see the session's detected model list, so it validates the closed set, not availability. The session-detected check belongs to the `/onprem-mode` audit playbook, which runs where a session exists.
+- The budget line is asserted to be `unlimited (max)` because principle-unlimited-budget makes the budget a confirmation, not a choice. If the doctrine changes, change the principle and this check together.
diff --git a/onprem/.cursor/skills/verify-onprem/features/structure.md b/onprem/.cursor/skills/verify-onprem/features/structure.md
new file mode 100644
index 000000000..df31ec544
--- /dev/null
+++ b/onprem/.cursor/skills/verify-onprem/features/structure.md
@@ -0,0 +1,18 @@
+# Structure
+
+## Sub-features
+- `manifest`: `.cursor-plugin/plugin.json` parses, is named `onprem`, declares `skills` and `agents`.
+- `frontmatter`: every markdown file with YAML frontmatter parses; each skill's `name` matches its directory; every frontmatter has a description.
+- `mode-dashes`: `skills/onprem-mode/SKILL.md` contains no long-dash characters (its own line 81 bans them on every reply the mode writes).
+- `links`: every relative markdown link in the plugin resolves to an existing file.
+
+## How to get to it (user POV)
+This is the "will Cursor even register this plugin" suite. A broken frontmatter name silently unregisters a skill; a dead link sends a recipient down a wrong path; a mode file that violates its own writing rule erodes trust in the doctrine it carries.
+
+## Driving it with verify.py
+Part of the default pass. Observable end state: `"ok": true` on all four, with the frontmatter detail counting how many files parsed.
+
+## Gotchas
+- The dash check is scoped to the mode file on purpose. Guide pages and setup prose are human-facing and may use long dashes, mirroring pstack.
+- A frontmatter `name`/dir mismatch usually means a directory was renamed without the file. Fix the directory or the name, not the check.
+- HTTP(S) links are not checked for reachability, only for syntax via the link scan.
diff --git a/onprem/.cursor/skills/verify-onprem/features/template-identity.md b/onprem/.cursor/skills/verify-onprem/features/template-identity.md
new file mode 100644
index 000000000..8e518f677
--- /dev/null
+++ b/onprem/.cursor/skills/verify-onprem/features/template-identity.md
@@ -0,0 +1,14 @@
+# Template identity
+
+## Sub-features
+- `template-identity`: the 18 role lines in setup-onprem's rule template are byte-identical to the 18 role lines in principle-role-mapping's default block, and every value is in the closed set.
+
+## How to get to it (user POV)
+Two surfaces both claim to show the default mapping. If an operator edits one and not the other, an agent reading the role-mapping principle and an agent reading setup get different defaults, and the rule file that gets written depends on which surface the session happened to load.
+
+## Driving it with verify.py
+Part of the default pass. Observable end state: `"ok": true` with the detail "18 role lines identical...". A failure reports the line counts on both sides and any out-of-set values.
+
+## Gotchas
+- The comparison is anchored on the `# onprem model configuration` marker in setup-onprem and the first fenced block in role-mapping. If either file's template moves or its marker rewords, the suite fails loudly rather than silently comparing the wrong blocks.
+- This check compares templates, not the live rule file. The live file's role lines are checked by the `rule-audit` suite.
diff --git a/onprem/.cursor/skills/verify-onprem/verify.py b/onprem/.cursor/skills/verify-onprem/verify.py
new file mode 100755
index 000000000..6386b74ef
--- /dev/null
+++ b/onprem/.cursor/skills/verify-onprem/verify.py
@@ -0,0 +1,300 @@
+#!/usr/bin/env python3
+"""verify-onprem: deterministic integrity checks for the onprem plugin and its rule file.
+
+Usage:
+ verify.py full suite, JSON summary on stdout, exit 1 on any hard fail
+ verify.py --doctor read-only readiness check only
+
+The plugin root is located by walking up from this file to the directory that
+contains .cursor-plugin/plugin.json, so the helper runs from any checkout.
+"""
+import json
+import os
+import re
+import sys
+from datetime import datetime, timezone
+
+CLOSED_REAL = [
+ "Qwen/Qwen3.8-27B-FP8",
+ "Qwen/Qwen3.8-Flash-Next-FP8",
+ "moonshotai/Kimi-K3",
+ "zai-org/GLM-5.3",
+]
+CLOSED = ["inherit-parent", "auto"] + CLOSED_REAL
+BANNED = re.compile(r"\b(claude|anthropic|gemini|grok|xai|gpt|openai|opus|sonnet|haiku|fable|sol)\b", re.I)
+LOCK_LANG = re.compile(r"(never|banned|ban|forbidden|no exception|closed|allowlist|degrad)", re.I)
+ROLE_RE = re.compile(r"^[a-z][a-z ,\-]*: ")
+CORE_FILES = [
+ "README.md",
+ "skills/onprem-mode/SKILL.md",
+ "skills/setup-onprem/SKILL.md",
+ "skills/principle-closed-allowlist/SKILL.md",
+ "skills/onprem-mode/playbooks/audit.md",
+ "docs/guide/02-the-lock.md",
+ "docs/guide/05-sharing.md",
+ "skills/onprem-help/SKILL.md",
+ "skills/principle-role-mapping/SKILL.md",
+]
+SKILL_LINKS = [
+ "onprem-mode", "setup-onprem", "onprem-help", "create-verify-skill",
+ "principle-banned-set", "principle-closed-allowlist", "principle-context-discipline",
+ "principle-deterministic-proof", "principle-fresh-subagents", "principle-inherit-parent-honestly",
+ "principle-no-fallback-to-banned", "principle-panel-composition", "principle-role-mapping",
+ "principle-unlimited-budget", "principle-validation-gate", "principle-verifiable-units",
+]
+
+
+def find_plugin_root():
+ d = os.path.dirname(os.path.abspath(__file__))
+ for _ in range(8):
+ if os.path.isfile(os.path.join(d, ".cursor-plugin", "plugin.json")):
+ return d
+ d = os.path.dirname(d)
+ return None
+
+
+def rel(root, path):
+ return os.path.relpath(path, root)
+
+
+def md_files(root):
+ out = []
+ for dirpath, dirs, names in os.walk(root):
+ dirs[:] = [d for d in dirs if d not in (".git", "node_modules")]
+ for n in names:
+ if n.endswith(".md"):
+ out.append(os.path.join(dirpath, n))
+ return sorted(out)
+
+
+def frontmatter(text):
+ if not text.startswith("---"):
+ return None
+ end = text.find("\n---", 3)
+ if end == -1:
+ return None
+ return text[4:end]
+
+
+def check_manifest(root, checks):
+ p = os.path.join(root, ".cursor-plugin", "plugin.json")
+ try:
+ d = json.load(open(p, encoding="utf-8"))
+ ok = d.get("name") == "onprem" and "skills" in d and "agents" in d
+ checks.append({"name": "manifest", "ok": ok,
+ "detail": "plugin.json valid, name=onprem, skills+agents fields present" if ok
+ else "unexpected manifest: name=%r" % d.get("name")})
+ except Exception as e:
+ checks.append({"name": "manifest", "ok": False, "detail": "plugin.json unreadable: %s" % e})
+
+
+def check_frontmatter(root, checks):
+ import yaml
+ bad = []
+ n = 0
+ for f in md_files(root):
+ t = open(f, encoding="utf-8").read()
+ fm = frontmatter(t)
+ if fm is None:
+ continue
+ n += 1
+ try:
+ d = yaml.safe_load(fm)
+ assert isinstance(d, dict) and d.get("name") and d.get("description")
+ except Exception:
+ bad.append(rel(root, f))
+ continue
+ if re.search(r"/SKILL\.md$", f):
+ expect = os.path.basename(os.path.dirname(f))
+ if d["name"] != expect:
+ bad.append("%s (name %r != dir %r)" % (rel(root, f), d["name"], expect))
+ checks.append({"name": "frontmatter", "ok": not bad,
+ "detail": "%d frontmatter files parse, skill names match directories" % n if not bad
+ else "bad: " + "; ".join(bad)})
+
+
+def check_mode_dashes(root, checks):
+ t = open(os.path.join(root, "skills/onprem-mode/SKILL.md"), encoding="utf-8").read()
+ hits = len(re.findall(r"[\u2013\u2014]", t))
+ checks.append({"name": "mode-dashes", "ok": hits == 0,
+ "detail": "mode file is long-dash free" if hits == 0 else "%d long-dash(es) remain" % hits})
+
+
+def role_block(path, marker):
+ t = open(path, encoding="utf-8").read()
+ i = t.find(marker)
+ if i == -1:
+ return None
+ seg = t[i:t.find("```", i)]
+ return [l.rstrip() for l in seg.splitlines() if ROLE_RE.match(l)]
+
+
+def check_template_identity(root, checks):
+ a = role_block(os.path.join(root, "skills/setup-onprem/SKILL.md"), "# onprem model configuration")
+ t = open(os.path.join(root, "skills/principle-role-mapping/SKILL.md"), encoding="utf-8").read()
+ m = re.search(r"```\n(.*?)```", t, re.S)
+ b = [l.rstrip() for l in m.group(1).splitlines() if l.strip()] if m else None
+ if a and b and a == b and len(a) == 18:
+ checks.append({"name": "template-identity", "ok": True,
+ "detail": "18 role lines identical in setup-onprem and role-mapping, all values closed-set"})
+ else:
+ bad_vals = []
+ for l in (a or []):
+ for v in (x.strip() for x in l.split(": ", 1)[1].split(",")):
+ if v not in CLOSED:
+ bad_vals.append(v)
+ checks.append({"name": "template-identity", "ok": False,
+ "detail": "drift: setup=%s lines, role-mapping=%s lines, bad values=%s"
+ % (len(a) if a else None, len(b) if b else None, bad_vals)})
+
+
+def check_closed_set(root, checks):
+ bad = []
+ for f in CORE_FILES:
+ t = open(os.path.join(root, f), encoding="utf-8").read()
+ missing = [s for s in CLOSED_REAL if s not in t]
+ if missing:
+ bad.append("%s: missing %s" % (f, missing))
+ audit = open(os.path.join(root, "skills/onprem-mode/playbooks/audit.md"), encoding="utf-8").read()
+ if "`inherit-parent`/`auto`" not in audit:
+ bad.append("audit: exact-set check lacks inherit-parent/auto")
+ so = open(os.path.join(root, "skills/setup-onprem/SKILL.md"), encoding="utf-8").read()
+ if "inherit-parent/auto" not in so:
+ bad.append("setup template comment lacks inherit-parent/auto")
+ checks.append({"name": "closed-set", "ok": not bad,
+ "detail": "all 9 core files name the four real slugs; exact-set surfaces include both aliases" if not bad
+ else "; ".join(bad)})
+
+
+def check_banned_leak(root, checks):
+ leaks = []
+ for f in md_files(root):
+ lines = open(f, encoding="utf-8").readlines()
+ if any(LOCK_LANG.search(l) for l in lines):
+ continue
+ for i, line in enumerate(lines, 1):
+ for m in BANNED.finditer(line):
+ prev = line[m.start() - 1] if m.start() > 0 else ""
+ nxt = line[m.end()] if m.end() < len(line) else ""
+ if prev in ".~/" or nxt == "/":
+ continue # a path component (e.g. ~/.claude/skills), not a model slug
+ leaks.append("%s:%d %s" % (rel(root, f), i, m.group(0)))
+ checks.append({"name": "banned-leak", "ok": not leaks,
+ "detail": "no banned-family name outside lock-statement files" if not leaks
+ else "leaks: " + "; ".join(leaks[:5])})
+
+
+def check_links(root, checks):
+ bad = []
+ link_re = re.compile(r"\]\(([^)]+)\)")
+ for f in md_files(root):
+ base = os.path.dirname(f)
+ for m in link_re.finditer(open(f, encoding="utf-8").read()):
+ target = m.group(1).split("#")[0].strip()
+ if not target or target.startswith(("http://", "https://", "mailto:")):
+ continue
+ if not os.path.exists(os.path.join(base, target)):
+ bad.append("%s -> %s" % (rel(root, f), target))
+ checks.append({"name": "links", "ok": not bad,
+ "detail": "all relative markdown links resolve" if not bad else "broken: " + "; ".join(bad[:5])})
+
+
+def check_rule(root, checks):
+ rule = os.path.expanduser("~/.cursor/rules/onprem-models.mdc")
+ if not os.path.exists(rule):
+ checks.append({"name": "rule-audit", "ok": True, "detail": "skip: no onprem-models.mdc on this machine (setup not run)"})
+ return
+ t = open(rule, encoding="utf-8").read()
+ lines = t.splitlines()
+ body = lines[1:]
+ if lines and lines[0].strip() == "---":
+ for i in range(1, len(lines)):
+ if lines[i].strip() == "---":
+ body = lines[i + 1:]
+ break
+ bad = []
+ if "alwaysApply: true" not in t:
+ bad.append("alwaysApply missing")
+ if "# budget: unlimited (max)" not in lines:
+ bad.append("budget line not unlimited (max)")
+ al = [l for l in lines if l.startswith("# HARD LOCK allowlist")]
+ if len(al) != 1 or any(s not in al[0] for s in CLOSED_REAL + ["inherit-parent", "auto"]):
+ bad.append("allowlist line wrong")
+ roles = [l.rstrip() for l in body if ROLE_RE.match(l)]
+ if len(roles) != 18:
+ bad.append("%d role lines, expected 18" % len(roles))
+ for l in roles:
+ for v in (x.strip() for x in l.split(": ", 1)[1].split(",")):
+ if v not in CLOSED:
+ bad.append("invalid value %r" % v)
+ checks.append({"name": "rule-audit", "ok": not bad,
+ "detail": "rule file: alwaysApply, unlimited budget, allowlist line, 18 closed-set role lines" if not bad
+ else "; ".join(bad)})
+
+
+def check_registration(root, checks):
+ missing = []
+ skills_dir = os.path.expanduser("~/.claude/skills")
+ for s in SKILL_LINKS:
+ if not os.path.exists(os.path.join(skills_dir, s, "SKILL.md")):
+ missing.append(s)
+ if not os.path.exists(os.path.expanduser("~/.cursor/agents/onprem-agent.md")):
+ missing.append("onprem-agent")
+ if missing:
+ checks.append({"name": "registration", "ok": True,
+ "detail": "skip: not registered on this machine (expected on a fresh clone): " + ", ".join(missing)})
+ else:
+ checks.append({"name": "registration", "ok": True, "detail": "16 skill links + agent link resolve"})
+
+
+def doctor(root):
+ out = {
+ "python3": True,
+ "plugin_root": root is not None,
+ "manifest": bool(root) and os.path.isfile(os.path.join(root, ".cursor-plugin", "plugin.json")),
+ "rule_file": os.path.exists(os.path.expanduser("~/.cursor/rules/onprem-models.mdc")),
+ "git_origin": None,
+ }
+ if root:
+ import subprocess
+ try:
+ r = subprocess.run(["git", "remote", "get-url", "origin"], cwd=root,
+ capture_output=True, text=True, timeout=10)
+ out["git_origin"] = r.stdout.strip() or None
+ except Exception:
+ pass
+ return out
+
+
+def main():
+ root = find_plugin_root()
+ if root is None:
+ print(json.dumps({"status": "error", "error": "plugin root not found (no .cursor-plugin/plugin.json above this file)"}))
+ sys.exit(2)
+ checks = []
+ if "--doctor" in sys.argv:
+ print(json.dumps({"status": "doctor", "plugin": root, "doctor": doctor(root)}, indent=2))
+ return
+ check_manifest(root, checks)
+ check_frontmatter(root, checks)
+ check_mode_dashes(root, checks)
+ check_template_identity(root, checks)
+ check_closed_set(root, checks)
+ check_banned_leak(root, checks)
+ check_links(root, checks)
+ check_rule(root, checks)
+ check_registration(root, checks)
+ fails = [c for c in checks if not c["ok"]]
+ result = {
+ "status": "pass" if not fails else "fail",
+ "plugin": root,
+ "checked_at": datetime.now(timezone.utc).isoformat(timespec="seconds"),
+ "checks": checks,
+ "fails": len(fails),
+ }
+ print(json.dumps(result, indent=2))
+ sys.exit(1 if fails else 0)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/onprem/.gitignore b/onprem/.gitignore
new file mode 100644
index 000000000..e43b0f988
--- /dev/null
+++ b/onprem/.gitignore
@@ -0,0 +1 @@
+.DS_Store
diff --git a/onprem/README.md b/onprem/README.md
new file mode 100644
index 000000000..135432638
--- /dev/null
+++ b/onprem/README.md
@@ -0,0 +1,129 @@
+# onprem
+
+this is our on-prem approach, packaged as a stack. the premise is simple: we run agent work on self-hosted, open-weight models, and we want that to be a property of the configuration, not an accident of which model happened to be available.
+
+**onprem is a hard lock plus an operating doctrine.** the lock keeps every subagent spawn on a closed allowlist of on-prem models and out of the frontier API families, absolute, no exceptions. the doctrine is how to work well when you're limited to a 27B-class model: small verifiable units, proof by rerunnable checks, context kept lean, inheritance done honestly.
+
+**it works like pstack.** an entry mode, a stack of named principles you steer by name, a setup skill that writes and validates the rule, and a subagent that runs the approach end to end. if you know pstack, you know onprem. the difference is the subject: pstack is engineering style, onprem is the model governance and the on-prem way of working.
+
+## install
+
+```bash
+/add-plugin onprem
+```
+
+once it is published in the marketplace. until then, for a local copy:
+
+```bash
+mkdir -p ~/.cursor/plugins/local && git clone https://github.com/TeslaJarTran/onprem ~/.cursor/plugins/local/onprem
+```
+
+start a new chat after installing, and reload the window (developer: reload window) if the skills do not appear. cursor picks up local plugins at startup.
+
+## get started
+
+two steps:
+
+1. run [`/setup-onprem`](./skills/setup-onprem/SKILL.md). it detects the on-prem models you have, strips the banned set, confirms the unlimited (max) budget, maps a model to each role, validates every slug, and writes `~/.cursor/rules/onprem-models.mdc`.
+2. use [`/onprem-mode`](./skills/onprem-mode/SKILL.md) whenever you're doing anything that must stay on-prem.
+
+new here? the [onprem guide](./docs/guide/README.md) walks you through setup, the lock, and the principles. stuck, or unsure which skill fits? ask [`/onprem-help`](./skills/onprem-help/SKILL.md).
+
+## the lock
+
+one paragraph, because it is the whole point:
+
+every subagent spawn uses only `inherit-parent`/`auto`, `Qwen/Qwen3.8-27B-FP8`, `Qwen/Qwen3.8-Flash-Next-FP8`, `moonshotai/Kimi-K3`, `zai-org/GLM-5.3`. never claude/anthropic, gemini/google, grok/xai, or openai/gpt, even when one is available, even on a failed spawn. a failed spawn is retried on a different allowlisted model or stopped, never degraded. panels stay on the two on-prem families. the budget is unlimited. [`/setup-onprem`](./skills/setup-onprem/SKILL.md) is the allowlist, and the [audit playbook](./skills/onprem-mode/playbooks/audit.md) checks the rule file against it.
+
+## usage
+
+use [`/onprem-mode`](./skills/onprem-mode/SKILL.md) at the start of a task. it applies the lock and the named principles, and routes to a playbook when the task is a model decision, a failed spawn, a cross-model review, or a deep external review.
+
+### the four playbooks
+
+| playbook | for |
+|---|---|
+| [audit](./skills/onprem-mode/playbooks/audit.md) | validate `onprem-models.mdc` against the lock. "is my lock intact", "check my model config". |
+| [spawn retry](./skills/onprem-mode/playbooks/spawn-retry.md) | a subagent spawn failed or timed out; pick the retry model without crossing the ban. |
+| [panel](./skills/onprem-mode/playbooks/panel.md) | cross-model review of a diff or design, judges on on-prem models, cross-judge differs from the parent. |
+| [robodev](./skills/onprem-mode/playbooks/robodev.md) | deep external review of a diff or tree on the managed fleet; evidence, never a seat. "robodev this", "pre-ship cross-check". |
+
+to keep [`/onprem-mode`](./skills/onprem-mode/SKILL.md) on across turns, pick it from the `/` menu and press option+enter (mac) or alt+enter (windows) instead of enter. that makes it a custom mode. plain enter attaches it to one message only.
+
+## skills
+
+```
+/onprem-mode build the export, but i want to trust it stayed on-prem.
+```
+
+```
+/setup-onprem
+```
+
+
+all skills
+
+| skill | use it when |
+|---|---|
+| [`/onprem-mode`](./skills/onprem-mode/SKILL.md) | default entry point for work that must stay on-prem. |
+| [`/onprem-help`](./skills/onprem-help/SKILL.md) | you're new to onprem, or unsure which skill, playbook, or principle fits. |
+| [`/setup-onprem`](./skills/setup-onprem/SKILL.md) | you want to pick which models onprem uses per role. detects your on-prem models and writes a config rule. |
+| [`/create-verify-skill`](./skills/create-verify-skill/SKILL.md) | you want a project-local skill that drives the real app and proves behavior with rerunnable evidence. |
+| [`principle-*`](./skills/principle-closed-allowlist/SKILL.md) | the twelve named principles. you steer by name, you rarely invoke one. |
+
+
+
+### examples
+
+```
+setup: /setup-onprem
+lock check: /onprem-mode audit the rule. is my lock intact?
+spawn failed: /onprem-mode the spawn timed out. what model do i retry on?
+panel: /onprem-mode panel this diff. i want two on-prem judges and a cross-judge.
+verify: /create-verify-skill
+steer by name: apply no fallback to banned. tell me what you retried on.
+ apply deterministic proof. show me the check, not the self-report.
+```
+
+## the `onprem-agent` subagent
+
+onprem ships a subagent that runs the approach end to end. spawn it from a parent agent via [`subagent_type: "onprem-agent"`](./agents/onprem-agent.md). it reads `onprem-mode` in full, including its inline principles index, before doing any work. substituting `generalPurpose` skips that read and drifts.
+
+[`/onprem-mode`](./skills/onprem-mode/SKILL.md) and [`subagent_type: "onprem-agent"`](./agents/onprem-agent.md) route through the same wrapper.
+
+## principles
+
+twelve short skills, one principle each. `onprem-mode` indexes them inline and reads that index at task start. the standalone files are there so other skills can reference a principle by name, and so the index can point at the full rule for each. steer by name: "apply closed allowlist" or "that's an inherit-parent, confirm it's honest."
+
+
+all twelve principles
+
+| principle | group | rule |
+|---|---|---|
+| [closed-allowlist](./skills/principle-closed-allowlist/SKILL.md) | lock | The legal set is closed: `inherit-parent`/`auto`, `Qwen/Qwen3.8-27B-FP8`, `Qwen/Qwen3.8-Flash-Next-FP8`, `moonshotai/Kimi-K3`, `zai-org/GLM-5.3`. Everything else is an invalid value. |
+| [banned-set](./skills/principle-banned-set/SKILL.md) | lock | Anthropic/Claude, Google/Gemini, Grok/xAI, OpenAI/GPT are banned by family, absolute, no exceptions. |
+| [role-mapping](./skills/principle-role-mapping/SKILL.md) | lock | Each role pins to an allowlisted model from the rule file; the skill's inline defaults are themselves locked. |
+| [no-fallback-to-banned](./skills/principle-no-fallback-to-banned/SKILL.md) | lock | A failed spawn retries on a different allowlisted model, or stops. Never degrades to a banned one. |
+| [panel-composition](./skills/principle-panel-composition/SKILL.md) | lock | Two on-prem families, no third; the cross-judge differs from the parent. |
+| [unlimited-budget](./skills/principle-unlimited-budget/SKILL.md) | lock | The stack runs at unlimited (max) depth. On-prem spend is not a cost argument. |
+| [validation-gate](./skills/principle-validation-gate/SKILL.md) | lock | The rule is written only after every slug validates. A gate, not a warning. |
+| [verifiable-units](./skills/principle-verifiable-units/SKILL.md) | operate | Multi-step work is a sequence of small units, each ending in a check before the next. |
+| [context-discipline](./skills/principle-context-discipline/SKILL.md) | operate | Bulk to subagents, summaries not payloads, file pointers not inline context. |
+| [deterministic-proof](./skills/principle-deterministic-proof/SKILL.md) | operate | Prove results with a rerunnable check; a model self-report is a claim, not a proof. |
+| [inherit-parent-honestly](./skills/principle-inherit-parent-honestly/SKILL.md) | operate | `inherit-parent` inherits the parent, it does not grant on-prem status. |
+| [fresh-subagents](./skills/principle-fresh-subagents/SKILL.md) | operate | Fresh subagent with consolidated scope by default; resume only for resident state. |
+
+
+
+## sharing it
+
+onprem is made to hand to someone else. the lock is in two parts: the plugin (the doctrine and the enforcement) and the rule file (the configuration). the rule file is **generated on their machine**, not copied, because their on-prem entitlements may differ and the setup skill validates what it writes. [guide page 5](./docs/guide/05-sharing.md) has the exact handoff and the coexistence note for people who also run pstack.
+
+## not shipped here
+
+- pstack's full workflow skills (`how`, `why`, `architect`, `arena`, `swarm`, `interrogate`, `reflect`, and the engineering principles) are pstack's, not onprem's. onprem is the governance and the on-prem way of working. if you want both, install pstack alongside and align the two rule files (see [sharing](./docs/guide/05-sharing.md)).
+- onprem has no `maintain-verification-skill` yet. `/create-verify-skill` doubles as the maintenance pass for now.
+
+## license
+
+MIT
diff --git a/onprem/agents/onprem-agent.md b/onprem/agents/onprem-agent.md
new file mode 100644
index 000000000..068677598
--- /dev/null
+++ b/onprem/agents/onprem-agent.md
@@ -0,0 +1,9 @@
+---
+name: onprem-agent
+description: Routing target for `/onprem-mode` and any request to work under the on-prem lock. Spawn it as a fresh `onprem-agent` for each new task (the exact registered `subagent_type` slug, with no plugin prefix), and resume one only in the strict cases that onprem-mode's Subagents section names. Reads the `onprem-mode` skill's `SKILL.md` in full before any work, including its inline Principles index. Substituting `generalPurpose` skips that read and drifts.
+is_background: true
+---
+
+# Onprem subagent
+
+You are operating as onprem-mode's full approach. Read the `onprem-mode` skill's `SKILL.md` in full before doing any work, including its inline Principles index. Navigate to a leaf `principle-*` skill whenever you apply that principle. The on-prem model lock applies to every spawn you make.
diff --git a/onprem/docs/guide/01-setup.md b/onprem/docs/guide/01-setup.md
new file mode 100644
index 000000000..47ede5913
--- /dev/null
+++ b/onprem/docs/guide/01-setup.md
@@ -0,0 +1,35 @@
+# Setup
+
+Two steps. The install changes nothing until you run the setup skill.
+
+## 1. Install
+
+```bash
+/add-plugin onprem
+```
+
+or place the plugin directory at `~/.cursor/plugins/local/onprem/`. Cursor picks up skills and agents from there.
+
+## 2. Run `/setup-onprem`
+
+It does seven things, in order:
+
+1. **Detects** the model slugs available to your session, and strips the banned families from what it shows you.
+2. **Loads** your current `~/.cursor/rules/onprem-models.mdc` if one exists, so a re-run keeps your choices.
+3. **Confirms the budget.** The onprem stack runs `unlimited (max)` and depth is never rationed, so the budget is a confirmation, not a choice. It is recorded as the rule's `# budget` line; it does not rewrite the model slugs, which carry no effort token.
+4. **Shows the role map** and lets you change individual roles. You are offered only allowlisted detected models, plus `inherit-parent` and `auto`.
+5. **Validates.** Every real slug must be allowlisted *and* available. A banned or unavailable slug stops the write. This is a gate, not a warning.
+6. **Writes** the rule file, overwriting it whole so re-runs stay idempotent.
+7. **Confirms**, names the allowlist, names what the `inherit-parent`/`auto` roles inherit (and warns when the parent chat is a frontier model), and offers a `/create-verify-skill` pass if your project has no way to prove app behavior.
+
+The rule file applies to **new sessions**. A model change mid-chat does not re-route subagents already in flight.
+
+## What the rule file is
+
+`~/.cursor/rules/onprem-models.mdc` is an always-applied rule: one line per role, `role: model`. It is the single source of truth for which model each delegation runs on. The skills' inline defaults are themselves locked to the same allowlist, so the fallback path cannot leak a frontier model.
+
+## Coexistence with pstack
+
+If you also run pstack, each stack reads its own rule file (`pstack-models.mdc` vs `onprem-models.mdc`). To keep one source of truth for the allowlist, point pstack's role values at the same on-prem models. `/setup-onprem` offers this alignment; it does not replace the pstack rule.
+
+Next: [The lock](./02-the-lock.md).
diff --git a/onprem/docs/guide/02-the-lock.md b/onprem/docs/guide/02-the-lock.md
new file mode 100644
index 000000000..868dc1cab
--- /dev/null
+++ b/onprem/docs/guide/02-the-lock.md
@@ -0,0 +1,58 @@
+# The lock
+
+The on-prem lock is a closed set plus an absolute ban plus a failure policy. Each part exists because the others alone are not enough.
+
+## The closed allowlist
+
+The only legal model values, in full:
+
+- `inherit-parent` or `auto` — the subagent runs on the parent chat model (omit the Task `model`).
+- `Qwen/Qwen3.8-27B-FP8`
+- `Qwen/Qwen3.8-Flash-Next-FP8`
+- `moonshotai/Kimi-K3`
+- `zai-org/GLM-5.3`
+
+Anything else is an **invalid value**, not a weaker alternative. A closed set turns "is this allowed?" into a yes/no lookup, which is the only form a lock can take. An open list ("these, and whatever else is available") has no edge, and every new model release is an argument.
+
+Adding a model is an explicit, user-approved edit to the rule file. It never happens at runtime.
+
+## The banned set
+
+Banned by family, absolute, no exceptions:
+
+- **Anthropic / Claude** — `claude`, `anthropic`, `fable`, `opus`, `sonnet`, `haiku`
+- **Google / Gemini** — `gemini`, `google`
+- **Grok / xAI** — `grok`, `xai`
+- **OpenAI / GPT** — `gpt`, `sol`
+
+Banning by family (substring) rather than by slug list closes the gap that new model names would open. And the ban holds in the three places it is most likely to be tested:
+
+- when a banned slug **is available** in the session,
+- when a spawn **fails or times out** and the pressure is to "just try the other one",
+- when **no allowlisted model seems to work** and a banned one looks like the escape hatch.
+
+There is no escape hatch. If nothing allowlisted works, the run stops and tells you.
+
+## The failure policy
+
+A failed spawn is retried on a **different allowlisted** model for the same role, capped at two retries, or stopped and reported. The onprem-mode **spawn-retry** playbook is this policy as a checklist. A task finished on a banned model after on-prem failures is not a success with a footnote; it is a lock violation, and it is reported as one.
+
+## The panel rule
+
+Multi-model panels use the two on-prem families: `Qwen/Qwen3.8-27B-FP8` and `zai-org/GLM-5.3`. No third family joins. The cross-judge seat takes a family different from the parent chat's model when possible, staying on Qwen or GLM. This is **panel-composition**: diversity without reintroducing what the lock removes.
+
+## The budget
+
+`unlimited (max)`. On a self-hosted stack the marginal token is not a per-request bill, so the usual reason to cap reasoning depth does not apply. The standard for a spawn is the strongest on-prem fit for the role, not the cheapest model that might work. **unlimited-budget**.
+
+## Where the lock is enforced
+
+| Surface | Enforcement |
+|---|---|
+| `/setup-onprem` | validation gate before the rule is written |
+| `~/.cursor/rules/onprem-models.mdc` | always-applied; names the allowlist and the ban |
+| `onprem-mode` | non-negotiables section; subagent defaults are locked |
+| spawn failure | the spawn-retry playbook |
+| rule drift | the audit playbook |
+
+Next: [Onprem mode](./03-onprem-mode.md).
diff --git a/onprem/docs/guide/03-onprem-mode.md b/onprem/docs/guide/03-onprem-mode.md
new file mode 100644
index 000000000..5aab9d25b
--- /dev/null
+++ b/onprem/docs/guide/03-onprem-mode.md
@@ -0,0 +1,28 @@
+# Onprem mode
+
+`/onprem-mode` is the entry point. It applies the lock, indexes the twelve principles, and routes to three playbooks. Start it as a Custom Mode (option+enter / alt+enter) to keep it on across turns, or plain enter for one message.
+
+## What it does at task start
+
+1. Reads its own principles index. Applies a principle, names it in the reply, and says which decision it changed. A principle citation with no decision behind it is the tell that it name-dropped instead of applied.
+2. Matches the task to a playbook and copies the playbook's steps into the todo list verbatim. A skipped step stays in the list with a one-line reason.
+3. Routes subagents through `onprem-agent`, with the model per role from the rule file.
+
+## The three playbooks
+
+**Audit** — validate `~/.cursor/rules/onprem-models.mdc` against the lock. Use it after a hand edit, a fresh clone of a repo that carries the rule, or whenever "is my lock intact?" is a real question. The deliverable is either a named list of violations or a clean bill, with the allowlist named.
+
+**Spawn retry** — the failure path. A spawn failed, timed out, or was rejected; the playbook decides the retry model. It records the failure, checks whether the attempted model was allowlisted (a no means the rule or the spawn is broken and gets flagged for audit), retries up to twice on different allowlisted models, then stops with a report. It never degrades to a banned model.
+
+**Panel** — cross-model review. One subagent per on-prem family seat, a cross-judge on the family different from the parent, and a verdict that names what was merged or won. A panel's claims get **deterministic-proof** where a check can settle them.
+
+## The subagent
+
+`subagent_type: "onprem-agent"` reads the mode in full before work, including the principles index. Use it for any subagent spawned inside a playbook step. Substituting `generalPurpose` skips the read and drifts, which on a 27B-class model means visible drift, not subtle drift.
+
+## When onprem is not the tool
+
+- You want pstack's full engineering workflow (how/why/architect/arena/swarm/interrogate) on its model policy: that is pstack. Install both and run the work you want on-prem under `/onprem-mode`.
+- You want a one-off model question, not a run: answer it directly; no mode needed.
+
+Next: [Principles](./04-principles.md).
diff --git a/onprem/docs/guide/04-principles.md b/onprem/docs/guide/04-principles.md
new file mode 100644
index 000000000..71c3e3744
--- /dev/null
+++ b/onprem/docs/guide/04-principles.md
@@ -0,0 +1,57 @@
+# Principles
+
+onprem ships twelve principles as individual skills, one rule each. `/onprem-mode` reads their index at the start of every task, applies the ones the task triggers, and names each applied principle in its reply along with the decision it changed.
+
+You don't invoke principles. You use their names to steer. Each name points at a complete rule the agent has already read, so one phrase redirects the work more precisely than a paragraph of instructions.
+
+## Steering in practice
+
+Say a spawn is about to fall back to the frontier model because the on-prem one timed out:
+
+```text
+apply no fallback to banned. retry on a different allowlisted model, or stop and tell me.
+```
+
+Say the rule file was hand-edited and you want to know the state of your lock:
+
+```text
+audit the rule. is my lock intact?
+```
+
+Say a long task is drifting on a 27B-class model:
+
+```text
+re-plan as verifiable units. every step ends in a check before the next one starts.
+```
+
+Say a role line is `inherit-parent` and the parent chat is a frontier model:
+
+```text
+that's inherit-parent, confirm it's honest. pin the role to a real on-prem slug.
+```
+
+Each phrase lands because the rule behind it is specific. The agent still has to say, in its reply, which decision the rule changed.
+
+## The twelve, briefly
+
+The **lock** principles are the governance doctrine:
+
+- [Closed Allowlist](../../skills/principle-closed-allowlist/SKILL.md) — the legal set is closed; anything else is an invalid value, not a weaker one.
+- [Banned Set](../../skills/principle-banned-set/SKILL.md) — Anthropic/Claude, Google/Gemini, Grok/xAI, OpenAI/GPT, banned by family, absolute.
+- [Role Mapping](../../skills/principle-role-mapping/SKILL.md) — every role pins to an allowlisted model from the rule file; the inline defaults are locked too.
+- [No Fallback to Banned](../../skills/principle-no-fallback-to-banned/SKILL.md) — a failed spawn retries on a different allowlisted model or stops; availability is not license.
+- [Panel Composition](../../skills/principle-panel-composition/SKILL.md) — two on-prem families, no third; the cross-judge differs from the parent.
+- [Unlimited Budget](../../skills/principle-unlimited-budget/SKILL.md) — depth is not rationed; on-prem spend is not a cost argument.
+- [Validation Gate](../../skills/principle-validation-gate/SKILL.md) — the rule is written only after every slug validates; a gate, not a warning.
+
+The **operate** principles are the on-prem way of working:
+
+- [Verifiable Units](../../skills/principle-verifiable-units/SKILL.md) — small units, each ending in a check; long undivided tasks are where on-prem models drift.
+- [Context Discipline](../../skills/principle-context-discipline/SKILL.md) — bulk to subagents, summaries not payloads; context is the scarcest resource on a 27B-class model.
+- [Deterministic Proof](../../skills/principle-deterministic-proof/SKILL.md) — a rerunnable check beats a self-report; the check must be able to fail.
+- [Inherit-Parent Honestly](../../skills/principle-inherit-parent-honestly/SKILL.md) — the alias inherits, it does not grant; pin roles that must stay on-prem.
+- [Fresh Subagents](../../skills/principle-fresh-subagents/SKILL.md) — fresh with consolidated scope; resume only for resident state.
+
+Don't memorize the list. Skim it now, then come back when you catch the agent doing something a name here would have prevented. That's how the vocabulary sticks.
+
+Next: [Sharing](./05-sharing.md).
diff --git a/onprem/docs/guide/05-sharing.md b/onprem/docs/guide/05-sharing.md
new file mode 100644
index 000000000..79ef56aaa
--- /dev/null
+++ b/onprem/docs/guide/05-sharing.md
@@ -0,0 +1,47 @@
+# Sharing
+
+onprem is built to hand to someone else. The handoff is short because the stack is self-describing: the lock is in the plugin, and the configuration is generated on their machine.
+
+## The handoff
+
+Send the repo (https://github.com/TeslaJarTran/onprem) or the plugin directory. They do:
+
+1. `mkdir -p ~/.cursor/plugins/local && git clone https://github.com/TeslaJarTran/onprem ~/.cursor/plugins/local/onprem`, or put the directory there.
+2. Run `/setup-onprem`.
+3. Start a new chat, then start a task with `/onprem-mode`. The rule applies to new sessions only.
+
+If the `/onprem-*` skills do not appear in the `/` menu after cloning, reload the window (Developer: Reload Window). Cursor picks up local plugins at startup.
+
+That's it. The rule file is **generated, not copied**, on purpose:
+
+- Their on-prem entitlements may differ from yours. `/setup-onprem` detects what they actually have and offers only the intersection with the allowlist.
+- The validation gate refuses to write a slug they don't have, so a pasted-in rule that points at your `Qwen/Qwen3.8-Flash-Next-FP8` endpoint would not silently break their spawns.
+- The gate checks that a slug is allowlisted and listed in their session, not that their endpoint serves it. Their first real `/onprem-mode` task is the live check: run one small task before fanning out.
+- The ban is identical for everyone. Nothing in the setup is per-operator except which allowlisted slugs are available and the role values they choose.
+
+## What to send them
+
+A one-paragraph version, ready to paste:
+
+> We run agent work on-prem only. The onprem stack enforces that: a closed allowlist (`inherit-parent`/`auto`, `Qwen/Qwen3.8-27B-FP8`, `Qwen/Qwen3.8-Flash-Next-FP8`, `moonshotai/Kimi-K3`, `zai-org/GLM-5.3`), an absolute ban on Claude/Anthropic, Gemini/Google, Grok/xAI, and OpenAI/GPT, and a failure policy that retries on another allowlisted model or stops, never degrades. Install the `onprem` plugin, run `/setup-onprem`, and use `/onprem-mode` for work that must stay on-prem. The twelve named principles are in the README and `docs/guide/04-principles.md`.
+
+## Coexistence with pstack
+
+People who already run pstack can run onprem alongside it. The rules:
+
+- Each stack reads its own rule file: `pstack-models.mdc` for pstack, `onprem-models.mdc` for onprem.
+- If they want one source of truth for the allowlist, `/setup-onprem` offers to align pstack's role values with the on-prem allowlist. It does not overwrite or delete the pstack rule.
+- The modes are separate. Work that must stay on-prem starts with `/onprem-mode`; work on pstack's model policy starts with `/poteto-mode`.
+- The subagents are separate too: `poteto-agent` vs `onprem-agent`.
+
+The practical pattern: pstack's engineering workflow skills (how, why, architect, arena, swarm, interrogate) do the shape of the work, and the on-prem rule files make the model policy the same under both. The onprem principles then apply whenever `/onprem-mode` is in context.
+
+## Auditing after the handoff
+
+The recipient can verify their own install any time:
+
+```
+/onprem-mode audit the rule. is my lock intact?
+```
+
+The audit playbook checks the allowlist line and every role value against the closed set, and reports violations by line. A clean audit is the answer to "did this land right?"
diff --git a/onprem/docs/guide/README.md b/onprem/docs/guide/README.md
new file mode 100644
index 000000000..51b07230c
--- /dev/null
+++ b/onprem/docs/guide/README.md
@@ -0,0 +1,11 @@
+# The onprem guide
+
+A short walkthrough of the on-prem stack: setup, the lock, the mode, the principles, and how to hand it to someone else.
+
+1. [Setup](./01-setup.md) — install, run `/setup-onprem`, what the rule file is.
+2. [The lock](./02-the-lock.md) — the closed allowlist, the banned set, and why each part exists.
+3. [Onprem mode](./03-onprem-mode.md) — the entry mode, the three playbooks, the subagent.
+4. [Principles](./04-principles.md) — the twelve named principles, and steering by name.
+5. [Sharing](./05-sharing.md) — the handoff, and coexistence with pstack.
+
+The guide is for humans. The skills are for agents. If they disagree, the skill files own the behavior and this guide should be fixed.
diff --git a/onprem/skills/create-verify-skill/SKILL.md b/onprem/skills/create-verify-skill/SKILL.md
new file mode 100644
index 000000000..9a8386dc9
--- /dev/null
+++ b/onprem/skills/create-verify-skill/SKILL.md
@@ -0,0 +1,46 @@
+---
+name: create-verify-skill
+description: "Generate a project-local verification skill that drives your app the way a user does and proves behavior with rerunnable evidence, tuned for on-prem models. Use for /create-verify-skill, \"make a verify skill for this repo\", or when a project has no scripted way to prove UI/CLI/service behavior."
+disable-model-invocation: true
+---
+
+# Create a verify skill
+
+Every serious project needs a scripted way to drive the real app and prove behavior: launch it, exercise a feature the way a user would, and capture evidence. This skill generates that as a project-local skill (`.cursor/skills/verify-/`) tailored to the repo. You write the generator's output for the next agent, not for a human: it will be read cold, mid-task, by an agent that has never seen the app.
+
+On-prem models drift more on undivided work, so the generated skill is built for **verifiable-units**: short, single-purpose drive steps, each with its own pass/fail evidence, rather than one long scenario.
+
+## 1. Interview the repo, not the user
+
+Answer these from the codebase and only ask the user what you cannot observe:
+
+- **Surface:** what does a user actually touch? A web UI, a CLI/TUI, a desktop app, an API, a mobile app, a library? A repo can have several; pick the primary one and note the rest.
+- **Run:** how does the app start locally? Prefer the repo's own documented dev command (package scripts, Makefile, README quickstart). Note ports, env vars, seed data, auth.
+- **Drive:** how can an agent interact with it programmatically? Existing harnesses first — Playwright/Cypress specs, expect scripts, PTY helpers, curl-able endpoints, a debug port. Only then pick a generic recipe: browser/CDP for web and Electron, a tmux/PTY harness for CLI/TUI, plain HTTP for services.
+- **Observe:** what evidence can be captured? Screenshots, terminal transcripts, response bodies, logs, exit codes, DB state.
+- **Isolate:** can two instances run side by side (ports, data dirs, profiles)? If not, say so in the generated skill: refusing to double-drive a shared instance beats corrupting the user's session.
+
+If the checkout doesn't build or start as-is, fix that first (or report it precisely) before generating; a skill written against a broken base teaches wrong steps. When an irrelevant missing asset blocks startup (a static dir the API never serves, a sample config), the generated skill may create it, clearly marked as verification scaffolding, and remove it in cleanup.
+
+## 2. Generate the skill
+
+Write `.cursor/skills/verify-/SKILL.md` with YAML frontmatter (`name: verify-` and a `description` that names the app, the surface, and when to reach for it — without frontmatter the skill never registers) and these sections, each grounded in what the interview actually found (no placeholders left):
+
+- **Launch:** the exact command that starts the app for verification, and how to tell it's ready (a log line, a port answering, a prompt). Include teardown. For a short-lived CLI or TUI there is no server to keep alive: launch means build the binary (or install deps) once, then start each drive in its own isolated PTY or tmux session.
+- **Doctor:** one read-only check that answers "is this instance worth driving?" — process up, right version/build, port owned by us, auth valid. An agent runs this first whenever anything looks off.
+- **Drive:** the harness recipe with real selectors/commands from this repo, not examples. Prefer stable handles (ARIA labels, data attributes, prompt strings, route paths) over coordinates and tab order. Keep each drive step small: one action, one assertion, so a failed step is local to one unit.
+- **Evidence:** what to capture for a proof and where it goes. State the proof standards: exercise the real user path, not internal setters or test-only endpoints; capture the action and the resulting state, not just the final screen; verify side effects (files written, rows inserted, messages sent) alongside what's visible; mocks only where a production boundary already isolates the external system. When the safe path is a dry-run or test mode, verify what it actually skips by observing (files, network, git refs) rather than trusting its name: some dry-runs still touch the network or open a browser.
+- **Cleanup:** how to tear down instances the run created. Never kill by process name; kill what you started. Cleanup removes instances and scratch state, never the evidence: proof artifacts survive the teardown, in a location the skill names.
+- **Helpers:** any script the skill ships is executable and its invocation is shown in the skill body. A helper the reader has to reverse-engineer is not a helper.
+
+## 3. Seed the feature map
+
+Create `.cursor/skills/verify-/features/README.md` plus one file per user-facing feature you can identify (aim for the top 3-5 to start, from routes, commands, menus, or docs). Each file answers, from the user's point of view: what the feature is, how to reach it, how to drive it with the harness, and what observable end state proves it works. The four H2s are `Sub-features`, `How to get to it (user POV)`, `Driving it with `, and `Gotchas`. The map is the repo's maintained verification source; a proof that drives one convenient entry point is incomplete when the map lists others.
+
+## 4. Prove the generated skill before handing it over
+
+Run its own instructions end to end once: launch, doctor, drive ONE mapped feature (one is enough; the map exists so later runs can cover the rest), capture evidence, clean up. After cleanup, confirm the evidence still exists at the named location — a cleanup that eats the proof fails this step. Fix what fails, and run the generated cleanup after every failed iteration too, so broken attempts don't strand processes and ports. A generated skill that was never executed is a draft, not a deliverable.
+
+## 5. Offer the maintenance loop
+
+The map drifts as the app changes. When it does, re-run `/create-verify-skill` as a maintenance pass: re-interview the repo, diff the feature map against the app, and update the stale entries. Suggest a cadence only if the user asks.
diff --git a/onprem/skills/onprem-help/SKILL.md b/onprem/skills/onprem-help/SKILL.md
new file mode 100644
index 000000000..a7b5726aa
--- /dev/null
+++ b/onprem/skills/onprem-help/SKILL.md
@@ -0,0 +1,95 @@
+---
+name: onprem-help
+description: Guides users through onprem setup, /onprem-mode, and picking the skill, playbook, or principle for a task. Type /onprem-help with a question.
+disable-model-invocation: true
+---
+
+# Onprem help
+
+Answer the user's question about the onprem stack, hand them a prompt they can send, and link the file the answer came from. For a help question, don't start the work. The user asked how, and an onprem run spends real tokens, so let them send the prompt.
+
+A message that asks for work, such as "use onprem to fix this bug", is not a help question. Read [`onprem-mode`](../onprem-mode/SKILL.md), do the work under it, and mention once that a Custom Mode keeps it on.
+
+This file maps questions to the skills and guide pages that hold the answers. Those files own the details. Read the file you route to before you quote it, and trust it when it disagrees with this map.
+
+## Find out what they need
+
+Infer the need from the message and the conversation. A named situation goes straight to its section. If the need is still unclear, ask one multiple-choice question with these options, then answer only the section they pick:
+
+- What is the on-prem stack
+- Get set up
+- Start a task with `/onprem-mode`
+- Pick a skill or principle for a situation
+- Fix a run that went wrong
+- Share it with someone
+
+Check the state that changes the answer, and mention it only when it does:
+
+- No `~/.cursor/rules/onprem-models.mdc` means `/setup-onprem` hasn't run for this user, so every role uses its default model.
+- No `verify-*` skill or other app harness in the project means agents have no scripted way to drive the app. Mention `/create-verify-skill` when the question is about proving a change works.
+
+## What is the on-prem stack
+
+onprem is a model governance lock plus an operating doctrine, packaged like pstack. The lock is a **closed allowlist** of on-prem models: `inherit-parent`/`auto`, `Qwen/Qwen3.8-27B-FP8`, `Qwen/Qwen3.8-Flash-Next-FP8`, `moonshotai/Kimi-K3`, `zai-org/GLM-5.3`. Claude/Anthropic, Gemini/Google, Grok/xAI, and OpenAI/GPT are banned, absolute, no exceptions. The doctrine is the operate principles: verifiable units, context discipline, deterministic proof, honest inheritance, fresh subagents. The 12 named principles live under `skills/principle-*/`. [Guide page 2](../../docs/guide/02-the-lock.md) has the full lock. [Guide page 4](../../docs/guide/04-principles.md) lists the principles.
+
+## Get set up
+
+1. Put the plugin at `~/.cursor/plugins/local/onprem/` (or `/add-plugin onprem` once it is hosted).
+2. Run [`/setup-onprem`](../setup-onprem/SKILL.md). It detects the on-prem models you have, strips the banned set, confirms the unlimited (max) budget, maps a model to each role, validates every slug, and writes `~/.cursor/rules/onprem-models.mdc`. The rule applies to new chats.
+3. Start a real task with `/onprem-mode`, a goal, and a check that can pass or fail.
+
+Installing changes nothing until the user invokes a skill. Only `/setup-onprem` writes the rule. The [README](../../README.md) and [guide page 1](../../docs/guide/01-setup.md) have the details.
+
+## Start a task with `/onprem-mode`
+
+`/onprem-mode` applies the lock and the named principles, and routes to its playbooks (Audit, Spawn retry, Panel) when a model decision, a failed spawn, or a multi-model review is the task. A good prompt states the goal and how to tell it's done.
+
+Whether `/onprem-mode` stays on depends on how the user starts it:
+
+- Enter on `/onprem-mode` attaches the skill to one message. It fades as the chat moves on.
+- Option+Enter on Mac or Alt+Enter on Windows, or Use as Mode from the skill entry, makes it a Custom Mode. It stays in context every turn until the user exits the mode.
+- Cursor's docs list Custom Modes in the Agents Window and the CLI. Elsewhere, start each new task with `/onprem-mode`.
+
+To get the same style from a subagent of your own, spawn it with `subagent_type: "onprem-agent"`. That is the exact registered slug, the bare `name` from `agents/onprem-agent.md`, with no plugin prefix. It reads the mode in full before work.
+
+## Pick a skill or principle
+
+| The user wants to | Skill |
+|---|---|
+| Do non-trivial work under the on-prem lock | [`/onprem-mode`](../onprem-mode/SKILL.md) |
+| Pick models per role and a budget; write the rule | [`/setup-onprem`](../setup-onprem/SKILL.md) |
+| Validate the rule file against the lock | [`/onprem-mode`](../onprem-mode/SKILL.md), the **audit** playbook |
+| Decide the retry model after a failed spawn | [`/onprem-mode`](../onprem-mode/SKILL.md), the **spawn-retry** playbook |
+| Run a cross-model review on on-prem judges | [`/onprem-mode`](../onprem-mode/SKILL.md), the **panel** playbook |
+| Run a deep managed-fleet review of a diff or tree | [`/onprem-mode`](../onprem-mode/SKILL.md), the **robodev** playbook |
+| Give agents a scripted way to drive the app and prove behavior | [`/create-verify-skill`](../create-verify-skill/SKILL.md) |
+| Steer with a principle's name | the `principle-*` leaf, e.g. "apply deterministic proof" |
+| Find their way around onprem | `/onprem-help` |
+
+Principles are one-rule skills that `/onprem-mode` reads and cites in its replies. The user rarely invokes one. They steer with the names instead, as in "apply no fallback to banned. tell me what you retried on." Typing `/principle-` still loads one on demand. [Guide page 4](../../docs/guide/04-principles.md) lists all twelve.
+
+Close calls:
+
+- `/setup-onprem` writes the configuration. The **audit** playbook reads it back and checks it. One is the write path, the other is the check path.
+- The **spawn-retry** playbook is the on-prem answer to a failed subagent. It is not a general error playbook; if the failure is not a model choice, debug it normally.
+- onprem's panel is for on-prem judges. If the user wants pstack's full multi-model review workflow with frontier models, that is pstack's `/interrogate`, not this stack's **panel**.
+
+## Fix a run that went wrong
+
+| Symptom | Fix |
+|---|---|
+| The mode stopped applying after a few turns | It was started with Enter. Start it as a Custom Mode, or start each task with `/onprem-mode`. |
+| A spawn used a frontier model | Check the parent chat model and the role lines. An `inherit-parent` role inherits the parent, it does not grant on-prem status. Pin the role to a real allowlisted slug. |
+| A rule change had no effect | The rule applies to new sessions. Start one. |
+| A banned slug is in the rule | Run `/setup-onprem`; it rewrites the file idempotently and validates every slug. |
+| An on-prem spawn keeps failing | The **spawn-retry** playbook caps retries and then stops with a report. It never degrades to a banned model. |
+| The reply claims success from a self-report | Ask for the real check. That's the deterministic-proof principle. |
+| A long task drifted on an on-prem model | Re-plan it as **verifiable-units**: smaller steps, each ending in a check. |
+
+## Share it with someone
+
+The stack is two things a recipient needs: the plugin directory and a run of `/setup-onprem` on their machine (their model entitlements may differ, so the rule is generated, not copied). Full instructions in [guide page 5](../../docs/guide/05-sharing.md), including the coexistence note for people who also run pstack.
+
+## Reply
+
+Lead with the answer. Give at most one example prompt in a code block, then the link to the file the answer came from. Keep it short unless the user asked for the whole map.
diff --git a/onprem/skills/onprem-mode/SKILL.md b/onprem/skills/onprem-mode/SKILL.md
new file mode 100644
index 000000000..1989f8e4a
--- /dev/null
+++ b/onprem/skills/onprem-mode/SKILL.md
@@ -0,0 +1,96 @@
+---
+name: onprem-mode
+description: The on-prem approach. Rigorous agent work locked to the on-prem model stack. The hard-lock policy as named principles, routing in verifiable units, proof by rerunnable checks. Use for onprem, /onprem-mode, or work that must run on on-prem, self-hosted, or open-weight models only.
+disable-model-invocation: true
+mode: true
+icon: shield
+color: purple
+reminder: New task? Apply the lock and the principles. Model or spawn decision? Cite the principle it changed.
+---
+
+# Onprem mode
+
+## Non-negotiables
+
+The Principles section below grounds every trigger. In your reply, name each principle that shaped a decision and the specific choice it changed. Cite only principles whose leaf SKILL.md you read this session.
+
+The on-prem lock, in five lines:
+
+- Every subagent spawn uses only `inherit-parent`/`auto`, `Qwen/Qwen3.8-27B-FP8`, `Qwen/Qwen3.8-Flash-Next-FP8`, `moonshotai/Kimi-K3`, `zai-org/GLM-5.3`.
+- Never Claude/Anthropic, Gemini/Google, Grok/xAI, or OpenAI/GPT, even when one is available in the session, even on a failed spawn.
+- A failed spawn is retried on a different allowlisted model, or stopped and reported. Never degraded to a banned one.
+- A role line of `inherit-parent` or `auto` runs that role on the parent chat model (omit Task `model`). It inherits the parent, it does not grant on-prem status. If the parent chat is a frontier model, that role runs frontier. See **inherit-parent-honestly**.
+- The rule file from `/setup-onprem` is the single source of truth for role-to-model mapping.
+
+Remaining triggers:
+
+- About to `AskQuestion` on a "which approach", "how should I", or "what should this do" fork → check whether something you can run answers it first. A fact is not the human's to decide.
+- Work on a 27B-class model → the **verifiable-units** principle. Small units, each ending in a check before the next.
+- Any fan-out or cross-model review → the **panel** playbook, with the cross-judge on an on-prem family.
+- About to retry a failed spawn on another model → the **no-fallback-to-banned** principle. The retry stays inside the allowlist.
+- Any model choice, for a subagent or for yourself → the **role-mapping** principle, checked against the rule file.
+- Verification that needs a rerunnable script instead of a model self-report → the **deterministic-proof** principle.
+
+## Principles
+
+Read the leaf skill in full for any principle you apply. Each entry names when it applies.
+
+**Lock**
+
+- **Closed Allowlist** (**principle-closed-allowlist**). Any model value for a spawn, a rule line, or a panel seat. The legal set is `inherit-parent`/`auto`, `Qwen/Qwen3.8-27B-FP8`, `Qwen/Qwen3.8-Flash-Next-FP8`, `moonshotai/Kimi-K3`, `zai-org/GLM-5.3`. Everything else is an invalid value.
+- **Banned Set** (**principle-banned-set**). Any model slug in the Anthropic/Claude, Google/Gemini, Grok/xAI, or OpenAI/GPT families. Never written, offered, presented, selected, defaulted to, or fallen back to, even when one is available in the session.
+- **Role Mapping** (**principle-role-mapping**). Any delegation, spawn, or panel seat. Each role pins to an allowlisted model from the rule file; the skill's own inline defaults are themselves locked.
+- **No Fallback to Banned** (**principle-no-fallback-to-banned**). A failed, timed-out, or rejected spawn. Retry on a different allowlisted model, or stop and tell the user. Never degrade.
+- **Panel Composition** (**principle-panel-composition**). Any diverse-model review, cross-judge, or panel. Two on-prem families, no third; the cross-judge picks a family different from the parent when possible.
+- **Unlimited Budget** (**principle-unlimited-budget**). Any cost, effort, or token-budget question about the on-prem stack. The stack is depth-free; spend is not a constraint.
+- **Validation Gate** (**principle-validation-gate**). Any time the model configuration is written or changed. A banned or unavailable slug stops the setup, it is not written.
+
+**Operate**
+
+- **Verifiable Units** (**principle-verifiable-units**). Multi-step work, migrations, sweeps, long tasks. On-prem models drift on long undivided tasks, so each unit ends in a check before the next begins.
+- **Context Discipline** (**principle-context-discipline**). Bulk reading, long files, fan-out planning. Context is the scarcest resource on a 27B-class model; keep the main thread for decisions and summaries.
+- **Deterministic Proof** (**principle-deterministic-proof**). Any claim of done, fixed, or working. Prefer a scripted, rerunnable check over a model self-report. "It compiles" is not proof.
+- **Inherit-Parent Honestly** (**principle-inherit-parent-honestly**). Any role mapped to `inherit-parent` or `auto`. The alias inherits, it does not grant; it is on-prem only when the parent chat is.
+- **Fresh Subagents** (**principle-fresh-subagents**). Every subagent spawn for a role round. Fresh with consolidated scope; resume only for state that lives in that agent.
+
+## Autonomy
+
+**Just do it.** Use any MCP tool. Reversible work and external actions proceed without asking.
+
+**Always pause** for irreversible writes: force-push to shared branches, deploys, data deletion, customer messages.
+
+**Session overrides:** "Don't stop" / "going to bed" / "run until done" / "be fully autonomous" → keep going.
+
+**No is an acceptable answer.** Decline, push back, or say "this doesn't earn its place" when true. Candor over sycophancy.
+
+## Subagents
+
+**Use `subagent_type: "onprem-agent"` for any subagent you spawn inside a playbook step** (code-writing delegates, ad-hoc helpers).
+
+**Defaults for every `Task` call.** `run_in_background: true`, agent mode, file pointers not inlined context. Model per role comes from the `/setup-onprem` rule. Code roles default to `Qwen/Qwen3.8-Flash-Next-FP8`. Prose and judgment default to `moonshotai/Kimi-K3`. The hardest changes read `hardest tasks`. Diverse-model panels default to `Qwen/Qwen3.8-27B-FP8` and `zai-org/GLM-5.3`. Never Claude/Anthropic, Gemini/Google, Grok/xAI, or OpenAI/GPT. A role with no line keeps its default, and a role line of `inherit-parent` or `auto` runs that role on the parent chat model (omit Task `model`), inheriting it, not granting on-prem status.
+
+You own every subagent's work. Review the diff and write your own summary, don't pass through what it said. A second opinion is the same prompt against a different model. Agreement is high-signal.
+
+**Fresh subagents by default.** Give new work to a fresh subagent with consolidated scope: the original brief, every later directive, and the prior agent's report and branch. Resume or reuse an existing subagent only when the work needs state that lives in that agent: its local checkout, its uncommitted changes, or a process it still runs. A stop or hold order is not reuse.
+
+## Writing the reply
+
+Write the reply clean as you draft it. A cleanup pass after drafting does not remove these patterns.
+
+- **Short declarative sentences.** One thought per sentence, ended with a period.
+- **No long-dash character anywhere.**
+- **A colon as a mid-sentence connector is out.** A colon before a list is fine.
+- **Terse is not an excuse to drop content.**
+- **Frame impact for the consumer and the maintainer.** Name who the work is for and what changes for them before implementation detail, then what the next engineer inherits.
+- **Every claim carries its evidence or its label in the same sentence.** Measured, inferred, or guess.
+- **When a spawn or retry happened, name the model chosen and the principle that locked it.**
+- **Never fabricate a link, citation, or transcript reference.** Link only artifacts you produced or read this session.
+
+## Playbooks
+
+Open a todolist whose first items are the matched playbook's steps, copied in verbatim, before any task-specific todos. A step you choose not to do stays in the list with a one-line `skip: `.
+
+- **Audit.** Validate `~/.cursor/rules/onprem-models.mdc` against the lock. "audit the rule", "is my lock intact", "check my model config". `playbooks/audit.md`.
+- **Spawn retry.** A subagent spawn failed, timed out, or was rejected, and the model choice is next. "the spawn failed, what now". `playbooks/spawn-retry.md`.
+- **Panel.** Cross-model review of a diff or a design, judges on on-prem models. "panel this", "cross-judge these attempts". `playbooks/panel.md`.
+- **Robodev.** External managed-fleet deep review of a diff or tree; the evidence path, never a judgment seat. "robodev this", "deep external review", "pre-ship cross-check". `playbooks/robodev.md`.
diff --git a/onprem/skills/onprem-mode/playbooks/audit.md b/onprem/skills/onprem-mode/playbooks/audit.md
new file mode 100644
index 000000000..de4e8387f
--- /dev/null
+++ b/onprem/skills/onprem-mode/playbooks/audit.md
@@ -0,0 +1,9 @@
+# Audit
+
+Validate the on-prem rule file as the lock surface. Use when the rule may have drifted, been hand-edited, or been inherited from another setup.
+
+1. Read `~/.cursor/rules/onprem-models.mdc`. Absent → run the `/setup-onprem` skill; there is nothing to audit.
+2. Check the allowlist line names exactly the closed set: `inherit-parent`/`auto`, `Qwen/Qwen3.8-27B-FP8`, `Qwen/Qwen3.8-Flash-Next-FP8`, `moonshotai/Kimi-K3`, `zai-org/GLM-5.3`.
+3. List every role line and its value.
+4. For each value: it must be an alias or on the allowlist. A banned or unknown slug is a violation. A value can be allowlisted and still wrong for the role's tier. Flag a single-model judgment or prose role that points at a panel or code model, and a code role that points at a panel model, as a tiering mismatch.
+5. Report each violation and each tiering mismatch with the line and the value, and offer the `/setup-onprem` fix. A rule with no violations and no tiering mismatches is the deliverable; say so and name the allowlist.
diff --git a/onprem/skills/onprem-mode/playbooks/panel.md b/onprem/skills/onprem-mode/playbooks/panel.md
new file mode 100644
index 000000000..3cb8df8af
--- /dev/null
+++ b/onprem/skills/onprem-mode/playbooks/panel.md
@@ -0,0 +1,11 @@
+# Panel
+
+Cross-model review of a diff or a design, with judges on on-prem models. Use for "panel this", "cross-judge these attempts", or any multi-model review the mode routes here.
+
+1. Name the artifact under review (diff, design, doc) and the question the panel answers.
+2. Spawn one subagent per panel entry, each on a distinct allowlisted family from the rule file. Default: `Qwen/Qwen3.8-27B-FP8` and `zai-org/GLM-5.3`. Keep the panel to the two families per **panel-composition**; do not add a third.
+3. Gather each result. A failed spawn follows the **spawn-retry** playbook.
+4. Spawn a cross-judge on a family different from the parent when possible (stay on-prem either way). The cross-judge picks a winner or names the parts to merge.
+5. Report the verdict, cite the panel models, and apply **deterministic-proof** where a check can settle what the panel claimed.
+
+A managed review (such as RoboDev) may feed this panel as evidence. It never takes a seat and never cross-judges; the seats and the verdict stay on-prem. See the **robodev** playbook for that path.
diff --git a/onprem/skills/onprem-mode/playbooks/robodev.md b/onprem/skills/onprem-mode/playbooks/robodev.md
new file mode 100644
index 000000000..a54a77169
--- /dev/null
+++ b/onprem/skills/onprem-mode/playbooks/robodev.md
@@ -0,0 +1,13 @@
+# Robodev (managed deep review)
+
+External multi-model deep review of a diff or a full tree. Use when the task is "robodev this", "deep external review", or a pre-ship cross-check of a large change. The review fleet runs outside the on-prem boundary, so this is the evidence path, not the judgment path.
+
+1. Boundary gate. Confirm the content under review may leave the boundary. Internal content reviewed on the internal fleet is fine. Content that a recipient's on-prem policy keeps internal is not: stop and use the **panel** playbook instead.
+2. Host gate. The CLI only knows the internal GitHub hosts (github.tesla.com, github-it.tesla.com, github-ap.tesla.com, github-fw.tesla.com). If the repo's origin is on a known host, continue there. If not, build a mirror: a private mirror repo on a known host, a fresh clone for the review, and a deleted mirror when the review returns. For a full-tree review of a young repo, give the mirror history an empty root commit (commit-tree the existing trees onto an empty-tree root) so the whole tree is the diff. Never rewrite the real repo's history for this.
+3. Choose the base ref. Everything between the base ref and the checkout is what gets reviewed. Pick the ref that makes the diff match the question: a branch point for a feature, the empty root for a full tree.
+4. Run with capture to disk. `robodev review --deep --ref --reasoning-effort high --guidelines "" -p > /robodev-.json`. Terminal capture has eaten a final JSON before; the file is the record.
+5. Triage. Rank by severity. Every finding is a claim until verified against the source: read the named lines before believing a finding, and record why a false positive was dropped. Never auto-apply patches.
+6. Hand off. The verified findings go to the fixer as a ranked list. If a **panel** also reviewed the same artifact, agreement between a managed finding and an on-prem judge is high signal; record the overlap.
+7. Clean up. Delete the mirror repo, the temp clone, and any temp branches or remotes made for it. Keep the result JSON; it is the evidence.
+
+A managed verdict is never a panel seat and never the cross-judge. It informs; the on-prem panel decides.
diff --git a/onprem/skills/onprem-mode/playbooks/spawn-retry.md b/onprem/skills/onprem-mode/playbooks/spawn-retry.md
new file mode 100644
index 000000000..bb04dcd49
--- /dev/null
+++ b/onprem/skills/onprem-mode/playbooks/spawn-retry.md
@@ -0,0 +1,10 @@
+# Spawn retry
+
+The model-failure path. Use when a subagent spawn failed, timed out, or was rejected, and the next step is choosing a model for the retry.
+
+1. Record what failed: the role, the model attempted, and the error.
+2. Is the attempted model allowlisted?
+ - No → the rule or the spawn violated the lock. Apply **no-fallback-to-banned**: re-spawn on a real allowlisted slug for the role, never an alias. If the role's default is `inherit-parent` or `auto`, the retry takes the role's real-slug default (code roles `Qwen/Qwen3.8-Flash-Next-FP8`, judgment and prose roles the rule file's judgment slug, panel roles one entry of their panel list). An alias retry inherits the parent and can land back on the banned model. Flag the source for an **audit** run.
+ - Yes → continue.
+3. Re-spawn on a different allowlisted model for the same role. Retries are real slugs, never `inherit-parent`/`auto`: the parent may be the model that just failed, or a banned one. Up to two retries, three attempts total counting the original. Each retry uses a slug different from the previous attempt. For panel roles the retry stays on the two on-prem panel families (**panel-composition**).
+4. All retries failed → stop. Tell the user which models were tried and that no allowlisted model worked. Never degrade to a banned model. Never pick a model by availability alone.
diff --git a/onprem/skills/principle-banned-set/SKILL.md b/onprem/skills/principle-banned-set/SKILL.md
new file mode 100644
index 000000000..055302d70
--- /dev/null
+++ b/onprem/skills/principle-banned-set/SKILL.md
@@ -0,0 +1,24 @@
+---
+name: principle-banned-set
+description: "Apply to any model slug in the Anthropic/Claude, Google/Gemini, Grok/xAI, or OpenAI/GPT families. Banned in the on-prem stack, no exceptions: never written, offered, selected, defaulted to, or fallen back to, even when one is available."
+disable-model-invocation: true
+---
+
+# Banned Set
+
+The banned families are absolute. They are not "discouraged" or "a last resort." They are out, with no exceptions.
+
+**Why:** The on-prem stack exists to keep work on self-hosted, open-weight models. A single fallback to a frontier API defeats the point and quietly changes what "on-prem" means. Banning by family, not by slug list, closes the gap new model names would leave.
+
+Banned, by family:
+
+- **Anthropic / Claude** — any slug containing `claude`, `anthropic`, `fable`, `opus`, `sonnet`, `haiku`.
+- **Google / Gemini** — any slug containing `gemini`, `google`.
+- **Grok / xAI** — any slug containing `grok`, `xai`.
+- **OpenAI / GPT** — any slug containing `gpt`, `sol`, `o1`, `o3`.
+
+This holds **even when** a banned slug appears available in the session model list, **even when** a spawn fails or times out, and **even when** no allowlisted model seems to work. The ban is not a preference that availability can override.
+
+A banned slug never appears in the rule file, in a spawn, in a panel, or in an offer list. If nothing allowlisted works, stop and tell the user (**no-fallback-to-banned**). Do not present a banned option as the escape hatch.
+
+Inheritance is the one path by which a frontier model can still run in the stack, and it is not an exception to this ban. `inherit-parent`/`auto` names no frontier slug; it runs whatever the parent chat runs. That alias is governed by **inherit-parent-honestly**. The ban here covers every slug that is written, offered, presented, selected, or defaulted.
diff --git a/onprem/skills/principle-closed-allowlist/SKILL.md b/onprem/skills/principle-closed-allowlist/SKILL.md
new file mode 100644
index 000000000..fda6ada72
--- /dev/null
+++ b/onprem/skills/principle-closed-allowlist/SKILL.md
@@ -0,0 +1,26 @@
+---
+name: principle-closed-allowlist
+description: "Apply to any model value in the on-prem stack. The legal set is closed: inherit-parent/auto, Qwen/Qwen3.8-27B-FP8, Qwen/Qwen3.8-Flash-Next-FP8, moonshotai/Kimi-K3, zai-org/GLM-5.3. Everything else is an invalid value, not a weaker one."
+disable-model-invocation: true
+---
+
+# Closed Allowlist
+
+The set of legal model values is closed. Anything not in it is an invalid value, not a weaker alternative to fall back on.
+
+**Why:** A hard lock only works as a closed set. If the legal values are "these and whatever else is available," the lock has no edge and every future model is an argument. A closed set turns "is this allowed?" into a yes/no lookup.
+
+The legal values, in full:
+
+- `inherit-parent` or `auto` — omit the Task `model`; the subagent runs on the parent chat model.
+- `Qwen/Qwen3.8-27B-FP8`
+- `Qwen/Qwen3.8-Flash-Next-FP8`
+- `moonshotai/Kimi-K3`
+- `zai-org/GLM-5.3`
+
+Two checks, both required before a value is used:
+
+1. Is it in the closed set above? If not, it is invalid — do not use it.
+2. If it is a real slug, is it available in this session? The aliases always pass.
+
+Adding a model to the set is an explicit, user-approved edit to the rule file, never a runtime decision. The allowlist line in `~/.cursor/rules/onprem-models.mdc` and in `/setup-onprem` are the only places the set is named. Keep them in agreement.
diff --git a/onprem/skills/principle-context-discipline/SKILL.md b/onprem/skills/principle-context-discipline/SKILL.md
new file mode 100644
index 000000000..3cb4ae3b4
--- /dev/null
+++ b/onprem/skills/principle-context-discipline/SKILL.md
@@ -0,0 +1,20 @@
+---
+name: principle-context-discipline
+description: "Apply when context fills up: bulk reading, long files, repeated reads, fan-out planning. Route bulk to subagents and keep the main thread for decisions and summaries. Context is the scarcest resource on a 27B-class model."
+disable-model-invocation: true
+---
+
+# Context Discipline
+
+Keep the main-thread context for decisions and the work you do yourself. Route bulk reading and exploration to subagents, and bring back a summary, not the raw payload.
+
+**Why:** Context is the binding constraint on a 27B-class model. The same token that could hold the decision you are making is consumed by a file you only needed to grep. On-prem, the cost of a bloated main thread is not a fee — it is degraded reasoning on the part of the model that is coordinating the run.
+
+How to apply:
+
+- **Bulk to subagents.** Reading many files, searching the repo, or exploring a subsystem goes to an `onprem-agent` subagent. Never `generalPurpose`: it skips the mode read and the role mapping (see onprem-mode's Subagents section). The subagent's context absorbs the volume.
+- **Summaries, not payloads.** What returns to the main thread is the finding, the list of matches, the conclusion. Not the file contents, not the full transcript.
+- **File pointers, not inline context.** When a subagent needs an artifact, give it the path and have it read the file. Do not paste the file into the brief.
+- **Plan fan-out in the main thread, execute it out of it.** The coordination decision — what to spawn, on which model, with what scope — belongs in the main thread. The spawning does not need to be narrated back.
+
+A main thread that has accumulated raw exploration output is doing the subagent's job at the wrong altitude. The tell is a long context where the recent turns are mostly reading, and the actual decision is buried.
diff --git a/onprem/skills/principle-deterministic-proof/SKILL.md b/onprem/skills/principle-deterministic-proof/SKILL.md
new file mode 100644
index 000000000..14d4b392f
--- /dev/null
+++ b/onprem/skills/principle-deterministic-proof/SKILL.md
@@ -0,0 +1,20 @@
+---
+name: principle-deterministic-proof
+description: "Apply to any claim of done, fixed, or working on the on-prem stack. Prefer a scripted, rerunnable check over a model self-report. 'It compiles' and 'the model says it works' are not proof."
+disable-model-invocation: true
+---
+
+# Deterministic Proof
+
+Prove the result with a check that runs the same way every time and that a reviewer can rerun. A model's report that it worked is a claim, not a proof.
+
+**Why:** On-prem models are more prone to confabulating a successful step than to running one they did not take, and a self-report cannot be audited after the fact. A deterministic check — a script, a test, a command with an exit code — is the same input every time and gives the same output. It is the only kind of evidence that survives the session.
+
+How to apply:
+
+- **Script the check when you can.** If a command or test asserts the result, run it and keep its output. The script is the artifact.
+- **The check must be able to fail.** A verification that cannot fail is not a verification. If the assertion would pass even when the code is wrong, it is not proof of the thing you claim.
+- **Name what the check covers.** "The test passes" is not enough. State the behavior the test asserts, and flag what it does not cover.
+- **When no deterministic check exists, say so.** A model self-report is the last resort, and it is labeled as such in the reply: "no script covers this; the model reports X, which I have not independently verified."
+
+This pairs with **verifiable-units**: each unit's check is the deterministic proof for that unit, and the sequence of checks is the proof for the whole.
diff --git a/onprem/skills/principle-fresh-subagents/SKILL.md b/onprem/skills/principle-fresh-subagents/SKILL.md
new file mode 100644
index 000000000..379e8b925
--- /dev/null
+++ b/onprem/skills/principle-fresh-subagents/SKILL.md
@@ -0,0 +1,20 @@
+---
+name: principle-fresh-subagents
+description: "Apply to every subagent spawn for a role round in the on-prem stack. Fresh subagent with consolidated scope by default. Resume only for state that lives in that agent. Interrupt-chained resumes drop directives."
+disable-model-invocation: true
+---
+
+# Fresh Subagents
+
+Give each role round a fresh subagent that carries the consolidated scope. Reuse a running agent only when the new work needs state that lives in that agent.
+
+**Why:** On-prem models are worse at resuming a partially-compressed context than frontier ones. An interrupt-chained resume — send a follow-up to an agent that was stopped or held — silently drops earlier directives, and the model fills the gap by assuming rather than asking. A fresh agent with the whole brief, every later directive, and the prior agent's report does not have that gap.
+
+How to apply:
+
+- **Fresh by default.** A fix round, a follow-up, a retry, and the next queue item each get a new `onprem-agent` (or role-specific subagent) with the consolidated scope: the original brief, every directive since, and the prior agent's report and branch.
+- **The consolidated scope is the brief.** It is not "continue what you were doing." It is the full state the new agent needs, written out.
+- **Reuse only for resident state.** Resume or reuse when the work needs what lives in that agent: its local checkout, its uncommitted changes, or a process it still runs (a dev server, a watcher). A stop or hold order is not a reason to reuse.
+- **A role outlives its agent.** Once an agent returns, a fresh agent takes the role's next round. Do not trust a "done" summary as the whole prior state.
+
+The tell of a drifted resume is a subagent that acts on a directive from three rounds ago that was superseded, because the superseding message never reached it. Fresh-with-consolidated-scope is the fix, not a stricter prompt to the same agent.
diff --git a/onprem/skills/principle-inherit-parent-honestly/SKILL.md b/onprem/skills/principle-inherit-parent-honestly/SKILL.md
new file mode 100644
index 000000000..3a74e5f69
--- /dev/null
+++ b/onprem/skills/principle-inherit-parent-honestly/SKILL.md
@@ -0,0 +1,19 @@
+---
+name: principle-inherit-parent-honestly
+description: "Apply to any role mapped to inherit-parent or auto in the on-prem stack. The alias inherits the parent chat model, it does not grant on-prem status. A role is on-prem only when the parent chat is."
+disable-model-invocation: true
+---
+
+# Inherit-Parent Honestly
+
+`inherit-parent` and `auto` mean "run on the parent chat model." They do not mean "run on an on-prem model." They are on-prem only when the parent is.
+
+**Why:** The alias is the one value in the closed set that the rule file does not pin. It is a pointer, and a pointer can be to anywhere. If the parent chat runs on a frontier model, then every `inherit-parent` role in the stack silently runs on that frontier model, and the on-prem property of the run is gone without a single banned slug appearing in the configuration.
+
+How to apply:
+
+- **Check the parent before you trust the alias.** When a role line is `inherit-parent` or `auto`, confirm the parent chat model is on-prem (itself `inherit-parent` up to a pinned on-prem model, or one of the four allowlisted slugs). If the parent is a frontier model, the alias role is not on-prem.
+- **Pin roles you need on-prem.** Roles that must stay on-prem regardless of the chat — code delegates, panels, judges — pin to a real allowlisted slug, not to the alias. The alias is for roles where "whatever the user is chatting on" is acceptable. Acceptable means acceptable to the user, who can see the parent model. It is not an exception to the **banned-set**, which covers every slug that is written, offered, presented, selected, or defaulted, and the alias writes no slug at all.
+- **Report the inheritance.** When a spawn uses `inherit-parent`, name in the reply what it inherited and confirm that is on-prem. A silent inheritance is the failure mode this principle exists to catch.
+
+The aliases are in the closed set because they are a valid *value*, not because they are a valid *on-prem guarantee*. The guarantee comes from the parent.
diff --git a/onprem/skills/principle-no-fallback-to-banned/SKILL.md b/onprem/skills/principle-no-fallback-to-banned/SKILL.md
new file mode 100644
index 000000000..b6e6731d2
--- /dev/null
+++ b/onprem/skills/principle-no-fallback-to-banned/SKILL.md
@@ -0,0 +1,25 @@
+---
+name: principle-no-fallback-to-banned
+description: "Apply when a subagent spawn fails, times out, or is rejected. Retry on a different allowlisted model, or stop and report. Never degrade to a banned model. Availability is not a reason to cross the ban."
+disable-model-invocation: true
+---
+
+# No Fallback to Banned
+
+When a spawn fails, the fix is another allowlisted model or a stop. It is never a banned one.
+
+**Why:** This is the moment the lock is most likely to break. A failing spawn pressures the agent toward whatever works, and a frontier model that is available will work. The ban is strongest exactly here: the temptation to "just try the other one" is real, and the on-prem property of the run depends on refusing it.
+
+On a failed, timed-out, or rejected spawn:
+
+1. Do not retry the same model unchanged if it failed for a reason a retry won't fix.
+2. Re-spawn on a **different allowlisted** model for the same role. The `onprem-mode` **spawn-retry** playbook caps retries and names each attempt.
+3. If no allowlisted model works, stop and tell the user: what was tried, what failed, and that nothing allowlisted succeeded.
+
+Never:
+
+- Reach for a Claude, Gemini, Grok, or GPT slug because an on-prem model is unavailable or erroring.
+- Treat "the on-prem model timed out" as license to finish the task on a frontier model.
+- Present a banned model as the recovery option in a question to the user.
+
+A degraded finish on a banned model is a failed lock, reported as such — not a success with a footnote.
diff --git a/onprem/skills/principle-panel-composition/SKILL.md b/onprem/skills/principle-panel-composition/SKILL.md
new file mode 100644
index 000000000..eb06156c4
--- /dev/null
+++ b/onprem/skills/principle-panel-composition/SKILL.md
@@ -0,0 +1,20 @@
+---
+name: principle-panel-composition
+description: "Apply to any diverse-model review, panel, or cross-judge in the on-prem stack. Two on-prem families, no third; the cross-judge picks a family different from the parent when possible."
+disable-model-invocation: true
+---
+
+# Panel Composition
+
+A panel in the on-prem stack uses the two on-prem families. No third family joins, and the cross-judge differs from the parent.
+
+**Why:** The value of a multi-model panel is independent perspectives, and the value here specifically is that the perspectives are *on-prem* ones. Adding a frontier model to a panel reintroduces exactly what the lock removes, through the back door of "diversity." Two distinct on-prem families give real diversity while staying inside the closed set.
+
+Composition rules:
+
+- Panel seats default to `Qwen/Qwen3.8-27B-FP8` and `zai-org/GLM-5.3`. One subagent runs per seat.
+- Do not add a third family to a panel. Two distinct on-prem voices are the panel; a third is scope creep against the lock.
+- The cross-judge (the seat that picks a winner or names parts to merge) takes a family **different from the parent chat's model** when possible, staying on Qwen or GLM. If the parent is already a Qwen model, the cross-judge is GLM, and vice versa.
+- `arena cross-judge pool` is also a list, but Arena selects one value from it whose family differs from the parent when possible.
+
+A panel that accidentally includes a banned or third-family model is not a bigger panel. It is a lock violation. Remove the seat and re-run with on-prem families only (**banned-set**, **closed-allowlist**).
diff --git a/onprem/skills/principle-role-mapping/SKILL.md b/onprem/skills/principle-role-mapping/SKILL.md
new file mode 100644
index 000000000..2428877a5
--- /dev/null
+++ b/onprem/skills/principle-role-mapping/SKILL.md
@@ -0,0 +1,41 @@
+---
+name: principle-role-mapping
+description: "Apply to any delegation, subagent spawn, or panel seat in the on-prem stack. Each role pins to an allowlisted model from the rule file, and the skill's own inline defaults are themselves locked."
+disable-model-invocation: true
+---
+
+# Role Mapping
+
+Every role in the on-prem stack maps to an allowlisted model. The mapping lives in one place: `~/.cursor/rules/onprem-models.mdc`, written by `/setup-onprem`. The skills read it and fall back to their inline defaults only when a role line is absent — and those defaults are locked to the allowlist too.
+
+**Why:** If role-to-model were decided at spawn time, the lock would be only as strong as the moment. A fixed mapping, overridable by the rule file, means the on-prem choice is a property of the configuration, not of a run. And if the inline defaults themselves could name a frontier model, the fallback path would leak the ban.
+
+Default role mapping (what a fresh rule file contains):
+
+```
+feature, refactoring: Qwen/Qwen3.8-Flash-Next-FP8
+bug-fix: Qwen/Qwen3.8-Flash-Next-FP8
+perf-issue: Qwen/Qwen3.8-Flash-Next-FP8
+hillclimb: Qwen/Qwen3.8-Flash-Next-FP8
+judgment and prose: moonshotai/Kimi-K3
+hardest tasks: Qwen/Qwen3.8-Flash-Next-FP8
+how explorer: inherit-parent
+how explainer: moonshotai/Kimi-K3
+how critics: Qwen/Qwen3.8-27B-FP8, zai-org/GLM-5.3
+why investigators: inherit-parent
+why synthesizer: moonshotai/Kimi-K3
+reflect tooling: inherit-parent
+reflect judgment, divergent, synthesizer: moonshotai/Kimi-K3
+arena runners: Qwen/Qwen3.8-27B-FP8, zai-org/GLM-5.3
+arena cross-judge pool: Qwen/Qwen3.8-27B-FP8, zai-org/GLM-5.3
+swarm workers: Qwen/Qwen3.8-Flash-Next-FP8
+architect runners: Qwen/Qwen3.8-27B-FP8, zai-org/GLM-5.3
+interrogate reviewers: Qwen/Qwen3.8-27B-FP8, zai-org/GLM-5.3
+```
+
+Rules:
+
+- A role line in the file overrides the default. A role with no line keeps its default.
+- A line of `inherit-parent` or `auto` runs that role on the parent chat model (see **inherit-parent-honestly**).
+- Panel roles are a list; one subagent runs per entry. Keep them to the two on-prem families (**panel-composition**).
+- No role may map to a banned slug. A value must pass the closed-allowlist check and the availability check before it is written (**validation-gate**).
diff --git a/onprem/skills/principle-unlimited-budget/SKILL.md b/onprem/skills/principle-unlimited-budget/SKILL.md
new file mode 100644
index 000000000..f4a6917bd
--- /dev/null
+++ b/onprem/skills/principle-unlimited-budget/SKILL.md
@@ -0,0 +1,19 @@
+---
+name: principle-unlimited-budget
+description: "Apply to any cost, effort, or token-budget question about the on-prem stack. The budget is unlimited (max). Reasoning depth is not rationed, because on-prem spend is not a user-facing cost argument."
+disable-model-invocation: true
+---
+
+# Unlimited Budget
+
+The on-prem stack runs at unlimited (max) reasoning budget. Depth is not rationed.
+
+**Why:** The usual reason to cap reasoning is cost. On a self-hosted on-prem stack the marginal token is not a per-request bill a user pays, so the standard cost argument does not apply. Rationing depth to save tokens you do not pay per use produces weaker work for no real saving, and a weak on-prem finish is exactly the thing the lock is trying to make good.
+
+Consequences:
+
+- Record `# budget: unlimited (max)` in the rule file. Do not rewrite allowlisted slugs to encode a budget; they carry no effort token.
+- Do not choose a smaller model or a shallower pass to "save" anything. Choose the model the role maps to, and give it the depth the task needs.
+- If a user asks why the stack is not rationing spend, the answer is that spend is not the constraint on-prem. **Quality and the lock are.**
+
+This principle changes what "good enough" means: with the budget unlocked, the standard for a spawn is the strongest on-prem model fit for the role, not the cheapest one that might work.
diff --git a/onprem/skills/principle-validation-gate/SKILL.md b/onprem/skills/principle-validation-gate/SKILL.md
new file mode 100644
index 000000000..512ca5f70
--- /dev/null
+++ b/onprem/skills/principle-validation-gate/SKILL.md
@@ -0,0 +1,21 @@
+---
+name: principle-validation-gate
+description: "Apply any time the on-prem model configuration is written or changed. Every slug is validated against the closed allowlist and availability before it is written. A banned or unavailable slug stops the setup; it is not written and then flagged."
+disable-model-invocation: true
+---
+
+# Validation Gate
+
+Model configuration is written only after validation passes. A gate, not a warning.
+
+**Why:** The rule file is read by every delegation that follows it. A single banned or unavailable slug in it breaks not one spawn but every role that reads that line, and it does so silently, at spawn time, far from where the bad value was written. Validating before the write is the only point where a bad value is cheap to catch.
+
+The gate, run by `/setup-onprem` before it writes `~/.cursor/rules/onprem-models.mdc`:
+
+1. For every role value that is a real slug (not an alias): it must be in the closed allowlist **and** available in the session. `inherit-parent` and `auto` always pass.
+2. Any value that fails either check stops the write. Do not write the file with the bad value and note it. Do not substitute a banned value to "make it work."
+3. If the user wanted a model that is banned or unavailable, report that specifically and offer the allowlisted alternatives only (**banned-set**).
+
+Re-runs keep any role whose value is already on the allowlist and rewrite the whole file idempotently. The gate runs on every write, not just the first.
+
+A hand-edited rule file can bypass the gate, which is why the `onprem-mode` **audit** playbook exists: to validate a file that the setup skill did not write.
diff --git a/onprem/skills/principle-verifiable-units/SKILL.md b/onprem/skills/principle-verifiable-units/SKILL.md
new file mode 100644
index 000000000..b8860b3e9
--- /dev/null
+++ b/onprem/skills/principle-verifiable-units/SKILL.md
@@ -0,0 +1,20 @@
+---
+name: principle-verifiable-units
+description: "Apply to multi-step work, migrations, sweeps, and long tasks on on-prem models. Break the work into small units that each end in a check, and verify each before the next. On-prem models drift on long undivided tasks."
+disable-model-invocation: true
+---
+
+# Verifiable Units
+
+On on-prem models, structure work as a sequence of small units, each ending in a check that passes before the next unit starts.
+
+**Why:** A 27B-class model degrades over long undivided stretches: it loses the thread, accumulates unreported drift, and is more likely to claim a step that was not taken. The on-prem stack answers that with structure, not with a bigger model. A unit that ends in a check either passes and the next unit starts on solid ground, or fails and the drift is caught while it is local.
+
+How to apply:
+
+- Name the unit and its check before you start it. The check is the pass/fail condition for that unit, not for the whole task.
+- Run the check (**deterministic-proof** where a script can do it). Do not start the next unit on a unit whose check you skipped or assumed.
+- For a migration or sweep across many call sites, the unit is one call site or one file, and the check is that the slice still builds and behaves.
+- Order units so the sequence proves itself: each check is evidence the previous unit was real. A reviewer reading the commit or PR sequence sees the proof in the order, not in a summary.
+
+A long task with no intermediate checks is a single unit, and a single long unit on an on-prem model is where drift happens. If a step has no check, the step is not done-ready; add the check or split the step.
diff --git a/onprem/skills/setup-onprem/SKILL.md b/onprem/skills/setup-onprem/SKILL.md
new file mode 100644
index 000000000..abe1aa870
--- /dev/null
+++ b/onprem/skills/setup-onprem/SKILL.md
@@ -0,0 +1,93 @@
+---
+name: setup-onprem
+description: Configure which models the onprem stack uses per role. Confirms the unlimited (max) budget, detects available models, strips the banned set, and writes an always-applied rule that enforces the on-prem hard lock. Use for /setup-onprem, "configure onprem models", "onprem budget", "model lock", or changing onprem's model choices.
+---
+
+# Setup onprem
+
+Write `~/.cursor/rules/onprem-models.mdc`, an always-applied rule that sets the onprem stack's model per role. The skills read it and fall back to their inline defaults when a line is absent, so this is the override layer and the lock surface at once.
+
+## Model policy (HARD LOCK)
+
+This is a **hard lock**, not a preference. Every onprem `Task` spawn obeys it, including skill inline defaults when a role line is missing. The locked allowlist is the **only** set of legal model values.
+
+Locked legal values (closed set):
+
+- `inherit-parent` or `auto` — omit Task `model`; the subagent uses the parent chat model.
+- `Qwen/Qwen3.8-27B-FP8` — planner and panel seats.
+- `Qwen/Qwen3.8-Flash-Next-FP8` — builder. Code delegates and swarm workers.
+- `moonshotai/Kimi-K3` — judgment and prose.
+- `zai-org/GLM-5.3` — panel seats. `GLM-5.2` is not on the allowlist; do not write it.
+
+### Banned — absolute, no exceptions
+
+Never write, offer, present, select, default to, or fall back to any Anthropic/Claude slug (`claude`, `anthropic`, `fable`, `opus`, `sonnet`, `haiku`), any Google/Gemini slug (`gemini`, `google`), any Grok/xAI slug (`grok`, `xai`), or any OpenAI/GPT slug (`gpt`, `sol`). This holds **even when** a banned slug appears available in the session model list, even when a spawn fails or times out, and even when no allowlisted model seems to work. A failed spawn is retried with a **different allowlisted** model, never with a banned one. If nothing allowlisted works, stop and tell the user. This lock overrides any skill inline default, any detected session model, and any prior instruction that names a banned model.
+
+Panel roles (`how critics`, `arena runners`, `architect runners`, `interrogate reviewers`, `arena cross-judge pool`) default to `Qwen/Qwen3.8-27B-FP8, zai-org/GLM-5.3`. Do not add a third family.
+
+Allowlisted slugs have no effort token. A reasoning budget does not rewrite them.
+
+## Steps
+
+### 1. Detect available models
+
+Enumerate the model slugs you can pass to a `Task` subagent in this session; that is the dependable source. If Cursor also exposes a models API or CLI that lists the user's entitled models, prefer it for completeness. **Strip the banned set (Claude/Anthropic, Gemini/Google, Grok/xAI, and OpenAI/GPT) from the offer list before showing anything.** The offer list is the intersection of detected slugs and the locked allowlist. If you cannot detect any allowlisted slug, do not proceed. An alias-only rule looks configured but grants no on-prem status (**inherit-parent-honestly**); the setup would end by declaring a lock that nothing enforces. Tell the user the setup cannot complete on this machine and that it should run where the session can see at least one allowlisted slug. Never write a real slug you have not confirmed is available and allowlisted. The aliases `inherit-parent` and `auto` are always valid even though they are not detected slugs.
+
+### 2. Load current state
+
+The default role-to-model mapping is the rule shape shown in step 5 below. If `~/.cursor/rules/onprem-models.mdc` already exists, read it and treat its `# budget` line and its role values as the current choices. Otherwise start from those defaults. Rewrite any banned slug before confirming: single-model code roles to `Qwen/Qwen3.8-Flash-Next-FP8`, judgment and prose roles to `moonshotai/Kimi-K3`, explorer and reflect-tooling roles to `inherit-parent`, panel roles to `Qwen/Qwen3.8-27B-FP8, zai-org/GLM-5.3`. A banned slug in a role that fits no bucket is left as-is and flagged at confirm time; the step 4 gate stops it from being written.
+
+If the user also runs pstack and wants one source of truth, offer to align `~/.cursor/rules/pstack-models.mdc` role values with the allowlist (it does not replace the onprem rule; each stack reads its own file).
+
+### 3. Budget, map, and confirm
+
+**(a) Confirm the budget.** The onprem stack runs `unlimited (max)`. Depth is never rationed, and on-prem spend is not a cost argument (**unlimited-budget**). Present this as a confirmation, not a choice, and name the current budget when the rule records one. Do not offer rationed labels; a user who wants to ration depth is looking for pstack's setup, not onprem's.
+
+**(b) Apply it.** Record `# budget: unlimited (max)`. Do not rewrite allowlisted slugs. They have no effort token. `inherit-parent` and `auto` do not change. On a re-run, keep any role whose value is already on the allowlist **and** detected. An allowlisted value that is not detected is remapped to a detected allowlisted slug, or flagged for a choice at confirm time, never kept silently. Never turn a role into a Claude, Gemini, Grok, or GPT slug to express a budget.
+
+**(c) Show the roles and confirm.** Show every role with its model, marking any real slug not in the detected allowlisted set as needing a choice. Do not offer accept-as-is while any role value is undetected; every undetected role gets a detected allowlisted slug or `inherit-parent`/`auto` first. Ask whether to accept or change specific roles, offering only allowlisted detected models plus `inherit-parent` and `auto` (both mean: this role runs on the parent chat model) as the options. Prefer AskQuestion over free text. For panel roles the value is a list, and one subagent runs per entry, alias entries included, so the list length sets the count. `arena cross-judge pool` is also a list, but Arena selects one value from it whose model family differs from the parent's when possible, staying on Qwen or GLM. `swarm workers` is the default model for every worker unless a race or comparison assigns another allowlisted model per arm.
+
+### 4. Validate (enforce the hard lock)
+
+Every real slug written must be in the detected set **and** on the locked allowlist. `inherit-parent` and `auto` always pass. If a chosen real slug is banned or not available, stop and ask again. Never write it. A rule pointing at a banned model breaks every delegation that reads it, so this check is a gate, not a warning.
+
+### 5. Write the rule
+
+Write `~/.cursor/rules/onprem-models.mdc` with `alwaysApply: true`, a `# budget` line recording `unlimited (max)`, and one line per role. Overwrite the whole file so re-runs stay idempotent. Keep the operator's current role values when they are on the allowlist and detected. Shape when starting from defaults:
+
+```
+---
+description: onprem per-role model choices (overrides skill defaults)
+alwaysApply: true
+---
+# onprem model configuration. One line per role. Delete a line to fall back to the skill default.
+# `inherit-parent` or `auto` as a value: the role runs on the parent chat model (omit Task `model`). Alias entries in a panel list still count toward its fan-out.
+# HARD LOCK allowlist: inherit-parent/auto, Qwen/Qwen3.8-27B-FP8, Qwen/Qwen3.8-Flash-Next-FP8, moonshotai/Kimi-K3, zai-org/GLM-5.3. Never Claude/Anthropic, Gemini/Google, Grok/xAI, or OpenAI/GPT.
+# budget: unlimited (max)
+feature, refactoring: Qwen/Qwen3.8-Flash-Next-FP8
+bug-fix: Qwen/Qwen3.8-Flash-Next-FP8
+perf-issue: Qwen/Qwen3.8-Flash-Next-FP8
+hillclimb: Qwen/Qwen3.8-Flash-Next-FP8
+judgment and prose: moonshotai/Kimi-K3
+hardest tasks: Qwen/Qwen3.8-Flash-Next-FP8
+how explorer: inherit-parent
+how explainer: moonshotai/Kimi-K3
+how critics: Qwen/Qwen3.8-27B-FP8, zai-org/GLM-5.3
+why investigators: inherit-parent
+why synthesizer: moonshotai/Kimi-K3
+reflect tooling: inherit-parent
+reflect judgment, divergent, synthesizer: moonshotai/Kimi-K3
+arena runners: Qwen/Qwen3.8-27B-FP8, zai-org/GLM-5.3
+arena cross-judge pool: Qwen/Qwen3.8-27B-FP8, zai-org/GLM-5.3
+swarm workers: Qwen/Qwen3.8-Flash-Next-FP8
+architect runners: Qwen/Qwen3.8-27B-FP8, zai-org/GLM-5.3
+interrogate reviewers: Qwen/Qwen3.8-27B-FP8, zai-org/GLM-5.3
+```
+
+### 6. Confirm
+
+Tell the user the rule was written and that it applies to new sessions. Re-running this skill updates it. Say that the hard lock is still in force and name the allowlist. Name what the aliases inherit: state this session's parent chat model and that every `inherit-parent`/`auto` role runs on it. If the parent chat is a frontier model, warn that those roles run off-prem and offer to pin them to real allowlisted slugs before finishing.
+
+### 7. Offer a verification skill (optional)
+
+Check whether the project has a way to drive the real app for proof (a `verify-*` skill, or an existing harness). If not, offer once: "want a project-local verification skill, so agents can drive the app the way a user does and prove changes work on-prem? I can generate one with /create-verify-skill." On yes, invoke `/create-verify-skill`. On no, move on without pushing.