summaryrefslogtreecommitdiff
path: root/warming
diff options
context:
space:
mode:
Diffstat (limited to 'warming')
-rw-r--r--warming/warm.lua72
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