Humble beginnings
The first version of Gantry had a single orchestration pattern and it was hard-coded with Rust. After a while I added another pattern, for a different kind of workflow. And then a third one.
At some point I realized that I would want more flexibility. One of the key goals for Gantry was to structure some project-specific workflows that would not be appropriate to hard-code in a general tool.
I couldn't find any existing language or format that was a good fit, so eventually I set out to create a new one. The result is Gantry Patterns.
I hesitate to call it a language, but it's something like that.
These days, Gantry has no hard-coded orchestrations patterns at all. Instead, Gantry defines a set of primitives which can be used to define a wide range of patterns, and an interpreter.
Here is a short introduction:
Semantics: Simple and beautiful
Three producing constructs:
agentlaunches a harness like Claude Code or Codex and passes it a prompt. It also takes more than a dozen optional parameters.gateruns your test suite, or some other script, and provides machinery for rigorously handling verification.commandruns any external program, e.g. some script. This lets you inject basically anything you like into a workflow.
Four composition constructs:
foriterates over a list.ifprovides simple conditionals.callinvokes a Gantry pattern.stopends the Gantry run.
Two more keywords tie things together:
patternis the abstraction for a pattern or a subpattern.outputis used to reference files produced by agent, command or gate.
That's it. That's the whole language.
Syntax: God-awful ugly (but practical)
Gantry Patterns is a data-format-hosted DSL. It doesn't have its own grammar. Instead, it uses TOML. The inspiration here is Ansible playbooks, GitHub Actions workflows, Kubernetes manifests and others which use YAML.
The main benefit is that no custom parser is needed. Much of the tooling is readily available and battle-tested. And it's tolerably easy for anyone, man or machine, to get started editing Gantry patterns.
There are drawbacks, of course. It might be hard to express some things. It's impossible to express anything elegantly. As a multi-decade Rubyist, I sometimes feel disgusted by my own creation.
I thought about including an example of a Gantry pattern as a code block here in this post. But it was so ugly that I couldn't do it.
If you must, go read the default patterns that Gantry ships with here. If you want to spare your eyes, read about them in the next section.
Default patterns
Gantry ships with three default patterns. One of them is the one I use for 95% of my own Gantry runs, called 'build'. The other two are called 'map' and 'until'.
Build is about decomposition. Break a big chunk of work down into smaller chunks, small enough that a single agent session can execute them without working up too large a context window.
Map executes the same task multiple times for different values of some parameter. The original use case was to generate a number of reports, one for each country in a list of countries.
Until is my take on the much-talked-about 'loop' concept. Define a goal and then let agents iterate until the goal is met. It runs the Build pattern multiple times until a goal check returns true.
Custom patterns
I also have a number of patterns that are custom-made for various projects I am involved with. Here are some examples:
Producing Gantry releases. Once I have picked a commit and a version number, this pattern cuts the release, tests it, getting it ready for publication and also it updates various reference docs and a few more things. For example, it records screencasts of all Gantry runs which were involved in creating the released code and publishes them to the Replays section of the Gantry website.
A research automation workflow for a science-adjacent project I am working on. Given a certain kind of code, search for research papers, extract data from the papers and validate the data against scientific databases. Then multiple rounds of agents cross-checking and looking for issues until three consecutive rounds have not found anything objectionable.
Podcast post-production. Takes raw audio material, transcribes it, combs through it for known keywords and cuts the audio into segments. It also measures audio levels and produces a mix where all speakers sound evenly loud, converging on a target LUFS value. It also assembles various metadata and sets everything in order so everything can be published once I have verified the output by ear.
What more do you want?
These are just the first few things I personally wanted to use Gantry for. Well, except Until. That one I made just because people keep yapping about Ralph Wiggum and 'loop engineering'. Just so those people would feel at home, I made my own goal-loop pattern and dutifully ran it a couple of times to see if it worked. And it did. slow clap
I don't find 'loops' useful for the things I work on. I like to have more control. I'm happy to let my agent orchestration runs go on for a day or two. If something takes a week, I don't mind breaking it up so I can check in now and then. Yes, yes, I'm a fossil. Meatbag in the loop. So shoot me. Go use a truly avantgardistic meta-harness instead of dusty old Gantry.
The way I think about it, Gantry is about reliably sequencing an arbitrary number of agent sessions for tasks that are well-defined and too complex to fit into a single session without having the clanker self-lobotomize by filling up its context window.
For me, that's mostly software development, implementing complex features in this or that codebase. Or producing a set of artifacts, e.g. a series of screencasts of clanker walls-of-text. Or replacing some Rube Goldberg tangle of little scripts for executing some messy workflow.