182 comments

[ 4.8 ms ] story [ 248 ms ] thread
> concurrency

by that logic Elixir is even better for agents.

also the link at the bottom of the page is pretty much why I ditched Go: https://go.dev/blog/error-syntax

The AI landscape moves so fast, and this conservative, backwards looking mindset of the new Go dev team doesn't match the forward looking LLM engineering mindset.

I don't use either Go or LLMs, but isn't the point of LLMs that they write the tedious boilerplate for you? What's the value in a small syntactic improvement if the computer is generating it all anyway?
Elixir's concurrency model is fundamentally different than Go's; it's not just syntax difference.
by your logic, only considering part of the argument is as good as considering the entire argument.
Well elixir doesn't produce goroutines (managed threads), they produce "lightweight processes" which have isolated memory. These are more expensive to grow and aren't as easy to share data between one another, although they're much more fault tolerant as a result. It could be better however the underlying concurrency model in Elixir is relatively unique
It's been a while since I was in the weeds on this, but if I remember correctly they're strictly speaking mostly isolated. Binaries above a certain size share storage between processes, so moving big blobs between processes is cheap.
They also have their own system to share data between processes, although I haven't used it. Generally though it's a unique tool that's not always interchangeable with Go
> also the link at the bottom of the page is pretty much why I ditched Go: https://go.dev/blog/error-syntax

Funnily, it's also one of the reasons I stay with Go.

Error handling is the most contraversial Go topic, with half the people saying it's terrible and needs a new syntax, and half saying it's perfect and adding any more syntax will ruin it.

Absolutely!

Elixir's lightweight processes and distribution story make it ideal for orchestration, and that includes orchestrating LLMs.

Shameless plug, but that's what many people have been using Oban Pro's Workflows for recently, and something we demonstrated in our "Cascading Workflows" article: https://oban.pro/articles/weaving-stories-with-cascading-wor...

Unlike hatchet, it actually runs locally, in your own application as well.

If it had a larger learning base, quite possibly.

Erlang possibly even more so. The argument that pure code is generally safer to vibe code is compelling to me. (Elixir's purity is rather complicated to describe, Erlang's much more obvious and clear.) It's easier to analyze that this bit of code doesn't reach out and break something else along the way.

Though it would be nice to have a language popular enough for the LLMs to work well on, that was pure, but that was also fast. At the moment writing in pure code means taking a fairly substantial performance hit, and I'm not talking about the O(n log n) algorithm slowdowns, I mean just normal performance.

Elixir is great for agents.
This makes me want to build agents in Elixir now
As a functionally-code-illiterate-vibe-coder, I can confirm that LLMs are good at writing Go code.
As a highly-literate developer with almost 30 years of experience, I can also confirm that LLMs are very good at writing Go.
"The key point here is our programmers are Googlers, they’re not researchers. They’re typically, fairly young, fresh out of school, probably learned Java, maybe learned C or C++, probably learned Python. They’re not capable of understanding a brilliant language but we want to use them to build good software. So, the language that we give them has to be easy for them to understand and easy to adopt." – Rob Pike

This fits LLMs pretty well too it seems!

That's my assumption too: it's a simple language with very good tooling that's used by reasonably serious projects with good code quality. As a result the LLMs have both high quality training data and the output is easier to get right.
AI engineers will literally invent a new universe before they touch JavaScript.

The death knell for variety in AI languages was when Google rug-pulled TensorFlow for Swift.

Why is JS particularly good for agents?
Because it integrates great with browsers and people know the language already for node.js and the packages in npm can work for both?
A uniform language and ecosystem has been the siren song of JS for over a decade and I've yet to see it work out in any meaningful way.

Use whatever you like.

I mean, what else do you use to run things in the browser?

Pouchdb. Hypercore (pear). It’s nice to be able to spin up JS versions of things and have them “just work” in the most widely deployed platform in the world.

TensorflowJS was awesome for years, with things like blazeface, readyplayer me avatars and hallway tile and other models working in realtime at the edge. Before chatgpt was even conceived. What’s your solution, transpile Go into wasm?

Agents can work in people’s browsers as well as node.js around the world. Being inside a browser gives a great sandbox, and it’s private on the person’s own machine too.

This was possible years ago: https://www.youtube.com/watch?v=CpSzT_c7_UI&t=10m30s

> what else do you use to run things in the browser?

I do my best to run as little in the browser as possible. Everything is an order of magnitude simpler and faster to build if you do the bulk of things on a server in a language of your choice and render to the browser as necessary.

I was wondering if there was something particular about AI, but that's just the standard reason people give to use JS for anything.
The same reason it’s good for web servers. It excels at even-driven applications.
This is a specious argument. It's event-driven because it has callbacks?
“Specious” meaning what exactly?
I'd say TypeScript is currently the best choice for agents. For one, MCP tooling is really solid, the language itself is easy, fast to develop in, and not esoteric.
Avoiding JavaScript like the plague that it is, is not unique to AI engineers.

-Someone who has written a ton of JS over the past... almost 30 years now.

Choosing Python over JavaScript is one of the more perplexing decisions I've seen.
It's not so perplexing when you understand that Python has long had the best ecosystem of libraries for data science and ML, from which the current wave of AI stuff was born. There are plenty of reasons to dunk on Python, but the reality is lots of people were getting real work done with it in the run up to where we are today.
There are choices at multiple levels.

Yes, today’s ML engineer has practically no choice but to use Python, in a variety of settings, if they want to be able to work with others, access the labor market without it being an uphill battle, and most especially if they want to study AI / ML at a university.

But there were also the choices to initially build out that ecosystem in Python and to always teach AI / ML in Python. They made sense logistically, since universities largely only teach Python, so it was a lowest-common-denominator language that allowed the universities to give AI / ML research opportunities to everyone, with absolutely no gatekeeping and with a steadfast spirit of friendly inclusion (sorry, couldn’t resist the sarcastic tangent). I can’t blame them for working with what they had.

But now that the techniques have grown up and graduated to form multibillion-dollar companies, I’m hopeful that industry will take up the mantle to develop an ecosystem that’s better suited for production and for modern software engineering.

How good is JS interop with C/C++/BLAS? That's the basic stepping stone, I think. If you cannot make something in JavaScript that can compete with numpy there's little chance that things will change anytime soon.
I don’t know the details as specifically, since I haven’t been able to justify investing my efforts in the non-flagship ecosystem within the TensorFlow project after it previously added its Swift version to the Google Graveyard, but TensorFlow.js is doing something in this direction for the Node.js version. This info is at: https://www.tensorflow.org/js/guide/nodejs

“Like the CPU package, the module is accelerated by the TensorFlow C binary. But the GPU package runs tensor operations on the GPU with CUDA.”

They note that these operations are synchronous, so using them will sacrifice some of JavaScript’s effectiveness at asynchronous event processing. This is not different from Python when you are training or serving a model. JavaScript’s strengths would shine brighter when coordinating agents / building systems that coordinate models.

When it comes to modern Python, the only thing that can make it not production-ready is it being slow. Given that people in machine learning are using Python as a glue language for AI/ML libraries, this negligibly impacts their workflow.
Oh yeah. Personally I also try to avoid Python but as the rest of this thread covers it's pretty deeply rooted in ML/AI so I think we're stuck with it - at least for a while.
JavaScript has known issues with math. It should be avoided for that reason alone, no?
This is the way.

JS is a terrible language to begin with, and bringing it to the backend was a mistake. TS doesn’t change the fact that the underlying language is still a pile of crap.

So, like many, I’ll write anything—Go, Rust, Python, Ruby, Elixir, F#—before touching JS or TS with a ten-foot pole.

You think Python is a better language than TS?
(comment deleted)
Anything at this point.
Python is terrible in the age of LLMs because type checking doesn't work properly.
Python is basically the only language that’s used to train the models.

Sure, the libs are mostly written in C/C++, but all of them have first-class support for Python and oftentimes Python only. Serving the model is a different story and you can use whatever language to do so.

As someone who has worked in the DS realm for an extended period of time, I can tell you Python has practically zero competition when it comes to data wrangling and training models. There are plenty of contenders when it comes to serving the models or building “agents.”

As for type checking, yeah, it sucks big time. TS is a much better type system than the bolted-on hints in Python. But it’s still JS at the end of the day. All the power of V8, a zillion other runtimes, and TS gets marred by a terribly designed language.

This doesn't contribute much to the discussion.

Use whatever language works well for you and the task at hand, but many enjoy fullstack JS/TS.

> Python, Ruby

It's 2025, Node.js has been around since 2009, yet these languages' still use C-based interpreters by default, and their non-standard JIT alternatives are still much worse than V8.

Define “worse.” V8 is a runtime; it can’t fix JavaScript’s terrible design. V8 is amazing but runs a crappy language. Both Ruby and Python were created by actual language designers, and that still shows even decades after their original conception.
I wish we had better concurrency models in the ML world. I tried doing some ML in Go a few months back and it's basically impossible; there's just no library support and doing anything requires a gRPC call or a wrapper. Python has limitations and C++ has a tendency to make everything too verbose.
> High concurrency

> Share memory by communicating

> Centralized cancellation mechanism with context.Context

> Expansive standard library

> Profiling

> Bonus: LLMs are good at writing Go code

I think profiling is probably the lowest value good here, but would be willing to hear out stories of AI middleware applications that found value in that.

Cancelling tasks is probably the highest value good here, but I think the contending runtimes (TS/Python) all prefer using 3P libraries to handle this kind of stuff, so probably not the biggest deal.

Being able to write good Go code is pretty cool though; I don't write enough to make a judgement there.

> Bonus: LLMs are good at writing Go code

Good at writing bad code. But most of the code in the wild is written by mid-level devs, without guidance and on short timelines.. i.e bad code. But this is a problem with all languages, not just Go.

Go is great for command-line tools because of library support and fast-starting single-binaries. While most of the benefits in the article are also shared with JavaScript, I wonder if the CLI advantage will help and whether command-line agents will become a thing ("grepllm"?)

The language of agents doesn't matter much in the long run as it's just a thin shell of tool definitions and API calls to the backing LLM.

For long-running, expensive processes that do a lot of waiting, a downside is that if you kill the process running the goroutine, you lose all your work. It might be better to serialize state to a database while waiting? But this adds a lot of complexity and I don’t know any languages that make it easy to write this sort of checkpoint-based state machine.
Temporal is pretty decent at checkpointing long-running processes and is language agnostic.
That's the issue with goroutines, threads, or any long running chain of processes. The tasks must be broken up into atomic chunks, and the state has to be serialized in some way. That allows failures to be retried, errors to be examined, results to be referenced later, and the whole thing to be distributed between multiple nodes.

It must in my view at least, as that's how Oban (https://github.com/oban-bg/oban) in Elixir models this kind of problem. Full disclosure, I'm an author and maintainer of the project.

It's Elixir specific, but this article emphasizes the importance of async task persistence: https://oban.pro/articles/oban-starts-where-tasks-end

> For long-running, expensive processes that do a lot of waiting, a downside is that if you kill the goroutine, you lose all your work.

This is true regardless of the language. I always do a reasonable amount of work (milliseconds to up to a few seconds) worth of work in a Go routine every time. Anything more and your web service is not as stateless as it should be.

OP here - this type of "checkpoint-based state machine" is exactly what platforms which offer durable execution primitives like Hatchet (https://hatchet.run/) and Temporal (https://temporal.io/) are offering. Disclaimer: am a founder of Hatchet.

These platforms store an event history of the functions which have run as part of the same workflow, and automatically replay those when your function gets interrupted.

I imagine synchronizing memory contents at the language level would be much more overhead than synchronizing at the output level.

This is also how our orchestrator (written in Go) is structured. JP describes it pretty well here (it's a durable log implemented with BoltDB).

https://fly.io/blog/the-exit-interview-jp/

Nice! It makes a lot of sense for orchestrating infra deployments -- we also started exploring Temporal at my previous startup for many of the same reasons, though at one level higher to orchestrate deployment into cloud providers.
Yep, though I haven’t used them, I’m vaguely aware that such things exist. I think they have a long way to go to become mainstream, though? Typical Go code isn’t written to be replayable like that.
I think there's a gap between people familiar with durable execution and those who use it in practice; it comes with a lot of overhead.

Adding a durable boundary (via a task queue) in between steps is typically the first step, because you at least get persistence and retries, and for a lot of apps that's enough. It's usually where we recommend people start with Hatchet, since it's just a matter of adding a simple wrapper or declaration on top of the existing code.

Durable execution is often the third evolution of your system (after the first pass with no durability, then adding a durable boundary).

What are the main differences between temporal and hatchet?
The primary difference is that Hatchet is an all-purpose platform for async jobs, so while durable execution is a pattern that we support, we have a lot of other features like concurrency and fairness control, event ingestion, custom queues, dynamic rate limiting, streaming from a background job, monitoring, alerting, DAG-based executions, etc. There's a bit more on this/our architecture here: https://news.ycombinator.com/item?id=43572733.

The reason I started working on Hatchet was because I'm a huge advocate of durable execution, but didn't enjoy using Temporal. So we try to make the development experience as good as possible.

On the underlying durable execution layer, it's the exact same core feature set.

I actually working on an agent library in golang and this is exactly the thought process I've come up with. If we have comprehensive logging we can actual reconstruct the agents state at any position. Allowing for replays etc. You just need the timestamp(endpoint) and the parent run and you can build children/branched runs after that.

Through the use of both a map that holds a context tree and a database we can purge old sessions and then reconstruct them from the database when needed (for instance an async agent session with user input required).

We also don't have to hold individual objects for the agents/workflows/tools we just make them stateless in a map and can refernce the pointers through an id as needed. Then we have a stateful object that holds the previous actions/steps/"context".

To make sure the agents/workflows are consistent we can hash the output agent/workflow (as these are serializable in my system)

I have only implemented basic Agent/tools though and the logging/reconstruction/cancellation logic has not actually been done yet.

Just a drive-by thought, but: What you're describing sounds a lot like Temporal.io. I guess the difference is the "workflow" of an agent might take different paths depending on what it was asked to accomplish and the approach it ends up taking to get there, and that's what you're interested in persisting, replaying, etc. Whereas a Temporal workflow is typically a more rigid thing, akin to writing a state machine that models a business process -- but all the challenges around persistence, replay, etc, sound similar.

Edit: Heh, I noticed after writing this that some sibling comments also mention Temporal.

I've been considering good ways to use a task queue for this, and might just settle for a rudimentary one in a Postgres table.

The upside is that agent subtasks can be load balanced among servers, tasks won't be dropped if the process is killed, and better observability comes along with it.

The downside is definitely complexity. I'm having a hard time planning out an architecture that doesn't significantly increase the complexity of my agent code.

oh my god these things run on a gpu don't they? they have nothing to do with golang? to the extent they run on a cpu they're heavy; we're not like solving the c10k problem with agents
Agents don't typically, and any LLM they're calling is likely hosted remotely.
I'm not sure how valid most of these points are. A lot of the latency in an agentic system is going to be the calls to the LLM(s).

From the article: """ Agents typically have a number of shared characteristics when they start to scale (read: have actual users):

    They are long-running — anywhere from seconds to minutes to hours.
    Each execution is expensive — not just the LLM calls, but the nature of the agent is to replace something that would typically require a human operator. Development environments, browser infrastructure, large document processing — these all cost $$$.
    They often involve input from a user (or another agent!) at some point in their execution cycle.
    They spend a lot of time awaiting i/o or a human.
"""

No. 1 doesn't really point to one language over another, and all the rest show that execution speed and server-side efficiency is not very relevant. People ask agents a question and do something else while the agent works. If the agent takes a couple seconds longer because you've written it in Python, I doubt that anyone would care (in the majority of cases at least).

I'd argue Python is a better fit for agents, mostly because of the mountain of AI-related libraries and support that it has.

> Contrast this with Python: library developers need to think about asyncio, multithreading, multiprocessing, eventlet, gevent, and some other patterns...

Agents aren't that hard to make work, and you can get most of the use (and paying users) without optimizing every last thing. And besides, the mountain of support you have for whatever workflow you're building means that someone has probably already tried building at least part of what you're working on, so you don't have to go in blind.

That's true from a performance perspective but, in building an agent in Go, I was thankful that I had extremely well-worn patterns to manage concurrency, backlogs, and backpressure given that most interactions will involve one or more transactions with a remote service that takes several seconds to respond.

(I think you can effectively write an agent in any language and I think Javascript is probably the most popular choice. Now, generating code, regardless of whether it's an agent or a CLI tool or a server --- there, I think Go and LLM have a particularly nice chemistry.)

Go still has a much better concurrency story. It’s also much less of a headache to deploy since all you need to deploy is a static binary and not a whole bespoke Python runtime with every pip dependency.
Go is definitely better, but with uv you can install all dependencies including python with only curl
Is that what uv sync does under the hood, just curl’s over all dependencies and the python version defined in .python-version?
I think they meant you can use curl to install uv and then you don't need to (manually) install anything else
Yeah that’s what I meant, apologies if unclear
Agents are the orchestration layer, i.e. a perfect fit for Go (or Erlang, or Node). You don't need a "mountain of AI-related libraries" for them, particularly given the fact that what we call an agent now has only existed for less than 2 years. Anything doing serious IO should be abstracted behind a tool interface that can (and should) be implemented in whatever domain specific tooling is required.
I wouldn’t underestimate the impact of having massive communities around a language. Basically any problem you have has likely already been solved by 10 other people. With AI being as frothy as it is, that’s incredibly valuable.

Take for example something like being able to easily swap models, in Python it’s trivial with litellm. In niche languages you’re lucky to even have an official, well mantained SDK.

And I wouldn't overestimate a language's popularity. It's mostly a social phenomenon and rarely has anything to do with technical prowess.

I agree that integration with the separate LLMs / agents can and does accelerate initial development. But once you write the integration tooling in your language of choice -- likely a few weeks worth of work -- then it will all come down to competing on good orchestration.

Your parent poster is right: languages like Erlang / Elixir or Golang (or maybe Rust as well) are better-equipped.

I built Plandex[1] (open source CLI coding agent focused on large projects and tasks) in Go and I’ve been very happy with that decision.

Beneath all the jargon, it’s good to remember that an “agent” is ultimately just a bunch of http requests and streams that need to be coordinated—some serially and some concurrently. And while that sounds pretty simple at a high level, there are many subtle details to pay attention to if you want to make this kind of system robust and scalable. Timeouts, retries, cancellation, error handling, thread pools, thread safety, and so on.

This stuff is Go’s bread and butter. It’s exactly what it was designed for. It’s not going to get you an MVP quite as fast as node or python, but as the codebase grows and edge cases accumulate, the advantages of Go become more and more noticeable.

1 - https://github.com/plandex-ai/plandex

(comment deleted)
Agents to do what? Take ML/AI, all the infra and tools are Python/C++ so what exactly is the Agent going to help you with Go? Many such domains- gaming, HFT, HPC, Scientific Computing, Systems, UX, Enterprise etc. etc. Seems it really helps Go's sweet spot - CLI's and Networking services.
Agents mostly don't run ML/AI code; they're a structured loop around LLM calls and exist mostly to give an LLM access to local tools in some application domain; think "reading my email for me" rather than "driving ML systems".
That doesn't negate what OP was saying at all, the better support in Python isn't for "running ML/AI code" but in things like agent frameworks, observability tools, SDKs, etc. None of which directly run AI code but are still helpful/necessary and for the most part better represented (and supported) in the Python world, although that seems like it's slowly changing.
There are agent frameworks in most languages at this point so the question just comes down to "can you invoke tools for the problem you want to solve in that language". Yes, Python is really great at that. So is Go and Javascript.

I think I'd condense this out to "this is not a really important deciding factor in what language you choose for your agent". If you know you need something you can only get in Python, you'll write the agent in Python.

I don't think I agree with the Go is good in LLMs.

But outside of that - ML in go is basically impossible. Trying to integrate with the outside ecosystem of go is really difficult - and my experience has been that Claude Code is far less effective with Go then it is with Python, or even Swift.

I ditched a project I was writing in Go and replaced it with Swift (this was mostly prompt based anyways). It was remarkably how much better the first pass of the code generation was.

What if... hear me out... You learn to write code instead of generating it...there is a drastic improvement in code quality if you can actually write it.
I've been messing around with an Elixir + BEAM based agent framework. I think a mixture of BEAM + SQLite is about as good as you can get for agents right now.

You can safely swap out agents without redeploying the application, the concurrency is way below the scale BEAM was built for, and creating stateful or ephemeral agents is incredibly easy.

My plan is to set up a base agent in Python, Typescript, and Rust using MCP servers to allow users to write more complex agents in their preferred programming language too.

you should check out the Extism[0] project and the Elixir SDK[1]. This would allow you to write the core services, routing, message passing, etc in Elixir, and leverage all the BEAM/OTP have to offer, and then embed "agents" written in other languages which are small Wasm modules that act like in-process plugins.

[0]: https://github.com/extism/extism [1]: https://github.com/extism/elixir-sdk

That's a really interesting idea. My original thought was to use MCP as the way to define other agents, but I'll have to do some more research into extism!
Any reason for SQLite use, instead of the BEAMs built-in mnesia data store?

https://www.erlang.org/doc/apps/mnesia/mnesia.html

I'm still in the exploration/experimentation stage of the project, but I'm currently using a mixture of SQLite, PostgreSQL, S3, and DuckDB.

My original thought was to spin up SQLite databases as needed because they are super lightweight, well-tested, and supported by almost every programming language. If you want to set up an agent in another programming language via MCP, but you still want to be able to access the agent memory directly, you can use the same schema in a SQLite database.

I may end up using mnesia for more metadata or system-oriented data storage though. It's very well designed imo.

But one of the biggest reasons has just been the really nice integration with DuckDB. I can query all of the SQLite databases persisted in a directory and aggregate some metadata really easily.

go is terrible for agents IMO great for agents? lisp and prolog.
Not without a supporting ecosystem!
Go is a good fit for many use-cases.
In practice the library ecosystem is just way behind Python. Maybe after you’re trying to optimise once you’ve worked out how to do stuff, but even the Langchain Go port is wayyyyy behind.
I wrote the start to an agent library in Go. Its quite rough as most of it was implemented through using AI but I had a lot of ideas through planning/building it.

1. If you make your agents/workflows serializable you can run/load them from a config file or add/remove them from a decoupled frontend. You can also hash them to make versioning easy to track/immutable.

2. If you decouple the stateful object from the agent/workflow object you can just store that through sufficient logging then you can rebuild any flow at any state and have branching by allowing traces to build on one another. You can also restart/rerun a flow starting at any location.

3. You can allow for serializable tools by having a standard HttpRequestTool then setup cloudflare workers/any external endpoints for the actual toolcall logic. Removing primary server load and making it possible to add/remove tools without rebuilding/restarting.

Given this system in golang you can have a single server which supports tens of thousands of concurrent agent workflows.

The biggest problem is there isn't that many people who are working on it. So even if you can make agents 100x more efficient by running in Go it doesn't really matter if cost isn't the biggest factor for the final implementations.

The actual compute/server/running costs for big AI Agent implementation contracts is <1%, so making it 100x more efficient doesn't really matter.

Go is great for concurrency. Not quite there for agent support. The problem isn't performance or message passing it's the agent middleware i.e logging, tracing, retries, configuration

You need a DSL either supported in the language or through configuration. These are features you get for free in python and secondly JavaScript. You have to write most of this yourself in go

So far bigger bottleneck I have found in writing agents is in scaling integrations and not the for loop for agent. Lack of libraries for go is a really big challenge.
Following the article's logic, Elixir is a better fit for agents. Ideal I would say.
We have been having good luck writing Go with an agent. Sketch is mostly written with itself. (There is special Go handling built into the agent, e.g. automatically running gofmt/goimports after files change.) https://github.com/boldsoftware/sketch
(comment deleted)
Every single feature of an "agent" they have described is just...generic software development. Writing loops. if/else statements to branch execution paths. Waiting on input. Spawning child processes and communicating with them. Running CPU-bound operations (like parsing).

So every discussion about the "best" programming language is really you telling the world about your favorite language.

Use Go. Use Python. Use JavaScript. Use whatever the hell else you want. They are all good enough for the job. If you are held back it won't be because of the language itself.

For an agent that executes locally, or an agent that doesn't execute very often, I'd agree it's arbitrary.

But programming languages make tradeoffs on those very paths (particularly spawning child processes and communicating with them, how underlying memory is accessed and modified, garbage collection).

Agents often involve a specific architecture that's useful for a language with powerful concurrency features. These features differentiate the language as you hit scale.

Not every language is equally suited to every task.

It is not. Human coding languages and paradigms revolve around solving problems related to issues that human struggle with. We need AI coding languages that are easy to read and verify by humans, but should solve problems that AI agents struggle with.