luv

Workshop wiki

harness.lisp

application-agent/harness.lisp

system luv/application-agent · 36 definitions · on GitHub

in-package#:luv.application-agent

One agent owns one provider connection and one FIFO mailbox worker. ask only publishes a turn to that worker. Provider turns, tool settlement, observer callbacks, and provider close therefore never execute on the application's canvas thread or race one another on the provider socket.

define-conditionapplication-agent-released
error
agent:initarg:agent:readerreleased-application-agent
:report
lambda
conditionstream
formatstream"Application agent ~S is released."
released-application-agentcondition
define-conditionagent-observer-failure
warning
observer:initarg:observer:readeragent-observer-failure-observer
kind:initarg:kind:readeragent-observer-failure-kind
object:initarg:object:readeragent-observer-failure-object
cause:initarg:cause:readeragent-observer-failure-cause
:report
lambda
conditionstream
formatstream"Agent observer ~S failed during ~S: ~A"
agent-observer-failure-observercondition
agent-observer-failure-kindcondition
agent-observer-failure-causecondition
defclassapplication-agent
openai:agent
application:initarg:application:accessor%application-agent-application
%turns:initform'
:accessor%application-agent-turns
%current-turn:initformnil:accessor%application-agent-current-turn
handles:initform:readerapplication-agent-handles
observers:initform'
:accessorapplication-agent-observers
observer-failures:initform'
:accessor%application-agent-observer-failures
state:initform:open:accessor%application-agent-state
state-lock:initform
sb-thread:make-mutex:name"application agent state"
:readerapplication-agent-state-lock
worker-mailbox:initform
sb-concurrency:make-mailbox:name"application agent provider work"
:readerapplication-agent-worker-mailbox
worker-thread:initformnil:accessor%application-agent-worker-thread
worker-finished-p:initformnil:accessor%application-agent-worker-finished-p
close-thread:initformnil:accessor%application-agent-close-thread
close-finished-p:initformnil:accessor%application-agent-close-finished-p
close-error:initformnil:accessor%application-agent-close-error
close-ready:initform
sb-thread:make-waitqueue:name"application agent provider closed"
:readerapplication-agent-close-ready
turn-function:initarg:turn-function:initform#'openai:agent-turn:readerapplication-agent-turn-function
close-function:initarg:close-function:initform#'openai:close-agent:readerapplication-agent-close-function
close-failure-function:initarg:close-failure-function:initform#'warn:readerapplication-agent-close-failure-function
observer-failure-function:initarg:observer-failure-function:initform#'warn:readerapplication-agent-observer-failure-function
:documentation

Provider agent plus application owner, transcript, observers, and lifecycle.

defmethodinitialize-instance:after
unless
%application-agent-applicationagent
error":APPLICATION is required."
defvar*current-agent*nil"The APPLICATION-AGENT whose CLIM command is executing dynamically."
defunapplication-agent-state
agent
sb-thread:with-mutex
application-agent-state-lockagent
%application-agent-stateagent
defunapplication-agent-application
agent

Return agent's application while attached, or NIL after release.

sb-thread:with-mutex
application-agent-state-lockagent
%application-agent-applicationagent
defunapplication-agent-worker-thread
agent
sb-thread:with-mutex
application-agent-state-lockagent
%application-agent-worker-threadagent
defunapplication-agent-close-thread
agent
sb-thread:with-mutex
application-agent-state-lockagent
%application-agent-close-threadagent
defunapplication-agent-close-finished-p
agent
sb-thread:with-mutex
application-agent-state-lockagent
%application-agent-close-finished-pagent
defunapplication-agent-close-error
agent
sb-thread:with-mutex
application-agent-state-lockagent
%application-agent-close-erroragent
defunapplication-agent-release-finished-p
agent

Whether both agent's close request and FIFO worker have terminated.

sb-thread:with-mutex
application-agent-state-lockagent
and
%application-agent-close-finished-pagent
%application-agent-worker-finished-pagent
defunapplication-agent-turns
agent

Return a stable newest-first snapshot of agent's retained turns.

sb-thread:with-mutex
application-agent-state-lockagent
copy-list
%application-agent-turnsagent
defunapplication-agent-current-turn
agent
sb-thread:with-mutex
application-agent-state-lockagent
%application-agent-current-turnagent
defunapplication-agent-observer-failures
agent
sb-thread:with-mutex
application-agent-state-lockagent
copy-list
%application-agent-observer-failuresagent
defunadd-agent-observer

Attach function once while agent is open.

sb-thread:with-mutex
application-agent-state-lockagent
unless
eq:open
%application-agent-stateagent
pushnewfunction
application-agent-observersagent
:test#'eq
function
defunremove-agent-observer

Detach function if present; safe during notification and after release.

sb-thread:with-mutex
application-agent-state-lockagent
let
present-p
memberfunction
application-agent-observersagent
:test#'eq
setf
application-agent-observersagent
removefunction
application-agent-observersagent
:test#'eq
not
nullpresent-p
defunretain-observer-failure
agentfailure
sb-thread:with-mutex
application-agent-state-lockagent
when
eq:open
%application-agent-stateagent
pushfailure
%application-agent-observer-failuresagent
when
>
length
%application-agent-observer-failuresagent
+observer-failure-limit+
setf
cdr
nthcdr
%application-agent-observer-failuresagent
nil
defunnotify-agent-observers
agentkindobject

Notify an atomic observer snapshot, containing every failure independently.

let
observers
sb-thread:with-mutex
application-agent-state-lockagent
copy-list
application-agent-observersagent
dolist
observerobservers
handler-case
funcallobserveragentkindobject
error
cause
let
failure
make-condition'agent-observer-failure:observerobserver:kindkind:objectobject:causecause

Reporting is diagnostic, not another observer. A WARN handler that promotes warnings to errors (or any custom reporter that fails) must not prevent the remaining observers from running.

handler-case
funcall
application-agent-observer-failure-functionagent
failure
error
nil
object
defunnote-tool-call
agentcall
notify-agent-observersagent:call-startedcall
defunnote-tool-call-finished
agentcall
notify-agent-observersagent:call-finishedcall
defunevent-key-name
key
remove-if-not#'alphanumericp
stringkey
defunevent-value
eventkey
cdr
findkeyevent:key#'car:test
lambda
wantedactual
string-equal
defmethodopenai:handle-agent-event
let
when
andturn
listpevent
not
keywordp
firstevent
let
type
event-valueevent:type
when
stringptype
cond
membertype'
"response.reasoning_summary_text.delta""response.reasoning_text.delta"
:test#'string=
setf
turn-thoughtturn
concatenate'string
turn-thoughtturn
or
event-valueevent:delta
""
string=type"response.reasoning_summary_part.added"
unless
string=
turn-thoughtturn
""
setf
turn-thoughtturn
concatenate'string
turn-thoughtturn
string#\Newline
string#\Newline
string=type"response.output_text.delta"
setf
turn-textturn
concatenate'string
turn-textturn
or
event-valueevent:delta
""
defunrun-turn
agentturn

Run turn to completion in agent's sole provider-owning worker.

setf
turn-startedturn
get-internal-real-time
turn-statusturn
:thinking
handler-case
progn
sb-thread:with-mutex
application-agent-state-lockagent
unless
eq:open
%application-agent-stateagent
setf
%application-agent-current-turnagent
turn
notify-agent-observersagent:turn-startedturn
let
*handles*
application-agent-handlesagent
setf
turn-responseturn
funcall
application-agent-turn-functionagent
agent
turn-promptturn
let
text
and
turn-responseturn
openai:agent-response-text
turn-responseturn
when
andtext
>
lengthtext
length
turn-textturn
setf
turn-textturn
text
setf
turn-statusturn
:done
error
condition
setf
turn-errorturn
condition
turn-statusturn
:failed
notify-agent-observersagent:turn-finishedturn
turn
defunreject-released-turn
agentturn
setf
turn-startedturn
get-internal-real-time
turn-errorturn
make-condition'application-agent-released:agentagent
turn-statusturn
:failed
defunreport-application-agent-close-failure
agentcondition
handler-case
funcall
application-agent-close-failure-functionagent
condition
error
nil
defunclose-application-agent-in-lifecycle-thread
agent

Promptly initiate agent's terminal provider close, containing failures.

OPENAI:CLOSE-AGENT only publishes a WebSocket close frame. It is deliberately allowed to overlap the sole worker's active receive so the socket's :CLOSED event can wake NEXT-AGENT-EVENT. Ordinary provider turns remain confined to the FIFO worker, and this lifecycle path is created exactly once.

let
failurenil
handler-case
funcall
application-agent-close-functionagent
agent
error
condition
setffailurecondition
sb-thread:with-mutex
application-agent-state-lockagent
setf
%application-agent-close-erroragent
failure
%application-agent-close-finished-pagent
t
sb-thread:condition-broadcast
application-agent-close-readyagent
agent
defunapplication-agent-worker-loop
agent

Own agent's provider turns in mailbox FIFO order until release.

unwind-protect
loop
multiple-value-bind
messagereceived-p
sb-concurrency:receive-message
application-agent-worker-mailboxagent
declare
ignorereceived-p
ecase
firstmessage
:release
returnagent
sb-thread:with-mutex
application-agent-state-lockagent
setf
%application-agent-worker-finished-pagent
t
sb-thread:condition-broadcast
application-agent-close-readyagent
defunensure-application-agent-worker
agent

Return agent's sole worker. Caller holds agent's state lock.

let
worker
%application-agent-worker-threadagent
if
andworker
sb-thread:thread-alive-pworker
worker
setf
%application-agent-worker-finished-pagent
nil
%application-agent-worker-threadagent
sb-thread:make-thread:name"application agent provider worker"
defunask
text&keyagent

Publish one provider turn to agent's FIFO worker and return immediately.

unlessagent
error":AGENT is required by the application-neutral ASK."
check-typetextstring
let
turn
make-instance'turn:prompttext
sb-thread:with-mutex
application-agent-state-lockagent
unless
eq:open
%application-agent-stateagent
pushturn
%application-agent-turnsagent

Mailbox publication shares the state lock with transcript publication, so concurrent callers have one deterministic application order.

sb-concurrency:send-message
application-agent-worker-mailboxagent
list:turnturn
turn
defunask-and-wait
text&keyagent
timeout120
print-pt

ask and wait up to timeout seconds on the caller, never the canvas thread.

let
turn
asktext:agentagent
wait-for-turnturn:timeouttimeout
turn
defunwait-for-application-agent-release
agent&keytimeout

Wait for agent's close path and FIFO worker, returning NIL on timeout.

Release state and application detachment do not wait for this join point. A finite timeout therefore remains safe even when a provider-specific close function stalls or fails to wake its active receive.

let
deadline
andtimeout
+
get-internal-real-time
round
*timeoutinternal-time-units-per-second
sb-thread:with-mutex
application-agent-state-lockagent
loopuntil
and
%application-agent-close-finished-pagent
%application-agent-worker-finished-pagent
do
let
remaining
anddeadline
/
-deadline
get-internal-real-time
floatinternal-time-units-per-second1.0
when
andremaining
not
pluspremaining
unless
sb-thread:condition-wait
application-agent-close-readyagent
application-agent-state-lockagent
:timeoutremaining
agent
defunrelease-application-agent
agent

Detach agent and initiate its provider close exactly once.

Terminal state is visible before this function returns. A dedicated lifecycle thread promptly sends the provider's non-destructive close frame so an active receive can wake; it never executes on the caller or application canvas. The sole FIFO worker rejects queued turns and exits after the active turn settles. Concurrent callers return NIL; the teardown owner returns true. wait-for-application-agent-release is the explicit bounded join point.

let
release-pnil
startup-failurenil
sb-thread:with-mutex
application-agent-state-lockagent
when
eq:open
%application-agent-stateagent
setf
%application-agent-stateagent
:released
%application-agent-applicationagent
nil
%application-agent-current-turnagent
nil
application-agent-observersagent
'
%application-agent-observer-failuresagent
'
release-pt
sb-concurrency:send-message
application-agent-worker-mailboxagent
list:release
handler-case
setf
%application-agent-close-threadagent
sb-thread:make-thread:name"application agent provider closer"
error
condition
setfstartup-failurecondition
%application-agent-close-erroragent
condition
%application-agent-close-finished-pagent
t
sb-thread:condition-broadcast
application-agent-close-readyagent
whenrelease-p
release-handle-table
application-agent-handlesagent
whenstartup-failure
release-p