## 1. Project Overview & Quickstart (ponylang/INSTALL_DOCKER)

# INSTALL_DOCKER

Open-source repository ponylang/INSTALL_DOCKER

### Repository Details
- **Repository:** [ponylang/INSTALL_DOCKER](https://github.com/ponylang/INSTALL_DOCKER)
- **Primary Language:** Code

*Note: High-volume repository documentation is actively indexed and synchronized by YakaAI.*

## 2. Official Technical Reference & Guides (ponylang/ponylang.github.io)

## File: README.md

# ponylang-website

Source for the [Pony website](https://www.ponylang.io).

## Interested in contributing?

Please see our [contributing guidelines](CONTRIBUTING.md)

## Interacting with the community

Please make sure to read our [code of conduct](CODE_OF_CONDUCT.md).

---

## File: docs/blog/posts/claude-you-some-pony-for-great-good.md

---
date: 2026-04-10T08:00:00-04:00
title: "Claude You Some Pony for Great Good"
authors:
  - seantallen
categories:
  - Engineering
draft: false
---

Here's how it is:

Two months ago I wrote about [teaching Claude to write Pony](teaching-claude-to-write-pony.md). That post was about the process — mentoring an LLM like a junior developer, building up engineering principles, establishing review loops. Today, I'm here to talk about the LLM skills I've been working on. They're in [ponylang/llm-skills](https://github.com/ponylang/llm-skills), and if you're working on Pony with an LLM, you should try them.



My bar for the skills is the same bar I use for human contributors: output I would accept and merge. Not perfection. Projects grow by accepting anything that improves the situation. And they die by getting hung up on getting everything "just so". My goal as I've been building these skills is to make it easier for you to push your Pony project forward.

There are eleven skills in the repo covering everything from language reference material to multi-agent code review to release note conventions. I'm not going to walk through all of them. This post highlights three that I think are worth understanding in some depth: what they do, how they work, and what you can expect from them.

Results vary across the skills. Code review produces excellent results. I'm not happy with where the software design skill is yet. The skills are a work in progress — I'm refining them as I use them, and some areas are further along than others.

## What's a skill?

A skill is a block of instructions your LLM loads on demand. Think of it as a procedure manual for a specific kind of work. You don't carry the whole thing around — you load what you need when you need it. `/pony-ref` loads the Pony language reference before a coding session. `/pony-code-review` loads a multi-agent review process before reviewing a PR. `/pony-debug` loads a structured debugging protocol when you're tracking down a bug. Each one gives the LLM knowledge or structure it wouldn't have on its own.

Skills don't load themselves. You either invoke them manually — type `/pony-ref` and the LLM loads the Pony language reference — or you put an instruction in your CLAUDE.md that tells the LLM when to load them. My CLAUDE.md has an entry that says to load `/pony-ref` at the start of any conversation where the working directory is a Pony project. I never think about it. It just happens.

The skills live at [ponylang/llm-skills](https://github.com/ponylang/llm-skills). Run `python3 install.py` and they're symlinked into your Claude Code skills directory.

I use these every day across all the Pony projects I work on, from the compiler and runtime to libraries like [ponylang/postgres](https://github.com/ponylang/postgres). They're not a side project I maintain when I get around to it. I'm actively developing and refining them as I work. Expect them to change often. The specifics of what I describe below may look different by the time you read this, possibly very different. That said, let's dive in.

## pony-ref

You know what is worse than slow code? An LLM crawling the internet trying to find Pony documentation and information. Trust me, it is infuriatingly slow. It took less than a couple days of working with Claude on Pony code for me to whip up the first version of pony-ref.

pony-ref is concentrated reference material that you should load at the start of every Pony coding session. It covers the capabilities table, subtyping rules, viewpoint adaptation, common gotchas, PonyCheck property-based testing patterns, and standard library pitfalls. Behind the quick reference, there are deeper documents — academic papers on the type system, runtime and GC synopses, tutorial and patterns content — that the LLM pulls from when it needs more depth. The reference documentation updates nightly from the Pony website and tutorial, so pull the repo often.

pony-ref helps a ton, but you'll need to keep an eye on the output. The LLM will produce far better code with the skill than without, but there will be mistakes. Code that even I, as someone who hates the word "idiomatic," thinks "isn't idiomatic".

## pony-code-review

A single LLM reviewing code does what you'd expect. It reads the code, checks for obvious problems, confirms the logic is correct. It's fine. It's also incomplete in the same way any single-pass review is. Every reviewer has blind spots.

pony-code-review runs multiple agents on the same code, each one approaching it with a different way of thinking. The correctness reviewer traces logic forward: given the inputs this code is designed to handle, does it handle them right? The adversarial reviewer starts from the other end: what would a bad outcome look like, and what specific inputs produce it? The tests reviewer applies counterfactual thinking: if the code under test were broken in exactly the way this test targets, would the assertion actually fire? There are more — security, API design, performance, consistency with project principles — and the skill selects which ones to deploy based on the scope of the change.

Same code. Different cognitive approaches. The findings go to a synthesis agent that combines them, resolving disagreements, cross-referencing uncertainties, and surfacing things that only become visible when you compare the outputs side by side.

I call it an ensemble because that's what it is — each agent's blind spots are different, and the combination covers more ground than any individual pass.

Running multiple agents costs more tokens than running one. Each agent runs its own internal review before returning results. The synthesis agent reviews its work too. It adds up. That's a deliberate choice. I'm trading tokens for quality, and the quality difference justifies the cost. A thorough review that catches a capabilities error or a test gap before it ships is worth more than the tokens it burned.

Fair warning, pony-code-review is not for the faint of heart. Your ego might get bruised. It can find tons of issues. It is very good at what it does and you will probably get knocked down a few pegs if you run it on your own code. And that's ok. Not everyone can score four touchdowns in a single game. Sometimes you just get beat up by the defense. Stop by [Office Hours](../../community/office-hours.md) and ask Red about the review his ODBC project got.

## pony-debug

LLMs can spend twenty minutes chasing a hypothesis they never tested. They'll confidently explain why the bug is in module A when it's actually in module B, fix a problem that doesn't exist while the real one sits untouched. This isn't because they can't reason. Give an LLM a genuinely hard problem — subtle type system interactions, concurrency bugs, root causes buried across thousands of lines — and when the reasoning chain connects, the results can seem almost magical. The problem is empiricism. LLMs reason about code instead of testing their assumptions. A developer who's been burned enough times will say "let me just check" before committing to a theory. An LLM without structure will build an elaborate model of what the bug must be, and when the evidence doesn't fit, it adjusts the model instead of questioning the premise.

pony-debug forces the discipline. Characterize the failure. Gather context. Build a minimal reproduction. Then iterate through hypothesis-experiment-observe cycles. The key word is *experiment*. Not "reason about what would happen if I changed this." Actually run the code. Read the output. Let the results update your understanding. Each step is a checkpoint — you don't move forward until the current one is solid. That usually keeps the LLM from doing what it wants to do, which is latch onto a theory in the first thirty seconds and spend the rest of the session making the evidence fit.

Just like with pony-ref, pony-debug helps a lot, but LLMs "have a mind of their own". You should still probably check in occasionally when it is debugging to make sure it isn't running in circles like a chicken with its head cut off.

## Skills are one piece

In [Teaching Claude to Write Pony](teaching-claude-to-write-pony.md), the biggest takeaway was that engineering principles matter more than anything else. Your CLAUDE.md — the principles, conventions, and working style you build up through use — shapes how the LLM approaches every task. The skills give it knowledge and process. Your CLAUDE.md gives it judgment.

pony-code-review produces better results when the LLM already has principles like "make illegal states unrepresentable" and "prefer explicit over implicit" to guide its decisions. pony-debug works better when the CLAUDE.md reinforces "verify empirically before asserting." The skills provide structure. The principles provide the values that guide decisions within that structure.

This isn't autopilot. You need to understand the code, review the output, and make the calls that require context the LLM doesn't have. The skills raise the floor. Your principles and your judgment set the ceiling.

If you have a shit CLAUDE.md, I expect the skills will give you shit results. If your CLAUDE.md is big rock candy mountain, then perhaps the sun really will shine every day on your Pony code.

## Before you install

I test with Claude Opus 4.6, high effort on. That's the configuration I use every day, and it's the only one I can vouch for. The install script is built for Claude Code. Any LLM harness that supports sub-agents should work in principle, but I haven't tested other harnesses or other models.

The ensemble skills burn tokens. That's by design. If cost is a concern, the non-ensemble skills — pony-ref, pony-debug, and the rest — work without the multi-agent overhead. But the ensemble skills exist because the quality is worth the cost, and I haven't regretted that tradeoff.

## Try them

The skills are at [ponylang/llm-skills](https://github.com/ponylang/llm-skills). Clone the repo, run the install script, and see what you think. And if you find yourself thinking they are pretty swell, follow along with changes via the [CHANGELOG](https://github.com/ponylang/llm-skills/blob/main/CHANGELOG.md).

If you get stuck, come find me on the [Pony Zulip](https://ponylang.zulipchat.com). The [#beginner help](https://ponylang.zulipchat.com/#narrow/stream/189985-beginner-help) stream is a good place to start.

---

## File: docs/blog/posts/early-history-of-pony.md

---
date: 2017-05-06T10:55:40-04:00
title: An Early History of Pony
authors:
  - sylvan
categories:
- History
draft: false
---
Sean T. Allen says I should write an early history of Pony. He says people might be interested. He's generally right about this stuff, but I feel a bit awkward about the process. I know I will forget things, or worse, forget people. Fortunately, Sean gave me an outline on Slack based on stuff I've told him, so here goes.



## Sean: You’ve told me this story many times.

I had been telling friends for years that I would eventually write a programming language. Way back when (starting in 2001), I had written a flight simulator engine that I worked on for years. I ended up building an asynchronous message queue system for doing the physics simulation end of it. And struggling with memory bugs, of course.

When the flight sim company folded in 2010, I went to work for a financial market data company. I was still thinking a lot about the flight sim, and about other systems, I had worked on over the years (embedded operating systems, distributed cryptographic systems, peer-to-peer VOIP, that kind of stuff) and yet again I was struck by how many of the same problems kept cropping up.

Anyway, I spent a lot of time reading academic papers at this point. A lot of time. Papers on type systems, on garbage collectors, on distributed schedulers, back to type systems, that sort of thing. Interestingly, a research group called SLURP (Sound Languages Underpin Reliable Programming) at Imperial College London was connected to a lot of the papers; particularly Professor Sophia Drossopoulou.

I also chatted a lot with Thomas Munro, Harry Richardson, and Andy McNeil. Andy worked elsewhere, but Thomas and Harry were at the same financial market data firm. I didn't stay there very long.

## Sean: C based actor library. Goes vroom.

It totally went vroom. In 2011 I started working for a major investment bank, running their European electronic trading infrastructure team. I had two of the best contractors ever (Alex and Albert) and we built up a great team in London. The point of this part of the story is that while we did a bunch of different stuff, one of the main things we did was write infrastructure code for low-latency (a few microseconds) high-throughput (hundreds of thousands of messages per second) applications.

As part of this, I decided to write a library for writing actors in C. I used some fast queues based on Dmitry Vyukov's work, wrote a work-stealing scheduler, had complex macros for writing behaviour handlers, that sort of stuff. Other teams used it to deploy some high-performance applications, and it was really fast.

## Sean: Why did that not work?

Because C. And C++ too, of course - the application level code was generally written in C++. Over and over again, programmers ran into memory errors. And not just the usual problems with dangling pointers (premature free) and leaks (postmature free?) but persistent problems with data-races.

Programmers would send a pointer from one actor to another, convinced that it was safe, and it would turn out it wasn't. Sometimes they would then add non-message-passing based synchronisation (locks, lock-free algorithms, etc.) to the data types. This usually resulted in subtle deadlock conditions (which often only appeared occasionally under high load on production systems), but even when such bugs weren't encountered (which is different from saying they weren't there) there was a performance cost. And that was not good.

## Sean: This is not so good.

Yeah, exactly. While we could squeeze a lot of performance out of these systems, we weren't gaining productivity. We weren't really losing productivity either, of course, compared to programming with thread pools, work-bucketing, and synchronisation primitives, but the frustration of having what appeared to be an actor-model program die on memory problems was pretty severe.

## Sean: This could be so much better!

Yeah, exactly! I started spending my free time thinking about type systems. I had a feeling that it would be possible to express the actor memory isolation guarantees to prevent data races using some kind of type annotation.

The C actor library did message passing without making copies. Usually, actor-model languages make copies, like when Erlang sends a message. Using ahead-of-time compiled native code was important for performance, sure, but to be faster than existing C/C++ multithreading approaches, we couldn't spend all that time copying messages. This is of course what lead to the data races.

It also leads to the memory leaks and dangling pointers, of course, with confusion about which actor should be responsible for freeing some data. I experimented with reference counts but ran into the usual problems with performance (too many reference count updates) and restricted object graph shapes (no cyclic object graphs).

## Sean: A LANGUAGE.

Ok, sure, a language. But _never_ write a programming language. That's like rule #1. Everybody will just tell you it isn't needed and then argue about syntax.

Besides, so much of what I wanted was already available in C: an ahead-of-time optimizing native-code compiler, for example. I had been looking at compiler back-ends like C-- for some time, but around this time I started working with LLVM. We used LLVM at the investment bank to write a statically-typed ahead-of-time compiled Lisp variant that did no memory allocation (no really, there are some compelling use cases for that, strange as it sounds). In fact, I even managed to hire Harry and Andy, and Harry did all the real work on the Lisp thingy.

But then, so much wasn't available. Particularly a data-race-free type system and a garbage collector. I had been trawling through the literature for a while, and while I found some _brilliant_ stuff, nothing was quite what I wanted. A lot of stuff was very influential, but not quite exactly what I was looking for.

## Sean: Imperial.

Right, Imperial College London. I went back to school. Or rather, I started a Ph.D. program at Imperial, with Sophia Drossopoulou as my advisor. I was still working at the investment bank, so I did the Ph.D. program part-time. I had realised I would never be able to prove to myself that the type system and garbage collector ideas floating around in my head were sound unless I learned some formal skills.

## Sean: WHEEEE THIS IS FUN!

Super fun! Wow, amazing fun. Sophia taught me formal skills and we worked together on proving stuff sound. We did GC papers, type system papers, everything was coming together, it was fantastic. But my day job was still C/C++ on top of the C library.

Then I met Sebastian Blessing. Seb was doing a Masters at Imperial, and when Sophia and I advertised for a Masters student to do a project on extending our ideas to distributed computing, he jumped at it. And he did an amazing job.

But Sebastian went much further than that. He wanted a compiler and runtime to exist, and he thought the best way to do that was to start a company. A company that could build the compiler and runtime, and then offer commercial support. And the amazing part is, he made it happen.

So we started a company called Causality: Sebastian, Sophia, Constantine Goulimis, and me. I quit my job at the investment bank, and we even hired Andy, which was great, although for various reasons, not Harry, which was not great.

## Sean: A language is born to leverage the awesome of this runtime over here.

That's right! We did it. We built the thing. We started in June 2014 and by September 2014 we compiled and ran our first Pony program. The compiler and runtime improved, and in May 2015 we open sourced the whole thing.

When we did, I sent Sean a note telling him that I had built a thing. He wrote back. That turned out to be a _really_ big deal. Really big.

We continued to do research at the same time, refining the type system and the various runtime protocols. Juliana Franco started doing some of her Ph.D. work around the Pony garbage collector. Two more Imperial students, George Steed and Luke Cheeseman, did extensive projects. Tobias Wrigstad and Dave Clarke made the decision to base _their_ language, Encore, on the Pony runtime.

Unfortunately, a start-up based on a programming language is a notoriously bad idea. I'm not saying it's impossible - but there isn't a great track record for them, and unfortunately, Causality went the same way.

Fortunately, by then, we had a fantastic open source community. Sean Allen, Joe Eli McIlvain, Benoit Vey, Theo Butler, Perelandric, Reini Urban, Gordon Tisher, Dipin Hora, Jonas Lasauskas, John Mumm, Andy Turley, George Marrows, Darach Ennis, Tommy McGuire, Carl Quinn, Malthe Borch, Kevin Cantú, Markus Fix, Chris Double, Dan Connolly, Paul Liétar, and many others.

So now I work at Microsoft Research in Cambridge with some of the most amazing people I've ever met in my life, and I work on an open source programming language with some of the most amazing people I've ever met in my life, and hopefully we will continue to make great tools that great people want to use to do great things.

## Sean: To help.

Yes, literally that.

## Sean: May the pony be with you.

And also with you.

## Sean: The big reveal on why it's called "Pony"?

Back in the flight sim days, when I would make my friends groan by telling them yet again about all the things I was going to do when I wrote a programming language, one of the people I would tell was Nathan Mehl. And one time, when I gave him yet another laundry list, he said: "yeah, and I want a pony".

Also, Harry likes horses.

---

## File: docs/blog/posts/eleven-years-to-a-finite-recursive-type-alias.md

---
date: 2026-05-17T16:00:00-04:00
title: "Eleven years to a finite recursive type alias"
authors:
  - seantallen
categories:
  - ponyc
  - Finite Recursive Type Aliases
draft: false
---

There's a pull request open against the Pony compiler. It's in review right now. When it lands, and it will land soon, you'll be able to write this:

```pony
use "collections"

type JsonValue is
  ( String
  | F64
  | Bool
  | None
  | Array[JsonValue]
  | Map[String, JsonValue])
```

Today the compiler rejects it. `JsonValue` mentions `Array[JsonValue]`, which mentions `JsonValue`, and ponyc throws up its hands: `type aliases can't be recursive`. That has been true for the entire history of the language. It's about to stop being true, and the pull request that changes it closes out the oldest open issue in the ponyc repository.



## The workaround

That JSON type is how you describe JSON in a language that has union types. A JSON value is a string, or a number, or a boolean, or null, or an array of JSON values, or an object mapping strings to JSON values. The definition refers to itself because JSON refers to itself. An array holds more JSON. An object holds more JSON. You cannot write the shape down without the shape pointing back at itself.

Pony has had union types from very early on. What it has not had is an alias that can point at itself. So without that, you write your JSON types like this:

```pony
type JsonValue is (String | F64 | Bool | None | JsonArray | JsonObject)

class JsonArray
  var data: Array[JsonValue]

class JsonObject
  var data: Map[String, JsonValue]
```

The scalars go straight into the union. The recursive parts, the array and the object, get wrapped in classes, because a class is allowed to mention whatever it wants. It works. It is the only thing that works. But you are writing those wrapper classes to get around the type system, not because your design asked for them. Every place you touch a JSON array you are really touching a `JsonArray` that holds the array you actually wanted.

None of that code stops working. The wrapper-class types you have already built keep compiling. Recursive aliases are a new option, not a migration. The change is all additive.

Not every recursive alias is going to be legal once this lands. `type Loop is Loop` is nonsense, and the compiler will still say so. The rule, in one sentence: the recursion has to pass through something that holds its contents behind a pointer, like `Array` or `Map`, and there has to be a way out, some option in a union that does not loop back. `Array[JsonValue]` clears the first bar. `String` and `None` and the rest of that union clear the second. `type Loop is Array[Loop]` clears the first and fails the second, and it will be rejected. How the compiler works that out is a story for another day.

## The nag

The oldest open ponyc issue is number 267. Joe Eli McIlvain opened it on July 23rd, 2015. He was writing a pure-Pony JSON library, hit the recursive alias wall, and filed a bug with a JSON type as the example. Eleven years later, that JSON type is the example in the pull request that closes his issue.

Two hundred sixty-seven. ponyc's issue and PR numbers are well past five thousand now. Number 267 is the oldest issue still open. When this pull request merges, it will not be.

## Why it waited eleven years

An issue does not stay open for eleven years because nobody noticed it. People noticed. People wanted it. It stayed open because fixing it is genuinely hard. Here is why.

Type aliases in Pony were never really things. They were text. When you wrote `type JsonArray is Array[JsonValue]`, the compiler did not write down "there is an alias named JsonArray." It found every place you used `JsonArray` and pasted `Array[JsonValue]` in. A few passes into compilation, the alias was gone. There was no `JsonArray` anywhere in the program the compiler was looking at. Just `Array[JsonValue]`, written out longhand everywhere you had used the short name.

You cannot make text substitution recursive. If `JsonArray` expands into something containing `JsonValue`, and `JsonValue` expands into something containing `JsonArray`, the pasting never stops. The compiler loops until it dies. So it did not allow it. Not because recursive aliases are a bad idea, but because the machinery underneath them had no room for the concept.

Sylvan Clebsch, who created Pony, said as much on the issue back in 2015:

> The problem is we are disallowing all type alias recursion, when we should only disallow infinite type alias recursion. [...] Checking this is certainly possible, but non-trivial. But we should do this.

Non-trivial, but we should do this. That is the whole history of the issue in six words. Sylvan came back in 2018 and wrote down the actual roadmap. Stop expanding aliases. Make them first-class things the compiler carries around and reasons about. Teach the whole type system to understand them. He called that "a significant amount of work." And then, about the final step, the part that actually detects which recursion is safe and which is not, he wrote:

> This should be relatively easy.

Hold onto that line.

That is the first reason the issue waited: the change is deep. It runs from the front of the compiler to the back. But there is a second reason, and it is the one I did not understand until I was inside it. The change is not just deep. It is a grind.

## Seventy-nine commits

I started from the same place Sylvan would have. Stop expanding aliases, make them first-class, teach the type system. That is the part we both thought would be a significant amount of work, and I braced for it. It is the foundation the whole feature stands on. If there was going to be a slog, that is where the slog would be.

It took two or three days.

I was not doing it alone. I have been doing a lot of my Pony work lately with Claude Code. I think. Claude executes. And the foundation, the part I had braced for, went fast. Way faster than I imagined. I remember the feeling exactly. "Well, would you look at that. This is going to be not so bad. Thanks, robot."

I was calling it daylight. It was barely even dusk.

Here is what I had missed. The part of the roadmap that Sylvan called "a significant amount of work," the foundation, that is the part that went in two or three days. The part he called "relatively easy," detecting which recursion is finite and which runs forever, that is the part still in review. That is the part with seventy-nine commits on it and counting. Seventy-nine commits on one pull request. Not spread across the three it took to get here. On the last one alone.

Sylvan was wrong about which part was easy. So was I, and I have spent about a decade inside this compiler. I looked at the roadmap and came to the same conclusions as Sylvan. This isn't surprising. We often think alike, but still. There I was. I too thought the foundation was the mountain, the rest was the walk down. We both knew this compiler well. And neither of us saw the swarm of locusts on the horizon.

What do seventy-nine commits look like? They look like threading one piece of information through one function, then the next function, then the next, across dozens of files, because some corner of the type checker now needs to know something it never needed to know before. They look like a couple of days spent building an approach, and then a couple more days spent backing out of it because it was the wrong approach. None of it is hard the way a clever algorithm is hard. It is hard the way digging a long ditch is hard. It does not take brilliance. It takes not stopping.

That is where Claude actually earned its keep. Not in the two or three days that felt like magic. That part was a tease. It earned it in the long stretch after, where the work is mechanical and endless and every instinct I had was telling me to go do something more interesting. I took a wrong turn that cost me two days. Without Claude, it would have cost me two weeks. And spending two weeks to find out I had taken the wrong approach entirely is the kind of thing that would have made me close the laptop and not come back. Claude made the boring, punishing parts survivable. That's a load and a half.

All that pain is the real answer to why issue 267 waited eleven years. It was not waiting on an idea. Sylvan had the idea in 2015 and the roadmap in 2018. It was waiting on someone willing to grind through it. For eleven years nobody was. Handed this exact task a few years ago, I would not have been either. I do not think I have gotten this far because I got more determined than the people before me. I think I have gotten this far because the grind finally got cheap enough to do.

---

## File: docs/blog/posts/embed-you-a-ponyc-for-great-good.md

---
date: 2026-04-14T08:00:00-04:00
title: "Embed You a ponyc for Great Good"
authors:
  - seantallen
categories:
  - ponyc
draft: false
---

The `ponyc` command you run every day is a `main()` function with a terminal-width detector glued to it. The actual compiler is a library called libponyc. ponyc is a wrapper around that library, and the wrapper is [149 lines of C](https://github.com/ponylang/ponyc/blob/main/src/ponyc/main.c).

That's the setup for this post. The Pony compiler is a library and you can link against it. And because you can link against it, you can build your own tools. And if you want your tool to be one binary instead of a ball of loose dependencies, you want libponyc-standalone.



## libponyc-standalone: the compiler in a bag

libponyc is the library ponyc wraps. On its own, it isn't enough to compile a Pony program. It pulls in LLVM. It pulls in the Pony runtime. It pulls in a handful of other things that ponyc would otherwise link in on its own. Build a tool against libponyc the hard way and you'd be reproducing ponyc's link line by hand, then keeping it in sync as the compiler's dependencies change. No thanks.

libponyc-standalone does the bundling for you. It's one static `.a` with everything you need inside it: libponyc, LLVM, the supporting libraries. Link against it and you get a self-contained binary[^1]. No shared libraries to ship alongside. No "works on my machine because I happen to have the right LLVM installed."

Static because self-contained binaries are next to godliness and dynamic linking is the Devil's plaything.

The API is C, which means the wrapper you put on top can be whatever you want. Write it in C. Write it in Rust. Write it in Go. Write it in Pony. The `.a` doesn't care.

What we wrote it in was Pony.

## The prettier half

libponyc-standalone is technically just the `.a`. The interesting part is what's on top of it. Call it the other half of libponyc-standalone: a Pony wrapper that gives you a real API to the compiler's internals.

The wrapper is Matthias's pony-ast. I'd started something similar years ago and never got it over the line. His was better.

It lives in the ponyc repo at [`tools/lib/ponylang/pony_compiler/`](https://github.com/ponylang/ponyc/tree/main/tools/lib/ponylang/pony_compiler). The basic shape looks like this:

```pony
match Compiler.compile(source_dir, [pony_path]
  where limit = PassFinaliser)
| let p: Program =>
  // walk packages → modules → AST
| let e: Array[Error] val =>
  // do something with the errors
end
```

The return type is `Program` on success, `Array[Error] val` on failure. Errors are a real type, with file, position, and a message. Not strings you grep. Not an `errno` you decode. Data.

`limit = PassFinaliser` is where you tell the compiler how far down the pipeline to run. The compiler is a sequence of passes — parse, typecheck, finalize, codegen — and not every tool wants the whole thing. A language server wants to stop after typecheck so it can surface errors fast. A documentation generator doesn't need codegen. The `limit` parameter says "stop here." Pick your pass, pay for what you use.

The `Program` itself is a tree: a program has packages, a package has modules, a module has an AST. Walk it however you like.

That's the whole entry point. Ten lines of it covers what most tools need to get started.

## Why it's still private

The wrapper is private today. You'll notice it's under `tools/lib/` in ponyc rather than in the standard library, which is where an API like this belongs long-term.

That's deliberate. The API is in flux. We're actively developing it while we build tools on top of it, and every tool we build surfaces something that should be different. Putting it in the standard library means committing to the API as it stands, and we're not ready to commit. Better to land it in stdlib once with something good than to land it three times while we figure out what good looks like.

The outer shape — the `Compiler.compile` / `Program` / `Error` layer the snippet above shows — is probably stable. The deeper bits, the parts you'd reach for if you were doing something the existing tools don't, are what's moving.

It'll migrate to the standard library eventually.

## What's next

The Pony ecosystem has three tools that need access to the Pony compiler:

- [pony-lsp](../../use/pony-lsp.md), a language server
- [pony-lint](../../use/linting.md), a linter
- [pony-doc](../../use/documentation.md), a documentation generator

Each of them gets its own post in the coming weeks. Each uses the wrapper a little differently. The LSP runs compilations as actors and reacts to what comes back. The linter walks ASTs looking for patterns. The doc generator walks typed declarations. The wrapper is common. But perhaps you won't find what each tool does with it so common now.

libponyc-standalone has been sitting in ponyc for a long time. We're finally doing something with it.

Before we close, let me address the elephant in the room. Some folks might have noticed this is the second "for great good" post I've done in a short period of time. Some people might say that's too much. I say [too much is never enough](https://www.youtube.com/watch?v=WY6vKtC695I).

[^1]: As much as possible on macOS.

---

## File: docs/blog/posts/improving-runtime-test-coverage.md

---
date: 2026-06-26T21:00:00-04:00
title: "Improving Runtime Test Coverage"
authors:
  - seantallen
categories:
  - runtime
draft: false
---

Pony's runtime has a set of stress tests. They run in CI, each one a program that exercises the runtime over and over for an extended period of time: one passes messages between actors without a break, another opens and closes network connections again and again, sending data back and forth. The idea is that the more you push on a part of the runtime you think is bug-prone, and the longer you keep at it, the more likely you are to shake out a bug that only turns up once in a while. For the runtime, bug-prone means "susceptible to concurrency issues". And we hammer away at those areas as much as we can. Or try to anyway. That was the idea.



That idea has been a bit iffy lately. It has been a long time since a stress test run found a problem. At this point, when I get a notification that one has failed, I assume it is an environment issue in GitHub like "couldn't pull the test container". There are two ways I can look at that. One, the runtime is rock solid. Two, our tests could be improved. In reality it's probably a little column A, a little column B.

The runtime has seen a lot of hardening over the years, but at the same time, the stress tests we've been running the last few years are lacking. They are stale. They haven't changed since they were created and worse yet, they are static. They run the same way every time. They only exercise certain runtime options, and the ones they do touch run exactly the same every day. If any of those code paths were broken, the stress tests should catch it, but there's a whole state space they simply never explore. This is the story of what happened when I started to make our stress tests more dynamic.

Two things had to change. The first was to stop running the same fixed setup every time. There is still one program, but every run varies a couple of things. One is the workload: how many actors there are, how they connect, how much they send to each other. The other is the runtime underneath, set through its own options, like how many scheduler threads it runs on and how the garbage collector is tuned. The new setup is incorporating ideas from [swarm testing](https://www.youtube.com/watch?v=wzfC7Q-xNik): one fixed setup only ever exercises one path through the runtime; randomizing both, run after run, eventually covers a huge range, and turns up bugs a handful of hand-picked setups never would. We are a lot more likely now to encounter weird emergent behavior than we were before.

Varying the setup was the first change. It should help flush out bugs. But flushing out bugs isn't everything. Reproducing them easily is important as well. I've spent way too much time tracking down weird bugs that come up in CI where at best I have an assertion failure and a backtrace to work from. I get to the conclusion and fix the issue but it can take a long time. When a run breaks the runtime, I want to replay that exact failure, as many times as it takes to understand it. A bug that shows up once and never the same way again is still fixable, but I'd rather not spend my time chasing one down.

The Pony runtime has a mode that can help: systematic testing. Normally the runtime runs actors in parallel across a set of scheduler threads, and the order they interleave depends on those threads and the hardware under them. Running a program 2 times in a row won't run in the exact same way. The output might be the same, but how the runtime "got us there" won't be. Systematic testing makes running a Pony program a lot more deterministic. When it is on, we run the scheduler threads one at a time instead of in parallel so we can control the order that actors are run in. A program that does nothing non-deterministic of its own then runs the same way every time.

The new stress test is deterministic. Its randomness all comes from a seed, and it reads no clock or other outside state like calling out to OS functions that could change ordering. This means nothing in it varies from one run to the next. When we run it under systematic testing with a fixed seed, we get the exact same run. When something fails in CI, we can replay it from its seed for as long as it takes to understand wtf happened.

That's the idea, anyway. Humans, we are fallible, so the stress test has a sanity check in it. It records a fingerprint of each run, a summary of the order things happened in, and compares them. Every run actually runs twice with the same parameters and we compare the fingerprints to make sure they are the same. If they aren't then we know that we either messed up the determinism of the program or the runtime isn't deterministic with the options we gave it.

I fired up the stress test locally, ran it and the fingerprint test failed. My first thought was "wtf": a deterministic test cannot produce that difference, so it had to be the runtime, in how it ran the program. But how?

It turns out two bugs were breaking the replay. The first was time. The scheduler read the system clock in a few places, and the system clock is different on every run, so those reads made the same seed run differently. Replacing them with a counter that advances with the run fixed it. The second had nothing to do with the program at all. Reading the system clock is a classic way that programs break determinism. But the 2nd, it surprised me. It's one of those things that is obvious in hindsight, but you are unlikely to come up with without debugging.

The cause was ASLR. Every run, the operating system loads a program at fresh, random memory addresses, so an attacker can't predict where anything is. It makes no difference to what the program computes, but it does put every actor at a different address on every run.

In several places, the runtime orders actors by that address. It holds a set of actor pointers and works through them in the order the addresses fall. One of those orders is the order the scheduler runs actors in. Under systematic testing that order should come from the seed and nothing else, but the runtime took that order partly from the addresses, which the seed does not control. ASLR changes the addresses every run, so the same seed ran the actors in a different order each time, and the rest of the run followed.

The first place I found the problem was in the backpressure system, where the runtime mutes actors that are overwhelming a slower one. It holds pointers to them and reschedules them in address order. The same thing turned up in two more places: the reference counting between actors, and the cycle detector. Each one ordered actors by pointer, so ASLR scrambled the order in each. The fix is the same wherever it applies: order the actors by the sequence they were created in, which ASLR can't move, instead of by their address.

So how did these bugs "creep in"? Systematic testing was introduced four years ago, and apparently no one tried to use it until now. It was passing its basic tests, but... irony of ironies: they were incredibly static and didn't exercise much of the state space.

I'm kind of amused that I started with the idea to find bugs with this new test running daily in CI. But it found bugs before I ever even got it to CI. I guess it has already earned its keep.

---

## File: docs/blog/posts/lasr-week-in-pony-072025.md

---
draft: false
authors:
  - seantallen
categories:
  - "Last Week in Pony"
title: "Last Week in Pony - July 20, 2025"
date: 2025-07-20T07:00:06-04:00
---

Welcome to the July 20, 2025 edition of Last Week in Pony! This week we're ending support for MacOS on Intel processors and introducing the new ponylang/ssl library. It replaces the deprecated crypto and net_ssl libraries. We also have several new releases, including ponylang/ssl 1.0.0.



## MacOS for Intel Support is Coming to an End

[GitHub announced](https://github.blog/changelog/2025-07-11-upcoming-changes-to-macos-hosted-runners-macos-latest-migration-and-xcode-support-policy-updates/#macos-13-is-closing-down) they're ending support for MacOS on Intel processors in November.

We'll remove Intel MacOS support from our CI sometime before then. Changes we make will no longer be tested on Intel MacOS. We won't be providing pre-built versions of ponyc, corral, and ponyup for Intel MacOS anymore.

If you're using Pony on Intel MacOS, you'll need to build everything from source.

[We haven't decided](https://ponylang.zulipchat.com/#narrow/channel/190359-ci/topic/macOS.20for.20intel.20support) exactly when we'll remove support, but it'll be sometime before November 2025.

## Items of Note

### New OpenSSL/LibreSSL Support Library

We had two libraries before: [ponylang/crypto](https://github.com/ponylang/crypto) and [ponylang/net_ssl](https://github.com/ponylang/net_ssl). Both were API wrappers around OpenSSL and LibreSSL. We've merged them into a single library called [ponylang/ssl](https://github.com/ponylang/ssl).

We've deprecated ponylang/crypto and ponylang/net_ssl. They're now read-only repositories. Use ponylang/ssl instead.

The transition is straightforward:

- Update your corral.json to point at the new dependency
- Update your use statements from `use crypto` and `use net_ssl` to `use ssl/crypto` and `use ssl/net`

### Pony Development Sync

The [recording](https://vimeo.com/1102058085) of the July 15th, 2025 sync is available.

### Office Hours

This week's Office Hours had myself and Niclas. We talked a lot about Sweden and Swedish things. Eventually we ended with him showing me what he's [been working on](https://github.com/niclash/pink2web). It's pretty cool. You should grab me and have him give you a demo.

## Releases

- [ponylang/github_rest_api 0.2.1](https://github.com/ponylang/github_rest_api/releases/tag/0.2.1)
- [ponylang/http 0.6.4](https://github.com/ponylang/http/releases/tag/0.6.4)
- [ponylang/http_server 0.6.3](https://github.com/ponylang/http_server/releases/tag/0.6.3)
- [ponylang/lori 0.6.2](https://github.com/ponylang/lori/releases/tag/0.6.2)
- [ponylang/postgres 0.2.2](https://github.com/ponylang/postgres/releases/tag/0.2.2)
- [ponylang/ssl 1.0.0](https://github.com/ponylang/ssl/releases/tag/1.0.0)

---

_Last Week In Pony_ is a weekly blog post to catch you up on the latest news for the Pony programming language. To learn more about Pony, check out [our website](https://ponylang.io) or our [Zulip community](https://ponylang.zulipchat.com).

Got something you think should be featured? There's a GitHub issue for that! Add a comment to the [open "Last Week in Pony" issue](https://github.com/ponylang/ponylang.github.io/issues?q=is%3Aissue+is%3Aopen+label%3Alast-week-in-pony).

---

## File: docs/blog/posts/last-week-in-pony-010123.md

---
draft: false
authors:
  - seantallen
categories:
  - "Last Week in Pony"
title: "Last Week in Pony - January 1, 2023"
date: 2023-01-01T07:00:06-04:00
---

The big news is that you should update your Pony version to 0.52.5. It's very important if you are using Pony 0.51.2 to 0.52.3 that you update as soon as possible.



## Items of Note

### Upgrade Your Pony Version

In the process of [improving auto-recovery for constructors](https://github.com/ponylang/ponyc/pull/4124), a very very very nasty bug was introduced Pony 0.51.2. The nasty bug is one where the compiler will allow you to mutate immutable objects thereby breaking the basic safety guarantees that Pony provides.

The bug was fixed in Pony 0.52.4 and a number of tests to assure that no such breakage happens in the future. Please update to Pony 0.52.5 to get the safety fix from 0.52.4 and a corresponding follow on in Pony 0.52.5.

### Some Instability Expected

In [Pony 0.52.3](https://github.com/ponylang/ponyc/releases/tag/0.52.3) we removed a sometimes unsafe garbage collection optimization. Runtime code that has been little used (or perhaps not at all used) over the years is now used a lot. Unfortunately, it appears that something in that code is suspect. Core team member Gordon Tisher has found that the unit tests for a project of his started segfaulting after the optimization was removed.

The bug in question is a very odd one and not obvious in its source. At the moment, we know there's clearly something in code that wasn't used prior to 0.52.3 that is causing a problem. It is possible that this problem or others might impact other Pony users.

All our tests including our nightly stress tests run without issue so we don't believe that any issues will be widespread. And, we suspect that any issues will probably share a root cause.

If you encounter any runtime segfaults with 0.52.3 or later that you didn't get prior to 0.52.3, please drop by the [runtime stream](https://ponylang.zulipchat.com/#narrow/stream/190365-runtime) in the [Ponylang Zulip](https://ponylang.zulipchat.com/) and let us know.

### New OpenSSL 3.0.7 builder image

We've added a new builder image with OpenSSL 3.0.7 installed. If you need a docker environment to test Pony programs with OpenSSL 3.0.7, you can use the new image.

It has two tags, `release` and `latest`. Release is updated with each new ponyc release, latest is updated every night with the most recent nightly version of ponyc.

See the [Docker Hub repository](https://hub.docker.com/r/ponylang/shared-docker-ci-x86-64-unknown-linux-builder-with-openssl_3.0.7/tags) for some additional information.

This is our first builder image to target the new OpenSSL 3.x API series and will be used to test various ponylang projects maintained by the core team for compliance with OpenSSL 3.x.

### Office Hours

We have an open Zoom meeting every Friday for the community to get together and well, do whatever they want. In theory, Sean T. Allen "owns" the meeting and will often set an agenda. Anyone is welcome to show up and participate. Got a Pony related problem you need help solving and prefer to do it synchronously? Give Office Hours a try.

We took a break from discussing distributed cycle detection, the topic of our last couple Office Hours, and returned to a previous topic. Adrian, Sean, and Red spent a couple hours going over the problems with compile times that Red can into with his GTK wrapper library. After a lot of discussion to make sure the issue was fully understood, Sean suggested a slightly different design for Red to try that should greatly improved compile times.

Sean says... "It was nice to return to the using office hours to help solve a problem for a community member. Variety is after all, the spice of life."

Interested in giving attending Office Hours sometime? There's a [calendar you can subscribe to](https://calendar.google.com/calendar/ical/4465e68ae24131ae00461a40893f2637a2c9ac510e311a44ff78680e2f183ce3%40group.calendar.google.com/public/basic.ics) to stay up-to-date with the schedule. We do our best to keep the calendar up-to-date.

## Releases

- [ponyc 0.52.4](https://github.com/ponylang/ponyc/releases/tag/0.52.4)
- [ponyc 0.52.5](https://github.com/ponylang/ponyc/releases/tag/0.52.5)

## Highlighted Issues

Pony is a volunteer driven project. Nothing gets down without someone volunteering their time and helping to push things forward. Yes, there are folks who dedicate more time than others and a core team that dedicates time specifically for guiding Pony's development. Everyone's time is limited, so each week, we highlight a couple of issues that we hope will inspire someone to volunteer their time to help fix.

In addition to our highlighted issues, you can find more that we are looking for assistance on by visiting just about any [repository in the ponylang org](https://github.com/ponylang/) and looking for issues labeled with "help wanted"

If you are interested in working on either issue or any other issue from a Ponylang repository, you can get in touch on the issue in question or, even better, join us on the [Ponylang Zulip](https://ponylang.zulipchat.com/) to strike up a conversation.

This week's issues as selected by Ryan A. Hagenson are:

### Improve Range and Reverse

With the addition of empty Ranges nearing completion (see [pr #4280](https://github.com/ponylang/ponyc/pull/4280)), we once again want to highlight the official request to improve Range and Reverse in a consistent manner. During the RFC process for adding empty Ranges, we discussed potential traps within the current API which we would like to redesign out of possibility. A few different ideas were considered in the course of discussing the empty Ranges RFC, but we ultimately decided to accept the existing empty Ranges RFC in order to officially close _some_ of the known traps -- leaving the rest up to a new RFC to incorporate into a redesign. An example of a trap that will remain after empty Ranges are merged is that the type parameter to `Range` will still interact with numeric literals in an unexpected way so that `Range[ISize](0, 100, -1)` reasonably produces nothing (i.e., `[]`), but `Range[USize](0, 100, -1)` produces `[0]` -- this is because a `USize` literal of `-1` wraps around to the max `USize` value. Someone taking this issue should read the conversation around empty Ranges, consider the options presented there, and write up a new RFC that closes known traps in a consistent manner.

[rfcs #204](https://github.com/ponylang/rfcs/issues/204)

### Document use of `digestof`

We do not currently have a section or paragraph dedicated to helping new Pony developers learn the use of the `digestof` keyword. The first step to this issue is determining where it should go in the Tutorial, then deciding on the level of detail that should be included (i.e., should it be a section, a paragraph, a new page entirely, and so on). Someone taking this issue should have read through the Tutorial at least once (so you have an idea of all of its sections) and have used the `digestof` keyword. Ryan A. Hagenson (as the issue author and the writer here) is willing to help on this issue at any stage, reach out on the issue ticket or in Zulip.

[pony-tutorial issue #453](https://github.com/ponylang/pony-tutorial/issues/453)

## RFCs

Major changes in Pony go through a community driven process where members of the community can write up "requests for change" that detail what they think should be changed and why. RFCs can range from simple to complex. We welcome your participation.

The only RFC action this week was the ["Introduce Empty Ranges" RFC](https://github.com/ponylang/rfcs/blob/main/text/0076-introduce%20empty%20ranges.md) going into [PR review](https://github.com/ponylang/ponyc/pull/4280).

---

_Last Week In Pony_ is a weekly blog post to catch you up on the latest news for the Pony programming language. To learn more about Pony, check out [our website](https://ponylang.io), our Twitter account [@ponylang](https://twitter.com/ponylang), or our [Zulip community](https://ponylang.zulipchat.com).

Got something you think should be featured? There's a GitHub issue for that! Add a comment to the [open "Last Week in Pony" issue](https://github.com/ponylang/ponylang.github.io/issues?q=is%3Aissue+is%3Aopen+label%3Alast-week-in-pony).

Interested in making a change, or keeping up with changes to Pony? Check out the [RFC repo](https://github.com/ponylang/rfcs). Contributors welcome!

---

## File: docs/blog/posts/last-week-in-pony-010222.md

---
draft: false
authors:
  - theobutler
categories:
  - "Last Week in Pony"
title: "Last Week in Pony - January 2, 2022"
date: 2022-01-02T11:12:34-05:00
---

Happy new year! Version 0.45.2 of ponylang/ponyc has been released.



## Items of note

- Audio from the [2021-12-21](https://vimeo.com/916367409) Pony development sync call is available.

## Releases

- Version 0.45.2 of ponylang/ponyc has been released.
See the [release notes](https://github.com/ponylang/ponyc/releases/tag/0.45.2) for more details.

---

_Last Week In Pony_ is a weekly blog post to catch you up on the latest news for the Pony programming language. To learn more about Pony check out [our website](https://ponylang.io), our Twitter account [@ponylang](https://twitter.com/ponylang), or our [Zulip community](https://ponylang.zulipchat.com).

Got something you think should be featured? There's a GitHub issue for that! Add a comment to the [open "Last Week in Pony" issue](https://github.com/ponylang/ponylang.github.io/issues?q=is%3Aissue+is%3Aopen+label%3Alast-week-in-pony).

Interested in making a change, or keeping up with changes to Pony? Check out the [RFC repo](https://github.com/ponylang/rfcs). Contributors welcome!

---

## File: docs/blog/posts/last-week-in-pony-010520.md

---
draft: false
authors:
  - theobutler
categories:
  - "Last Week in Pony"
title: "Last Week in Pony - January 5, 2020"
date: 2020-01-05T12:59:32-05:00
---

Ponyup now supports the macOS builds of ponyc! We highly recommend that you update ponyup to the latest version, even if you are a Linux user.



## Items of note

- Version 0.5.0 of ponyup has been released. See the [release notes](https://github.com/ponylang/ponyup/releases/tag/0.5.0) for more details. We recommend that users of prior ponyup versions delete any `~/.pony/` or `~/.local/share/ponyup/` directories before [installing the latest ponyup](https://github.com/ponylang/ponyup#install-ponyup).

---

_Last Week In Pony_ is a weekly blog post to catch you up on the latest news for the Pony programming language. To learn more about Pony check out [our website](https://ponylang.io), our Twitter account [@ponylang](https://twitter.com/ponylang), or our [Zulip community](https://ponylang.zulipchat.com).

Got something you think should be featured? There's a GitHub issue for that! Add a comment to the [open "Last Week in Pony" issue](https://github.com/ponylang/ponylang.github.io/issues?q=is%3Aissue+is%3Aopen+label%3Alast-week-in-pony).

Interested in making a change, or keeping up with changes to Pony? Check out the [RFC repo](https://github.com/ponylang/rfcs). Contributors welcome!