Anatomy of okl · guardrails and a learning loop

Load your rules.
Hold your agent to them.
Keep learning as you build.

Start okl with the rules, ADRs, standards and specs you already have. It briefs your coding agent with the ones that share each task's words, on every prompt in Claude Code and on request in other MCP agents, and when the code a lesson covers changes, the pre-push hook holds the push until its check passes again, or the code is back to what passed. New rules, mistakes, drift and failing tests keep adding lessons as you build.

6

Re-checked or retired

You decide

okl reverify lists each stale lesson's stored check and re-runs it once you confirm. A pass stamps the lesson again; a fail means fix the code, or change the lesson on purpose.

Claude Code When the agent first stops after changing files, it asks what the work taught, listing up to five lessons it was briefed on and five whose files changed. okl update keeps the proof unless the files change; okl retire keeps the reason.

5

Held when code moves

okl runs it

Commit a change to a file a lesson covers and its proof is stale. The push waits, and any briefing that includes the lesson marks it STALE, until its check passes again or the code is back to what passed.

It compares content with the proven commit, so putting the code back clears it. okl init installs the hook in that clone, and --no-verify skips it; okl init --ci gates every PR in CI instead, and --git-hook keeps both.

1

Where lessons come from

Up frontyour rules, ADRs, standards and specs, plus starter lessons for your stack As you buildnew rules, mistakes, drift the gate finds, failing tests, reviews

Claude Code /seed-from-docs proposes lessons from your docs into a file you review before okl seed imports it.

This one: a review found get_order hands any customer's order to any signed-in user.

you decideokl runs it
4

Briefed before each task

okl runs it, in Claude Code

okl picks up to twelve lessons that share the task's words and puts them in the agent's context before it starts.

Claude Code A hook runs on every prompt; if okl can't brief, the prompt is blocked or told so. Other MCP agents can call okl_check; nothing calls it for them.

2

Written as a lesson

You decide

You or your agent write it down in one shape: when you see this, do that, because. It can name the files it covers.

Scope repo briefs only here; scope org can reach other repos once it is in a pack, a JSON file in git they load with okl seed. The store is one gitignored file in each clone; no server needed.

3

Proven by your check

okl runs it

okl runs the check you chose and stamps the lesson only when it passes, keeping the command, the commit and the time.

A pass is exit 0, plus the text you expect if you name one. For a lesson that covers files, a stamp with no observed run counts as unproven. Every run is logged, pass or fail.

One lesson, all the way round

Real okl 0.8.7 output from a throwaway repo, shortened where … marks a cut. The # notes are ours.

orders-api
2
$ okl record --type Rule --scope repo --id order_scope --files app/orders.py --tags security \
    --title "Order lookups are scoped to the signed-in customer" \
    --symptom "an order is fetched by id with no check that it belongs to the caller" \
    --fix "filter the lookup by the caller's id; another customer's order is a 404, not a 403" \
    --body "Without the owner check, any signed-in user can read any order by trying ids (IDOR, CWE-639)."
order_scope
3
$ okl verify order_scope --run "pytest -q" --expect passed
✓ verified order_scope — Order lookups are scoped to the signed-in customer
  evidence: `pytest -q` exit 0, matched 'passed' on commit 5c1e804c3e04 @ 2026-10-12T20:14Z
…
4
$ okl check --task "Add an endpoint that returns one order by id for the signed-in customer"
## OKL briefing — orders-api · task: Add an endpoint that returns one order by id for the signed-in customer
_12 relevant node(s) from the org's encoded body._
…
# third of the twelve; the first came from a .NET project's pack, the second is about Starlette's TestClient:
- **FIX: Order lookups are scoped to the signed-in customer** [order_scope] — when you see: an order is fetched by id …
    → filter the lookup by the caller's id; another customer's order is a 404, not a 403
    why: Without the owner check, any signed-in user can read any order by trying ids (IDOR, CWE-639).
…
# in Claude Code, a prompt hook puts this briefing in the agent's context before every turn, and shows the person:
okl · briefed 12 lesson(s): Missing ownership scope check is an IDOR…; Starlette's TestClient moved to httpx2 in…;
  Order lookups are scoped to the signed-in… (+9 more)
5
# later, someone "simplifies" app/orders.py:
-    return order if order and order["owner"] == user else None
+    return order
$ git commit -qam "simplify get_order" && git push origin main
OKL drift: 1 rule(s) may be stale (source changed after verification):
  • [order_scope] Order lookups are scoped to the signed-in customer
      files: app/orders.py
…
okl pre-push: lessons drifted (above). Re-check them with 'okl reverify', commit,
  then push again. 'git push --no-verify' skips this check.
error: failed to push some refs to '…/remote.git'
6
$ okl reverify --yes
okl reverify will run 1 stored check(s), from the store:
  [order_scope] pytest -q
✗ check FAILED (exit 1) — NOT stamping verification.
…
FAILED tests/test_orders.py::test_another_customers_order_is_not_found - Asse...
…
okl reverify: 0 re-verified, 1 failed, 0 need a first check.
# the change had dropped the owner check; put back, the file matches its proven content again:
$ git commit -qam "restore the owner check" && git push origin main
OKL drift: OK — 1 rule(s) checked, no encoded rule's governed source changed after its last verification.
…

The encoding loop

Where a lesson lives

Whether a rule was decided up front or taught by a mistake, a drift or a review, put it where the next person or agent will meet it. If it keeps being broken, move it somewhere stricter, up to a failing test.

Briefing onlyUseful for some tasks. Most lessons.
okl
Watched for driftCovers specific code: files and a check.
okl
Build-breakingBroken before, or costly: a failing test.
your CI

A line in CLAUDE.md or AGENTS.md suits the rare lesson every session needs. Moving a lesson is a person's call.

Measured

What the briefing did

42%→7%
78 of 186 unbriefed13 of 188 briefed

attempts that repeated a known defect

Eight A/B runs on eight tasks built to invite a defect, three samples per arm: sonnet writes one-shot code with the briefing put in its prompt, and haiku judges it blind. The runs (Aug 30 to Sep 26) predate today's subject-word search. Not measured: the hook, the gate, the write side, improvement over time. The example above shows how the parts work, not that they help.

Receipts in evals/results/: ab-20260830-0003, ab-20260901-0133, ab-20260901-1238, ab-20260902-0538, ab-20260903-0157, ab-20260903-1214, ab-20260903-1323, ab-20260926-0528 · evals/REPORT.md §5, finding 7

Under the hood

How it ranks, what it costs

Rank
the prompt's subject words, up to 40 → BM25 in SQLite full-text search (title 8, body 4, symptom 4, fix 2, tags 1) → 36 fetched → filtered by scope, and org lessons by applies_to and the repo's interests → top 12
Cost
about 1,770 tokens for a full briefing of twelve, and about 0.1 s for okl check; no embeddings, no model calls
Stale
the committed content of the lesson's files differs from the proven commit (commit times, if that commit is missing); a lesson with files and no observed pass holds the push too

Where it breaks

  • A task that shares no exact word with a lesson misses it: no stemming, so "orders" won't match "order".
  • No score floor: one shared word can earn a slot.
  • A file is watched whole: any edit needs a re-run.
  • A check proves what it tests, and nothing more.