Skip to content

Releases: emeraldleaf/okl

okl 0.9.0

Choose a tag to compare

@emeraldleaf emeraldleaf released this 25 Oct 22:49
0809add

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 hybrid and rerank extras 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 that uv.lock matches pyproject.toml (it had drifted for weeks, which is where the alerts came from), and Dependabot now updates both through its uv ecosystem.
  • 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.py is the selection grid and evals/probe_rephrase.py --common-share the probe's switch.
  • okl recap --report: how a session went (#279). From the local store's own logs and git, over a window (--since as ISO 8601 or 90m, 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 json for scripts. It says what it does not cover yet: corrections made with okl 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. --refresh re-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, beside core.FETCH_MULTIPLIER and evals/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 and okl check --rerank fell back to the first-stage order. okl embed now fetches it to $XDG_CACHE_HOME/okl/models (~/.cache/okl/models), or to FASTEMBED_CACHE_PATH when that is set. The embedder is unaffected (model2vec reads the Hugging Face hub cache). Run okl embed once 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_OFFLINE once, at import, so in a process whose first load was online (okl embed fetching 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, honouring TRANSFORMERS_OFFLINE as the library does.

okl 0.8.9

Choose a tag to compare

@emeraldleaf emeraldleaf released this 24 Oct 21:11
a7d6d34

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 --rerank runs a local ONNX cross-encoder (Xenova/ms-marco-MiniLM-L-6-v2, installed with the new rerank extra, cached once by okl embed) over the first 24 candidates (fused when --hybrid is on) on each record's title, symptom and aliases, and reorders them before the gates; "rerank": true in the config or OKL_RERANK=1 turns it on for every check, the service's check request carries it, each briefed record carries its rerank_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 (receipts rerank-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 --rerank and prints the median check latency; evals/rerank_select.py, evals/rerank_spike.py and evals/embedder_select.py are 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's nearmiss arm stays for a later instrument.
  • The index arm, as an experiment (#258, REPORT §4r). okl check --index appends every other live lesson in scope as id and title with one line telling the agent to okl show a relevant one before writing code; the service and client carry the flag. The A/B harness gains --arms baseline,briefed,indexed, a GENERATOR_TOOLS setting that lets claude -p run okl show through a logging shim, per-sample fetch records in the receipt, and evals/heldout_tasks.jsonl (24 blind rephrasings as harness tasks). Measured (REPORT §4r, receipt ab-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 successful okl show. The doc-init-flags rule's check is ci/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 embed writes one vector per record from a local static embedding model (minishlab/potion-retrieval-32M, installed with the new hybrid extra, downloaded once); okl check --hybrid fuses the nearest records with the full-text candidates (linear, a convex combination, or rrf), and a candidate whose cosine reaches 0.40 passes the relevance floor by meaning; "hybrid": "linear" in the config or OKL_HYBRID turns 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.py and 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.py reads OKL_BRIEF_INTERESTS as 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.md gives 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_moved and 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 seed says 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 seed prints 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 its matched_terms in 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_ids column; older rows read as none), and the client logs after the drift marks rather than before. okl metric gains 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 json carries it, and the service serves it at /metric/hygiene. okl seed prints the same two findings as warnings before importing, and --strict exits 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...
Read more

okl 0.8.8

Choose a tag to compare

@emeraldleaf emeraldleaf released this 13 Oct 12:43
690d6d0

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.md covers 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 check runs (#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

Choose a tag to compare

@emeraldleaf emeraldleaf released this 12 Oct 01:53
ae1d327
  • 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,
    and okl drift --gate and CI are unchanged. Re-run okl init to refresh the hook.

  • okl init names 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

Choose a tag to compare

@emeraldleaf emeraldleaf released this 12 Oct 00:26
8bee437
  • 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_TASK still 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 as id, ci and
    db, and get, are kept, and so is a filler word inside a dotted name, so "Next.js" still
    briefs. okl search is 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.py scores 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 from docs/overview/okl-overview.html; the detailed
    diagram stays below it. ci/check-diagram-figures.sh now 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

Choose a tag to compare

@emeraldleaf emeraldleaf released this 11 Oct 05:11
70e93a8
  • 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 new okl 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 have recap, or a transcript it cannot read, the question is as before. It reads
    a 200 MB transcript in under a second.

0.8.4

Choose a tag to compare

@emeraldleaf emeraldleaf released this 11 Oct 04:33
f624ab1
  • 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, and okl reverify through it, now keeps
    each run (command, expected signal, outcome, commit) in a new check_run table: its own
    table, so an older okl rewriting a lesson's row cannot lose it. okl show prints how
    often the check passed and failed and its last failure; okl_get returns the latest
    runs and the counts. A shared service gets POST /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=1 let 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. Without OKL_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 venv okl whose 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 on PATH. When a command fails to start within 2 s, the
    prompt hook now moves on to the next layer (OKL_BIN, then the pin, then PATH, 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 a docker run whose 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-run okl 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 port OKL_SERVICE_URL names. The client's JSON parse failed with a
    ValueError, which okl check reported 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_record tool says MAYBE RECORDED when a write may have landed. It caught
    only ValueError, 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_update and okl_retire already said MAYBE. It now says the service may have
    stored the lesson, to look with okl_search before 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 briefed idor_endpoint sample 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

Choose a tag to compare

@emeraldleaf emeraldleaf released this 11 Oct 01:51
890b269
  • 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), and okl check never
    read SUPERSEDES, so a replaced lesson was briefed beside its replacement.

    • okl retire <id> --reason "..." (and the okl_retire MCP 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. --obsolete takes it out of briefings. A resolved defect is not
      retired and keeps briefing against its return.
    • The reason goes to a new retirement table rather than a column on the lesson, so an
      older okl rewriting the lesson's row cannot lose it. okl show and okl_get list a
      lesson's retirements.
    • Only okl retire changes a retirement: okl update and okl record refuse the retired
      statuses, and neither okl 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, and okl retire on one that governs files refreshes
      okl-drift.json (the MCP tool cannot, so it says to run okl export --drift).
      Exposure metrics skip them too, and okl dedup recommends okl retire --by instead
      of a hand-written SUPERSEDES link okl check never read.
    • A shared service gets POST /retire.
    • Upgrade every okl that writes to a store before retiring lessons in it: an older
      okl's seed resets a retired status, and its verify refuses 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, /record and 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 the okl_update MCP tool) changes only the fields
      given and keeps the rest, the proof and the first creation date included. Changing
      --files clears the stamp, because a check of other files proves nothing about these;
      it keeps the stored check, so okl reverify re-runs it, and refreshes okl-drift.json
      as okl verify does. An empty value clears an optional field, --ttl-days "" too.
      Updating a seed: lesson warns that the next okl seed restores the pack's text.
    • okl record --id <existing> is refused (exit 2), naming okl update; --replace
      (which needs --id) overwrites the lesson deliberately and keeps only its first
      creation date. The MCP okl_record tool does not offer replace: an agent is pointed
      at okl_update. okl seed still updates its own lessons in place, and now keeps their
      first creation date too.
    • okl show <id> (and okl_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 in evals/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
      replace on /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 and ttl_days a
      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 teach okl update.
  • The MCP server reports okl's version (#159). Its initialize reply carried an empty
    serverInfo.version on 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
    initialize over 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 org reaches another repo through a pack that repo loads. They now say
    that; org still means a lesson true in any repo.

0.8.2

Choose a tag to compare

@emeraldleaf emeraldleaf released this 10 Oct 20:24
f2c1a6c
  • 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 bare TimeoutError),
    hung up without replying (ConnectionResetError, RemoteDisconnected), or sent back
    something that is not HTTP or is cut off mid-reply (BadStatusLine, IncompleteRead,
    as from OKL_SERVICE_URL pointed at the wrong port): okl check died 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
    and okl verify no 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.py no longer counts its briefings as exposure. It fetched one
    briefing per eval task through the normal client without OKL_BRIEFING_LOG=0, so each run
    wrote 8 rows of false exposure into the store okl metric reads. It now switches the log
    off, as the A/B harness does.

0.8.1

Choose a tag to compare

@emeraldleaf emeraldleaf released this 09 Oct 02:17
606a6b0
  • 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 like Application Support, could
    not be run. With no other okl on PATH, 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 okl is 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_BIN and okl_bin now take only what
    okl init writes, as the pre-push hook already did: a path to okl, or
    <python> -m okl.
    A launcher with arguments, such as uv run okl, used to be split
    into words and now is not; put it in a script and point OKL_BIN at that.
  • The Stop hook, /record and 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-hook and no --ci. They now say what
    happens everywhere: the briefing and okl drift mark 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).