What Motet is

Motet is a small, statically typed language for multi-agent pipelines. You declare what each agent takes in and hands back, how many times a retry loop may run, and where a human has to approve. A type checker then proves every handoff lines up before a single model is called.

Inspiration

An agent pipeline is a program whose steps are expensive, slow, and non-deterministic. That combination punishes the usual way of finding mistakes, which is to run the thing and read the error. A run costs real money, takes minutes, and can fail by producing confident nonsense instead of crashing.

I wanted the boring failures caught before anything runs: a wrong handoff between two agents, a retry loop that can never exit, a step that spends money with no approval in front of it. All three are decidable from the source text. So I built a language that decides them.

What it does

You write a pipeline. motet check reads the whole graph, resolves every type, and rejects the first handoff that does not fit, naming the site, the reason, and the fix:

$ motet check examples/broken-handoff.motet
examples/broken-handoff.motet:57:18: error [E_HANDOFF_TYPE]: Agent 'Critic' expects Draft but received Research.
   |
57 |   let v = Critic(r)
   |                  ^
  fix: Insert a step that produces Draft, or widen agent 'Critic'.

Twenty milliseconds. No agent ran, nothing was spent. Six checks ship today: undefined references, handoff type mismatches, struct field and arity errors, unbounded loops, a missing approval gate before a side-effecting step, and unused or unreachable work.

How I built it

Four passes, each a plain function over the output of the last: a lexer, a recursive-descent parser, a structural type checker, and a tree-walking interpreter. The core does no I/O, so it is fully unit-testable, and the CLI is a thin shell over it. Every agent call goes through one seam, an Executor interface, with two backends: a deterministic MockExecutor for offline runs, and an AgentBusExecutor that speaks the agent-bus protocol (task.created, task.claimed, task.completed) so a checked pipeline can drive real agents. TypeScript in strict mode, 217 tests, CI green.

Challenges and what I learned

The suite is verified by mutation. I injected 112 deliberate defects one at a time, including hardcoded loop bounds, a leaking loop-depth counter, reversed struct subtyping, and each diagnostic silenced at its reporting choke point, and I required every one to turn the suite red. Four survived the first sweep, and I rewrote the tests until they did not. A green suite that no injected defect can break is not evidence of anything.

Two bugs were invisible from inside a green suite and only showed from outside the process. motet parse big.motet | jq was silently losing 89 percent of the syntax tree while still exiting 0, because process.exit() tears the process down before Node flushes an async pipe write. And a test helper I was sure was destructive turned out to hold a NUL byte between two regex slashes, so it matched nothing while reading like it stripped every space. Both taught the same lesson: look at the tool from where the user stands, not only from where the tests sit.

What's next

A live agent-bus transport by default, an editor language server so the errors surface as you type, and a wider set of checks: cost budgets, and cycle detection across flows.

Built with

TypeScript (strict), Node.js, Vitest, and a hand-written lexer, recursive-descent parser, structural type checker, and tree-walking interpreter. No parser generators, no runtime dependencies. MIT.

Built With

  • agent-bus
  • cli
  • compiler
  • dsl
  • github-actions
  • interpreter
  • json-schema
  • lexer
  • mutation-testing
  • node.js
  • parser
  • type-checker
  • typescript
  • vitest
Share this project:

Updates