diff options
| author | t <t@tjp.lol> | 2026-10-30 01:58:33 -0600 |
|---|---|---|
| committer | t <t@tjp.lol> | 2026-10-30 08:17:49 -0600 |
| commit | 61863eb46d88a69e95c3f748a1e9df2d4b547f2a (patch) | |
| tree | af257d8e46d500327d315c711fe381057ce25cc9 /warming/warm.lua | |
| parent | a139aba0d7426c5961ea26ec8d1ce70de25f12e6 (diff) | |
Drive the warm turn from the luv timer with callback run_async, caching
only its termination, and abandon with a nonjoining close. Removes native
job polling. Adds binding-check.lua, a real-binding check against local HTTP
fixtures, to mise run check.
Requires a Pantograph host with callback-form run_async.
Diffstat (limited to 'warming/warm.lua')
| -rw-r--r-- | warming/warm.lua | 72 |
1 files changed, 34 insertions, 38 deletions
diff --git a/warming/warm.lua b/warming/warm.lua index e5e41fc..c8b3af3 100644 --- a/warming/warm.lua +++ b/warming/warm.lua @@ -18,11 +18,8 @@ -- store `panto.null_store()` -- the warm turn's user message and -- response are appended to the throwaway conversation and -- persisted nowhere. --- turn `agent:run_async { prompt, dispatch_tools = false }` on its --- own pump thread, so the UI thread never blocks. Tools are --- never dispatched: a misbehaving model cannot run real --- tools from a keep-alive prompt. The response is drained --- and dropped. +-- turn callback-form `agent:run_async(request, callback)`, started +-- by the timer. Progress is ignored; only termination is cached. -- -- Nothing here raises: the binding reports failures by raising, and every -- call goes through `try` so callers get one `nil, message` shape. @@ -40,7 +37,8 @@ end -- start(prompt) -> handle | nil, err -- -- Everything that can fail before the turn starts fails here: a caller --- either gets a handle to poll or a message. +-- either gets a handle to inspect or a message. The callback retains the +-- throwaway Lua owners until termination, and never retains event payloads. function M.start(prompt) local panto = require("panto") local ext = panto.ext @@ -111,50 +109,48 @@ function M.start(prompt) end end - local started, job = try(agent.run_async, agent, { + local handle = { agent = agent, store = store, conv = conv } + local function on_delivery(delivery) + if handle.abandoned or handle.result or type(delivery) ~= "table" or delivery.kind ~= "termination" then + return + end + local status = delivery.status + local err = type(delivery.error) == "table" and delivery.error.message or nil + handle.result = { status = status, err = err } + end + + local started, stream = try(agent.run_async, agent, { prompt = prompt, dispatch_tools = false, - }) + }, on_delivery) if not started then - return nil, "warm turn: " .. tostring(job) + return nil, "warm turn: " .. tostring(stream) end - if job == nil then + if stream == nil then return nil, "the warm turn could not be started" end - - return { agent = agent, job = job, store = store, conv = conv } + handle.stream = stream + return handle end --- poll(handle) -> nil while the pump runs, then a settled --- { status = "completed"|"failed"|"cancelled", err = ... }. Drains buffered --- events as it goes so a chatty response cannot grow the queue. The result --- content is deliberately discarded; `close` joins the (settled) pump. +-- poll(handle) returns only the cached terminal state. It never calls into +-- the native stream; callbacks own terminal observation and discard events. function M.poll(handle) - local job = handle.job - while true do - local ok, event = pcall(job.next_event, job) - if not ok or event == nil then - break - end - end - local ok, result = pcall(job.result, job) - if not ok then - pcall(job.close, job) - return { status = "failed", err = tostring(result) } - end - if type(result) ~= "table" then - return nil - end - pcall(job.close, job) - return { status = result.status or "failed", err = result.err } + return handle.result end --- abandon(handle): cancel and join the pump, on session teardown. The --- cancel half shuts the response socket down, so the join returns promptly --- even mid-request. +-- Abandon without joining. close() suppresses future callbacks and requests +-- cancellation; the binding manager retains native dependencies until worker +-- retirement. The caller keeps this handle through state teardown. function M.abandon(handle) - pcall(handle.job.request_cancel, handle.job) - pcall(handle.job.close, handle.job) + if handle.abandoned then + return + end + handle.abandoned = true + handle.result = nil + if handle.stream then + pcall(handle.stream.close, handle.stream) + end end return M |
