Repository navigation
Releases: emeraldleaf/okl
Release list
okl 0.9.0
okl now needs Python 3.11 or newer: 3.10 is past end of life, and no filelock or pillow
release that installs on it is free of the advisories open against it (0.8.9 stays
installable there). The relevance floor weighs a one-word match by how common the word is,
which refuses three of the six no-match prompts instead of one at no recall cost; okl recap --report says how a session went; okl seed --refresh takes a pack's newer lessons without
the rest of the pack; the reranker's model leaves the system temp directory; and a
check-time model load stays offline after an online one in the same process.
- okl needs Python 3.11 or newer (#285). Python 3.10 reached end of life on 2026-10-01, and the libraries the
hybridandrerankextras pull in have moved on: no release of filelock or pillow that installs on 3.10 is free of the advisories open against it (filelock 4.1.0 has six, one high; pillow 12.3.0 has twelve, one critical), and the fixes need 3.11. On 3.10, pip keeps installing 0.8.9, the last release for it. The files okl copies into a repo still run on 3.10, since they run on that repo's Python. For contributors: CI drops its 3.10 job, adds 3.15 (released 2026-10-09), and checks thatuv.lockmatchespyproject.toml(it had drifted for weeks, which is where the alerts came from), and Dependabot now updates both through itsuvecosystem. - The relevance floor weighs a one-word match by how common the word is (#277, REPORT §4v). A record that shares one word with the task in its title or symptom is briefed on that word only when at most one record in twenty carries it; a word in five or fewer records always counts, so a small store keeps the old rule; two shared words and a one-word alias admit as before. Measured by the probe at one commit against the control: no recall row moved (held-out, authored or the probe's own) and three of the six no-match prompts are now refused at a three-lesson budget, where one was ("give me a doc for that too" 11 → 2, the search prompt 12 → 6). The share is a scan of the store per check, 12 ms at 323 records. The pipeline diagrams and the README's floor sentence name the rule;
evals/common_word_grid.pyis the selection grid andevals/probe_rephrase.py --common-sharethe probe's switch. okl recap --report: how a session went (#279). From the local store's own logs and git, over a window (--sinceas ISO 8601 or90m,6h,2d; otherwise the transcript's start, otherwise a day): prompts briefed and the lessons shown most, lessons shown while stale, lessons recorded and retired with their reasons, check runs with every failure named, commits, the lessons that govern files changed in the window, and what is stale now. Markdown by default,--format jsonfor scripts. It says what it does not cover yet: corrections made withokl update, which carry no timestamp, and anything a shared service logged. 0.4 s on this repo's store.okl seed --refresh: pick up a pack's newer lessons without taking the rest of the pack. A store that was seeded with the starter subset could not receive the symptoms and aliases the packs gained (REPORT §4o, §4p) except by importing whole packs, which pours a .NET or geospatial pack into a Python repo.--refreshre-imports only the records the store already holds, from the packs they came from, keeps their verification as every re-seed does, writes an edge only when both ends were refreshed, finds the bundled packs from the store's own seed ids when no path is given, and names the packs it was seeded from that are not bundled (a repo's own file) so they can be passed by path.- Two retrieval follow-ups measured, nothing changed in behaviour (REPORT §4u). The admission rule §4t proposed for the reranker (first-stage cosine and second-stage rank together) is settled on the selection set: no line admits a lifted lesson without admitting chat filler (
evals/admission_grid.py). The floor's two-word rule for a candidate only the semantic arm returned was pre-registered and measured: it admits the one lesson it was built for and the cutoff still drops it behind fifteen kept records, so the knob (core.SEMANTIC_WORD_FLOOR) ships at None, besidecore.FETCH_MULTIPLIERandevals/window_grid.py, which found both neutral on the selection set. The probe gains--semantic-words. The next retrieval question is the order among kept records, not the floor. - The reranker's model is cached under the cache home, not the system temp directory. fastembed's default cache is
$TMPDIR/fastembed_cache, which macOS prunes on its own schedule, so a model that loaded offline one day was "not cached" the next andokl check --rerankfell back to the first-stage order.okl embednow fetches it to$XDG_CACHE_HOME/okl/models(~/.cache/okl/models), or toFASTEMBED_CACHE_PATHwhen that is set. The embedder is unaffected (model2vec reads the Hugging Face hub cache). Runokl embedonce online after upgrading; the old copy in the temp directory is not read. - A check-time model load stays offline after an online one in the same process (#290). huggingface_hub reads
HF_HUB_OFFLINEonce, at import, so in a process whose first load was online (okl embedfetching the reranker), later loads that set the variable still went to the network first: twelve connection attempts before the cache answered, and a test suite that stalled for minutes when the network was slow. The CLI was unaffected, since each command is its own process. okl's offline switch now sets huggingface_hub's own constant beside the variable and restores both, honouringTRANSFORMERS_OFFLINEas the library does.
okl 0.8.9
The retrieval work since 0.8.8, each step measured and most of it shipped off: a relevance
floor before the cutoff, a symptom and aliases on every advice record, an optional semantic
arm, and three arms that stay experiments (an index of every lesson, the near misses, a
cross-encoder second stage); hygiene metrics; the measured claims re-checked against their
receipts; the short README with the essays in docs/.
- A cross-encoder second stage, measured and shipped off (#258, REPORT §4t).
okl check --rerankruns a local ONNX cross-encoder (Xenova/ms-marco-MiniLM-L-6-v2, installed with the newrerankextra, cached once byokl embed) over the first 24 candidates (fused when--hybridis on) on each record's title, symptom and aliases, and reorders them before the gates;"rerank": truein the config orOKL_RERANK=1turns it on for every check, the service's check request carries it, each briefed record carries itsrerank_score, and without the extra or the model the first-stage order stands and the JSON says why. The model was chosen from five local cross-encoders, and the static embedder kept against five contextual ones, on a selection set that is not the held-out set (receiptsrerank-select-20261023.txt,embedder-select-20261023.txt). Measured by the probe at one commit against one store: one more held-out paraphrase of eight and one held-out bug report fewer, one of the probe's own tickets fewer, the precision prompts' counts unchanged, about 0.5 s a check with the model loaded. The pre-registered falsifier fired, so the stage ships as an experiment flag, off. The probe gains--rerankand prints the median check latency;evals/rerank_select.py,evals/rerank_spike.pyandevals/embedder_select.pyare the selection scripts. okl check --near-misses: the records that matched the task and were left out, by the floor or the cutoff, as id and title, at most twelve lines. Off by default. The pre-flight in REPORT §4s showed it could name the governing lesson in 3 of the 10 held-out tasks the briefing misses, on tasks where the briefed arm already reproduced nothing, and that the other 7 are not in the full-text candidate window at all; so no live run, and the harness'snearmissarm stays for a later instrument.- The index arm, as an experiment (#258, REPORT §4r).
okl check --indexappends every other live lesson in scope as id and title with one line telling the agent tookl showa relevant one before writing code; the service and client carry the flag. The A/B harness gains--arms baseline,briefed,indexed, aGENERATOR_TOOLSsetting that letsclaude -prunokl showthrough a logging shim, per-sample fetch records in the receipt, andevals/heldout_tasks.jsonl(24 blind rephrasings as harness tasks). Measured (REPORT §4r, receiptab-20261023-1303): given about 300 titles and an instruction to read a relevant one, the agent fetched the governing lesson once in 71 samples, so the arm stays an experiment and the hook gains no index; the harness pieces stay for the next variant. Pre-flight now gates the task file the run executes, and the fetch log counts only a successfulokl show. Thedoc-init-flagsrule's check isci/check-init-flags.py, which also reads the guide's prose. - An optional semantic arm beside the full-text search, measured and not made the default (#258, step 3).
okl embedwrites one vector per record from a local static embedding model (minishlab/potion-retrieval-32M, installed with the newhybridextra, downloaded once);okl check --hybridfuses the nearest records with the full-text candidates (linear, a convex combination, orrrf), and a candidate whose cosine reaches 0.40 passes the relevance floor by meaning;"hybrid": "linear"in the config orOKL_HYBRIDturns it on for every check. At check time the model loads from the local cache only, so a hook never reaches the network, and without the extra, the model or the vectors the briefing is the full-text one and the JSON says why. Measured with no paid model call (REPORT §4q): one more held-out paraphrase of eight found, nothing on tickets or bug reports, one record more on two of six short prompts, 0.8 s per prompt for the model load; a first pass at a 0.25 line and with any semantic candidate subject to the word rules is recorded and was revised. The vector search is a numpy matrix product (3 ms at 50,000 records); the ADR carries the amendment and the next tier. New:evals/hybrid_grid.pyand its receipt,evals/probe_rephrase.py --hybrid. - Lessons carry
aliases: the words a developer uses when one bites (#258, the second vectorless arm). A record may carry a "; "-separated list of phrases ("logged out on refresh; session lost"), indexed with the symptom at its weight in SQLite and Postgres (an older store rebuilds its index once on open); its words count toward the relevance floor's two shared words, and a one-word alias matched whole counts as the record's subject.okl record --alias(repeatable),okl update --alias(an empty value clears),okl show, the MCP tools, the service and seed packs (a JSON list) carry the field. All 136 org-scoped Rules, Defects and Gates in the seed packs gained two to eight aliases, written from each record's own content. Measured with no model (REPORT §4p): on a held-out set of 24 rephrasings written blind from the eval tasks, ticket 6→7, paraphrase 2→3, bug report 5→5 of 8; the probe's own bug-report row 3→8 and paraphrase 0→1 (sentences the author had read); the six short precision prompts unchanged on seeded stores; every governing lesson kept. A first pass in which any alias word counted as a record's subject answered a short prompt with twelve records again and was revised before shipping. New:evals/compare_seed_briefings.py(two seed trees, eight briefings, held-out recall and precision before and after),evals/heldout_variants.json,evals/probe_prompts.json. - The measured claims match every committed receipt, after an independent verification (2026-10-22). The README and REPORT §2b put the noise floor at 21 points or more (two runs of §4m's instrument; 26 points across the §4j boundary), where both still said 17; two tasks, not three, have never reproduced unbriefed, and the rate limiter's counts are quoted from the receipts (1 of 41 unbriefed, 2 of 44 briefed), with a test that recomputes the sentence. REPORT §5 finding 7 recuts its pool to the seven runs through §4h (68 of 163 against 11 of 164, the same 42% against 7%), reports §4i's run beside it with the eight-run figure the launch posts quoted, since §4i pre-registered the layout change as a new series, and names the three smoke receipts outside the pool; the README, the anatomy sheet and the docs quote the seven-run pool; §4n says what its identity test covers.
evals/preflight.pyreadsOKL_BRIEF_INTERESTSas the harness does, so the gate checks the briefing the run will use (receipts 1323 through 2254 ran filtered past an unfiltered gate, with react_fetch's rule absent from the arm), with a test.docs/RELATED-WORK.mdgives ContextCov its fourth, model-judged module, states Memory Transfer Learning's trajectory finding as the paper does, and scopes its "leaves open" claims to the seven papers read (#264). - The A/B harness refuses a run whose working tree moved. It records HEAD when a run starts and again when it writes the receipt; a difference marks the receipt
working_tree_movedand not usable, with both commits named, because the briefed arm renders from the checkout and a branch switch mid-run briefs the later samples with other code (REPORT §4o). - Every advice record gets a symptom, and
okl seedsays when one is missing (#258, step 2). A prompt that describes what went wrong finds a lesson through its symptom; twenty org Rules, Defects and Gates in the seed packs had none and were reachable only by a prompt that used their title's words. Each now has one, written in the words a developer would use when it bites.okl seedprints one line per file naming the records that lack a symptom (the same hygiene pass that warns on trace-shaped and over-scoped records), and the load-your-rules guide carries the authoring rule. - A relevance floor before the top-12 cutoff (#257). The search is an OR of the task's subject words, so a short or generic prompt used to fill every slot with records sharing one word with it. A record is now briefed only when it shares two subject words with the task, or one that is in its title or symptom, or one when the task has only one word; a record below the floor stays when it links one step to a kept one. What the floor left out is counted (
weak_matches), each briefed record names itsmatched_termsin the JSON, and both the briefing and the hook's one-line notice say when nothing matched closely. The pipeline diagram gains the stage. - Hygiene: stale-use and over-scope, reported and warned about (#252, #255). The exposure log now keeps which briefed lessons the briefing marked as changed since their last check (a new
drifted_idscolumn; older rows read as none), and the client logs after the drift marks rather than before.okl metricgains a hygiene block: stale-use (briefings that included such a lesson, over the briefings logged) and over-scope (live org lessons whose title, symptom or fix names one repository's world), each with its denominator and the lessons it flags, plus a count of trace-shaped org lessons;--format jsoncarries it, and the service serves it at/metric/hygiene.okl seedprints the same two findings as warnings before importing, and--strictexits 2 instead, for CI on a ledger repo. One detector module (okl.hygiene) feeds both, so the metric and the warning cannot disagree. - Docs:
docs/RELATED-WORK.md, a dated page with one row per 2026 paper on memory for coding agents: what each measured, with figures read from the PDFs, and where okl sta...
okl 0.8.8
No change to what okl does; this release carries the README to PyPI.
- The README, the overview and a new anatomy sheet say what okl is for: load your
rules, hold your agent to them, keep learning as you build (#208, #210). The overview
no longer claims more than the code does: a stamp with no run behind it is unproven
rather than refused, and the briefing before every task is labelled as Claude Code's.
The anatomy sheet follows one lesson all the way round in okl's own output. - A guide to loading the rules, ADRs, standards and specs a team already has (#211).
docs/LOAD-YOUR-RULES.mdcovers the import, proving the lessons that govern code,
keeping them in step with their source docs, teams, and keeping docs and comments
current, including what okl does not do yet. The README names/seed-from-docs. - The pipeline diagram traces the query
okl checkruns (#209). It had searched the
raw task sentence since 0.8.6 moved the briefing to the task's subject words, so its
counts were not the briefing's. The older Excalidraw diagram is retired. - The pooled figure the launch posts quote is in REPORT §5, finding 7 (#208), with a
test that recomputes it from its eight receipts.
okl 0.8.7
-
A new repo's pushes get a note, not an error (#204). Until a lesson governs a file,
the drift gate has nothing to check, and the pre-push hook printed "NOTHING CHECKED" and
"DRIFT NOT CHECKED" on every push, which read like a failure. It now says there is nothing
to gate yet and how to start. A missing or unreadable store still gets the loud warning,
andokl drift --gateand CI are unchanged. Re-runokl initto refresh the hook. -
okl initnames all six MCP tools. It said the server offers okl_check, okl_record
and okl_search; since 0.8.3 it also has okl_get, okl_update and okl_retire. Found by a
fresh install of 0.8.6 from PyPI. -
The README leads with what okl is. Its first screen says "Guardrails and a learning
loop for AI coding agents" and shows the one-page overview, which sat under "How it
works", about 370 lines down.
okl 0.8.6
-
A prompt with no text briefs nothing. An image or a file sent alone reached the prompt
hook as an empty prompt, and the hook briefed the last commit's subject instead: twelve
lessons about unrelated work. The hook now says there is nothing to brief on, to the
agent and the person, without asking okl (an okl before this release answers an empty
task with every lesson).OKL_TASKstill overrides, and a run with no prompt event at
all keeps the last-resort task. -
A prompt with no subject is no longer briefed (okl review R7, #178). Every word of a
prompt was OR-ed into the briefing's search, so "ok go", "do it" or "what about this one?"
matched enough of a store to fill the 12-lesson cap. The briefing now searches on the
prompt's subject words: single characters and a curated list of function words and chat
filler are left out, each term counts once, at most 40 are used, and a prompt with no term
of three or more characters briefs nothing. Short subject tokens such asid,ciand
db, andget, are kept, and so is a filler word inside a dotted name, so "Next.js" still
briefs.okl searchis unchanged. An empty prompt also no longer falls
through to a search that matched every lesson. -
A precision check for the briefing (okl review R8, #179).
evals/precision.pyscores 30
filler prompts on a store built only from the seed packs git tracks, and fails when one
briefs more than 3 lessons: before this change 18 did, 12 of them at the cap; now none.
It also reports which lessons take the most slots. REPORT §4l pre-registers the A/B run
this retrieval change needs before it ships. -
A one-page overview at the top of "How it works". Guardrails and a learning loop on one
page, in light and dark, rendered fromdocs/overview/okl-overview.html; the detailed
diagram stays below it.ci/check-diagram-figures.shnow reads HTML diagram sources too. -
The README's comparison credits the neighbours that do invalidate. It said mem0, Zep,
Letta and Cognee let memories accumulate; Zep closes a contradicted fact on a timeline and
mem0's model updates or deletes one. Utopia, an enterprise bitemporal knowledge graph, is
added. What none of them does is tie a memory to your code, which is the row's point. -
The A/B judge reads the whole submission (#182). It read only the first 8,000
characters, so a long submission reached it cut off mid-line: §4l lost a 15,585-character
sample the judge called no code. It now reads up to 40,000 characters, a longer submission
is unevaluable rather than judged on a cut copy, and each receipt records the window.
REPORT §4m pre-registers the change of instrument.
0.8.5
- The Stop question names the lessons the session touched (okl review R4, first slice).
It asked once, generically, whether the session learned anything, and told the agent
how to update or retire a lesson but never which ones to look at, so lessons were added
and almost never corrected. A newokl recap --transcript <path>lists up to five
lessons the session was briefed on most often, read from the prompt hook's context in a
Claude Code transcript, and up to five whose governed files the session changed
(uncommitted, or committed since it began), with the command for each case: retire,
update, re-verify, or say it was irrelevant. The agent proposes a change in one line and
makes it once the person agrees. The Stop hook adds it to its question; with an okl too
old to haverecap, or a transcript it cannot read, the question is as before. It reads
a 200 MB transcript in under a second.
0.8.4
-
Every run of a lesson's check is kept, pass or fail (okl review R3, first slice). The
stamp keeps only a lesson's last pass, so a check that failed, the run that caught a
change, left nothing but a line on stderr, and a check that has caught regressions looked
the same as one that never failed.okl verify, andokl reverifythrough it, now keeps
each run (command, expected signal, outcome, commit) in a newcheck_runtable: its own
table, so an older okl rewriting a lesson's row cannot lose it.okl showprints how
often the check passed and failed and its last failure;okl_getreturns the latest
runs and the counts. A shared service getsPOST /check-run; against an older service,
or if the write fails, verify warns that the run was not logged and its result stands. -
Every unbriefed prompt now says so (okl review R21).
OKL_OFFLINE=1let a prompt
through when okl could not run, with a line on stderr that neither the agent nor, in
most views, the person ever saw: the agent started the task as if a check had found
nothing. The prompt hook now hands the agent a notice that it was not briefed, with okl's
reason and that the absence of a briefing is not a clean check, and shows the person one
line saying the same. WithoutOKL_OFFLINE, a prompt okl cannot brief is still blocked. -
A pinned okl that cannot start no longer blocks a prompt another okl could brief
(#152). A venvoklwhose python was removed, by a Homebrew upgrade for example, still
exists and is executable, so the hooks accepted the pin and the prompt hook blocked on its
exit 126 even with a working okl onPATH. When a command fails to start within 2 s, the
prompt hook now moves on to the next layer (OKL_BIN, then the pin, thenPATH, then
python3 -m okl), never running a command that already failed, and adds the one it
passed over, with its fix, to the line you see. A command that ran and then exited 127,
such as adocker runwhose image lacks okl, is not run again: at three layers it could
have taken the hook past Claude Code's 30 s limit. A working pin costs nothing extra;
with nothing else to run, the block names the pin and says to re-runokl init. -
A 200 that is not JSON is no answer, not a refusal (okl review R10). Something other
than okl can answer at the service's address with a web page: a proxy's sign-in page,
or a web app on the portOKL_SERVICE_URLnames. The client's JSON parse failed with a
ValueError, whichokl checkreported as "OKL REFUSED THE CHECK" and the MCP tools as
a refusal. It is now reported as unreachable, showing the start of what came back, and
after a write it says MAYBE, since the service behind the page may have stored it. -
The MCP
okl_recordtool says MAYBE RECORDED when a write may have landed. It caught
onlyValueError, so a shared service that took the lesson and never answered reached
the agent as an opaque tool error, and one that could not be reached did too.
okl_updateandokl_retirealready said MAYBE. It now says the service may have
stored the lesson, to look withokl_searchbefore recording it again, and that
recording it again with the same id is safe; a refused connection says NOT RECORDED. -
The A/B harness no longer counts a sample with no code as a clean run (#164). The judge
now says whether the submission implemented the task at all. A plan, a description, a
question or a refusal is unevaluable: a failure, kept out of both arms' denominators and
counted toward the usability bar, with the code's length and an excerpt so the verdict
can be audited. §4j's briefedidor_endpointsample was a plan the harness scored clean.
Receipts also record the model each alias resolved to (review item R12), and the harness
refuses a judge that resolves to the generator's model under a different command string.
REPORT §4k pre-registers the change of instrument.
0.8.3
-
Retiring a lesson is a verb, with a reason (okl review R2). "Retracted" was the only
way out, retracting meant re-recording (which wiped the lesson), andokl checknever
read SUPERSEDES, so a replaced lesson was briefed beside its replacement.okl retire <id> --reason "..."(and theokl_retireMCP tool) keeps every field.
Without a flag the lesson is wrong: status retracted, briefed as AVOID.--by <new-id>
marks it superseded and writes<new> SUPERSEDES <id>: briefings leave it out before
the cut, so it never takes a live lesson's slot, and print one line pointing to the
replacement by id.--obsoletetakes it out of briefings. A resolved defect is not
retired and keeps briefing against its return.- The reason goes to a new
retirementtable rather than a column on the lesson, so an
older okl rewriting the lesson's row cannot lose it.okl showandokl_getlist a
lesson's retirements. - Only
okl retirechanges a retirement:okl updateandokl recordrefuse the retired
statuses, and neitherokl seed(eight bundled lessons say "live") nor--replace
brings a retired lesson back. - A replacement must be live and visible wherever the lesson it replaces was, which also
rules out a cycle. The pointer follows the latest replacement through a chain to a
live one; a superseded lesson with no replacement this repo can see stays briefed.
Retired lessons no longer use up the search's window. Retiring a lesson again with
the same reason and replacement changes nothing once the first call has finished; a
retry after a call that failed part-way finishes it without logging the reason twice. - Drift, the committed snapshot, the governs-nothing advisory and the briefing's STALE
mark skip retired lessons, andokl retireon one that governs files refreshes
okl-drift.json(the MCP tool cannot, so it says to runokl export --drift).
Exposure metrics skip them too, andokl deduprecommendsokl retire --byinstead
of a hand-written SUPERSEDES linkokl checknever read. - A shared service gets
POST /retire. - Upgrade every okl that writes to a store before retiring lessons in it: an older
okl'sseedresets a retired status, and itsverifyrefuses the new statuses.
-
Refining a lesson no longer wipes its proof (okl review R1). Recording a lesson again
with its--id, which the Stop hook,/recordand the encoding-loop skill all taught as
the way to refine one, replaced the whole row: its verification, its governed files and
its creation date were lost, and it quietly left drift.okl update <id> --<field> ...(and theokl_updateMCP tool) changes only the fields
given and keeps the rest, the proof and the first creation date included. Changing
--filesclears the stamp, because a check of other files proves nothing about these;
it keeps the stored check, sookl reverifyre-runs it, and refreshesokl-drift.json
asokl verifydoes. An empty value clears an optional field,--ttl-days ""too.
Updating aseed:lesson warns that the nextokl seedrestores the pack's text.okl record --id <existing>is refused (exit 2), namingokl update;--replace
(which needs--id) overwrites the lesson deliberately and keeps only its first
creation date. The MCPokl_recordtool does not offerreplace: an agent is pointed
atokl_update.okl seedstill updates its own lessons in place, and now keeps their
first creation date too.okl show <id>(andokl_get) prints one lesson whole, with its proof.- Every briefed lesson carries its
[id], so an agent can act on a lesson it was just
shown. That adds about 150 tokens to a full briefing on the bundled packs (~1,620 to
~1,770; receipts inevals/results/), and REPORT §4j registers it as a change of
treatment for the A/B. Its run (2026-10-10,ab-20261010-2314.json) read 4/23 unbriefed against 0/23
briefed (one briefed sample returned a plan, not code, and is not counted; #164): the ids are safe on the task set, and the unbriefed arm's own 26-point fall
from §4i starts a new series. The layout pre-flight confirms no content was lost, and its
summary line no longer prints a growth as "-5% smaller". - A shared service gets
POST /update(a closed, typed body),GET /node/{id}(any id,
/included; an unknown one is{"node": null}, so a 404 means an older service) and
replaceon/record. Its read-modify-write endpoints are serialised, since two
concurrent updates lost a change without an error. A client recording an id against a
service older than this refuses, instead of letting it overwrite: upgrade the service
with the client. Every write now checks that text fields are text andttl_daysa
whole number, so a wrong type is refused before it is stored. The client also closes
every HTTP error response it catches: each holds its socket, and R1 made 400s and 404s
routine, so the leak failed the Python 3.14 suite. - The Stop hook,
/record, the skill, the README and the guide now teachokl update.
-
The MCP server reports okl's version (#159). Its
initializereply carried an empty
serverInfo.versionon mcp 2.x and the SDK's own version on mcp 1.x, because okl passed
none. It now passes the installed okl version on both, and a test reads it from a real
initializeover stdio. The MCP build test also reads mcp 1.x's tool replies now, so it
passes on both SDK majors as its docstring claims. -
The Stop hook, the encoding-loop skill, CLAUDE.md and the README no longer say an org
lesson reaches every repo. By default each repo has its own store, so a lesson recorded
with--scope orgreaches another repo through a pack that repo loads. They now say
that;orgstill means a lesson true in any repo.
0.8.2
- A shared service that stalls, drops the connection or answers garbage now fails the
briefing closed, in time. The client translated a failure to connect, but not a
service that accepted the connection and then never answered (a bareTimeoutError),
hung up without replying (ConnectionResetError,RemoteDisconnected), or sent back
something that is not HTTP or is cut off mid-reply (BadStatusLine,IncompleteRead,
as fromOKL_SERVICE_URLpointed at the wrong port):okl checkdied with a
traceback and exit 1. Each try also waited 10 s, and the prompt hook tries three
times, so a stalled service held the hook about 31 s; Claude Code cancels a
UserPromptSubmit hook after 30 s and lets the prompt through, so the agent started
with no briefing. All of these are now reported as unreachable (exit 2, with a reason
naming the service and the cause), the timeout is 5 s, and the hook retries a failure
that came back within 2 s, the store rebuild the retry exists for, but not one that
took 3 s or more. The 5 s applies to each address a host resolves to and to each read,
not to the whole call: a stall on one address blocks in about 5 s, and a dual-stack
host whose firewall drops connections, which took 31.5 s even at 5 s a try, in about
10 s. A DNS lookup that hangs is still bounded only by the resolver. A test reads the
hook's retry rules and keeps its worst case under Claude Code's limit.okl record
andokl verifyno longer say a write was lost when it may not have been: these
failures arrive after the request was sent, so the service may have stored it, and
"NOT RECORDED" sent people to record the lesson again, as a second copy. They now say
"MAYBE RECORDED" (search before recording again, or reuse--id) and that verifying
again is safe. - Public claims about the A/B now match the receipts. README, REPORT.md and a shipped
seed comment named an IDOR, and once a React fetch, among the defects the A/B
reproduced; those tasks never reproduced at baseline in any committed receipt. The
defects that did are listed by name, the rate limiter's briefed-only reproductions are
stated, spa_tokens is described as reduced (97% → 55%) rather than prevented, and the
cross-model findings carry their single-run, cross-judge caveats. A shipped seed
Decision quoted the quarantined 2026-07-17 figures as "Measured"; it now cites the
committed series. Three tests hold these to the receipts. evals/layout_preflight.pyno longer counts its briefings as exposure. It fetched one
briefing per eval task through the normal client withoutOKL_BRIEFING_LOG=0, so each run
wrote 8 rows of false exposure into the storeokl metricreads. It now switches the log
off, as the A/B harness does.
0.8.1
- The Claude Code hooks run an okl whose path contains a space (#140). Both hooks cut
the path to okl at its first space, so an okl in a virtualenv under a project folder
with a space in its name, or installed under a folder likeApplication Support, could
not be run. With no other okl onPATH, the prompt hook then blocked every prompt,
blaming a file that existed, and the Stop hook skipped its question without saying so.
Both now keep the path whole, as the pre-push hook already did. A pinned
<python> -m oklis used only if that python can still import okl, so a venv whose okl
was uninstalled is skipped instead of run; with that form pinned, the check adds about
60 ms to each prompt and each stop.OKL_BINandokl_binnow take only what
okl initwrites, as the pre-push hook already did: a path to okl, or
<python> -m okl. A launcher with arguments, such asuv run okl, used to be split
into words and now is not; put it in a script and pointOKL_BINat that. - The Stop hook,
/recordand the encoding-loop skill no longer promise a gate a repo
may not have. They said a drift gate "goes red" when a lesson's code changes, which
was false in a repo set up with--no-git-hookand no--ci. They now say what
happens everywhere: the briefing andokl driftmark the lesson STALE until its check
passes again, and a drift gate, where the repo has one, fails. The README and the guide
said a lesson stays red "until someone re-runs" its check; they now say until it passes,
since a failing re-run leaves it red, or until the code is put back as it was when it
passed (drift compares content).