Back

Turning the Claude Code or Codex you already pay for into a course tutor, inside an ordinary desktop app.

Lerience is a desktop app for taking a real course from a frontier model. It runs the Claude Code or Codex the learner already has inside a course folder, where a tutor protocol makes the model interview them, plan a course, teach, and hold back answers. I built the protocol, the scripts that check the model's work, and the app around them, and it ships as an open-source app for Windows and macOS.

Lerience course workspace: the course rail with module progress on the left, a typeset lesson in the middle, and the tutor seminar on the right
The course workspace mid-course: rail, lesson, and seminar, on the demo course the landing page also runs.
Role
Full-stack developer, independent project
Timeframe
2026
Product
Open-source desktop app for Windows x64, Apple Silicon Macs, and Intel Macs
Core stack
Electron, TypeScript, React, Claude Agent SDK, Codex App Server

Learning from a chatbot works until you close the tab. Next week it has forgotten what tripped you up, nothing you did was checked, and it never had a reason to refuse you the answer. Developers already have the fix in how they work with coding agents: state in files and Git, rules in a markdown file, checks to run. It just assumes a terminal.

Lerience keeps the folder and the markdown file and puts an ordinary app around them. There is no account, no server, and no terminal. The tutor is the learner's own Claude Code or Codex at full capability. The app shows the course files, runs the agent and the engine scripts, and displays the interactive visuals the tutor writes.

Four versions, two of them wrong

It started as a course for myself. I wanted to learn AI engineering properly, so I wrote rules for a tutor into a CLAUDE.md, ran Claude Code in the folder, and took the course. The rules kept getting sharper, because the model kept finding ways to be helpful in the wrong direction. That became praxeum, an open-source protocol and local web UI for developers, which showed the teaching worked and that rules written in prose get broken. Every failure an audit found was an enforcement failure, and each one became a script.

Then I built the obvious product, a hosted version with accounts and a container per session. It worked for exactly one user and died on three findings: a server-side login invalidated the same account's tokens everywhere else, a strictly isolated local relay wasn't possible with the tooling at the time, and a tutor limited to typed tools couldn't write scaffolds or run its own checks. Subscription login only works inside the vendor's own client on the learner's machine, so that is where the tutor has to run. The desktop app follows from that, and I wrote its spec and the first decision records, each with the condition that would reopen it, before writing any app code.

The protocol

A course is a folder, and CLAUDE.md in it is the tutor protocol. Claude Code reads it natively and AGENTS.md points Codex at it. The first rule is never to write solution code. The learning happens between the scaffold and the passing checks, so the tutor may explain, ask, and give hints one level at a time, but never fill the gap, even when asked.

Every other rule came from a real failure, and the file says which one. Onboarding settles what the learner should be able to do, their actual background, their hours per week, and a deadline, before any plan exists. The learner approves the plan before module 00 is built. Only the current module exists in full, and the tutor writes the next one when the learner gets there, shaped by how the last one went.

  • Each module has a lesson, a scaffold with the important parts removed, checks the learner runs, three sealed hints, and review questions.
  • Hints escalate from a nudge to the approach to pseudocode. A second hint containing copyable code counts as a third and gets demoted.
  • Before a module is handed over, the tutor writes a sealed reference solution, proves the checks pass against it and fail against the scaffold, then deletes it.

The model proposes, code decides

The tutor keeps everything it can do: shell, files, web, MCP servers, its own sandbox and approval settings. It doesn't get authority over the record. Any state that has to be right is computed or checked by a script, and the app never treats model prose as state.

The tutor judges a recall answer, and a script does the spaced-repetition arithmetic, because a model editing the schedule by hand once flattened every interval the learner had earned. Another script refuses to start a session on top of an unclosed one. A QA script runs the sealed-reference check and lints the material before a module ships, and a second, cold review pass reads the module the way the learner will see it. QA fails if that review is missing, so the model checking its own work is enforced by code rather than requested in the prompt.

The app works the same way. At session open it runs the course checks and works out which review items are due, then hands the tutor the results, so whether the tutor actually read the files stops mattering. The course rail, due counts, and records are parsed from the files by the app.

Driving Claude Code and Codex

The renderer sees one session interface with one event stream, plus send, approve, interrupt, and end. Claude Code runs through the Agent SDK with the learner's own install and settings, so a session behaves like running claude in that folder. Codex runs through a client I wrote for its App Server over stdio, using only the stable API. It handles approvals, steering a turn that is already running, compaction, and token usage, and it rejects any request it doesn't recognise. Codex has no CLAUDE.md convention, so the adapter passes the protocol file in as developer instructions.

Neither adapter touches provider login or config, and credential-shaped text is kept out of transcripts. Codex reports its sandbox mode, but the app doesn't take its word: before the first turn it runs a fixed command through the sandbox to write a marker file into the course, then reads the bytes back. If they aren't there, the session stops with repair steps instead of failing halfway through a module.

Model, effort, and an autonomy level are applied through each provider's runtime settings for that session only, never written to the learner's config. There is a fully autonomous option on purpose: building a course can raise dozens of approval prompts, and an honest opt-out is safer than people learning to click Allow without reading.

Lerience tutor gate with two provider cards, Claude Code not signed in and Codex connected under a fixture account
The tutor gate on test fixtures: one provider is not signed in yet, the other is connected and names the account that will teach.

Sessions that survive walking away

The most committed user of the prototype never closed a session cleanly. They stopped mid-session and came back the next day, and that is when the cross-session notes should have been written. So Lerience treats abandonment as the normal end of a session. The transcript checkpoints continuously, and at the next open the app finds the unclosed session and has the tutor close it from the evidence before the new session starts with a recall quiz.

Git does what it did in the prototype: the tutor commits at close and history is the audit trail, so copying the folder moves the whole course. The transcript is an append-only log written to disk as it streams, with credential-shaped text redacted before it is stored.

Lerience onboarding: the tutor asks three interview questions before drafting a course
01 / 03
Onboarding is the tutor's interview as a conversation. The app keeps no wizard state of its own.

The interface

When a topic is spatial or dynamic, the tutor writes a self-contained HTML visual for it and places it in the lesson. Visuals load from a private scheme under a default-src 'none' CSP, in an iframe with no same-origin access: no network, no IPC, no filesystem. Self-contained is enforced by the browser, not requested from the model.

The app is a rail, the lesson, and the tutor conversation in one window. Colours live in one tokens file, and Tailwind is generated from it with the default palette removed, so a colour outside the tokens doesn't compile. A test checks WCAG contrast for every text colour on every background in both themes. Buttons follow the files: Run checks only appears when the module has checks. A browser harness mounts the production components against fixtures, and the landing page embeds that build with a scripted tutor.

Shipping it

The app runs an agent with shell access on someone's machine, so the boundaries had to be strict. The renderer is sandboxed and reaches the main process only through typed IPC, with every argument validated again on the main side and every file path confined to the course folder. The provider clients stay separately installed and authenticated; Lerience finds them but never bundles or updates them.

Distribution fails closed. Every launch verifies the bundled runtime against a manifest by size and SHA-256 and refuses to start on a mismatch. Updates need the learner's approval and are checked against release metadata signed with an Ed25519 key. CI builds on Windows and both Mac architectures, releases are created as drafts, and nothing is published until the downloaded files have been verified.

Outcome

Lerience is public and still early. What I take from it is a way of building around a model you can't fully trust: let it keep every capability, and take its word only where a wrong answer can't corrupt the record. That split between capability and authority runs from the quiz script to the release manifest.

It is also the project built most the way my essay on this site describes: agents writing much of the code from written rules and decision records, and my own attention spent on the decisions and the boundaries between the parts.