-- Self-check for panto-warming. Run with: -- -- panto lua _selfcheck.lua -- -- Every module under test is pure Lua (no session, no panto module needed), -- so plain lua5.4 runs it too. The fidelity probe at the bottom uses the -- host's real JSON codec when the interpreter provides one. local here = (arg and arg[0] or "_selfcheck.lua"):match("^(.*)/") or "." package.path = here .. "/?.lua;" .. here .. "/?/init.lua;" .. package.path local config = require("warming.config") local policy = require("warming.policy") local SEC, MIN, HOUR = 1000, 60000, 3600000 -- --------------------------------------------------------------------------- -- Duration strings -- --------------------------------------------------------------------------- assert(config.parse_duration("4 minutes") == 4 * MIN, "4 minutes") assert(config.parse_duration("30 seconds") == 30 * SEC, "30 seconds") assert(config.parse_duration("1 hour") == HOUR, "1 hour") assert(config.parse_duration("2h") == 2 * HOUR, "2h") assert(config.parse_duration(" 10 mins ") == 10 * MIN, "whitespace and synonyms") local function duration_fails(text, why) local ms, err = config.parse_duration(text) assert(ms == nil, "parse_duration accepted " .. why) assert(type(err) == "string", "parse_duration explains " .. why) end duration_fails("soon", "a non-duration") duration_fails("4", "a bare number") duration_fails("0 minutes", "a zero duration") duration_fails("-1 minutes", "a negative duration") duration_fails("1 fortnight", "an unknown unit") duration_fails(4, "a non-string") -- --------------------------------------------------------------------------- -- Layer merging -- --------------------------------------------------------------------------- local s = config.defaults() assert(s.enabled == false, "warming is off unless configured") assert(s.interval_ms == 4 * MIN and s.duration_ms == 30 * MIN, "opencode defaults") assert(s.prompt:find("OK"), "default prompt asks for OK") assert(config.apply_layer(s, true, "x") == true, "scalar true applies") assert(s.enabled == true, "scalar true enables") assert(config.apply_layer(s, { interval = "2 minutes" }, "x") == true, "table applies") assert(s.interval_ms == 2 * MIN, "interval overridden") assert(s.duration_ms == 30 * MIN, "omitted keys keep earlier values") assert(config.apply_layer(s, false, "x") == true, "scalar false applies") assert(s.enabled == false, "scalar false disables") assert(config.apply_layer(s, { enabled = false, duration = "10 minutes" }, "x") == true, "enabled key applies") assert(s.enabled == false, "warming.enabled = false disables") assert(s.duration_ms == 10 * MIN, "duration still merged while disabled") assert(config.apply_layer(s, { prompt = "stay put" }, "x") == true, "prompt applies") assert(s.prompt == "stay put" and s.enabled == true, "table presence enables") local function layer_fails(value, why) local ok, err = config.apply_layer(config.defaults(), value, "/x/config.toml") assert(ok == nil, "apply_layer accepted " .. why) assert(type(err) == "string" and err:find("/x/config.toml"), "error names the file for " .. why) end layer_fails("always", "a string scalar") layer_fails({ bogus = 1 }, "an unknown key") layer_fails({ prompt = "" }, "an empty prompt") layer_fails({ interval = "1 fortnight" }, "a bad duration") layer_fails({ enabled = "yes" }, "a non-boolean enabled") -- Layer walk over fake config layers (base < user < project < local). local docs = { ["/base/config.toml"] = { warming = true }, ["/user/config.toml"] = { warming = { interval = "2 minutes" } }, ["/project/config.toml"] = { warming = { duration = "10 minutes" } }, ["/local/config.toml"] = { warming = false }, } local fake_io = { read_file = function(path) return docs[path] and path or nil -- the "file contents" are its path end, parse = function(text) return docs[text] end, } local fake_ext = { dirs = { layers = { { name = "base", dir = "/base" }, { name = "user", dir = "/user" }, { name = "project", dir = "/project" }, { name = "local", dir = "/local" }, { name = "missing", dir = "/nowhere" }, } }, } local merged = assert(config.read(fake_ext, fake_io)) assert(merged.interval_ms == 2 * MIN and merged.duration_ms == 10 * MIN, "layers merge per key") assert(merged.enabled == false, "the highest layer's scalar wins") docs["/local/config.toml"] = { warming = { interval = "90 seconds" } } merged = assert(config.read(fake_ext, fake_io)) assert(merged.enabled == true and merged.interval_ms == 90 * SEC, "later layers enable and override") docs["/local/config.toml"] = { warming = { typo = true } } local bad, err = config.read(fake_ext, fake_io) assert(bad == nil and err:find("/local/config.toml"), "a bad layer fails with its path") docs["/local/config.toml"] = { warming = true } fake_io.parse = function() error("boom") end bad, err = config.read(fake_ext, fake_io) assert(bad == nil and err:find("boom"), "a parse error surfaces its reason") -- --------------------------------------------------------------------------- -- Timing policy: the opencode timelines -- --------------------------------------------------------------------------- local function fire_times(p, from, to, step) local fired = {} local t = from while t <= to do if p:due(t) then fired[#fired + 1] = t / MIN p:attempted(t) end t = t + step end return fired end -- 00:00 real request; warm every 4 minutes; stop at 30 minutes. do local p = policy.new(4 * MIN, 30 * MIN) assert(p:expired(0), "no window before any activity") p:activity(0) assert(not p:due(0), "no warm request at the moment of activity") local fired = fire_times(p, 15 * SEC, 40 * MIN, 15 * SEC) assert(#fired == 7, "seven warm attempts in the default window, got " .. #fired) for i, at in ipairs(fired) do assert(at == i * 4, "warm attempt " .. i .. " at " .. at .. "m, want " .. (i * 4) .. "m") end assert(p:expired(30 * MIN), "window closes at 30m") assert(not p:expired(29 * MIN), "window still open at 29m") end -- 00:00 real request; 00:20 real request restarts both timers; warms at -- 00:24; stops at 00:50. do local p = policy.new(4 * MIN, 30 * MIN) p:turn_started(0) assert(not p:due(4 * MIN), "a turn in flight blocks warming") p:turn_ended(20 * MIN) assert(not p:due(20 * MIN), "interval restarts after the turn") assert(not p:due(23 * MIN), "not due at 23m") assert(p:due(24 * MIN), "due at 24m") p:attempted(24 * MIN) assert(not p:due(27 * MIN + 59 * SEC), "next attempt is a full interval later") assert(p:due(28 * MIN), "due at 28m") assert(not p:expired(49 * MIN), "window runs to 50m") assert(p:expired(50 * MIN), "window closes at 50m") end -- An interval as long as the window: the window always expires first. do local p = policy.new(30 * MIN, 30 * MIN) p:activity(0) local fired = fire_times(p, 0, 60 * MIN, 15 * SEC) assert(#fired == 0, "interval >= duration means no warm request ever") end -- Entry eval contract: loading the extension chunk is side-effect-free and -- returns the one activation entry. local entry = assert(loadfile(here .. "/init.lua"))() assert(type(entry) == "table" and entry.name == "warming" and type(entry.activate) == "function", "init.lua returns the warming entry") -- Activation binds lifecycle timing to session_ready and the banner to the -- render boundary; neither handler is the old session_start event. do local handlers = {} local previous_panto = package.loaded.panto local previous_luv = package.loaded.luv local previous_config = package.loaded["warming.config"] local previous_policy = package.loaded["warming.policy"] local previous_warm = package.loaded["warming.warm"] package.loaded["warming.config"] = { default_io = function() return {} end, read = function() return { enabled = true, interval_ms = 1, duration_ms = 100, prompt = "OK" } end, } package.loaded["warming.policy"] = policy local real_warm = require("warming.warm") local handles = {} package.loaded["warming.warm"] = { start = function(...) local handle, err = real_warm.start(...) if handle then handles[#handles + 1] = handle end return handle, err end, poll = real_warm.poll, abandon = real_warm.abandon, } local starts, callback_queue, forbidden_calls = {}, {}, 0 local transcript, no_store = {}, {} local declarations = { { name = "tool", parameters = {} } } local session_store = { load = function(_, id) assert(id == "session-1", "warm request loads the active transcript") return transcript end, } local warm_agent = { set_tools = function(_, tools) assert(tools == declarations, "session tool declarations are copied") end, run_async = function(_, request, on_delivery) assert(type(on_delivery) == "function", "callback form is used") assert(request.prompt == "OK" and request.dispatch_tools == false, "the transient request preserves tool isolation") local stream = { close = function(self) self.close_calls = (self.close_calls or 0) + 1 end, } for _, name in ipairs({ "next_event", "result", "request_cancel", "join" }) do stream[name] = function() forbidden_calls = forbidden_calls + 1 error("legacy native-job method used: " .. name) end end starts[#starts + 1] = { request = request, callback = on_delivery, stream = stream } return stream end, } local current_time, timer package.loaded.panto = { file_system_jsonl_store = function(options) assert(options.dir == "/sessions", "the active session store is reused") return session_store end, null_store = function() return no_store end, agent = function(options) assert(options.conversation == transcript and options.store == no_store, "the warm agent is isolated over the replayed transcript") return warm_agent end, ext = { on = function(name, handler) handlers[name] = handler end, session_info = function() return { session_dir = "/sessions", session_id = "session-1", model = "model", reasoning = "low" } end, resolve_model = function(options) assert(options.model == "model" and options.reasoning == "low", "session model is retained") return { resolved = true } end, agent = { tools = function() return declarations end }, style = function() return { dim = "", reset = "" } end, }, } package.loaded.luv = { now = function() return current_time end, new_timer = function() timer = { start = function(self, _, _, callback) self.callback = callback self.active = true end, is_active = function(self) return self.active end, is_closing = function(self) return self.closing or false end, stop = function(self) self.active = false end, close = function(self) self.closing = true end, } return timer end, } local active_entry = assert(loadfile(here .. "/init.lua"))() active_entry.activate() assert(handlers.render_header and handlers.session_ready, "new event subscriptions are installed") assert(handlers.session_start == nil, "old session_start subscription is absent") current_time = 0 handlers.session_ready() assert(timer and timer.active, "session readiness starts the timer") local function tick(at) current_time = at timer.callback() end local function queue_delivery(index, delivery) callback_queue[#callback_queue + 1] = { index = index, delivery = delivery } end local function advance_delivery() local queued = table.remove(callback_queue, 1) assert(queued ~= nil, "callback queue has a delivery to advance") starts[queued.index].callback(queued.delivery) end tick(0) assert(#starts == 0, "the interval prevents an immediate turn") tick(1) assert(#starts == 1 and #callback_queue == 0 and handles[1].result == nil, "installation returns before any callback is queued or delivered") assert(handles[1].agent == warm_agent and handles[1].store == session_store and handles[1].conv == transcript, "owners stay rooted while the turn is live") queue_delivery(1, { kind = "result", result = { type = "message_start" } }) assert(handles[1].result == nil, "queued values do not settle before delivery") advance_delivery() tick(2) assert(#starts == 1 and handles[1].result == nil, "result events are discarded, not settled") queue_delivery(1, { kind = "termination", status = "completed" }) advance_delivery() assert(handles[1].result.status == "completed", "completed termination is cached") tick(3) assert(#starts == 1, "the completion tick only settles the previous attempt") tick(4) assert(#starts == 2, "the next timer turn starts after the interval") queue_delivery(1, { kind = "termination", status = "cancelled" }) advance_delivery() assert(handles[1].result.status == "completed", "a stale callback cannot rewrite a prior terminal") tick(4.5) assert(#starts == 2 and handles[2].result == nil, "a stale callback cannot settle or replace the current handle") queue_delivery(2, { kind = "termination", status = "failed", error = { message = "provider failure" } }) advance_delivery() tick(5) assert(handles[2].result.status == "failed" and handles[2].result.err == "provider failure", "failure termination and its diagnostic are cached") tick(6) assert(#starts == 3, "a failed attempt retries after the interval") queue_delivery(3, { kind = "termination", status = "cancelled" }) advance_delivery() tick(7) assert(handles[3].result.status == "cancelled", "cancel termination settles the attempt") tick(8) assert(#starts == 4, "a cancelled attempt also follows the retry policy") local abandoned = handles[4] handlers.session_end() assert(timer.closing and not timer.active, "session teardown stops and closes its timer") assert(abandoned.stream.close_calls == 1 and abandoned.abandoned, "teardown closes the live subscription without joining") assert(abandoned.agent == warm_agent and abandoned.store == session_store and abandoned.conv == transcript, "teardown retains owners through state retirement") queue_delivery(4, { kind = "termination", status = "completed" }) advance_delivery() assert(real_warm.poll(abandoned) == nil, "a stale post-close delivery cannot settle an abandoned turn") tick(9) handlers.session_ready() assert(#starts == 4, "stale timer/session callbacks cannot start work after teardown") assert(forbidden_calls == 0, "no next_event, native result, cancel-join, or join API was used") local inner = { render = function() return { "header" } end } local event = { get_component = function() return inner end } event.set_component = function(_, component) event.component = component end handlers.render_header(event) assert(event.component and event.component:render(80)[1] == "header", "render_header wraps the banner") package.loaded.panto = previous_panto package.loaded.luv = previous_luv package.loaded["warming.config"] = previous_config package.loaded["warming.policy"] = previous_policy package.loaded["warming.warm"] = previous_warm end -- --------------------------------------------------------------------------- -- Tool-schema fidelity probe (report-only) -- -- Warming copies the session's tool declarations to the warm request -- through the platform JSON codec: `agent:tools()` decodes the session's -- schema bytes and `set_tools` re-encodes them. If encode(decode(x)) is not -- byte-identical to x, the warm request's tool prefix diverges from the -- session's own requests and cannot write a cache entry they will hit (see -- the README). This probe measures how often that drift happens with the -- codec in this interpreter. It reports, but does not fail: the drift is a -- property of the codec, not of this rock. -- --------------------------------------------------------------------------- local ok_panto, panto = pcall(require, "panto") local json = ok_panto and type(panto) == "table" and panto.ext and panto.ext.json if json and type(json.encode) == "function" then local schemas = { { type = "object", properties = {}, additionalProperties = false }, { type = "object", properties = { query = { type = "string" } }, required = { "query" } }, { type = "object", title = "run", properties = { command = { type = "string" }, cwd = { type = "string" }, timeout = { type = "integer", minimum = 1 }, env = { type = "array", items = { type = "string" } }, }, required = { "command" }, additionalProperties = false }, { type = "object", properties = { name = { type = "string" }, description = { type = "string" }, schema = { type = "object", additionalProperties = true }, labels = { type = "array", items = { type = "string" } }, nested = { type = "object", properties = { a = { type = "number" }, b = { type = "boolean" }, c = { type = "null" }, } }, } }, } local drift = 0 for _, schema in ipairs(schemas) do local encoded = json.encode(schema) if json.encode(json.decode(encoded)) ~= encoded then drift = drift + 1 end end print(string.format( "probe: schema re-encode drift %d/%d (%s)", drift, #schemas, drift == 0 and "byte-stable" or "tool prefix fidelity degraded; see README")) else print("probe: skipped (no panto.ext.json in this interpreter)") end print("panto-warming selfcheck: OK")