<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Angga Bachtiar</title>
    <description>The latest articles on DEV Community by Angga Bachtiar (@anggbchtr).</description>
    <link>https://dev.to/anggbchtr</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4084410%2F00f667af-1cc9-49c9-b3ae-8c98971391a8.jpg</url>
      <title>DEV Community: Angga Bachtiar</title>
      <link>https://dev.to/anggbchtr</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/anggbchtr"/>
    <language>en</language>
    <item>
      <title>Building a Human-in-the-Loop Autonomous Coding Agent with n8n and Telegram</title>
      <dc:creator>Angga Bachtiar</dc:creator>
      <pubDate>Wed, 19 Aug 2026 07:37:50 +0000</pubDate>
      <link>https://dev.to/anggbchtr/building-a-human-in-the-loop-autonomous-coding-agent-with-n8n-and-telegram-46gp</link>
      <guid>https://dev.to/anggbchtr/building-a-human-in-the-loop-autonomous-coding-agent-with-n8n-and-telegram-46gp</guid>
      <description>&lt;p&gt;What if I could send a coding task from my phone, put the phone back in my pocket, and let an AI agent work on my development machine?&lt;/p&gt;

&lt;p&gt;And what if, whenever the agent wanted to do something that required my permission, it could simply message me on Telegram?&lt;/p&gt;

&lt;p&gt;That was the idea behind a small automation project I recently built.&lt;/p&gt;

&lt;p&gt;The final architecture combines &lt;strong&gt;Telegram, Cloudflare Tunnel, n8n, a lightweight Express.js runner, and Codex&lt;/strong&gt; to create a remote, human-in-the-loop autonomous coding workflow.&lt;/p&gt;

&lt;p&gt;The interesting part is that almost everything still runs on my own development machine.&lt;/p&gt;

&lt;p&gt;I didn't deploy n8n or my Codex runner to a public server just to make Telegram communication possible. Instead, I use &lt;strong&gt;Cloudflare Tunnel&lt;/strong&gt; as the bridge between the public internet and my local n8n instance.&lt;/p&gt;

&lt;p&gt;The result looks roughly like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;                    INTERNET
                        │
                        ▼
┌─────────────┐   ┌───────────────┐
│  Telegram   │──▶│  Cloudflare   │
│    Bot      │   │    Tunnel     │
└─────────────┘   └───────┬───────┘
                          │
                          │ secure tunnel
                          ▼
                 ┌────────────────┐
                 │   Local n8n    │
                 └───────┬────────┘
                         │
                         ▼
                 ┌────────────────┐
                 │ Express Runner │
                 └───────┬────────┘
                         │
                         ▼
                 ┌────────────────┐
                 │     Codex      │
                 └───────┬────────┘
                         │
                         ▼
                    Local Repo
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And when Codex requires approval:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Codex
  │
  │ approval required
  ▼
Express Runner
  │
  ▼
n8n
  │
  ▼
Telegram
  │
  │ Approve / Reject
  ▼
n8n
  │
  ▼
Express
  │
  ▼
Codex continues
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This article explains how I built it and, more importantly, some of the architectural problems that appeared once I tried turning a coding agent into something I could actually operate remotely.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Goal
&lt;/h2&gt;

&lt;p&gt;AI coding agents are already capable of doing much more than generating code snippets.&lt;/p&gt;

&lt;p&gt;They can inspect a repository, modify multiple files, execute shell commands, run tests, inspect failures, fix their implementation, and repeat the process.&lt;/p&gt;

&lt;p&gt;But there was still one inconvenience in my workflow:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;I had to be sitting at my development machine to initiate and supervise the work.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I wanted something closer to this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Me, from Telegram:

"resume-web:
Add rate limiting to the API
and add tests."

        ↓

Codex works on my PC

        ↓

Telegram:

"Task completed.
12 tests passed."
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;All from my phone.&lt;/p&gt;

&lt;p&gt;But I had another requirement.&lt;/p&gt;

&lt;p&gt;I didn't want to achieve remote coding by simply giving an AI agent unrestricted control over my machine.&lt;/p&gt;

&lt;p&gt;If the agent wants to perform something outside the permissions I've granted, I still want to make that decision.&lt;/p&gt;

&lt;p&gt;So the real goal became:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Build an autonomous coding workflow where normal development work can proceed independently, while sensitive actions are escalated back to my phone for approval.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I think &lt;strong&gt;human-in-the-loop autonomous coding&lt;/strong&gt; is the best description for it.&lt;/p&gt;




&lt;h1&gt;
  
  
  Why Telegram?
&lt;/h1&gt;

&lt;p&gt;The first question was how I wanted to interact with the system.&lt;/p&gt;

&lt;p&gt;I could have built a web dashboard.&lt;/p&gt;

&lt;p&gt;But then I'd have to build authentication, a frontend, notifications, mobile responsiveness, task history, and several other things unrelated to the actual experiment.&lt;/p&gt;

&lt;p&gt;Telegram already gives me most of that.&lt;/p&gt;

&lt;p&gt;It provides:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a mobile interface&lt;/li&gt;
&lt;li&gt;push notifications&lt;/li&gt;
&lt;li&gt;bots&lt;/li&gt;
&lt;li&gt;commands&lt;/li&gt;
&lt;li&gt;messages&lt;/li&gt;
&lt;li&gt;inline buttons&lt;/li&gt;
&lt;li&gt;callback events&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;More importantly:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;it's already on my phone.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;So Telegram became my remote control.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Telegram = Remote UI
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h1&gt;
  
  
  The Localhost Problem
&lt;/h1&gt;

&lt;p&gt;There was an immediate problem.&lt;/p&gt;

&lt;p&gt;My n8n instance runs locally.&lt;/p&gt;

&lt;p&gt;Something like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;http://localhost:5678
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's perfectly fine when I'm sitting at my computer.&lt;/p&gt;

&lt;p&gt;But Telegram obviously cannot send webhook requests to:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;localhost:5678
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;localhost&lt;/code&gt; only exists from the perspective of my machine.&lt;/p&gt;

&lt;p&gt;I needed:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Public Internet
      ↓
Public HTTPS endpoint
      ↓
My local n8n
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;One solution would have been deploying n8n somewhere publicly accessible.&lt;/p&gt;

&lt;p&gt;But I specifically wanted to keep this automation running on my own machine.&lt;/p&gt;

&lt;p&gt;That's where &lt;strong&gt;Cloudflare Tunnel&lt;/strong&gt; entered the architecture.&lt;/p&gt;




&lt;h1&gt;
  
  
  Exposing Local n8n with Cloudflare Tunnel
&lt;/h1&gt;

&lt;p&gt;Cloudflare Tunnel lets a locally running service become reachable through a public hostname without exposing the machine itself directly.&lt;/p&gt;

&lt;p&gt;Instead of opening a router port like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Internet
   ↓
Public IP
   ↓
Port forwarding
   ↓
My PC
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I run &lt;code&gt;cloudflared&lt;/code&gt; locally.&lt;/p&gt;

&lt;p&gt;The connection is initiated &lt;strong&gt;outbound&lt;/strong&gt; from my machine toward Cloudflare.&lt;/p&gt;

&lt;p&gt;Conceptually:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;My PC
 │
 │ outbound connection
 ▼
Cloudflare
 │
 │ public HTTPS
 ▼
Internet
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then Cloudflare can map a public hostname to my local n8n service.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;automation.example.com
          │
          ▼
  Cloudflare Tunnel
          │
          ▼
http://localhost:5678
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This means Telegram can reach a public HTTPS webhook while n8n itself continues running locally.&lt;/p&gt;

&lt;p&gt;So my webhook path can conceptually become:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;https://automation.example.com/webhook/telegram
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;while the actual service receiving it is still:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;http://localhost:5678
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I don't have to expose n8n's port directly to the internet.&lt;/p&gt;




&lt;h1&gt;
  
  
  Why Cloudflare Tunnel Instead of Port Forwarding?
&lt;/h1&gt;

&lt;p&gt;I didn't want my architecture to become:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Telegram
   ↓
MY_PUBLIC_IP:5678
   ↓
n8n
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That would require exposing an inbound service from my development network.&lt;/p&gt;

&lt;p&gt;Cloudflare Tunnel gives me a cleaner model:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;                    Public Internet
                           │
                           ▼
                    ┌─────────────┐
                    │ Cloudflare  │
                    └──────┬──────┘
                           │
                     secure tunnel
                           │
                    ┌──────▼──────┐
                    │ cloudflared │
                    │   My PC     │
                    └──────┬──────┘
                           │
                           ▼
                    localhost:5678
                           │
                           ▼
                          n8n
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The tunnel is initiated from the local machine rather than requiring inbound port forwarding.&lt;/p&gt;

&lt;p&gt;For a personal automation project like this, that was exactly what I wanted.&lt;/p&gt;




&lt;h1&gt;
  
  
  Cloudflare Is Only the Ingress Layer
&lt;/h1&gt;

&lt;p&gt;One thing that's important to clarify is that Cloudflare isn't running my coding workflow.&lt;/p&gt;

&lt;p&gt;It doesn't execute Codex.&lt;/p&gt;

&lt;p&gt;It doesn't orchestrate the tasks.&lt;/p&gt;

&lt;p&gt;And it doesn't run n8n.&lt;/p&gt;

&lt;p&gt;Its job in this architecture is much smaller:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Cloudflare Tunnel
       =
Public ingress to local n8n
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The actual execution still happens locally:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;LOCAL DEVELOPMENT MACHINE

cloudflared
     │
     ▼
    n8n
     │
     ▼
Express.js
     │
     ▼
Codex
     │
     ▼
Repository
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I like this separation because each component has a very specific responsibility.&lt;/p&gt;




&lt;h1&gt;
  
  
  Why n8n?
&lt;/h1&gt;

&lt;p&gt;Once Telegram can reach my local environment, I still need something to orchestrate the workflow.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Receive Telegram message
        ↓
Parse project + instruction
        ↓
Validate input
        ↓
Call Codex runner
        ↓
Wait for completion
        ↓
Format result
        ↓
Send Telegram response
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's where n8n fits.&lt;/p&gt;

&lt;p&gt;My simplified workflow looks something like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Telegram Trigger
       │
       ▼
Parse Command
       │
       ▼
HTTP Request
       │
       ▼
Local Codex Runner
       │
       ▼
Format Result
       │
       ▼
Telegram Message
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;n8n isn't the coding agent.&lt;/p&gt;

&lt;p&gt;It's the &lt;strong&gt;orchestrator&lt;/strong&gt;.&lt;/p&gt;




&lt;h1&gt;
  
  
  The Local Codex Runner
&lt;/h1&gt;

&lt;p&gt;I still needed something that could actually launch and communicate with Codex.&lt;/p&gt;

&lt;p&gt;I built a lightweight Express.js service for that.&lt;/p&gt;

&lt;p&gt;Conceptually, n8n calls:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="err"&gt;POST /codex
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;with something like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"project"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"resume-web"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"task"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Add rate limiting to the API and add tests."&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The runner maps a project alias to an actual local directory:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;projects&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;resume-web&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;D:&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="s2"&gt;Projects&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="s2"&gt;resume-web&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This was an important design decision.&lt;/p&gt;

&lt;p&gt;I don't allow Telegram to specify an arbitrary filesystem path.&lt;/p&gt;

&lt;p&gt;Instead of:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"path"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"C:&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="s2"&gt;whatever&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="s2"&gt;someone&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="s2"&gt;wants"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;the remote interface only knows:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;resume-web
backend-api
personal-rag
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The runner decides where those repositories actually exist.&lt;/p&gt;

&lt;p&gt;So:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Telegram
   │
   │ "resume-web"
   ▼
Express
   │
   │ lookup
   ▼
D:\Projects\resume-web
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That gives me a simple allowlist around what the remote system can operate on.&lt;/p&gt;




&lt;h1&gt;
  
  
  The First Codex Implementation
&lt;/h1&gt;

&lt;p&gt;My original runner was surprisingly simple.&lt;/p&gt;

&lt;p&gt;It essentially spawned Codex:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;child&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;spawn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;codex.cmd exec --sandbox workspace-write -&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;cwd&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;projectPath&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;shell&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then I collected stdout and stderr:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;stdout&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;""&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;stderr&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;""&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nx"&gt;child&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;stdout&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;data&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;stdout&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="nx"&gt;child&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;stderr&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;data&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;stderr&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Finally:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;child&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;close&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;code&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;success&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;code&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;output&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;stdout&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;stderr&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That produced:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Telegram
    ↓
Cloudflare
    ↓
n8n
    ↓
Express
    ↓
codex exec
    ↓
Repository
    ↓
Codex exits
    ↓
Express response
    ↓
n8n
    ↓
Telegram
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And it worked.&lt;/p&gt;

&lt;p&gt;I could literally send a coding instruction from my phone and receive the Codex result back on Telegram.&lt;/p&gt;

&lt;p&gt;But there was still one major problem.&lt;/p&gt;




&lt;h1&gt;
  
  
  What Happens When Codex Needs Approval?
&lt;/h1&gt;

&lt;p&gt;Giving an agent permission to edit code is one thing.&lt;/p&gt;

&lt;p&gt;Giving it unrestricted access to everything on the machine is another.&lt;/p&gt;

&lt;p&gt;I wanted Codex to work autonomously inside a controlled environment, but still ask me when it crossed a permission boundary.&lt;/p&gt;

&lt;p&gt;Normally this is easy when you're sitting in front of the CLI:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Codex wants to perform an action.

Approve?

[Y] Yes
[N] No
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But my Codex process is being triggered remotely.&lt;/p&gt;

&lt;p&gt;That created the most interesting problem in the entire project:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;How can Codex pause a task, tell Express that it needs approval, send that request all the way to my phone, and then continue the exact same task after I approve it?&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h1&gt;
  
  
  Process Completion Is Not Task Completion
&lt;/h1&gt;

&lt;p&gt;My first runner treated:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;child&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;close&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;...)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;TASK FINISHED
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That makes sense for a one-shot command.&lt;/p&gt;

&lt;p&gt;But it becomes limiting once the agent needs an interactive lifecycle.&lt;/p&gt;

&lt;p&gt;What I actually needed was:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Persistent Codex process

      │
      ├── Turn
      │    │
      │    ├── working
      │    ├── working
      │    ├── approval required
      │    │       ↓
      │    │      WAIT
      │    │       ↓
      │    │    approved
      │    │
      │    ├── continue
      │    │
      │    └── completed
      │
      └── ready for next turn
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That led me to using &lt;strong&gt;Codex App Server&lt;/strong&gt;.&lt;/p&gt;




&lt;h1&gt;
  
  
  Turning Approval Into an Event
&lt;/h1&gt;

&lt;p&gt;With an event-driven Codex integration, Express can receive lifecycle messages while the task is still running.&lt;/p&gt;

&lt;p&gt;An approval request is no longer equivalent to task completion.&lt;/p&gt;

&lt;p&gt;Instead:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;approval request
      ≠
task completed
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Codex can effectively tell the runner:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;"I'm still working on this turn,
but I need a human decision before
I can continue."
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The runner stores that pending approval and triggers a dedicated n8n webhook.&lt;/p&gt;




&lt;h1&gt;
  
  
  The Approval Journey
&lt;/h1&gt;

&lt;p&gt;This is probably my favorite part of the system.&lt;/p&gt;

&lt;p&gt;Suppose Codex needs permission.&lt;/p&gt;

&lt;p&gt;The request travels:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Codex
   ↓
Express Runner
   ↓
n8n
   ↓
Cloudflare Tunnel
   ↓
Telegram
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On my phone I receive something like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;⚠️ Codex Approval Required

Project:
resume-web

Action:
&amp;lt;requested operation&amp;gt;

[ Approve ]   [ Reject ]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I press:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Approve
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and the response travels back through the automation:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Telegram
   ↓
Cloudflare
   ↓
n8n
   ↓
Express Runner
   ↓
Codex
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The important part is that this isn't a new coding task.&lt;/p&gt;

&lt;p&gt;It's a response to the &lt;strong&gt;existing paused Codex turn&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Codex receives the decision and continues exactly where it stopped.&lt;/p&gt;




&lt;h1&gt;
  
  
  Keeping n8n Synchronous
&lt;/h1&gt;

&lt;p&gt;There was one design decision from my original implementation that I intentionally kept.&lt;/p&gt;

&lt;p&gt;The n8n request waits until Codex finishes.&lt;/p&gt;

&lt;p&gt;Why?&lt;/p&gt;

&lt;p&gt;Because it gives me an extremely simple final-result pipeline:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Codex finishes
      ↓
Express responds
      ↓
n8n immediately continues
      ↓
Telegram receives result
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I don't need:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;GET /task/status
GET /task/status
GET /task/status
GET /task/status
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;or another polling mechanism.&lt;/p&gt;

&lt;p&gt;Express simply awaits the Codex &lt;strong&gt;turn&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Conceptually:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;codexRunner&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;runTurn&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="nx"&gt;project&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;task&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If approval never happens:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Task
 ↓
Codex
 ↓
Complete
 ↓
HTTP response
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If approval happens:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Task
 ↓
Codex
 ↓
Approval required
 ↓
Telegram
 ↓
Approve
 ↓
Codex continues
 ↓
Complete
 ↓
HTTP response
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;From the original n8n workflow's perspective, both eventually produce the same result.&lt;/p&gt;




&lt;h1&gt;
  
  
  Profiling the Agent
&lt;/h1&gt;

&lt;p&gt;I also wanted to know how long Codex actually takes to perform tasks.&lt;/p&gt;

&lt;p&gt;So my runner logs execution duration.&lt;/p&gt;

&lt;p&gt;Originally:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"project"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"resume-web"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"durationMs"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;128421&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"success"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But remote approval introduced an interesting problem.&lt;/p&gt;

&lt;p&gt;Suppose:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Codex works         2 minutes
I ignore Telegram   6 minutes
Codex continues     1 minute
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Total wall-clock time:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;9 minutes
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But saying:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;"Codex took 9 minutes"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;wouldn't really be accurate.&lt;/p&gt;

&lt;p&gt;So I separated:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"durationMs"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;540000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"codexActiveMs"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;180000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"approvalWaitMs"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;360000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"approvalCount"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This opens up some interesting profiling possibilities later:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Average task duration
Average Codex active time
Approval frequency
Human response time
Failure rate
Commands per task
Performance by project
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h1&gt;
  
  
  The Full Architecture
&lt;/h1&gt;

&lt;p&gt;After putting everything together, my system now looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;                         INTERNET

                  ┌───────────────────┐
                  │     Telegram      │
                  │       Bot         │
                  └─────────┬─────────┘
                            │
                            │ HTTPS
                            ▼
                  ┌───────────────────┐
                  │    Cloudflare     │
                  │      Tunnel       │
                  └─────────┬─────────┘
                            │
                    outbound tunnel
                            │
════════════════════════════╪══════════════════
                    LOCAL MACHINE
                            │
                            ▼
                  ┌───────────────────┐
                  │        n8n        │
                  │   Orchestration   │
                  └─────────┬─────────┘
                            │
                            │ localhost
                            ▼
                  ┌───────────────────┐
                  │  Express Runner   │
                  │                   │
                  │ • project map     │
                  │ • task lifecycle  │
                  │ • approval state  │
                  │ • profiling       │
                  │ • logging         │
                  └─────────┬─────────┘
                            │
                            │ JSON-RPC
                            ▼
                  ┌───────────────────┐
                  │ Codex App Server  │
                  │                   │
                  │ • thread          │
                  │ • turn            │
                  │ • tools           │
                  │ • approvals       │
                  └─────────┬─────────┘
                            │
                            ▼
                  ┌───────────────────┐
                  │ Local Repository  │
                  │                   │
                  │ Code / Git / Test │
                  └───────────────────┘
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each component has a very specific job:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Telegram
    → Remote interface

Cloudflare Tunnel
    → Public ingress to my local environment

n8n
    → Workflow orchestration

Express.js
    → Codex process + task management

Codex
    → Autonomous coding agent

Git repository
    → Actual development workspace
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I think that separation is one of the reasons the project stayed relatively understandable.&lt;/p&gt;




&lt;h1&gt;
  
  
  Is This Really Autonomous Coding?
&lt;/h1&gt;

&lt;p&gt;I wouldn't describe this as fully autonomous software development.&lt;/p&gt;

&lt;p&gt;I'm still responsible for deciding what gets built, reviewing important changes, controlling permissions, and deciding what eventually reaches production.&lt;/p&gt;

&lt;p&gt;But once I provide a task, the implementation loop can happen independently:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Inspect
   ↓
Plan
   ↓
Modify
   ↓
Build
   ↓
Test
   ↓
Fail
   ↓
Debug
   ↓
Fix
   ↓
Retest
   ↓
Complete
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And if the agent reaches something requiring my judgment:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Agent
  ↓
Human decision required
  ↓
Telegram
  ↓
Me
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's why I prefer the term:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Human-in-the-loop autonomous coding.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h1&gt;
  
  
  Why I Didn't Just Give Codex Full Access
&lt;/h1&gt;

&lt;p&gt;There is obviously a much easier solution to the approval problem:&lt;/p&gt;

&lt;p&gt;Give the coding agent unrestricted access.&lt;/p&gt;

&lt;p&gt;Then there are no approval interruptions.&lt;/p&gt;

&lt;p&gt;But that's exactly what I didn't want.&lt;/p&gt;

&lt;p&gt;Instead, the philosophy behind my setup is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Normal development work
        ↓
     AUTOMATIC

Sensitive operation
        ↓
    ASK HUMAN
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Read repository
        → automatic

Edit workspace
        → automatic

Run tests
        → automatic

Inspect Git
        → automatic

Sensitive command
        → approval

Outside workspace
        → approval

Network operation
        → approval

Destructive operation
        → approval
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The goal isn't to remove myself from development.&lt;/p&gt;

&lt;p&gt;It's to move myself:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;FROM

execution loop

TO

decision loop
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And I think that's a much more useful form of automation.&lt;/p&gt;




&lt;h1&gt;
  
  
  What I Want to Add Next
&lt;/h1&gt;

&lt;p&gt;The current system already works, but there are several things I'd like to improve.&lt;/p&gt;

&lt;p&gt;I want commands such as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/status
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;to return something like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Project: resume-web
Task: Add API rate limiting
Status: Running
Duration: 02:41
Approvals: 0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I'd also like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/cancel
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;to interrupt an active Codex turn.&lt;/p&gt;

&lt;p&gt;Another obvious improvement is task queues.&lt;/p&gt;

&lt;p&gt;I don't necessarily want:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Task A ─┐
Task B ─┼─→ same repository
Task C ─┘
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;all editing files simultaneously.&lt;/p&gt;

&lt;p&gt;Instead:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;resume-web

Task A → RUNNING
Task B → QUEUED
Task C → QUEUED
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Git branch isolation would also make the system much safer:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Telegram task
      ↓
Create branch
      ↓
Codex works
      ↓
Tests
      ↓
Return diff
      ↓
Human review
      ↓
Merge
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And eventually, persistent state would help recover from crashes or restarts while approvals are pending.&lt;/p&gt;




&lt;h1&gt;
  
  
  What I Learned
&lt;/h1&gt;

&lt;p&gt;At first, I thought this project would mostly be about AI.&lt;/p&gt;

&lt;p&gt;It wasn't.&lt;/p&gt;

&lt;p&gt;The most interesting problems ended up being orchestration problems:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;How does Telegram reach localhost?

Who owns task state?

What does "completed" mean?

How does an agent pause?

How do approvals travel between systems?

How does execution resume?

What happens if something crashes?

Which operations should be automatic?

Which operations require humans?
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The model is only one part of an autonomous system.&lt;/p&gt;

&lt;p&gt;The infrastructure around the model determines whether it can actually operate reliably.&lt;/p&gt;




&lt;h1&gt;
  
  
  The Bigger Idea
&lt;/h1&gt;

&lt;p&gt;Before building this, my workflow looked roughly like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Open laptop
   ↓
Open terminal
   ↓
Navigate to repository
   ↓
Launch coding agent
   ↓
Give instruction
   ↓
Watch execution
   ↓
Approve action
   ↓
Wait
   ↓
Review
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Take phone
   ↓
Open Telegram
   ↓
Send task
   ↓
Put phone away
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then, if necessary:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Telegram:

⚠️ Codex needs approval

[Approve] [Reject]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And eventually:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Telegram:

✅ Task completed
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;My development machine is still doing the actual work.&lt;/p&gt;

&lt;p&gt;I'm still responsible for the important decisions.&lt;/p&gt;

&lt;p&gt;But I no longer need to sit in front of it while the agent performs every step.&lt;/p&gt;

&lt;p&gt;And that's probably the biggest thing I learned from this project:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;The next step for coding agents isn't necessarily removing developers. It's reducing how much of the execution loop requires a developer's continuous attention.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Sometimes the biggest improvement isn't another model.&lt;/p&gt;

&lt;p&gt;It's building better infrastructure around the one you already have.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>node</category>
      <category>n8n</category>
      <category>coding</category>
    </item>
  </channel>
</rss>
