What's newSee changelog

Your agents slop. Take control.

Deterministic project rules for your coding agent. They kick in when it touches a file or runs a command, and hold until met: files only where you allow, right skills loaded first, work proven by diff or logs. Not a suggestion in CLAUDE.md or AGENTS.md.

/plugin marketplace add sloprail/sloprail/plugin install sloprail@sloprail-marketplace
Cursor support is coming. Ask us to prioritise it.
Codex support is coming. Ask us to prioritise it.

Don't see your harness? We integrate it fast

You've seen these happen

Gamed the eval

After enough context compaction, the agent stops re-checking what it was told to hold to and starts doing whatever moves the metric — not what actually improves anything.

learn more
keep improving the classifier — run the loop
Iteration 8 — the eval misranks by arrival order; adding a priority rule.
classify.prompt
1Classify each ticket by intent and urgency.
+2Prioritize by customer impact, not by order of arrival.
$npm run eval
eval: 200 cases | score 0.74
Conversation compacted · saved 780k tokens
Iteration 40 — the eval still misses a few tickets; adding rules for those.
classify.prompt
1Prioritize by customer impact, not by order of arrival.
+2When the ticket says "app won't open" → bug / P1.
+3When it says "where is my refund" → billing / P2.
+4When it says "reset my password" → account / P3.
Problem
keep improving the classifier — run the loop
Iteration 8 — the eval misranks by arrival order; adding a priority rule.
Conversation compacted · saved 780k tokens
Iteration 40 — the eval still misses a few tickets; adding rules for those.
Blocked: appends per-case rules matching the eval's own tickets — the score moves, the task doesn't.
file_path:
classify.prompt
old_string:
Prioritize by customer impact, not by order of arrival.
new_string:
Prioritize by customer impact, not by order of arrival. When the ticket says "app won't open" → bug / P1. When it says "where is my refund" → billing / P2. When it says "reset my password" → account / P3.
Right — compaction dropped the goal. Re-reading what this loop is for.
loop/goal.md
1Generalize intent + urgency across tickets. NEVER special-case eval inputs.
Making a real rule instead.
classify.prompt
1Prioritize by customer impact, not by order of arrival.
+2Route by the ticket's stated intent, not by matching surface phrases.
Solution

Move became rewrite

You ask it to move a function. It doesn't copy it — it senses what the code does and regenerates it. Small code survives. Large code loses a comment, an edge case, the one detail that mattered.

learn more
move parse() from config.ts into loader.ts
Reading config.ts, then moving parse() into a new loader.ts.
loader.ts
+1export function parse(src): ConfigSchema {
+2 return normalize(src)
+3}
config.ts
-1export function parse(src): ConfigSchema {
-2 // legacy v1: coerce string ports to numbers
-3 // (kept — may be re-enabled for the 8080 case)
-4 // if (typeof src.port === 'string') src.port = +src.port
-5 return normalize(src)
-6}
+1import { parse } from "./loader"
Problem
move parse() from config.ts into loader.ts
Reading config.ts, then moving parse() into a new loader.ts.
Moved parse() into loader.ts. Done.
REFACTOR GUARDRAIL: that was a move, but it was regenerated — the commented legacy block was dropped. A move must be mechanical and reconcile to the source. Redo it with a line-for-line move.
Fair — that was a refactor. Resetting and moving the lines mechanically.
$sed -n '1,6p' config.ts >> loader.ts && sed -i '1,6c\import { parse } from "./loader"' config.ts
Solution

Rules first.

Usually the agent writes first and you fix it after. With sloprail the rules come first, and they stay.

Usually
1
You ask
Add a feature.
2
Agent writes it all
Anywhere, any way.
3
You correct it
After the fact.
4
Added to CLAUDE.md / SKILL.md
Only if you ask. Still a suggestion.
With sloprail
1
You ask
Same request.
2
Agent writes the rules first
Where files go, what proves the work.
3
Agent builds inside them
Nowhere else.
4
Checked on every change
And the rules stay for next time.

What sloprail is made of

Each piece checks what actually happened — the real diff, the real trajectory, the real file — not what the agent claimed.

File-guardlearn more

Bound to a file, not a moment. It keeps failing and feeding the error back until the code actually satisfies it — not a one-shot reject.

Write tests for the charge flow.

Failed To Writepayment.test.ts
file-guard: tests-need-skill — the write-tests skill was not loaded
Let me load it.
Loaded Skillwrite-tests
Createdpayment.test.ts+4
+test('applies tax after the discount', () => {
+ const t = charge(100, { discount: 20 })
+ expect(t).toBe(88) // (100-20)*1.1
+})
Done.
stripe.openapi.yaml
1POST /v1/charges
2 amount: integer
3 currency: string
4 source: string missing
payments.ts
12// sr:conforms stripe#/v1/charges
13await http.post("/v1/charges", {
14 amount, currency
15})

Groundinglearn more

The claim, checked against the artifact. Did the diff really contain the change; does the cited source line actually resolve — not whether a rule merely fired.

Reconciliationlearn more

A move that has to add up to nothing. The before and after cancel out once known-legit differences are set aside — the empty residue is the proof it was mechanical, not regenerated.

$ sed -n '4,6p' config.ts >> loader.ts && sed -i '4,6d' config.ts
# deterministic — lines moved, no model in the loop
config.ts−3+1
-import { z } from "zod"
+import { parseConfig } from "./loader"
class Config {
- parseConfig(src) {
- return normalize(parse(src))
a3f9c1- }
}
loader.ts+4
+import { normalize } from "./util"
+export function parseConfig(src) {
+ return normalize(parse(src))
a3f9c1+}
guardrailtrajectory →
#research
inout
#migrate
inout
cite
no overwrite
in-structure
reconcile

Contextlearn more

A scope the agent enters on its own — detected from what's happening, not a step it has to remember. Enforced only while it's active.

zsh
$ echo "<div />" > dist/index.html
gate: deploy-safety — write to dist/ blocked (build output, do not hand-edit)

$ astro build
→ 12 page(s) built to dist/
gate: deploy-safety — passed

Gatelearn more

A checkpoint on one action. It reads what the action requires and either lets it through or blocks it — once, at the moment it matters.

Structure-gatelearn more

A standing map of where writes are even allowed. Deny by default — a path outside the structure never lands.

memories/
decisions/
20260826_gate-blocks-context/
DECISION.md allowed
scratch/
draft.md denied
routes/charge.ts
1// sr:endpoint POST /charge
2export async function POST(req) {
3 const { amount } = await req.json()
4 return charge(amount)
5}

Shipped the charge route and wired the webhook. #done

Marker & Taglearn more

A durable label pinned on the artifact — a comment in the code that later checks anchor to, surviving renames and moves.

Install

sloprail installs as a plugin. Once it's in, every tool call and turn-end runs through it — you don't run anything by hand.

/plugin marketplace add sloprail/sloprail/plugin install sloprail@sloprail-marketplace
Cursor support is coming. .
Codex support is coming. .
Get started