This is a thing of beauty, no matter what the general feeling in the comments seems to be against LISP. And yeah, you can do it in JS/TS/Python/etc., but somehow it doesn’t feel as elegant.
I like Lisp, I’ve used Common Lisp with a passion, but this doesn’t seem like a valid argument for Lisp.
Homoiconicity, as I understand, is that the code is structured data that is easy to programmatically modify, hence allowing Lisp macros. While some might disagree, I see Rust macros as the closest thing that demonstrates homoiconicity in mainstream Algol-based languages, as Rust macros modify the loosely structured token stream to produce new Rust code.
Eval, on the other hand, that’s more of a capability that comes from Lisp’s runtime, which used to be unique when Lisp was thriving, but not anymore — JS, Python, Ruby, all of the runtime-based languages have an eval function. The fact that they are not used as much is more of a security issue, not a capability issue, and I am not sure how having eval can be argued as Lisp being the language of agents.
> I see Rust macros as the closest thing that demonstrates homoiconicity in mainstream Algol-based languages
I don't know if you consider Elixir mainstream, but IMO their macro system is much closer to lisp's ideal.
Elixir is basically Lisp, but with better syntax, a modern ecosystem, and running on the Beam. Unlike languages like Rust, Elixir's conditionals and function definitions are just calls in the AST, even though the syntax looks mainstream and not like paren soup.
> Elixir is basically Lisp, but with better syntax
Which so helpfully removes many of the benefits of lisps :P I don't understand this argument at all, if it's not s-expressions nor quacks like a lisp, in what way is it lisp at all? Making it algol/C-like already makes it like the rest of the 99% of the languages, without any of the easy benefits of the neat and simple syntax of lisps.
A Lisp program that writes a Lisp program really just needs to produce a list of (nested lists) of tokens. A JavaScript program that writes a javascript program needs to generate a string that is syntactically valid JavaScript code. That is a much bigger task than just constructing a (nested) list.
Because Lisp syntax is so much simpler than that of JavaScript etc. it is much easier to avoid errors when generating code. In JavaScript you can use JSON to generate data, but JSON can not carry functions around.
I think this idea makes a lot sense. Instead of making the LLM generate JSON or XML, why not make it generate Lisp, which can carry both programs and data?
> A Lisp program that writes a Lisp program really just needs to produce a list of (nested lists) of tokens. A JavaScript program that writes a javascript program needs to generate a string that is syntactically valid JavaScript code. That is a much bigger task than just constructing a (nested) list.
> Because Lisp syntax is so much simpler than that of JavaScript etc. it is much easier to avoid errors when generating code. In JavaScript you can use JSON to generate data, but JSON can not carry functions around.
First of all, the LLM does not produce structured tokens, it produces (tokens of) text. It does not have a concept of nested or structured tokens. Which means that producing a Lisp program and a JavaScript program is basically the same difficulty, i.e. LLMs producing function foo () {} is about the same task as producing (defun foo () ()).
In fact, because most Lisps uses the same token ( and ) for almost all delimiters, the LLM in fact gets confused a lot more than Algol-family languages that uses various different tokens for different purposes. It generates thinking traces that are a few screens long while trying to count the closing parenthesis and the depth, something that I have not found to be the case for other languages, even with languages much more obscure than Lisp. (And no, it is not a training data issue, because the Lisp family as a whole is pretty well represented in the data set, due to Emacs Lisp.)
> I think this idea makes a lot sense. Instead of making the LLM generate JSON or XML, why not make it generate Lisp, which can carry both programs and data?
You do realize that all programming languages contains both programs and data, right? i.e. JSON is literally a subset of the JavaScript language, all JSON documents are valid JavaScript code, can be embedded in JavaScript programs, and so on. This isn't even a JavaScript-specific thing, almost all recent programming languages have data structure literals.
The thing that makes Lisp unique is that it can modify programs as data in the language easily, and why would that be a unique capability that would be beneficial for LLMs, when it can just sed/awk or tree-sitter-parse programs with more conventional languages and modify it as easily?
Maybe this is a reductive comment, but how does this differ from just letting your agent bash tool a `python -c` command (or anything of that class)? I'm not really getting where this is a "wow" moment?
It is always nice to appreciate how much power you get out of (Model + the absolute bare minimum of control flow). There is just so much baked into the models now that given an inch they will take a mile.
If you give your agent a `bash` or `python -c` tool, it starts a separate process that produces some output and then exits. After that, only the output and the exit code are available.
In contrast, `eval` runs the code in the same execution context as the agent loop. When `eval` finishes, that execution context still exists. For example, any functions defined during an `eval` call remain available for later use.
That's technically true but practically an agent can just save the script file/rerun it/write a tool that lets it call a reply with memory etc.This aspect is a bit more elegant when it's in the execution context, but the core of "you don't need to predefine tools, the agent can dynamically write its own code" is not exactly new - that's pretty much the basis of why Claude code and codex and all the other agent harnesses are so much more effective than old ways if working with llms - they can just write giant incomprehensible bash scripts on the fly to accomplish random things without having pre-defined tools, write state to files, etc
The blog is about writing an agent when you dont already have an agent, but only a plain LLM. It stitches the minimal pieces together. Agents dont need lots of supporting infra, so it is good to keep the code concise. Not a wow moment for sure, though some people think that agents and harnesses are complicated.
Right but given that the agent only outputs text, and agents are perfectly happy writing python, the supposed benefit of Lisp is completely irrelevant here.
> the supposed benefit of Lisp is completely irrelevant here
Yeah, it's a small example (it's in the title, "100 lines") so obviously doesn't highlight the best benefits once you reach larger codebase size.
Still think ~8 lines for the core loop is probably more elegant, readable and concise than you can achieve in other algol/C-like languages, but happy to be shown that I'm wrong :)
You're not wrong. I did a similar agent in lisp back with Sonnet 3.5 and had a wow moment, but the wow was mostly for seeing an agent working effectively at all at that point in time.
The part that killed it for me was losing everything if the lisp crashed (sonnet 3.5 was prone to doing that) and solving persistence had too many edge cases and confused the model.
Later realized that writing the agent as 20 lines of bash was equivalently powerful to the lisp agent, but made persistence trivial from the easy file system interop.
you can use LFE (Lisp flavored erlang) = lisp on BEAM runtime.
you get a snippet from LLM, compile it to module, and hot-load it into the running node. the module lives in the node's code table, so it persists and every other agent can call it. not just the one that wrote it.
the agents themselves are seaprate supervised processes, so if one crashes - e.g. because the snippet was crap, it doesn't take down whole system.
of course you can do that in just elixir too, the lisp is just cosmetics really.
By my eye it's not that different, it's riffing in it from a Lisp perspective.
It's pretty amazing to write your own agent BTW. I've got a zero-dependency all-in-one-file agent harness I wrote myself. I use it all the time now because I can get it from anywhere and I can know EXACTLY what it'll do (as much as you can with any model), what it's been told vs not. Using it as a harness for models I'm hosting myself makes me feel like some kind of LLM homesteader: it's a set of tools I'll always have that will only change as much as I want it to change.
Which realistically, is the only way to be productive with agents. If you aren't running them inside of something that limits their scope/impact/potential damage, you're already doing it wrong.
Most of those "permission"-systems are built on the idea that an LLM can decide what to ask for approval to run or not anyways, which obviously don't work out great in practice. Might as well give them blanket permission to do whatever, then put them in an isolated environment.
I'm not saying you should, I'm saying you shouldn't clutter up every thread with this type of useless comment. It just detracts from the people that do want to engage with it.
Similarly, why should I be bothered reading this kind of comment in each and every discussion thread?
What does it contribute?
I can read and discern this for myself, I can then stop reading or decide I don’t care.
Seriously, at some point all you “ai writing sleuths” should just get your own discussion thread together. It’s been months of this , we get it already.
(Not directly just at you, but anyone who feels the need to drop these comments in every thread)
is that wrong though? you write all your code with AI already, but you still need to supervise and steer it. same with blog posts - as long as you steer it towards converying your point, it doesn't matter the text itself was written by AI rather than typed by you, does it?
Homoiconicity, as I understand, is that the code is structured data that is easy to programmatically modify, hence allowing Lisp macros. While some might disagree, I see Rust macros as the closest thing that demonstrates homoiconicity in mainstream Algol-based languages, as Rust macros modify the loosely structured token stream to produce new Rust code.
Eval, on the other hand, that’s more of a capability that comes from Lisp’s runtime, which used to be unique when Lisp was thriving, but not anymore — JS, Python, Ruby, all of the runtime-based languages have an eval function. The fact that they are not used as much is more of a security issue, not a capability issue, and I am not sure how having eval can be argued as Lisp being the language of agents.
I don't know if you consider Elixir mainstream, but IMO their macro system is much closer to lisp's ideal.
Elixir is basically Lisp, but with better syntax, a modern ecosystem, and running on the Beam. Unlike languages like Rust, Elixir's conditionals and function definitions are just calls in the AST, even though the syntax looks mainstream and not like paren soup.
Which so helpfully removes many of the benefits of lisps :P I don't understand this argument at all, if it's not s-expressions nor quacks like a lisp, in what way is it lisp at all? Making it algol/C-like already makes it like the rest of the 99% of the languages, without any of the easy benefits of the neat and simple syntax of lisps.
Because Lisp syntax is so much simpler than that of JavaScript etc. it is much easier to avoid errors when generating code. In JavaScript you can use JSON to generate data, but JSON can not carry functions around.
I think this idea makes a lot sense. Instead of making the LLM generate JSON or XML, why not make it generate Lisp, which can carry both programs and data?
> Because Lisp syntax is so much simpler than that of JavaScript etc. it is much easier to avoid errors when generating code. In JavaScript you can use JSON to generate data, but JSON can not carry functions around.
First of all, the LLM does not produce structured tokens, it produces (tokens of) text. It does not have a concept of nested or structured tokens. Which means that producing a Lisp program and a JavaScript program is basically the same difficulty, i.e. LLMs producing function foo () {} is about the same task as producing (defun foo () ()).
In fact, because most Lisps uses the same token ( and ) for almost all delimiters, the LLM in fact gets confused a lot more than Algol-family languages that uses various different tokens for different purposes. It generates thinking traces that are a few screens long while trying to count the closing parenthesis and the depth, something that I have not found to be the case for other languages, even with languages much more obscure than Lisp. (And no, it is not a training data issue, because the Lisp family as a whole is pretty well represented in the data set, due to Emacs Lisp.)
> I think this idea makes a lot sense. Instead of making the LLM generate JSON or XML, why not make it generate Lisp, which can carry both programs and data?
You do realize that all programming languages contains both programs and data, right? i.e. JSON is literally a subset of the JavaScript language, all JSON documents are valid JavaScript code, can be embedded in JavaScript programs, and so on. This isn't even a JavaScript-specific thing, almost all recent programming languages have data structure literals.
The thing that makes Lisp unique is that it can modify programs as data in the language easily, and why would that be a unique capability that would be beneficial for LLMs, when it can just sed/awk or tree-sitter-parse programs with more conventional languages and modify it as easily?
It is always nice to appreciate how much power you get out of (Model + the absolute bare minimum of control flow). There is just so much baked into the models now that given an inch they will take a mile.
In contrast, `eval` runs the code in the same execution context as the agent loop. When `eval` finishes, that execution context still exists. For example, any functions defined during an `eval` call remain available for later use.
Yeah, it's a small example (it's in the title, "100 lines") so obviously doesn't highlight the best benefits once you reach larger codebase size.
Still think ~8 lines for the core loop is probably more elegant, readable and concise than you can achieve in other algol/C-like languages, but happy to be shown that I'm wrong :)
The part that killed it for me was losing everything if the lisp crashed (sonnet 3.5 was prone to doing that) and solving persistence had too many edge cases and confused the model.
Later realized that writing the agent as 20 lines of bash was equivalently powerful to the lisp agent, but made persistence trivial from the easy file system interop.
you get a snippet from LLM, compile it to module, and hot-load it into the running node. the module lives in the node's code table, so it persists and every other agent can call it. not just the one that wrote it.
the agents themselves are seaprate supervised processes, so if one crashes - e.g. because the snippet was crap, it doesn't take down whole system.
of course you can do that in just elixir too, the lisp is just cosmetics really.
It's pretty amazing to write your own agent BTW. I've got a zero-dependency all-in-one-file agent harness I wrote myself. I use it all the time now because I can get it from anywhere and I can know EXACTLY what it'll do (as much as you can with any model), what it's been told vs not. Using it as a harness for models I'm hosting myself makes me feel like some kind of LLM homesteader: it's a set of tools I'll always have that will only change as much as I want it to change.
Most of those "permission"-systems are built on the idea that an LLM can decide what to ask for approval to run or not anyways, which obviously don't work out great in practice. Might as well give them blanket permission to do whatever, then put them in an isolated environment.
What does it contribute? I can read and discern this for myself, I can then stop reading or decide I don’t care.
Seriously, at some point all you “ai writing sleuths” should just get your own discussion thread together. It’s been months of this , we get it already.
(Not directly just at you, but anyone who feels the need to drop these comments in every thread)