I Built a Remote Control Plane for Cheap Coding Workers

How I use ChatGPT as the controller, a private GitHub control repo and an Actions-dispatched Mac runner as the delivery path, and OMP/GLM as the cheap execution worker — including what broke in the first real end-to-end test.

Updated 9/7/2026

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.

Diagram showing the verified loop: ChatGPT files a private agent:omp control issue, GitHub Actions dispatches the job to the self-hosted Mac runner, the OMP worker on GLM-5.3-Flash clones the target, implements, validates and commits locally, the job pushes the patch to a results branch in the private control repo with a short-lived token, and ChatGPT promotes it into the target pull request for review and merge, with RDC kept off the critical path
Swipe to see the full diagram.The verified control and data flow. The contract lives in a private control issue; the result returns through a results branch pushed with a short-lived token; only ChatGPT promotes the patch into the target pull request. RDC stays available for admin, debug and fallback. Evidence date: 7 September 2026. Download the editable Excalidraw source.

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

ComponentWhat it ownsWhat it does not own
ChatGPTintent, scope, issue creation, routing, review, merge decisionimplementation
GitHubissue, branch, PR and durable evidenceagent reasoning
Remote Desktop Commanderadmin, debug and fallback transport to the Macthe critical execution path, coding or durable state
OMPlocal worker session and coding toolsfinal authority
GLM-5.3-Flashlow-cost execution inside OMPscope 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

Diagram splitting the workflow into a control plane where ChatGPT and GitHub decide scope, file the private control issue, read the results proof and promote, review and merge the target pull request, and a worker plane where the GitHub Actions job, the self-hosted Mac runner and OMP on GLM take the dispatch, clone, implement, validate and commit locally before returning the patch, with a review boundary so the worker can never publish to the target or merge
Swipe to see the full diagram.The useful split is authority, not intelligence. The worker may take a dispatched job, inspect, edit, test and commit locally, and return a patch — never publish to the target repository or merge. The controller decides, files the contract, reads the proof and promotes it; it should not quietly do the implementation. Evidence date: 7 September 2026. Download the editable Excalidraw source.

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/null

That 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:

  1. a launchd LaunchAgent keeps an RDC supervisor process alive with KeepAlive;
  2. 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-remote

Secrets 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:

  1. Connect ChatGPT to GitHub and give it access to the repositories you want it to control.
  2. Connect RDC to the Mac and verify ChatGPT can make a real tool call on the machine.
  3. Authenticate gh locally so results can be published and pull requests created from the Mac.
  4. Install OMP and configure the OpenRouter-backed GLM route. Keep provider credentials local.
  5. Put repository guidance in AGENTS.md where a repo needs specific boundaries, style or validation rules.
  6. 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

Sequence diagram from the agent:omp control issue through the GitHub Actions dispatch to the Mac runner, OMP on GLM-5.3-Flash cloning the public target repo read-only, implementing, validating and committing locally, the job pushing the patch and evidence to a results/run branch in the private control repo with a short-lived repo-scoped token, ChatGPT reading the proof and promoting it into a target pull request, then CI checks and merge
Swipe to see the full diagram.Every hand-off in the verified dispatch is durable: the control issue, the results branch pushed with a short-lived token and the promoted pull request all outlive the runner, so nothing depends on a live session. Evidence date: 7 September 2026. Download the editable Excalidraw source.

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.

Diagram of four trust zones: ChatGPT holding GitHub access to both repositories and doing the deciding, reviewing and merging but never implementing; the private control repo holding durable issues, results branches and the encrypted OpenRouter secret while minting a short-lived repo-scoped token per job; the self-hosted Mac accepting jobs only from the private control repo and holding no target-repo credential; and the public target repo receiving only promoted pull requests with no self-hosted runner attached
Swipe to see the full diagram.Who holds which credential. The persistent runner answers only the private control repo; the worker's OpenRouter key resolves into a temporary config and its only output is a patch into the results branch; the public target repo never hosts a self-hosted runner and only ever receives pull requests ChatGPT promoted. Evidence date: 7 September 2026. Download the editable Excalidraw source.

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.