JITI kernel, OpenAI generations, and recovery ASCII diagrams
JITI: a CS50-style tour of a repairable Lisp kernel
The project keeps a running Lisp world alive while an outside controller asks an OpenAI model what to try next.
1. The big picture
"What should we try?"
|
v
+------------------+ +------------+ +------------------+
| Running Lisp | ----> | Controller | ----> | OpenAI Responses |
| world + kernel | view | | prompt| API |
+--------+---------+ +------+-----+ +--------+---------+
^ | |
| | action |
| v |
| +------+-----+ |
+-----------------+ SBCL worker|<---------------+
result / state | | JSON proposal
+-------------+
The model never directly edits memory. It proposes one small, validated action; the worker decides whether that action is safe and current.
2. What is the “world”?
+----------------------------------------------------------------+
| Lisp world |
| |
| data table function definitions application state |
| +-----------+ +-------------------+ +----------------+ |
| | :x = 0 | | (defun add ...) | | counters, ... | |
| +-----------+ +-------------------+ +----------------+ |
| |
| The world adapter knows how to: |
| observe it checkpoint it restore it |
| export it import it record accepted forms |
+----------------------------------------------------------------+
The kernel manages only what the world adapter can describe. External files, arbitrary threads, and other side effects need their own adapter support.
3. One generation of work
+--------+ +-----------+ +------------+ +----------+
| Observe| -> | Propose | -> | Validate | -> | Execute |
| world | | one action| | generation | | in worker|
+--------+ +-----------+ +------------+ +----+-----+
|
v
+--------+--------+
| Check invariants|
+--------+--------+
|
+-------------------+-------------------+
| |
v v
+----+-----+ +-----+----+
| Accept | | Restore |
| revision | | checkpoint|
+----------+ +----------+
4. Generations stop stale answers
Time ------------------------------------------------------------->
Kernel: view at generation 7 ------- state changes ------- generation 8
| ^
| |
OpenAI: receives view 7 -------- returns action tagged 7 ---+
|
Worker: sees current generation 8 |
| |
+-------------------- reject as stale --------------+
A generation is like a CS50 problem-set version number. The answer must match the version of the question that produced it.
5. A failed call and live restarts
Worker stack (still alive)
+------------------------------+
| worker-main |
| +------------------------+ |
| | evaluate application | |
| | error! | |<---- condition is signaled
| +-----------+------------+ |
| | |
| v |
| +------------------------+ |
| | condition-loop | |
| | restart menu: | |
| | 0/0 use-value | |
| | 0/1 retry | |
| +-----------+------------+ |
+--------------|---------------+
|
v
worker pauses and
waits for controller
A resume action selects a restart ID and supplies a Lisp list of arguments. The restart exists only while this worker is paused; it is not saved across a process restart.
6. Repairs are one transaction
checkpoint C
|
v
+--------+--------+
| outer evaluation|
| |
| repair 1 |
| repair 2 |
| resume call |
+--------+---------+
|
+---------+---------+
| |
v v
all checks pass any failure
| |
v v
keep changes restore C
The outer call and repairs share one provisional checkpoint. A failed repair rolls back the entire attempt, preventing half-applied changes.
7. Goals versus safety
candidate world
|
+---------+----------+
| |
v v
Safety invariants Goal predicates
"must never break" "what we want eventually"
| |
v v
failure => reject failure => keep working
|
v
all goals pass => success
This allows useful intermediate revisions: a candidate can be incomplete while still being safe.
8. Durable recovery
Process A Disk Process B
--------- ---- ---------
accepted world ---- export ----> revision-123/
manifest.sexp
exported world
|
+--> CURRENT = revision-123
crash
|
v
recover-session
|
+--------------+--------------+
| |
v v
read CURRENT read diagnostics
| |
v v
import revision keep recent history
| |
+--------------+--------------+
|
v
fresh worker, fresh stack
Recovery restores accepted managed code and data. It does not replay unfinished Lisp forms or pretend that an old call stack survived the crash.
9. The durable store
store/
├── CURRENT points to the accepted revision
├── revision-123/
│ ├── manifest.sexp identity, parent, SBCL version, events
│ └── exported world managed data and reconstructible code
└── events.sexp diagnostic attempts and conditions
CURRENT is authoritative. A torn final diagnostic record is archived during recovery and cannot replace the accepted revision.
10. The whole loop in one line
observe -> propose -> generation-check -> checkpoint -> execute
-> pause/repair if needed -> safety-check -> accept or restore
-> publish accepted revision -> observe again
11. Worked example: evolving a sales-tax calculator
Suppose the running application begins with one rule:
price = 100.00
rate = 5%
tax = 5.00
total = 105.00
The user asks: Create a sales-tax calculator using a 5% tax rate.
(progn
(defun sales-tax (price)
(* price 0.05))
(defun total-price (price)
(+ price (sales-tax price))))view generation 0
|
v
checkpoint world
|
v
install definitions
|
v
safety checks pass
|
v
accept revision 1, generation 1
(sales-tax 100.00) => 5.00 (total-price 100.00) => 105.00
Change 1: caller chooses the rate
The user asks: Support different tax rates.
(progn
(defun sales-tax (price rate)
(* price rate))
(defun total-price (price rate)
(+ price (sales-tax price rate))))revision 1
|
| checkpoint
v
try new definitions
|
+--> checks pass --> accept revision 2
|
+--> checks fail --> restore revision 1
(total-price 100.00 0.05) => 105.00 (total-price 100.00 0.08) => 108.00
Change 2: food has a different rate
The user asks: Use different rates for food and non-food items.
(progn
(defun sales-tax (price rate)
(* price rate))
(defun total-price (price item-type)
(let ((rate (if (eq item-type :food)
0.02
0.08)))
(+ price (sales-tax price rate))))) +------------------+
| total-price |
| price, item-type |
+--------+---------+
|
v
item-type = :food?
/ \
yes no
| |
rate 0.02 rate 0.08
\ /
v v
price + price * rate
(total-price 100.00 :food) => 102.00 (total-price 100.00 :clothing) => 108.00
Goals and safety checks
candidate calculator
|
+---------+----------+
| |
v v
goal predicates safety invariants
desired behavior must-never-break rules
| |
v v
food total is 102.00 tax is never negative
| |
v v
incomplete goal failure rejects attempt
means keep working
For example:
;; Goal: desired behavior
(lambda ()
(and (= (total-price 100.00 :food) 102.00)
(= (total-price 100.00 :clothing) 108.00)))
;; Safety invariant: never accept a negative tax
(lambda ()
(and (>= (sales-tax 100.00 0.02) 0)
(>= (sales-tax 100.00 0.08) 0)))A bad proposal rolls back
If a proposal accidentally makes food tax negative:
(defun total-price (price item-type)
(+ price
(if (eq item-type :food)
(* price -0.02)
(* price 0.08))))revision 2
|
v
checkpoint C
|
v
run bad proposal
|
v
safety invariant fails
|
v
restore C
|
v
revision 2 is still live
The next user request starts from the last accepted calculator, not from the half-applied bad change.
The revision history
+------------+ +------------+ +------------+
| Revision 1 | ----> | Revision 2 | ----> | Revision 3 |
| fixed 5% | | caller rate| | food rules |
+------------+ +------------+ +------------+
| | |
v v v
total(100) total(100,.08) total(100,:food)
105 108 102
Recovery after a crash
accepted revision 3
|
v
process crashes
|
v
read CURRENT
|
v
load revision 3
|
v
start fresh worker
|
v
food and non-food behavior returns
The accepted calculator and managed data return from disk. The old call stack and live restart objects do not; those exist only inside the original worker.