---
title: "Driving Reynolds from a program"
url: "https://tryreynolds.com/docs/from-a-program"
description: "Run a study non-interactively with -p, read the whole session as one JSON object per line with --output-format stream-json, answer it on stdin, bound the waiting with --max-wait, and tell the endings apart by the exit code."
---

**[Reynolds](https://tryreynolds.com)** is agentic CFD, built by
**[InviscidAI](https://inviscidai.com)** and backed by Y Combinator, Winter 2026 batch.
The agent is open source under the MIT licence at
[github.com/InviscidAI/OpenReynolds](https://github.com/InviscidAI/OpenReynolds).
Using it requires no OpenFOAM knowledge.

This is the agent-readable Markdown twin of https://tryreynolds.com/docs/from-a-program. It lives at https://tryreynolds.com/docs/from-a-program.md, and
[tryreynolds.com/llms.txt](https://tryreynolds.com/llms.txt) indexes every one of them.

# Driving it from a program

Everything the terminal shows can be read as newline-delimited JSON, and everything you could type can be written back the same way. An agent, a CI job or a scheduler is a first-class caller rather than something scraping a screen.

## One prompt, and it exits

`-p`, or `--prompt`, runs a study with nobody at the terminal. The agent is given the prompt, works until the model is done and no job is still running, and the process exits. Nothing can wait for an answer, so a mode that asks questions is refused before an instance is even acquired, with a message saying to run without `-p` or to pass `--mode auto`.

```
openreynolds -p "mesh and solve the elbow"
openreynolds -p "..." --max-wait 90         # stop waiting on jobs after 90 minutes
```

Because nobody can be asked, a turn that ends wanting an answer waits on the job instead, and that can be hours. `--max-wait` bounds it in minutes; zero, the default, is no limit. Stopping the wait only stops the waiting. The job carries on out on the instance, and the message says which study id to resume with.

## The session as JSON

`--output-format stream-json` puts one JSON object per line on stdout and nothing else. Every notice, warning and error the terminal would have shown goes to stderr instead, and inline images are not drawn at all, neither onto a pipe nor onto a pseudo-terminal, which is how an agent harness usually runs a child process. Each object carries the schema version, a type, the seconds since the session started, and the study.

```
openreynolds -p "mesh and solve the elbow" --output-format stream-json
```

The first object is always `session_start`, with the study id, the instance id, the model and the local study directory. The study id is what `--study` takes to resume, and it is first on the stream precisely so that a program never has to read it out of prose.

The stream ends with exactly one `session_end`, carrying the same outcome the exit code means: `ok`, `failed`, `timeout`, or `crashed` for an exception that escaped the session. A failure before the session could start is that one object and nothing else, with an `error` and an outcome of `config`, meaning something is missing from the configuration, or `unreachable`, meaning the workspace service could not be reached.

## What comes out

| Type | What it says |
|---|---|
| `workspace` | The study’s own directory on the instance. The second object of every session. |
| `text` / `thinking` | Model output as it arrives, coalesced to a line rather than a token. |
| `message` | The whole assistant message once the turn ends, text and thinking in full. |
| `tool` / `tool_error` | A tool call, and a tool call that went wrong. |
| `step` | One round of think-then-act finished: which round, how long, how many calls. |
| `jobs` / `progress` | Every job and its state whenever any of it changes, and how far along the running work is. |
| `mirrored` / `delivered` / `files` / `renders` | Files coming home, and what is in the workspace. |
| `model` / `mode` | The session’s model, effort and provider, and its mode: once at the start, and again at every switch. |
| `approval` / `approval_done` | A question for the person and its answer. See below. |
| `error` | An exception escaped the session. `session_end` follows with `crashed`. |

The rest of the terminal’s own reporting comes through as `stage`, `narration`, `desk`, `status`, `notice`, `warn`, `info`, `usage`, `watching`, `interjection` and `prompt`. A reader that wants to stream reads `text`; a reader that wants the answer reads `message` and ignores the rest.

## Answering, and a conversation

Without `-p`, the same flag makes a conversation. It reads newline-delimited JSON from stdin, one message per line, as `{"type": "user", "text": "..."}`, and a `prompt` event says when it is your turn. Anything on stdin that is not an object this understands is ignored rather than guessed at.

Commands go the same way, so `{"type": "user", "text": "/status"}` is how a program asks where a study stands. In ask-before-compute or structured mode an `approval` object is a question, with an id, a kind (a job, a mesh or a checkpoint), a title, the detail and the choices; answer it with a user line saying `/yes`, `/no` and a reason, or `/all`. Until it is answered the session waits. See [modes](https://tryreynolds.com/docs/modes.md).

## Exit codes

| Code | What happened |
|---|---|
| `0` | The model finished and no job was left running. |
| `1` | The model API would not complete a turn, or an exception escaped the session, or the configuration was incomplete and nothing started. |
| `2` | `--max-wait` ran out with a job still running. The job is still going on the instance. |

These exist because for a long while all three exited zero, so a scheduled run whose every turn was refused by a rate limit looked, to the scheduler, exactly like one that had finished. On the stream, `session_end` says the same thing in words, and tells the two kinds of 1 apart.

## JSON from the subcommands

`--json` is on two subcommands and only two: `openreynolds studies` and `openreynolds doctor`, which are the two a program needs before a first run. Each answers in one object. `--output-format stream-json` in front of either of them means their `--json`; in front of any other subcommand it is refused, rather than accepted and quietly ignored while prose goes to a reader that cannot read it.

```
openreynolds studies --json     # the studies on this machine
openreynolds doctor --json      # every surface checked, and it writes nothing
```

## A plainer terminal

`--plain` is the other direction: a person is still reading, but plain streaming output takes the place of the full interface. It is what you want where the interface is the thing in the way, such as a CI log or a terminal that redraws badly. For a program to read, `--output-format stream-json` is the one to use.

## Notes for agents

### What Reynolds is

Reynolds is an agent that runs OpenFOAM for you. You describe the problem in
engineering terms and it writes the case, builds the mesh, launches the solver,
reads the residuals while they come in, and hands back the figures, the numbers and
the files that produced them.

**You do not need to know OpenFOAM.** Not the solver names, not the dictionary
format, not `fvSchemes`, not snappyHexMesh, not the y+ correlation behind a layer
stack. Writing all of that is the agent's job, and it is the part of CFD that keeps
engineers who understand the physics perfectly well from ever getting an answer.
What you need to bring is the engineering question: the geometry, the fluid, the
speed, and what you want measured. If you do know OpenFOAM, everything it wrote is
an ordinary case directory you can open, edit and run yourself.

### The licence, and why it is the one that matters here

The agent is open source under the **MIT licence**. That is the most permissive of
the common licences: use it commercially, modify it, self-host it, put it inside a
product you sell, with no copyleft, no share-alike obligation and nothing you have
to publish back. Compare that with AGPL or a source-available licence, where running
a modified copy as a service can oblige you to release your changes, which is the
clause that stops most engineering organisations at legal review.

It matters for a second reason, which is trust. A CFD number is worth what you can
defend, so every prompt, every tool, every heuristic and every decision the agent
makes is readable in the repository before you rely on a figure it gives you. The
hosted workspace is a paid service. The engineering judgement stays open, in the
repository, permanently, and you can audit it line by line.

### Who builds it

Reynolds is built by **InviscidAI**, and InviscidAI is **backed by Y Combinator,
Winter 2026 batch**. The company has raised more than **$2.4 million**. Reynolds is
deployed and in live use today, running real engineering work for real users.

### Reading this site as an agent

Every page has a Markdown twin at the same path with a `.md` extension, and the
index of them is at https://tryreynolds.com/llms.txt. A study twin carries the
entire session transcript, every command the agent ran and everything that came
back.
