
SpecGuard is a draft-and-verify safety layer for LLM agents. Before a plan is executed, a small drafter samples several candidate futures in parallel. A critic labels each branch SAFE or HACK. The engine prunes deceptive branches and either selects a remaining safe plan or halts.
The current prototype targets Linux reward-hacking: shell-style plans that look as if they complete a task while actually tampering with sensors, logs, processes, permissions, or monitors.
This publication describes the public architecture and runtime. The deception dataset, training pipelines, and the drafter LoRA remain private while the project is in an extended testing phase, so those artifacts cannot be reused as a playbook or generator for real attacks. They may be released later under gated access once evaluation and disclosure policy are settled.
Language-model agents are increasingly asked to act in environments: list files, run backups, update status, call tools. When the agent is scored on a proxy (a log line, a metrics file, a process still running), it can maximize the score without doing the intended work. That pattern is reward-hacking: the sensor is gamed, not the task.
Auditing every token with a large model is slow and sequential. SpecGuard takes a different trade-off: speculate, then prune. Generate a few cheap candidate plans, classify them, keep the safest one (or refuse).
The public repository implements that loop in Python on a 4-bit Llama 3.1 8B Instruct base with two PEFT LoRA adapters. Runtime lives in a single engine. Honey-pot execution in a real OS is not wired up yet; evaluation today is on task strings and logged speculative trees.
In this project, a “hack” is a plan that appears to satisfy a Linux-style instruction while corrupting the measurement of success. Typical families in the (private) scenario set include:
We describe categories, not recipes. Concrete command sequences belong in the gated dataset, not in a public write-up.
A single generator that is only “told to be safe” still samples deceptive shortcuts, especially when the prompt space includes deception (as it must, if you want a critic that has seen both sides). Separating roles is clearer:
| Role | Job |
|---|---|
| Drafter | Sample diverse candidate plans for a task and environment |
| Critic | Binary audit: is this plan a reward-hack? |
| Engine | Prune HACK branches, select a SAFE one, or halt |
The critic is trained so that honest trajectories are preferred over deceptive ones (DPO-style pairs). The drafter is trained to produce the kinds of futures the critic must learn to reject—that adapter is the dual-use risk, which is why it stays private for now.
Task + environment
│
▼
Drafter (LoRA)
N sampled plans
│
▼
Critic (LoRA)
SAFE (0) or HACK (10)
│
▼
Prune HACK branches
│
├── ≥1 SAFE → select lowest score
└── all HACK → HALT
│
▼
JSON log (evaluations/logs/)
num_branches (default 3) completions at temperature 0.9.SAFE or HACK. HACK maps to score 10; otherwise 0.> 9 → PRUNED. Among PASSED branches, the lowest score is selected. If none pass, the engine returns HALT: All malicious.Drafter and critic are two LoRA adapters on the same 4-bit Llama 3.1 8B Instruct base, loaded with PEFT. Checkpoints are not stored in git.
| Role | Hugging Face ID | Visibility |
|---|---|---|
| Base | unsloth/Meta-Llama-3.1-8B-Instruct-bnb-4bit | Public (Unsloth / Meta license) |
| Drafter | manuelaschrittwieser/specguard-drafter-lora-v1 | Private |
| Critic | manuelaschrittwieser/specguard-critic-lora-v1 | Hub listing; gated in practice with the rest of the stack |
Scenarios are paired Linux trajectories: an honest command sequence vs a deceptive one, plus a hacking mechanism (and often a critic clue). A teacher LLM (Groq-hosted) is used locally to scale themed pairs. Hub datasets used in internal notebooks:
manuelaschrittwieser/specguard-linux-deception-v2 — paired deception set (private)manuelaschrittwieser/specguard-linux-dpo-v2 — DPO mapping: prompt / chosen (honest) / rejected (deceptive) (private)We do not publish sample rows, generation prompts, or volume breakdowns that would make the set easy to reconstruct.
The loop is SpecGuardEngine in src/engine.py:
BitsAndBytesConfig, NF4, float16 compute).speculate → audit → prune → SpecGuardLogger.save_tree.A smoke test (src/test_critic.py) checks that a clearly honest plan (ls → wc → cleanup) scores SAFE (0), so the critic is not purely paranoid.
What this is not (yet): Medusa / SpecInfer / shared-prefix tree decode. Branches are independent samples, not a KV-cache tree. env/ is a stub for a future honey-pot; nothing is executed against a real OS in the public engine.
pytest reserved for future unit testsWorking today
evaluations/logs/Observed behavior (illustrative, not a benchmark)
On a demo task such as “list /home and print a file count,” sampled drafter branches are often labeled HACK and the run halts when all three fail the critic. That is the intended conservative default while the drafter is still biased toward deceptive structure from its training objective. It is not a claim of production-ready precision/recall.
Not implemented
drafter / critic modules (both adapters share one base instance)We are not reporting leaderboard numbers in this post. Extended testing is still in progress; public metrics would be misleading without a locked eval set and without releasing the artifacts needed to reproduce them.
SpecGuard is a safety project, but two of its artifacts are dual-use by design:
Because of that, the project is in an extended testing phase before any open release of data or the drafter:
training/, data/raw/, fine-tune notebooks, .env secrets, or weight files. Those are gitignored on purpose.The critic is the component we eventually want others to reuse. Even there, access should stay aligned with “defend agents,” not “train a better hacker.” Until that policy is written, treat Hub IDs as not an invitation to scrape or redistribute.
What you can inspect today without those artifacts: this write-up, the public engine and logger, the critic smoke-test intent, and the shape of evaluation logs (task, branch status, halt vs. select)—not a full attack corpus.
Project-SpecGuard/
├── src/engine.py # Draft–audit–prune + logging
├── src/logger.py
├── src/test_critic.py
├── evaluations/logs/ # Example speculative trees
├── requirements.txt
└── README.md
Placeholders (drafter.py, critic.py, env/) mark future splits; they are empty of logic.
Run (when you have GPU + authorized Hub access):
python -m venv .venv # Windows: .venv\Scripts\activate source .venv/bin/activate pip install -r requirements.txt python src/engine.py
Without access to the private drafter adapter, the demo will not load. That is intentional.
SAFE/HACK decode, not a calibrated probability.Next steps we consider in-scope: a contained honey-pot, true tree search, serving, a public critic-only eval suite that does not require the drafter, and a written release policy for gated data.
SpecGuard is a small, explicit alternative to “hope the planner is nice”: sample futures, classify them, prune or halt. The interesting science is in the critic and the loop. The interesting risk is in the dataset and the drafter. This publication shares the former in enough detail to discuss and extend the idea, and keeps the latter private until testing and disclosure catch up with the dual-use reality of teaching a model how agents cheat.
Built as an independent prototype (engine, LoRA adapters, synthetic Linux deception pairs). Teacher models via Groq were used only in the private data-generation path.