Get in touch
Gabs

Case study by Gabriel Espinheira, senior full-stack engineer

Relay, my own AI harness

A macOS app where models from four providers write, draw, speak and render in one project folder, on plans I already pay for. I built it directing coding agents: first commit 28/09/2026, v0.9.0 on 08/10/2026.

A private tool that runs only on my Mac. Open to full-time roles and contracts.

What the repository says

236

commits in 12 days, from 28/09/2026

7

tagged versions in four days, up to v0.9.0

163,636

lines of product TypeScript

3,983

tests declared in 543 files

73

tools agents call over MCP

4

chat agents, plus 9 media generators

Counted from the repository on 09/10/2026 at commit d0aa1e5. Test figures are declarations in the code, not a test run.

Good models, no studio

My studio, SharpHaw, makes images, voices, films and copy for clients every week. The model I want for each job sits in a different app. A raw model forgets the prompt behind last week's image, can't keep a character the same across shots, and never looks at the frame it just rendered.

Relay is the harness around those models. It adds five things raw models lack, and every feature on this page answers to one of them.

Structure
traits and briefs become the prompt
Provenance
every file keeps the exact prompt that made it
Consistency
a character or a voice stays the same across shots
Choice
switch the model mid-session, fork, compare versions
Feedback
agents render and inspect their own frames

One project, every medium

A project is any folder on my Mac. Relay keeps its sessions, prompts and media in a relay/ folder beside the files. Each film below is a real recording of Relay 0.9.

01

Every agent in one composer

Claude Code, Codex, Grok and Antigravity run behind one provider contract, each on its own signed-in CLI. Claude Code runs through the Agent SDK and Codex through one shared app server. Grok and Antigravity share a single Agent Client Protocol adapter. Switch agents mid-conversation and the new one gets the transcript, trimmed to fit 24,000 characters.

Relay's Router page: the models Auto may pick, grouped by agent with their prices, and the routing table's first and second choice per task.
02

Auto picks the model for each message

Before each turn in Auto, a router model picks the agent, model and effort. It chooses from the models I switched on, using how fast each subscription is burning and a routing table built from public benchmark feeds. Its answer only counts if it names one of those options, and any failure runs the turn on the default model.

03

A studio where the prompt stays with the file

An artifact is a markdown file whose body is the prompt. Each generation writes a run record beside its output with the prompt, parameters, model and timing, so any image or voice can be traced and remade. Characters are built from traits, and writing a character's name in a prompt attaches its sheet.

04

Films written as code

A film is one HTML page whose seek function paints any frame as a pure function of time, with springs solved in closed form. Relay previews it live, renders it with eight subframes of motion blur, and mixes its sound to −14 LUFS. Agents lint their own films, prove each frame renders the same twice, and read contact sheets of the result.

05

Boards that agents draw on

Canvases are Excalidraw boards with cards for artifacts, sessions and links. Agents read and draw on them with six tools, and edits from me and from agents merge element by element. The board in this film is the plan of this page, drawn by an agent from one prompt.

A routine in Relay: its instructions, a weekday schedule at 09:00 in Lisbon, the next five runs, and exactly what the agent receives.
06

Work that runs without me

Routines run instructions on a schedule in a project, and the scheduler reads the wall clock every 15 seconds because timers stop while a Mac sleeps. Sub-agents split a session's work across agents and report back. Bits are always-on assistants with their own memory. They change a project only through a task, and only with the access I grant them.

One daemon, thin clients

One Node process owns every project, session, agent and file. The browser and the Electron app only render it. Commands go over HTTP, and every change comes back as an event, replayed from a buffer of 10,000 when a window reconnects. Closing every window never stops a turn.

App state lives in SQLite, built into Node, with full-text search kept in sync by triggers. Project content lives in files: prompts, run records, films and recordings sit in the project folder where any agent can read them.

A run record is a file, not a row.
  • Browser tabs
  • The Electron app

HTTP commands, SSE events

Relay daemon · Hono API, event log, MCP tools, terminals

Agents · Claude Code, Codex, Grok, Antigravity

Generators · GPT Image, Qwen3-TTS, ElevenLabs, Stable Audio, motion

SQLite for app state

Project folders for content

Built as if the web is hostile

The daemon runs agents that execute commands, on a port any web page can reach. The README tells every agent to treat the web and model output as hostile, and these are the defenses that follow from it.

Local only

The daemon binds 127.0.0.1, accepts only its own Host header against DNS rebinding, and rejects cross-site requests. Every call needs an HttpOnly cookie or the launch token, compared in constant time.

Agents get their own keys

Relay's MCP endpoint takes only per-session tokens kept in memory, never the launch token. Sub-agents can't delegate further, and a Bit's tool set has no shell and no project tools.

Files stay in the project

Every path resolves inside the project root before a read or write, nothing writes into .git, and deletes go to the Trash.

The browser waits for a person

In Relay's own browser, an agent's submits, sends, payments and deletes wait for my Allow, and Allow refuses if the page changed in the meantime.

Public addresses only

Link previews and agent fetches reach public addresses only, checked again at DNS lookup so rebinding can't swap them.

Agent-written films run sandboxed

A film is JavaScript an agent wrote, so it loads in a sandboxed frame through capability URLs signed per boot and expiring after six hours.

A Codex session in Relay asking before it runs a shell command: the command, then Allow once, Allow for this session and Decline.
In the ask mode, an agent waits for me before it runs a command. A real Codex session reviewing a component of this site.

The checks every change has to pass

The repository's instructions tell every coding agent to write the failing test first. A change is done when lint, types and the full test suite pass, and the pre-push hook runs all three on every push.

A fake agent for real tests

A deterministic fake provider, driven by keywords in the prompt, runs whole turns end to end without spending a credit: tools, approvals, questions and failures.

Design rules as a test

One test enforces the design system with seven exact-count allowlists. A new raw control fails it, and so does fixing an old one without lowering the count.

Plans that admit drift

Each of the 30 design plans ends with what was built, how it was checked and where the build departed from the plan. The code wins over the plan.

A refused release became a gate

On 08/10/2026 a version merge reached main without its release commit, and the release workflow refused to tag it and opened an issue. A new check on version pull requests followed, and v0.9.0 shipped about ten minutes later.

Twelve days, one author, many agents

Every commit carries my name, and the work happened in agent sessions. My local logs hold about 250 Claude Code and 164 Codex sessions on the repository. Relay has run sessions on itself since 30/09/2026.

I own code I didn't type through what surrounds it. Each major feature starts as a written plan, every agent reads the same rules, and the checks above must pass before anything is pushed.

The first Relay code landed four hours after the scaffold, as one commit of 17,941 lines with 216 tests. The films on my SharpOS and Taste Vault case studies were composed and rendered in Relay's motion engine, then encoded for the web.

Commits per day from 28/09 to 09/10/2026, merges excluded. The tallest day, 01/10, had 50.
Commits per day from 28/09 to 09/10/2026, merges excluded. The tallest day, 01/10, had 50.
DayCommits
28/093
29/0919
30/0918
01/1050
02/1017
03/1017
04/1018
05/1027
06/1030
07/1024
08/107
09/106

A hard call: films as a function of time

Problem

Agents had to make motion graphics with exact text, interface and brand, and preview them live. Every frame had to come out the same each time, so renders, checks and fixes could be trusted.

Options

A video model can't hold exact text or a brand, and every fix means paying for a new generation. Remotion needs a React bundle per film and a paid company license for my studio.

Choice

One HTML page with a seek function and a small vendored engine. Every animated value is a damped spring evaluated at time t, with no state carried between frames, so any frame can be painted in any order. That makes motion blur, parallel rendering and a determinism check possible.

Timers, animation frames, CSS transitions and random numbers are linted out.

Cost

Agents have to write in a constrained style and pass the lint. Rendering is offline and heavy: this site's films took 2 to 8 minutes each at 1080p, and 83 to 131 minutes when supersampled twice.

A smaller call: a Node daemon in a Bun repository

The repository runs on Bun, but the daemon runs on Node with its built-in SQLite, because the Claude Agent SDK and the test suite target Node. The cost is two runtimes in one repo, no Bun APIs in the server, and an experimental SQLite API kept behind one function.