We see it working in eev emacs with agent 1 similar to the multiagent article this week:

and then over in agent 2:

The agent enqueueing an action that gets sent to itself a bit later:

Early on, the leonardo system used a lisp webserver (Franz’ enterprise lisp aserve) web interfaces, however I have rehomed it solidly in emacs, the popular lisp editor tradition. We need some kind of clock outside of the software individual itself basically to propagate its remus kernel agent forward through time which was previously associated with the webserver.
Food in plants travels through living seive tube cells. Seive tubes are so specialised into food flow that they have sacrificed the ability to care for themselves. So there is a second kind of cell called a companion cell whose concern is just maintaining one seive tube cell while it controls food flow. In this metaphor food is knowledge, the seive tube cells are leonardo software-individual lisp images, and I am introducing companion cell lisp images that are dedicated to caring for the software-individual they are paired with.
We just dealt with swapping between active agents in one continuously running lisp image software-individual. However in that example, the leonardo software-individual propagates in time with each repeated press of the F8 key (emacs eev). I believe that computer programs should work like people using computer programs so it is natural to introduce a companion cell lisp image that literally just pushes the F8 key over and over like a human does.
Let us stick to the example of the multi-agent continuous-lisp-image leonardo system software individual from a moment ago for the leonardo system, then we will just start a second lisp image that cares for that one. The initial duty of care is to feed a never-ending todo-list of knowledge activities into it, in the way that a human brain feeds a never-ending todo list of conscious activities to the rest of the body. What exactly the todo-list is like will develop later; we need a method of feeding it at all first. I want to highlight using an embeddable common lisp program that is actually outside of emacs for this.
Denotationally I am just going to present starting the program as done from inside emacs. (I wrote McCLIM out of this article as a side direction).
• (eepitch-shell)
ecl
(asdf:load-system :mcclim)
(in-package :clim-user)
(load "~/.emacs.d/slime/start-swank.lisp")
• (slime-connect "localhost" 4005)
• (setq eepitch-buffer-name "*slime-repl ECL*")
Remembering that in the leonardo system base setup we are assuming, your leonardo software-individual is in *slime-repl clisp* (and started from inside emacs).
For a lot of reasons I want the embeddable common lisp to view its actions with respect to the software-individual clisp image as submitting common lisp interface manager commands for execution by an application frame.
A further level of indirection is that I actually want the just-push-F8 interaction to be modulated by emacs-server, so the *slime-repl ECL* considers its slime-connection to emacs (if it has one) to be about itself, and is asyncronously farming leonardo software individual (modulated by emacs eev eepitch) out to emacsclient to submit to the emacs-server.
Okay full disclosure the function turned out not to be trivial. Emacs is single-threaded by design, so that there is no question of something else happening halfway through one elisp invocation. Since the setup steps are deliberately smeared out to human-like lengths of time, emacs basically stops for a couple seconds, and resumes with the software individual outputing the sequence of actions’ outputs.
• (server-start)
(defclass leonardo-companion ()
((agent :initarg :agent
:accessor agent)
(repl-name :initarg :repl-name
:accessor repl-name)
(indiv-path :initarg :indiv-path
:accessor indiv-path)))
(defmethod emacsclient-pitch ((obj leonardo-companion)
soact-strings
&key (long-wait 1))
(uiop:run-program
(let ((*print-pretty* nil) (*print-case* :downcase))
(format nil "emacsclient -e '(progn ~@{~s~^ ~})'"
`(setq eepitch-buffer-name ,(repl-name obj))
`(sleep-for ,long-wait)
`(eepitch-line
,(format nil ".(uiop:chdir ~s)"
(indiv-path obj)))
`(sleep-for ,long-wait)
`(eepitch-line
,(format nil "~
.(setf (get *my-id* `leoname) `~a~
(get *my-id* `self-location) \"../../../~a/\")"
(agent obj) (agent obj)))
`(sleep-for ,long-wait)
`(eepitch-line ".(load-ef `kb-catal)")
`(sleep-for ,long-wait)
`(eepitch-line ,(format nil "soact ~{~a~^ ~}"
soact-strings))
`(sleep-for ,long-wait)))
:wait nil))
;;;
"demus"
"*slime-repl clisp*"
"~/leocommunity/saturn/demus/Process/main/"
(make-instance 'leonardo-companion :agent *** :repl-name ** :indiv-path *)
(defparameter *agent-1* *)
(emacsclient-pitch *agent-1*
'("[show hello-world]" "[show goodbye-cruel-world]"))
;;;
It is a bit of a mouthful because I need emacs to do this all at once (i.e. one elisp progn) and I wanted the loose safety to come from emulating a human waiting to see what happens between commands (the long waits).
Then, I figure that we should always try and guarantee we are in the right agent (like how print prints a line before not after). Since we are potentially always changing agents, if I want to load or save an entityfile/knowledgebase, I need in general to be performing a sequence of actions setking the right knowledgebase, then the action respecting that knowledgebase (soact [setk my-kb] [loadfil in-my-kb]).
• (setq eepitch-buffer-name "*slime-repl ECL*")
• (setq *unique-leonardo-queue* '(("[show hello]")))
;;;
(loop :with agent = *agent-1*
:for action-list :=
(swank:eval-in-emacs '(pop *unique-leonardo-queue*))
:when action-list :do
(case (car action-list)
(:exit (return))
(:change (psetf (agent agent)
(get (cdr action-list) :agent)
(indiv-path agent)
(get (cdr action-list) :indiv-path)
(repl-name agent)
(get (cdr action-list) :repl-name)))
(t (emacsclient-pitch agent action-list)))
(sleep 60))
Since the lisp image is outside of emacs, it is fine to tie up the lisp image, though your slime probably handles threading it for you unless you send it to the repl for transparency. I guess I should document it a bit
*unique-leonardo-queue* is popped.car is :exit, return-from the loopcar is :change, update :agent :indiv-path :repl-name to the plist in its cdr.("[crek foo-kb]" "[loadk foo-kb]" "[setk foo-kb]" "[show [in foo-kb]]")My reasoning is that the software-individual is only one step away from emacs, so it will be straightforward for the software-individual itself to have an action that controls what actions are enqueued in its future action queue (we will have articles about this later). emacs is also only one step away from the ECL repl companion cell so it is straightforward for it to retrieve as well. Conceivably, priority requests (from user interactions?) could be pushed to the emacs list queue from the human in the emacs as well.
In light of Pitman’s recollection on the show (you attend/listen/watch, right.) about the tens of thousands of dollars of maintenance cost per customer just to keep the software that was working last month still working this month with compatibility-breaking upgrades,
I got hung up in my own space on the fact that I do seem to write a program only to situationally reproduce the same program more or less from memory every time I encounter a similar situation, when I would actually like any particular problem to stay solved once I have solved it once.
Of course better yet would be for problems I have previously solved to pre-empt new problems I may yet encounter, and head them off at the pass. I.e. I want my good old-fashioned software to cut me out of the loop when I am not important.
This and the leonardo system is a step towards that.
Don’t forget https://emacsconf.org/2025 is this weekend! Yes, you, right now! (If you live in the future, you can watch the talks in hindsight).
screwlisp proposes kittens