Orcrist is a desktop coding agent whose harness is written fresh for every task, as a finite state machine, by a stronger model than the one that carries the work out.

Every coding agent has a harness: the loop around the model that hands it tools, decides when a step is done, and says what happens when one fails. Usually that loop lives in the app's source code and is the same for every task, so the only thing that varies with the job is the prompt. Here it is the other way round. The loop itself is the per-task artefact, and it is an object you can read, check and correct before any code is touched.

You give the agent a task. Before touching anything, the authoring model writes a machine for it, grounded in the grammar and the authoring guide in this repo. The execution model then runs that machine one state at a time: each state's instruction goes to it on its own, it works with real tools, the runtime measures what it can and records what the state is declared to report, and the machine's guards decide where the run goes next. The run ends in a final state, success or otherwise.

Splitting the two is the point. Deciding what the phases are, what would prove each one works, and what each is allowed to touch is judgment, and it is exactly what a smaller or local model lacks. Writing that down once, as a machine, is what lets the smaller model be the one that does the work.

A machine also has to say what happens when things go wrong, which a prompt does not. Failure paths, retry budgets and escalation states are all declared before the run starts, and the validator rejects a machine that cannot terminate, that leaves a state unreachable, or that lets the model grade its own work where a command could have settled it. That is what the DSL underneath is for: it is the language the harness is specified in, not the product.

A run in progress. The transcript carries the states as they happen; the panel on the right holds the harness itself: the machine, the state now executing with the instruction it was given, and the store, each location marked by who writes it, the agent, a measurement, or a set.

orcrist/

├── src/core/ the agent: the authoring loop, the executor, the tools, the providers

├── src/orcrist/ the language the harness is written in: parser, validator, evaluator

├── src/renderer/ the desktop UI

├── electron/ the main process and the preload bridge

├── metamodel/

│ ├── orcrist.langium the grammar, ground truth for the authoring step

│ └── authoring-guide.md the criteria the authoring model is told to follow

├── examples/*.orc canonical syntax, shown to the authoring model

├── scripts/ the self-test

├── prompts/ project prompts to run the agent against

├── brand/ the mark, the lockup, the icons

├── screens/ the screenshots in this README

└── docs/ how the app works, in depth

The grammar, the guide and the examples are read at runtime, not compiled in: the app walks up from its own folder until it finds metamodel/orcrist.langium. Editing the language therefore changes what the authoring step is grounded in without rebuilding anything, and a checkout with metamodel/ missing has nothing to author machines against.

- Node 20 or newer, with npm

- macOS, Linux or Windows

- An API key for Anthropic or OpenAI, or a local Ollama with a model that supports tool calling

git clone <this repo>

cd orcrist

npm installnpm install downloads the Electron binary in a postinstall step. If that step fails (see Troubleshooting), the rest still installs, and npm test and npm run build work without it.

npm startThat compiles the main process, bundles the renderer and launches the app. For iterative work:

npm run devwhich runs the Vite dev server with hot reload and points Electron at it.

npm run build # both halves

npm run build:main # main process + core, via tsc

npm run build:renderer # renderer, via ViteOutput goes to dist/: dist/main/ for the Electron side, dist/renderer/ for the UI. There is no packaging step yet; npm start runs the built app in place.

npm testThis parses and validates every .orc in examples/, checks that a set of deliberately broken machines is rejected for the right reasons, runs a machine end to end against a scripted mock provider, and checks the things a real run depends on: that cancelling a run actually stops the request, that a measured value beats a model's claim, that a state restricted to no tools is handed none, and that the store travels with every state instruction.

It needs no API key and makes no network calls.

- Open Settings → Providers and put in an API key. For Ollama there is no key: the models installed on the machine are listed for you under whichever role you point at it.

- In Settings → Models, choose the two models. They are the two halves described above:

- Authoring writes the harness. Give it the strongest model you have, because it decides the shape of the whole run, what counts as proof that each part works, and what each state is allowed to touch.

- Execution runs each state. This is where a smaller or local model is affordable, because the machine supplies the structure it would otherwise have to hold in its head.

- Create a project. A project is a name plus a workspace folder; every shell command and file operation is sandboxed to that folder, and the session history lives inside it under .orcrist-agent/.

- Send a task.

The two roles, set separately. Point one at Ollama and the models installed on the machine are listed underneath it, and clicking one uses it.

Settings → Palette changes the whole app's colours. Six palettes ship; each is six seed colours and everything else is derived from them.

"Electron failed to install correctly." The postinstall download failed on its own (a network hiccup, a proxy, a GitHub rate limit) while the rest of the install reported success. The symptom is node_modules/electron/ with no dist/ inside. Re-run just that step:

npm run fix:electronIf it fails again the error says why. Behind a proxy, set HTTPS_PROXY and retry. If GitHub is unreachable or rate-limiting, use a mirror:

ELECTRON_MIRROR="https://npmmirror.com/mirrors/electron/" npm run fix:electron"Could not find metamodel/orcrist.langium." metamodel/ is missing from the checkout, or the app was copied somewhere on its own. It looks up the folder chain from its own location, so metamodel/ and examples/ belong at the root of the repo, beside package.json.

A run stops at "no machine for this message". The authoring model decided the task is a single question with no process in it, and said so rather than wrapping one step in ceremony. The reason is printed in the transcript.