<?xml version="1.0"?>
<rss version="2.0">
<channel>
	<title>Planet Lisp</title>
	<link>http://planet.lisp.org/</link>
	<description>Planet Lisp</description>
	<language>en</language>


<item>
	<title>Joe Marshall: llambda.lisp on linux</title>
	<guid isPermaLink="true">http://funcall.blogspot.com/2026/08/llambdalisp-on-linux.html</guid>
	<link>http://funcall.blogspot.com/2026/08/llambdalisp-on-linux.html</link>
	
	<description>&lt;p&gt;A reader named Madhu sent me a patch for running &lt;a href="https://github.com/jrm-code-project/llambda"&gt;llambda.lisp&lt;/a&gt; under linux.  This patch uses mmap to pull the weights into the lisp address space outside the heap.&lt;/p&gt;
  
  &lt;p&gt;In addition, he tried to use a hugging face model that needed some default values, so he added them.&lt;/p&gt;

&lt;p&gt;He reports that he was able to get the model to do inference on his linux box with about an hour of hacking.  I have incorporated his patches and pushed the update to GitHub.&lt;/p&gt;</description>
	
	<pubDate>Sun, 09 Aug 2026 22:33:42 GMT</pubDate>
</item>

<item>
	<title>Joe Marshall: Why vibe code in Lisp?</title>
	<guid isPermaLink="true">http://funcall.blogspot.com/2026/08/why-vibe-code-in-lisp.html</guid>
	<link>http://funcall.blogspot.com/2026/08/why-vibe-code-in-lisp.html</link>
	
	<description>&lt;h1&gt;Why Target Common Lisp for Code Generation?&lt;/h1&gt;

&lt;p&gt;I've been asked twice now: if the generated code doesn't matter&amp;mdash;if the AI is doing the heavy lifting of writing the syntax&amp;mdash;why do I vibe code in Common Lisp?&lt;/p&gt;

&lt;p&gt;Why not target Python, TypeScript, or Java? These are mainstream languages with massive training sets. The models can generate code in them with a high degree of statistical accuracy. So why do I choose to target a niche language like Common Lisp for code generation?&lt;/p&gt;

&lt;p&gt;There are a lot of reasons, and they all come down to the same age-old question. Why use Lisp when you could use a more popular language? The answer is that language popularity is a poor proxy for utility and expressiveness. The Lisp community has long known this - it is why we chose Lisp in the first place. Selecting for popularity is what middle managers do to ensure that they can always find a warm body to maintain the code. It is not what elite hackers do.&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;&lt;strong&gt;The Baseline of Expertise&lt;/strong&gt; First, I have been programming in Common Lisp for decades. I know it intimately. Vibe coding requires a human architect to supervise the machine. When I look at the code generated by the model, I can tell in a fraction of a second whether it is any good, or if the model is hallucinating a dead-end. You cannot successfully orchestrate an AI in a language you don't deeply understand.&lt;/li&gt;

  &lt;li&gt;&lt;strong&gt;Abstraction over Implementation&lt;/strong&gt; Most modern languages force you to describe exactly &lt;em&gt;how&lt;/em&gt; a machine should shuffle bits around. Lisp was designed as a language for expressing high-level abstractions rather than expressing tedious implementation details. When I prompt the AI, I want it generating architectural logic, not fighting with boilerplate just to manage basic state.&lt;/li&gt;

  &lt;li&gt;&lt;strong&gt;Designed for the Elite&lt;/strong&gt; Let's be honest: Lisp is a language designed by and for elite hackers, not for the masses. It doesn't hold your hand, and it doesn't pander to lowest-common-denominator programming bootcamp patterns. When you use it as a target language, you are operating in an environment built for maximum expressiveness.&lt;/li&gt;

  &lt;li&gt;&lt;strong&gt;Homoiconicity and the AST&lt;/strong&gt; This is perhaps the biggest technical advantage. Lisp is homoiconic&amp;mdash;the code is structured as the data it manipulates. When an LLM generates Python or Java, it has to predict surface syntax: whitespace, brackets, semicolons, and rigid class structures. When an LLM generates Lisp, it is operating directly at the level of the Abstract Syntax Tree (AST). It is predicting pure structure. Removing the syntactic friction is a massive advantage for AI code generation.&lt;/li&gt;

  &lt;li&gt;&lt;strong&gt;Macros as Context Compression&lt;/strong&gt; In vibe coding, the LLM's context window is your most precious resource. Lisp's macro system allows for a highly effective form of context compression. Instead of the AI repeatedly generating verbose boilerplate, you can hide that boilerplate behind a macro. The AI learns the macro, uses it, and saves thousands of tokens, allowing you to maintain massive architectures within the model's memory constraints.&lt;/li&gt;

  &lt;li&gt;&lt;strong&gt;Introspection in the REPL&lt;/strong&gt; I do not operate the LLM in a sterile text editor. I operate it from within a Lisp REPL. This allows the LLM to introspect the program &lt;em&gt;while it is under development&lt;/em&gt;. If we need to know the state of a specific object or function, the model can query the live environment. You are not writing dead text; you are conversing with a living system.&lt;/li&gt;

  &lt;li&gt;&lt;strong&gt;Superior Error Handling&lt;/strong&gt; When the AI writes bad code (and it will), Lisp's condition system provides superior error handling and debugging facilities. Instead of a hard crash that requires a full reboot, the error is caught, and the LLM can analyze the stack trace and debug the generated code interactively, right at the point of failure.&lt;/li&gt;

  &lt;li&gt;&lt;strong&gt;No &lt;em&gt;Ab Initio&lt;/em&gt; Restarts&lt;/strong&gt; Using the REPL means you don't have to start your program &lt;em&gt;ab initio&lt;/em&gt; (from the beginning) every time you want to test a change. In a compiled, mainstream language, a one-line AI fix requires a full rebuild and state reset. In Lisp, you just redefine the specific function and immediately test it in the REPL while the rest of the application's state remains perfectly intact. The iteration speed is unmatched.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;You don't give an elite hacker a code monkey language. I want my AI to be an elite hacker, not simply a code monkey. If I expect my AI to work at an elite level, I should give it elite tools, not a code monkey language.&lt;/p&gt;</description>
	
	<pubDate>Thu, 06 Aug 2026 20:36:06 GMT</pubDate>
</item>

<item>
	<title>Joe Marshall: RFC 6238 in Common Lisp</title>
	<guid isPermaLink="true">http://funcall.blogspot.com/2026/08/rfc-6238-in-common-lisp.html</guid>
	<link>http://funcall.blogspot.com/2026/08/rfc-6238-in-common-lisp.html</link>
	
	<description>&lt;p&gt;I wanted to implement 2FA as per RFC 6238.  This is the Time-based
One-Time Password (TOTP) algorithm that is used by Google
Authenticator and other 2FA apps.  This was originally `vibe coded`.
The vibe coding got me 80% of the way there, and I made a manual pass
to turn it into a more functional style.&lt;/p&gt;

&lt;p&gt;Feel free to use this under an MIT license.&lt;/p&gt;

&lt;pre&gt;
;;; -*- mode: lisp; coding: utf-8-unix; -*-

;;; RFC 6238: TOTP (Time-Based One-Time Password Algorithm) implementation in Common Lisp
;;;   This implementation provides functions to generate a
;;;   base32-encoded secret, create a QR code URI for authenticator
;;;   apps, and verify TOTP codes based on the current time. It
;;;   adheres to the specifications outlined in RFC 6238 and RFC 4226.

;;; Dependencies: cl-base32, ironclad

(in-package "TOTP")

(defun generate-secret (&amp;amp;optional (length 10))
  (cl-base32:bytes-to-base32 (ironclad:random-data length)))

(defun generate-qr-uri (secret email &amp;amp;key (issuer "JRM-Code"))
  (format nil "otpauth://totp/~A:~A?secret=~A&amp;amp;issuer=~A" issuer email secret issuer))

(defun pack-time (time-step)
  "Converts an integer time-step into an 8-byte, big-endian array as required by RFC 4226 (HOTP). 
 Used to construct the message payload for the HMAC-SHA1 operation."
  (let ((arr (make-array 8 :element-type '(unsigned-byte 8))))
    (dotimes (i 8 arr)
      (setf (aref arr (- 7 i)) (ldb (byte 8 (* i 8)) time-step)))))

(defun universal-time-&amp;gt;unix-time (universal-time)
  (- universal-time 2208988800))

(defun universal-time-&amp;gt;time-step (universal-time)
  (floor (universal-time-&amp;gt;unix-time universal-time) 30))

(defun mac-&amp;gt;hash (mac)
  "Extracts a 6-digit TOTP code from a 20-byte HMAC-SHA1 result using dynamic truncation (RFC 4226).
 Takes the lower 4 bits of the final byte as an offset, extracts a 31-bit slice starting at that offset, 
 and returns the value modulo 1,000,000 to produce the final 6-digit integer."
  (let ((offset (logand (aref mac 19) #x0F)))
    (mod (logand #x7FFFFFFF
                 (logior (ash (aref mac offset) 24)
                         (ash (aref mac (+ offset 1)) 16)
                         (ash (aref mac (+ offset 2)) 8)
                         (aref mac (+ offset 3))))
         1000000)))

(defun mac-&amp;gt;hash-string (mac)
  (format nil "~6,'0D" (mac-&amp;gt;hash mac)))

(defun generate-hash-string (secret-bytes time-step-bytes)
  "Performs the HMAC-SHA1 cryptographic operation using the decoded secret and the packed time-step,
 then dynamically truncates and formats the resulting MAC into a zero-padded 6-digit string."
  (let ((hmac (ironclad:make-mac :hmac secret-bytes :sha1)))
    (ironclad:update-mac hmac time-step-bytes)
    (mac-&amp;gt;hash-string (ironclad:produce-mac hmac))))

(defun verify-totp (secret user-code &amp;amp;key (time (get-universal-time)) (window 1))
  "Verifies a user-provided 6-digit TOTP code against the base32 secret.
 Defaults to the current universal time. The :window keyword determines the allowable drift in 30-second steps
 (e.g., a window of 1 checks the previous, current, and next 30-second intervals).
 Returns T if the code matches within the window, otherwise NIL."
  (let ((secret-bytes (cl-base32:base32-to-bytes secret))
        (user-string (format nil "~6,'0D" (parse-integer (string user-code) :junk-allowed t)))
        (current-step (universal-time-&amp;gt;time-step time)))
    (do ((step (- current-step window) (1+ step))
         (limit (+ current-step window)))
        ((or (string= (generate-hash-string secret-bytes (pack-time step)) user-string)
             (&amp;gt; step limit))
         (not (&amp;gt; step limit))))))
&lt;/pre&gt;

&lt;p&gt;Get it at &lt;a href="https://github.com/jrm-code-project/totp/"&gt;http://github.com/jrm-code-project/totp/&lt;/a&gt;&lt;/p&gt;</description>
	
	<pubDate>Tue, 04 Aug 2026 12:14:58 GMT</pubDate>
</item>

<item>
	<title>Joe Marshall: Lisp-p</title>
	<guid isPermaLink="true">http://funcall.blogspot.com/2026/08/lisp-p.html</guid>
	<link>http://funcall.blogspot.com/2026/08/lisp-p.html</link>
	
	<description>&lt;p&gt;I needed a function that could tell whether a string was a valid
  Common Lisp program.  In theory, you could just call read on the
  string and see if it throws an error, but I don't want to throw
  random text at read.  It could contain a reader macro or something
  nasty.  It also would intern a ton of random symbols into the
  current package.  I wanted a function that would act mostly like the
  reader, but not CONS any data or intern any symbols.&lt;/p&gt;

&lt;p&gt;So I vibe coded a function that does just that.  It implements the
  reader algorithm as a state machine but does not actually read any
  data.  The state machine tracks the list and string delimeters and
  tokenizes the string, but it discards the tokens and does not intern
  any symbols.  It just checks that the state machine is in `top
  level' state at the end of the string.  If it returns NIL, the
  string is definitely going to cause an error if you try to read it.
  If it returns T, it does not guarantee that the string represents a
  valid Common Lisp program, but rather that it is not obvious that
  the reader will throw an immediate error.&lt;/p&gt;

&lt;p&gt;A curious edge case is that of an unpunctuated string.  The words
  in the string will read as a simple sequence of symbols, which is
  perfectly valid.&lt;/p&gt;

&lt;p&gt;The code is
  in &lt;a href="https://github.com/jrm-code-project/lisp-p/"&gt;lisp-p&lt;/a&gt;
  on GitHub.  You call the function &lt;code&gt;lisp-p&lt;/code&gt; with a string or a stream
  and it will return T or NIL.&lt;/p&gt;</description>
	
	<pubDate>Mon, 03 Aug 2026 00:10:39 GMT</pubDate>
</item>

<item>
	<title>Joe Marshall: Vibe Coding Reconsidered</title>
	<guid isPermaLink="true">http://funcall.blogspot.com/2026/07/vibe-coding-reconsidered.html</guid>
	<link>http://funcall.blogspot.com/2026/07/vibe-coding-reconsidered.html</link>
	
	<description>&lt;p&gt;A year ago, you couldn't vibe code in Lisp. Even the SOTA models
  had trouble balancing parentheses, and they'd hallucinate packages
  and symbols that didn't exist. A year makes a big difference in this
  field, and the latest models are capable of vibe coding moderately
  sized programs in syntactically correct Lisp.&lt;/p&gt;

&lt;p&gt;I have been experimenting with vibe coding in Common Lisp and I'm
  hooked. It is a blast. It is like having on hand a talented
  undergraduate who just took a Lisp course. If you give him
  small enough, focused tasks, he will churn out passable code. If
  you give him a good chunk of legacy code, he will churn out more
  code in the legacy style. The models are not good enough to do a
  full rewrite of a large codebase, but they are good enough to
  handle a small library with supervision.&lt;/p&gt;

&lt;p&gt;I find myself accepting a large amount of code with just a glance&amp;mdash;if
  it passes the Lisp reader, compiles, and the tests pass, I accept
  it. Unlike the code of a year ago, the generated code these days is
  far less buggy, and the models are pretty good at debugging their
  own code. I'll do spot checks on the code, but I don't bother
  reading it line by line unless I see something odd. If the model
  generates code in a style I don't like, I'll ask it to rewrite the
  code to be more to my liking.&lt;/p&gt;

&lt;p&gt;But frankly, you don't need to read the code at all. If there is a
  good test suite, the model will generate code that passes tests. If
  the code is functionally correct, it doesn't matter if the code is
  pretty. In one way, it doesn't matter if the code is easy for a
  human to read and maintain because we ask the model to maintain it.
  We treat the code as a black box and we constrain it to pass the
  tests. (We accept machine code largely unread.)&lt;/p&gt;

&lt;h2&gt;Failure Modes&lt;/h2&gt;

&lt;p&gt;By far the most common failure mode is the model getting the number
  of closing parentheses wrong. The tail end of a block of code is
  usually a bunch of closing parentheses, and the model will be
  tokenizing them in groups of 2 or 3. But the likelihood of the "))"
  token isn't very much different from the likelihood of the ")))"
  token, so the model will sometimes grab the wrong one.&lt;/p&gt;

&lt;p&gt;Depending on the model and the agent, when it tries to recover from
  the ensuing read error, it will re-compute the tokens in the output.
  It sometimes will thrash as it tries to balance parentheses,
  adding and removing them from various places in the code. (Sort of
  like a noob Lisp programmer.) Some models are more susceptible to
  this than others. I have found that the solution here
  is to pause the agent and manually fix the parentheses when the
  agent starts to thrash.&lt;/p&gt;

&lt;h3&gt;Vibe Coding Workflow&lt;/h3&gt;

&lt;p&gt;I've been using Copilot CLI and Gemini CLI to vibe code in Common
  Lisp. I start with a blank project directory and create an .asd
  file that loads the packages.lisp file and the main file for the
  project (which can start out as a "hello world"). Basically, make a
  minimal project that you can load with ASDF or Quicklisp.&lt;/p&gt;

&lt;p&gt;The models can work at moderate levels of abstraction, but they do
  better if there is existing code supporting the abstraction level,
  and this suggests a `bottom-up` approach to the problem rather than
  a `stratified` design. But the models are actually quite capable of
  starting at a moderate level of abstraction right from the get-go.&lt;/p&gt;

&lt;p&gt;So starting with a minimal project, I boot up the model and ask it
  to write the first things needed for the project&amp;mdash;some data
  structures, some utilities, a few tests. The very simple stuff that
  is easy for the model to do ab initio. Then I ask the model to
  write a minimal main function that will implement the basic
  functionality of the project&amp;mdash;a command loop, a server, what-have-you&amp;mdash;with
  stubs for everything. Once a framework is in place, the
  models are easily able to extend it.&lt;/p&gt;

&lt;p&gt;The agents will get into a loop of adding code, adding tests, and
  running all the tests. They will debug any test failures and only
  consider a task to be complete when all the tests pass.&lt;/p&gt;

&lt;p&gt;The model does not write great code, and you will accumulate
  technical debt if you accept it as is. But the model can write code
  that works and passes the tests. It is a good idea to pause during
  development and simply ask the model to find the technical debt in
  the code, enumerate it, and rank it in order of importance. Then
  you ask the model to address each item in turn and the model will
  clean up the code. After a couple of iterations of cleanup, the
  code will look no worse than what I've seen in many professional
  codebases.&lt;/p&gt;

&lt;p&gt;There are sort of two modes that you operate in: one is to modify
  the existing code (e.g. refactor) without disturbing the
  functionality; the other is to extend the functionality without
  disturbing the core operation. It is important to spend enough time
  refactoring and cleaning up. But the model is good at generating
  potential refactorings, and it is not good at knowing when to call
  it quits. It will happily churn away at your code making it
  `better' and doing more and more trivial refactorings. If you give
  the model one particular refactoring task and tell it to do just
  that one, it will do a good job.&lt;/p&gt;

&lt;p&gt;Refactoring is satisfying in a certain way, but adding features
  gives you more instant gratification. The models are good at adding
  features and extending existing code, especially if the feature
  shares any similarity with existing code.&lt;/p&gt;

&lt;p&gt;For more complex features and refactorings, tell the model that you
  want a 'plan' for the feature or refactoring. The model will come
  up with a multi-step plan, broken down into a series of tasks. The
  tasks in the plan are generally small enough to be handled by the
  model itself.&lt;/p&gt;

&lt;p&gt;The models are good enough to maintain a codebase, so once you
  have a project up and running, the model will generally choose file
  names and a directory structure that is appropriate to put in
  the .asd file. If you get the model started with a test suite, it will
  extend the tests as it extends functionality, or you can ask it to
  add specific tests.&lt;/p&gt;

&lt;p&gt;I have found that building a project by vibe coding it is an
  extremely rapid way to prototype. The model can churn out `obvious'
  code much faster than I can and it frees me up to think about the
  higher level design issues. I can build in a weekend what would
  have taken me a month before.&lt;/p&gt;</description>
	
	<pubDate>Mon, 27 Jul 2026 19:39:32 GMT</pubDate>
</item>

<item>
	<title>Joe Marshall: llambda.lisp</title>
	<guid isPermaLink="true">http://funcall.blogspot.com/2026/07/llambdalisp.html</guid>
	<link>http://funcall.blogspot.com/2026/07/llambdalisp.html</link>
	
	<description>&lt;p&gt;I wanted to run LLM models locally on my machine.  I discovered
  that llama.cpp is how people run models locally, and that the
  popular LLM servers like Ollama and lmstudio and unsloth use
  llama.cpp under the hood.&lt;/p&gt;

&lt;p&gt;llama.cpp is, of course, written in C++.  I don't care for C++ and
  I prefer Common Lisp.  With the appropriate declarations, Common
  Lisp code should be in the same performance ballpark as C++ code.
  So I decided to write a Common Lisp implementation of llama.cpp,
  which I call &lt;code&gt;llambda.lisp&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;It is available
  on &lt;a href="https://github.com/jrm-code-project/llambda"&gt;GitHub.com/jrm-code-project/llambda&lt;/a&gt;
  If you care to contribute, it could use routing for architectures
  other than gemma, GPU support, NPU support, and other features.&lt;/p&gt;</description>
	
	<pubDate>Sun, 12 Jul 2026 21:57:39 GMT</pubDate>
</item>

<item>
	<title>Eugene Zaikonnikov: The Net is Too Damn Fast</title>
	<guid isPermaLink="true">http://blog.funcall.org//lisp/2026/07/06/net-is-too-damn-fast/</guid>
	<link>http://blog.funcall.org//lisp/2026/07/06/net-is-too-damn-fast/</link>
	
	<description>&lt;p&gt;Recently I stumbled on a funny kind of race in distributed systems. I believe even the &lt;a href="https://en.wikipedia.org/wiki/Fallacies_of_distributed_computing"&gt;classic texts&lt;/a&gt; don't cover that.&lt;/p&gt;

&lt;p&gt;Say we have a system &lt;code class="language-plaintext highlighter-rouge"&gt;S&lt;/code&gt; sending commands to a receiver &lt;code class="language-plaintext highlighter-rouge"&gt;R&lt;/code&gt; over network. &lt;code class="language-plaintext highlighter-rouge"&gt;S&lt;/code&gt; maintains network outbox and inbox handled by separate threads. A command is expected to complete with certain result sent back before the deadline, or else &lt;code class="language-plaintext highlighter-rouge"&gt;S&lt;/code&gt; would assume a request timed out. Very basic so far.&lt;/p&gt;

&lt;p&gt;However a certain class of commands (and only it) was timing out. They would fail regularly on some networks, sporadically on others and seemingly never on some. Perplexingly they were really simple commands, amounting to little else than sending back a reading of &lt;code class="language-plaintext highlighter-rouge"&gt;R&lt;/code&gt;'s internal state. Increasing the command deadline had no apparent effect. Adding logging in places helped little and in fact often resulted in the issue disappearing.&lt;/p&gt;

&lt;p&gt;Simplifying it quite a bit the &lt;code class="language-plaintext highlighter-rouge"&gt;S&lt;/code&gt; side looked something like this:&lt;/p&gt;

&lt;figure class="highlight"&gt;&lt;pre&gt;&lt;code class="language-lisp"&gt;&lt;table class="rouge-table"&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class="gutter gl"&gt;&lt;pre class="lineno"&gt;1
2
3
4
5
6
7
8
9
10
11
12
13
14
&lt;/pre&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;pre&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;defun&lt;/span&gt; &lt;span class="nv"&gt;handle-outbox&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;socket&lt;/span&gt; &lt;span class="nv"&gt;queue&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nv"&gt;command&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;pop&lt;/span&gt; &lt;span class="nv"&gt;queue&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;handler-case&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;send-command&lt;/span&gt; &lt;span class="nv"&gt;socket&lt;/span&gt; &lt;span class="nv"&gt;command&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
      &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;network-error&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;c&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
	&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;log-network-error&lt;/span&gt; &lt;span class="nv"&gt;c&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
      &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:no-error&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;c&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
	&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;register-command-awaiting-response&lt;/span&gt; &lt;span class="nv"&gt;command&lt;/span&gt;&lt;span class="p"&gt;))))&lt;/span&gt;

&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;defun&lt;/span&gt; &lt;span class="nv"&gt;handle-inbox&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;socket&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;let*&lt;/span&gt; &lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nv"&gt;response&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;receive-incoming&lt;/span&gt; &lt;span class="nv"&gt;socket&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
	 &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;corresponding-command&lt;/span&gt;
	  &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;command-awaiting-response&lt;/span&gt; &lt;span class="nv"&gt;response&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;when&lt;/span&gt; &lt;span class="nv"&gt;corresponding-command&lt;/span&gt;
      &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;process-reply-for-command&lt;/span&gt; &lt;span class="nv"&gt;command&lt;/span&gt; &lt;span class="nv"&gt;response&lt;/span&gt;&lt;span class="p"&gt;))))&lt;/span&gt;
&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;You see now what was happening: &lt;code class="language-plaintext highlighter-rouge"&gt;S&lt;/code&gt; was sending the command, &lt;code class="language-plaintext highlighter-rouge"&gt;R&lt;/code&gt; processing it, sending the reply and &lt;code class="language-plaintext highlighter-rouge"&gt;S&lt;/code&gt; handling the reply &lt;em&gt;before&lt;/em&gt; the bookkeeping of outbox process would record the command. Then the response wouldn't match anything and be discarded as orphaned. Later the inbox would timeout the command (not shown).&lt;/p&gt;

&lt;p&gt;The bookkeeping wasn't even that heavyweight: just storing some structures and perhaps couple expensive calls to set up a condition variable but that was enough.&lt;/p&gt;

&lt;p&gt;Naturally any kind of latency (be it due to network load or extraneous syslog calls) would alleviate that. The fix was to make the code inelegant but correct by registering the command before the send attempt and un-registering in the event of failure.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;tl;dr a network can be way too fast&lt;/em&gt;&lt;/p&gt;</description>
	
	<pubDate>Mon, 06 Jul 2026 00:00:00 GMT</pubDate>
</item>

<item>
	<title>Joe Marshall: New chatbot</title>
	<guid isPermaLink="true">http://funcall.blogspot.com/2026/06/new-chatbot.html</guid>
	<link>http://funcall.blogspot.com/2026/06/new-chatbot.html</link>
	
	<description>&lt;p&gt;Lately I've been playing with writing a &lt;a href="http://github.com/jrm-code-project/chatbot"&gt;chatbot&lt;/a&gt; library in Common Lisp.&lt;/p&gt;

&lt;p&gt;My previous gemini bindings were getting unweildy.  I wanted to add the ability to run LLMs on my local machine but it turned out
  to be really kind of kludgy, so I decided to start from scratch with multiple back ends in mind.&lt;/p&gt;

&lt;p&gt;I've got it to the point where in supports multiple back ends, so now I can prompt local LLMs from Lisp.&lt;/p&gt;

&lt;p&gt;Recently I added the ability to recursively launch chatbots that can call each other.  Since the chatbots do not share 
  their contexts, this greatly reduces the context bloat of thet main chat because it can spawn off subtasks to a minion and not 
  pollute the main context.  This also allows you to create a federation of chatbots, each of which specializes in some topic and is overseen by a controlling chatbot that talks to the user.&lt;/p&gt;

&lt;p&gt;Chatbots can be serialized and checkpointed, so if one is carrying out an agentic task and Lisp crashes, when we restart
  the agentic tasks are restarted as well and pick up where they left off.&lt;/p&gt;

&lt;p&gt;IT turns out that recursive chats are a useful abstraction once you figure out how to use them.  Basically any prompt you may issue may also want to be issued by an llm and this enables that to happen.  It allows you to run subprocesses that would otherwise put junk in your context, for example reading the contents of a lange number of files.  If you put that into a rocursive chatbot, it could
  slurp up the files into its context without adding tokens to the parent chat.&lt;/p&gt;

&lt;p&gt;You can use a recursive chat as a `smart component'.  The recursive chat can have a specialized system instruction and can preload its context with relevant information specific to it.  It's context doesn't get diluted by the caller's context&lt;/p&gt;</description>
	
	<pubDate>Sun, 28 Jun 2026 22:52:33 GMT</pubDate>
</item>

<item>
	<title>Joe Marshall: Anecdote or data point</title>
	<guid isPermaLink="true">http://funcall.blogspot.com/2026/06/anecdote-or-data-point.html</guid>
	<link>http://funcall.blogspot.com/2026/06/anecdote-or-data-point.html</link>
	
	<description>&lt;p&gt;I saw that there was some argument over how much slower slot access is than struct access, so
I just decided to measure it naively.  I made a two slot sruct and a CLOS version of a CONS cell
with &lt;code&gt;car&lt;/code&gt; and &lt;code&gt;cdr&lt;/code&gt; slots
and I ran LTAK using regular lists, `lists' made from CLOS conses, and `lists' made from structs.
Here are the results:&lt;/p&gt;

&lt;pre&gt;
D:\repositories\clos-benchmark&amp;gt;sbcl --script run-benchmarks.lisp
Benchmark: ltak over native cons cells, CLOS my-cons nodes, and my-cons-struct nodes
Inputs: x=15 y=9 z=4 repeats=35

Scenario                   min-ms     mean-ms      max-ms      ratio
--------------------------------------------------------------------
native standard               0.129      0.146      0.186
clos standard                 1.346      1.365      1.475       9.37x
struct standard               0.172      0.175      0.179       1.20x
native optimized              0.068      0.069      0.073
clos optimized                0.411      0.414      0.419       6.04x
struct optimized              0.068      0.069      0.073       1.01x&lt;/pre&gt;

&lt;p&gt;In this naive use case, structs are same as native cons cells, but CLOS objects are one ninth the speed of a struct or cons cell if you just use it unoptimized, and one sixth the speed if optimizations are turned on.&lt;/p&gt;
  
&lt;p&gt;But the CLOS instance is more functional than the cons cell in mimics.  For instance, I could add a slot to the class and all the instances would be lazily updated with the new slot.  I can also subclass the CLOS class and the selector functions will continue to work.  Finally, I can redefine the CLOS closs while I'm developing it and all the instances will be uppdated.
  THe machinery to keep all this running is costing us our factor of 9.&lt;/p&gt;
  
  &lt;p&gt;But this might be worth the cost if we are running on a network where the bulk of the time will be transmitting the answer down the pipe once it is computed.  Taking a few extra milliseconds to compute the answer might be worth the convenience features of CLOS.&lt;/p&gt;</description>
	
	<pubDate>Thu, 25 Jun 2026 16:11:51 GMT</pubDate>
</item>

<item>
	<title>Joe Marshall: CLRHack: Meta-object Protocol</title>
	<guid isPermaLink="true">http://funcall.blogspot.com/2026/05/clrhack-meta-object-protocol.html</guid>
	<link>http://funcall.blogspot.com/2026/05/clrhack-meta-object-protocol.html</link>
	
	<description>&lt;h3&gt;Metaobject Protocol (MOP) Implementation in CLRHack&lt;/h3&gt;

  &lt;p&gt;The Metaobject Protocol in CLRHack is a high-performance implementation of the Common Lisp Object System (CLOS) integrated into
  the .NET 8.0 Common Language Runtime (CLR). It provides a complete meta-compilation pipeline that bridges the gap between dynamic
  Lisp semantics and the static CIL (Common Intermediate Language) execution model.&lt;/p&gt;

  &lt;h4&gt;Core Architecture&lt;/h4&gt;
  &lt;p&gt;The MOP is implemented through three primary layers:&lt;/p&gt;
  &lt;ol&gt;
      &lt;li&gt;&lt;strong&gt;The Metaobject Hierarchy (C#):&lt;/strong&gt; A set of foundational classes in &lt;code&gt;LispBase&lt;/code&gt; representing
  classes, methods, generic functions, and slot definitions.&lt;/li&gt;
      &lt;li&gt;&lt;strong&gt;The Runtime Engine (&lt;code&gt;MopRuntime&lt;/code&gt;):&lt;/strong&gt; A centralized orchestrator that manages class finalization,
  method combination, dispatch caching, and instance allocation.&lt;/li&gt;
      &lt;li&gt;&lt;strong&gt;The Compiler Bridge (Lisp):&lt;/strong&gt; Transformations in &lt;code&gt;ast.lisp&lt;/code&gt; that translate high-level CLOS forms
  (&lt;code&gt;defclass&lt;/code&gt;, &lt;code&gt;defmethod&lt;/code&gt;) into optimized runtime calls.&lt;/li&gt;
  &lt;/ol&gt;

  &lt;h4&gt;Instance Representation&lt;/h4&gt;
  &lt;p&gt;Because the CLR type system is strictly single-inheritance and statically defined, CLRHack decouples Lisp-level inheritance from
  C# inheritance. All CLOS instances are represented by the &lt;code&gt;StandardObjectInstance&lt;/code&gt; class, which contains:&lt;/p&gt;
  &lt;ul&gt;
      &lt;li&gt;A reference to its &lt;code&gt;ClassMetaobject&lt;/code&gt;.&lt;/li&gt;
      &lt;li&gt;A private &lt;code&gt;object[] storage&lt;/code&gt; array for instance slots, indexed by locations calculated during class
  finalization.&lt;/li&gt;
  &lt;/ul&gt;

  &lt;h4&gt;The Dispatch Pipeline&lt;/h4&gt;
  &lt;p&gt;Generic function invocation is the most complex part of the implementation. When a generic function is called:&lt;/p&gt;
  &lt;ol&gt;
      &lt;li&gt;&lt;strong&gt;Cache Lookup:&lt;/strong&gt; The &lt;code&gt;DiscriminatingFunction&lt;/code&gt; first checks a thread-safe
  &lt;code&gt;dispatchCache&lt;/code&gt; using an &lt;code&gt;InvocationCacheKey&lt;/code&gt; (a stack-allocated &lt;code&gt;struct&lt;/code&gt;) to find a previously
  computed effective method.&lt;/li&gt;
      &lt;li&gt;&lt;strong&gt;Applicability &amp; Precedence:&lt;/strong&gt; If the cache misses, the runtime computes all applicable methods and sorts
  them based on specializer specificity and the Class Precedence List (CPL).&lt;/li&gt;
      &lt;li&gt;&lt;strong&gt;Method Combination:&lt;/strong&gt; The &lt;code&gt;ComputeEffectiveMethod&lt;/code&gt; logic builds a nested execution chain
  following the &lt;strong&gt;Standard Method Combination&lt;/strong&gt; rules:
          &lt;ul&gt;
              &lt;li&gt;&lt;code&gt;:around&lt;/code&gt; methods are called first, with &lt;code&gt;call-next-method&lt;/code&gt; progressing to the next around
  method or the main chain.&lt;/li&gt;
              &lt;li&gt;The main chain executes all &lt;code&gt;:before&lt;/code&gt; methods, the primary method, and finally all &lt;code&gt;:after&lt;/code&gt;
  methods in reverse order.&lt;/li&gt;
          &lt;/ul&gt;
      &lt;/li&gt;
      &lt;li&gt;&lt;strong&gt;Fast Invocation:&lt;/strong&gt; The resulting effective method is compiled into a &lt;code&gt;Func&amp;lt;object[],
  object&amp;gt;&lt;/code&gt; that uses direct delegate invocation to minimize overhead.&lt;/li&gt;
  &lt;/ol&gt;

  &lt;h4&gt;Challenges and Solutions&lt;/h4&gt;

  &lt;h5&gt;1. Thread-Safe Non-Local Exits (call-next-method)&lt;/h5&gt;
  &lt;p&gt;&lt;strong&gt;Challenge:&lt;/strong&gt; &lt;code&gt;call-next-method&lt;/code&gt; and &lt;code&gt;next-method-p&lt;/code&gt; require access to the current
  invocation's state (the remaining methods and original arguments). Passing this state through every function call would break
  compatibility with standard Lisp function signatures.&lt;/p&gt;
  &lt;p&gt;&lt;strong&gt;Solution:&lt;/strong&gt; CLRHack utilizes &lt;code&gt;[ThreadStatic]&lt;/code&gt; fields in &lt;code&gt;MopRuntime&lt;/code&gt; to store the
  &lt;code&gt;currentNextMethods&lt;/code&gt; and &lt;code&gt;currentArguments&lt;/code&gt;. This ensures that even in highly concurrent environments (like a
  web server), each OS thread has its own isolated invocation context, allowing &lt;code&gt;call-next-method&lt;/code&gt; to function correctly
  without state leakage.&lt;/p&gt;

  &lt;h5&gt;2. Forward References and Lazy Finalization&lt;/h5&gt;
  &lt;p&gt;&lt;strong&gt;Challenge:&lt;/strong&gt; Lisp allows classes to refer to superclasses that haven't been defined yet. The runtime must handle
  these "zombie" classes without crashing the JIT compiler.&lt;/p&gt;
  &lt;p&gt;&lt;strong&gt;Solution:&lt;/strong&gt; The system implements a &lt;code&gt;ForwardReferencedClassMetaobject&lt;/code&gt;. When a class is defined, it is
  automatically finalized (computing its CPL and slot layout). If a superclass is missing, a forward reference is created. The
  &lt;code&gt;EnsureFinalized&lt;/code&gt; protocol ensures that inheritance is resolved and slot locations are assigned the moment the class is
  first instantiated or used in dispatch.&lt;/p&gt;

  &lt;h5&gt;3. Performance Overhead of the "MOP Bridge"&lt;/h5&gt;
  &lt;p&gt;&lt;strong&gt;Challenge:&lt;/strong&gt; A naive implementation of &lt;code&gt;slot-value&lt;/code&gt; or generic dispatch using C# reflection or linear
  searches is orders of magnitude slower than native C# member access.&lt;/p&gt;
  &lt;p&gt;&lt;strong&gt;Solution:&lt;/strong&gt; Three distinct optimizations were applied:
      &lt;ul&gt;
          &lt;li&gt;&lt;strong&gt;O(1) Slot Access:&lt;/strong&gt; Each &lt;code&gt;ClassMetaobject&lt;/code&gt; maintains a &lt;code&gt;SlotDictionary&lt;/code&gt;. Slot
  names are mapped to physical array indices during finalization, allowing &lt;code&gt;slot-value&lt;/code&gt; to perform a direct array access
  after a single dictionary lookup.&lt;/li&gt;
          &lt;li&gt;&lt;strong&gt;Compiler Primitives:&lt;/strong&gt; The compiler identifies &lt;code&gt;SLOT-VALUE&lt;/code&gt; and &lt;code&gt;MAKE-INSTANCE&lt;/code&gt;
  calls and emits direct CIL &lt;code&gt;call&lt;/code&gt; instructions to optimized &lt;code&gt;Lisp.MopRuntime&lt;/code&gt; methods, bypassing the general
  &lt;code&gt;Funcall&lt;/code&gt; path.&lt;/li&gt;
          &lt;li&gt;&lt;strong&gt;Zero-Allocation Cache Hits:&lt;/strong&gt; By making &lt;code&gt;InvocationCacheKey&lt;/code&gt; a &lt;code&gt;readonly struct&lt;/code&gt;
  and avoiding the cloning of the argument array during cache probes, the hot-path for generic function dispatch generates zero
  garbage for the .NET Collector.&lt;/li&gt;
      &lt;/ul&gt;
  &lt;/p&gt;

  &lt;h5&gt;4. Bootstrapping the COMMON-LISP Package&lt;/h5&gt;
  &lt;p&gt;&lt;strong&gt;Challenge:&lt;/strong&gt; Core CLOS functions like &lt;code&gt;make-instance&lt;/code&gt; must be available as symbols in the
  &lt;code&gt;COMMON-LISP&lt;/code&gt; package before user code runs, but they rely on the MOP runtime being fully initialized.&lt;/p&gt;
  &lt;p&gt;&lt;strong&gt;Solution:&lt;/strong&gt; A &lt;code&gt;MopRuntime.Initialize()&lt;/code&gt; method is injected into the entry point (&lt;code&gt;Main&lt;/code&gt;) of
  every generated assembly. This method interns the necessary symbols and binds them to &lt;code&gt;GenericFunctionClosureAdapter&lt;/code&gt;
  objects, ensuring that the MOP is "alive" before the first line of Lisp code executes.&lt;/p&gt;

&lt;hr /&gt;

&lt;p&gt;Vibe coding the MOP basically involved feeding chapters 4 and 5 of the &lt;i&gt;Art of the Meta-Object Protocol&lt;/i&gt; into the LLM and telling it to make an implementation plan.  It came up with a twenty-step plan to bootstrap CLOS.  I then spent the rest of the day instructing an agent to take on each task of the twenty-step plan in sequential order.  At the end of the day, I had a working MOP&lt;/p&gt;

&lt;p&gt;This is the end of my series of posts on CLRHack.&lt;/p&gt;</description>
	
	<pubDate>Sun, 31 May 2026 07:00:00 GMT</pubDate>
</item>


</channel>
</rss>
