I have spent a slightly ridiculous amount of time trying to answer one question: how do I keep a strong frontier model in charge of coding work without paying frontier-model prices for every file read, shell command, edit and test?
The latest answer is much simpler than the agent stacks I built before it.
ChatGPT is the controller. GitHub is the durable task system. GitHub Actions dispatches the work to a self-hosted runner on my Mac; Remote Desktop Commander (RDC), the original bridge, is now admin, debug and fallback only. OMP is the local coding harness. GLM-5.3-Flash, reached through OpenRouter, is the cheap worker model that does the long middle of the job.
The lifecycle is deliberately boring:
discuss → issue → worker → branch → pull request → review → merge
There is no scheduler, no autonomous issue triage, no worker that can merge its own code and no second orchestration framework pretending to be a manager. The point is to spend expensive intelligence on judgment and cheap tokens on execution.
The problem is not model quality. It is where you spend it
A strong coding model is useful at the start and end of a task: reconstructing intent, spotting ambiguity, deciding scope, reviewing a diff and judging whether the evidence is good enough to merge.
It is often economically daft to make that same model pay for the entire middle as well.
Repository inspection, repetitive edits, command output, lint passes, build logs and browser checks can consume far more context than the initial planning. If every one of those turns stays on the expensive model, the controller quietly becomes the worker and the cost advantage disappears.
My rule is therefore structural rather than prompt-based: the controller does not implement. If the worker gets stuck, the first response is to improve the contract or send a correction back to the same worker, not to let the expensive controller grab the keyboard.
That distinction is what makes the system useful.
Five components, five jobs
| Component | What it owns | What it does not own |
|---|---|---|
| ChatGPT | intent, scope, issue creation, routing, review, merge decision | implementation |
| GitHub | issue, branch, PR and durable evidence | agent reasoning |
| Remote Desktop Commander | admin, debug and fallback transport to the Mac | the critical execution path, coding or durable state |
| OMP | local worker session and coding tools | final authority |
| GLM-5.3-Flash | low-cost execution inside OMP | scope or merge decisions |
The harness and the model are separate choices. OMP is the harness; GLM is the model. I can swap OMP for Codex as the worker harness without changing the control model, and I can choose a different worker model without making it the controller.
This sounds pedantic until you debug the system. If you confuse the harness with the model, every failure becomes "the AI failed" rather than a specific question: did the bridge die, did the harness wedge, did the model produce bad work, or did the controller give it a crap contract?
Authority is the real boundary
The controller sends a contract across that boundary: an issue with concrete deliverables and acceptance criteria.
The worker sends proof back: a diff and validation results, which the controller turns into a pull request.
That makes review cheaper as well. ChatGPT does not need to replay the whole worker transcript. It can inspect the issue, the changed files and the validation evidence. The worker's self-assessment is not proof; the repository state is.
GitHub is the memory that matters
Agent sessions are useful caches. They are terrible databases.
For each task I use one issue, one branch and one PR. The issue contains the contract. The branch contains checkpoints. The PR contains the proposed result and review evidence. That means a worker session can disappear without taking the project history with it.
If the OMP session is healthy, ChatGPT resumes the same session and sends the next instruction. That preserves repository context and avoids paying to reconstruct everything from scratch.
If the session is lost or wedged, a fresh worker starts against the same issue and branch. It rebuilds from GitHub rather than from a conversation memory. At worst, I want to lose the current uncommitted step — not the task itself.
This is also why I do not reuse an unrelated parked worker session for a new issue. One task gets one worker context. Durable state survives outside it.
RDC turns ChatGPT into a remote task controller
RDC is the thin but important part of the system. ChatGPT can use it to inspect the authorised Mac, invoke local tools and start an OMP session in the right repository. The bridge carries commands; it is not itself an agent and it does not need the repository's reasoning context. This was the original dispatch route; since the 7 September follow-up below, GitHub Actions dispatches the worker and RDC is the admin, debug and fallback lane — worth having, never the critical path.
The local path looks roughly like this:
# prerequisites
gh auth status
omp --version
# controller launches the bounded worker
omp --cwd /path/to/repo \
--model z-ai/glm-5.3-flash \
--approval-mode yolo \
-p "Implement GitHub issue #N, validate and commit locally. Do not push and do not open a PR." \
</dev/nullThat final </dev/null is there for a reason. This article's test found that OMP print mode can wait for stdin when launched through RDC. Closing stdin explicitly turned a hanging launch into a working non-interactive worker session.
That is exactly the sort of boring integration edge case I want this architecture to expose.
The RDC process has its own recovery layer
Because RDC is a route from ChatGPT into the Mac, it is a control-plane dependency — no longer for dispatch, but still for admin, debug and fallback. I do not want a dead Node process to require me to find a terminal and restart it manually.
The current Mac setup therefore has two local recovery layers:
- a
launchdLaunchAgent keeps an RDC supervisor process alive withKeepAlive; - that supervisor runs the RDC remote process, restarts it when it exits, and watches its logs for known fatal states.
The local child-process recovery was tested by deliberately killing RDC and confirming that it came back and accepted another real tool call.
Useful checks are intentionally mundane:
launchctl print gui/$(id -u)/com.rajeev.rdc-remote
tail -f ~/Library/Logs/rdc-remote.log
launchctl kickstart -k gui/$(id -u)/com.rajeev.rdc-remoteSecrets stay out of all of this. GitHub authentication lives in gh; the OpenRouter key lives in local OMP configuration; neither belongs in an issue, commit, PR or article.
The first real test found a bigger failure than a dead child process
This article was meant to be the next end-to-end proof of the workflow.
ChatGPT created GitHub issue #116, reached the Mac through RDC, confirmed the local OMP and GitHub CLI setup, and launched an OMP session on z-ai/glm-5.3-flash. The GLM worker successfully read the issue, inspected the site's MDX and Excalidraw conventions, and read the workflow documentation that defined the task.
Then the entire RDC device disappeared from the ChatGPT side before the worker had pushed a branch.
Repeated RDC calls returned no connected device. The already-tested local launchd supervisor did not restore end-to-end reachability during this run.
That distinction matters. I had proved that the supervisor can recover a killed local RDC child. I had not proved that it can recover every failure between ChatGPT and the authorised device. Process supervision is not the same thing as end-to-end availability.
So this test does not get a fake green tick.
There was no pushed worker branch to recover from at that point, so the GitHub delivery lane was still clean. I used the connected GitHub control plane to land this article and its Excalidraw sources instead. That is a fallback, not the intended worker path, and it leaves one clear reliability task: determine why the authorised RDC device vanished and add an end-to-end health/recovery mechanism rather than only supervising the local child process.
I prefer that result to a demo that quietly papers over the failure. The architecture is now more accurately specified because a real task found the boundary of what had actually been tested.
How I set this up from scratch
The minimal version is six steps:
- Connect ChatGPT to GitHub and give it access to the repositories you want it to control.
- Connect RDC to the Mac and verify ChatGPT can make a real tool call on the machine.
- Authenticate
ghlocally so results can be published and pull requests created from the Mac. - Install OMP and configure the OpenRouter-backed GLM route. Keep provider credentials local.
- Put repository guidance in
AGENTS.mdwhere a repo needs specific boundaries, style or validation rules. - Smoke-test one tiny issue all the way through issue → worker → result → PR → controller review before trusting the loop with substantive work.
Steps 2 and 3 are the original RDC-era setup. Since the follow-up below, the dispatch entry point is a GitHub Actions runner on the same Mac, and the worker deliberately holds no broad credential for the target repository.
For an existing repository, ChatGPT should also check for an in-flight branch or PR before launching another worker. Duplicate workers are a very efficient way to buy two sets of tokens for one result.
Codex is an alternative harness, not a promotion
If OMP is unavailable or a particular task fits Codex better, Codex can occupy the worker slot.
Under Actions dispatch, the route for that slot is:
ChatGPT → control issue → Mac runner → Codex CLI worker → GLM model → results branch → ChatGPT review
The important bit is that Codex does not become a second controller just because its harness is more capable. In the verified alternative path, Codex can still run the same low-cost GLM worker model. Changing harness and changing model are independent decisions.
That gives me flexibility without turning the diagram into an agent zoo.
The cost controls are mostly boring discipline
The cheap-model choice helps, but the larger savings come from not spraying context everywhere.
- Keep issues small enough to fit one reviewable PR.
- Read relevant file sections instead of dumping whole repositories into context.
- Filter large command outputs before returning them to the model.
- Resume the same worker for corrections rather than spawning a fresh one.
- Escalate to a stronger model because the task demonstrated difficulty, not because its title sounded important.
- Keep the frontier controller out of implementation.
This is less glamorous than a twelve-agent orchestration framework. It is also much closer to the thing I actually wanted: talk to one capable controller, have it create durable work, send the expensive middle to cheap workers, and get a PR back to review.
What is deliberately not automated yet
At the moment I do not assume any of these exist:
- automated issue triage;
- scheduled autonomous workers;
- mandatory CI gates created by this workflow;
- automatic merging;
- a magical self-healing remote control plane that survives every RDC connectivity failure.
The last item is now evidence rather than cautionary prose.
The desired end state is still simple: I should be able to discuss a task with ChatGPT, let it create the issue, let it dispatch OMP/GLM or Codex/GLM through the remote Mac, and only see the work again when there is useful evidence to review.
This first proper publication test proved a lot of that path — and, more usefully, found the bit that is still bollocks under failure. That is the next thing to fix.
Follow-up, 7 September 2026: GitHub Actions now dispatches the worker
The vanishing RDC device had one clear fix: remove RDC from the critical execution path.
The official GitHub Actions self-hosted runner, v2.337.0, is now installed on the Mac and runs as a launchd service — the same supervision discipline the RDC process already had, applied to the worker's entry point instead of a remote-desktop bridge.
The runner is attached only to the private Rajeev-SG/chatgpt-agent-workflow control repository, not this public site repo. That separation is deliberate security hygiene: a persistent self-hosted runner should never be attached directly to a public repository, where untrusted workflow runs could request execution on my Mac. Untrusted code gets no route to the machine; the private control repo does.
The control flow I actually verified is:
ChatGPT → private control issue labelled agent:omp → GitHub Actions → Mac runner → OMP/GLM-5.3-Flash → local commit → result branch in the private control repo → ChatGPT → target pull request → review and merge
ChatGPT creates the private control issue and applies agent:omp. GitHub Actions dispatches the job to the Mac, where OMP clones the target, implements the target issue, validates it and commits locally. The worker deliberately has no broad GitHub credential for the public target repository. Instead, the Actions job packages the changed files and patch and pushes that result back to a results/run-… branch in the private control repo using its short-lived repo-scoped token. ChatGPT then reads that result and promotes it into the target repository's pull request through GitHub.
That extra hand-off is intentional rather than accidental. It keeps the cheap worker powerful enough to edit and test while preventing it from directly publishing into unrelated repositories, and it means the persistent Mac runner is never attached to public or untrusted workflows. The OpenRouter credential is an encrypted secret on the private control repo and is resolved into an isolated temporary OMP provider config for the job; it is not written into the issue or logs.
RDC is no longer on the critical execution path. It stays useful for administration, debugging and fallback — including diagnosing the runner while this test was being built — but GitHub can dispatch the coding job and receive its durable result without RDC being connected.
This follow-up itself was the end-to-end proof: Actions run #12 dispatched issue #118 to the self-hosted Mac runner, OMP/GLM-5.3-Flash made this edit, the Velite content build and full Next.js production build both passed, and the result returned through the private control repo for ChatGPT to review and turn into this pull request.