<?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 Repository-Aware AI Coding Loop in Rust</title>
      <dc:creator>Angga Bachtiar</dc:creator>
      <pubDate>Thu, 10 Sep 2026 16:13:27 +0000</pubDate>
      <link>https://dev.to/anggbchtr/building-a-repository-aware-ai-coding-loop-in-rust-1ef5</link>
      <guid>https://dev.to/anggbchtr/building-a-repository-aware-ai-coding-loop-in-rust-1ef5</guid>
      <description>&lt;p&gt;Most AI coding examples stop after generating code.&lt;/p&gt;

&lt;p&gt;The model receives a prompt, returns a proposed implementation, and the application prints the result. That is useful for experimentation, but it is not a complete engineering workflow. Code is only useful after it has been written to the repository, compiled, tested, and reviewed.&lt;/p&gt;

&lt;p&gt;I built &lt;a href="https://github.com/anggadb/loop-engine" rel="noopener noreferrer"&gt;Loop Engine&lt;/a&gt; to explore a more practical approach:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;plan → edit → verify → review → reflect
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Loop Engine is an open-source Rust CLI that runs this workflow against a local repository. It uses OpenRouter for model access and allows a different model to handle each phase.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the engine does
&lt;/h2&gt;

&lt;p&gt;A normal run performs the following steps:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Inspects the target repository.&lt;/li&gt;
&lt;li&gt;Creates an implementation plan.&lt;/li&gt;
&lt;li&gt;Lets the implementation agent read and modify files.&lt;/li&gt;
&lt;li&gt;Runs the repository's verification commands.&lt;/li&gt;
&lt;li&gt;Sends the actual changes and test output to a reviewer.&lt;/li&gt;
&lt;li&gt;Reflects on the result.&lt;/li&gt;
&lt;li&gt;Repeats when verification or review fails.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The loop only reports completion when:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The repository contains real file changes.&lt;/li&gt;
&lt;li&gt;The implementation agent explicitly finishes.&lt;/li&gt;
&lt;li&gt;No tool error remains unresolved.&lt;/li&gt;
&lt;li&gt;Every configured verification command passes.&lt;/li&gt;
&lt;li&gt;The reviewer approves the implementation.&lt;/li&gt;
&lt;li&gt;The reflector agrees that the objective is complete.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A model cannot complete the workflow merely by returning the word &lt;code&gt;COMPLETE&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Rust?
&lt;/h2&gt;

&lt;p&gt;The engine executes file operations and local verification commands, so predictable behavior matters.&lt;/p&gt;

&lt;p&gt;Rust gives the project:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Strong types for loop state and tool actions.&lt;/li&gt;
&lt;li&gt;Explicit error handling.&lt;/li&gt;
&lt;li&gt;Safe path validation.&lt;/li&gt;
&lt;li&gt;Good support for asynchronous HTTP and subprocess execution.&lt;/li&gt;
&lt;li&gt;A single installable CLI binary.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The engine also uses optimistic concurrency for file updates. An existing file must be read before it can be written. Immediately before writing, the engine confirms that the file still matches the version the agent read.&lt;/p&gt;

&lt;p&gt;This prevents the agent from silently overwriting a change made by the developer during the run.&lt;/p&gt;

&lt;h2&gt;
  
  
  Installing Loop Engine
&lt;/h2&gt;

&lt;p&gt;Clone the repository:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/anggadb/loop-engine.git
&lt;span class="nb"&gt;cd &lt;/span&gt;loop-engine
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Install the CLI:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;cargo &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;--path&lt;/span&gt; &lt;span class="nb"&gt;.&lt;/span&gt; &lt;span class="nt"&gt;--locked&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Create your local settings:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="n"&gt;Copy-Item&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;env&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;example&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;env&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nx"&gt;Copy-Item&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;loop-engine.json.example&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;loop-engine.json&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Add an OpenRouter API key to &lt;code&gt;.env&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;OPENROUTER_API_KEY=your-openrouter-key
OPENROUTER_HTTP_REFERER=your-localhost-url
OPENROUTER_X_TITLE=Loop Engine
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The environment file and local model configuration are excluded from Git.&lt;/p&gt;

&lt;h2&gt;
  
  
  Configuring models by phase
&lt;/h2&gt;

&lt;p&gt;Each phase can use a different OpenRouter model:&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;"models"&lt;/span&gt;&lt;span class="p"&gt;:&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;span class="nl"&gt;"plan"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"openai/gpt-4.1-mini"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"implement"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"openai/gpt-5.1-codex"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"review"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"openai/gpt-4.1-mini"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"reflect"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"openai/gpt-4.1-mini"&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;span class="nl"&gt;"requests"&lt;/span&gt;&lt;span class="p"&gt;:&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;span class="nl"&gt;"timeout_seconds"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;600&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;span class="nl"&gt;"execution"&lt;/span&gt;&lt;span class="p"&gt;:&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;span class="nl"&gt;"max_tool_calls"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"timeout_seconds"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;120&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"checks"&lt;/span&gt;&lt;span class="p"&gt;:&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;span class="p"&gt;}&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;For free experimentation, the phase models can be replaced with an available free model:&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;"models"&lt;/span&gt;&lt;span class="p"&gt;:&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;span class="nl"&gt;"plan"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"qwen/qwen3-coder:free"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"implement"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"qwen/qwen3-coder:free"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"review"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"qwen/qwen3-coder:free"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"reflect"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"qwen/qwen3-coder:free"&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;span class="nl"&gt;"execution"&lt;/span&gt;&lt;span class="p"&gt;:&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;span class="nl"&gt;"max_tool_calls"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"timeout_seconds"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;120&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"checks"&lt;/span&gt;&lt;span class="p"&gt;:&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;span class="p"&gt;}&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;Free models have stricter rate limits and may be less reliable. The exact list of available models can also change.&lt;/p&gt;

&lt;h2&gt;
  
  
  Inspecting a repository safely
&lt;/h2&gt;

&lt;p&gt;Before sending repository content to a model, inspect the generated snapshot:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="n"&gt;loop-engine&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;--repo&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"C:\projects\my-app"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;--inspect&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This command does not load the API key or make an OpenRouter request.&lt;/p&gt;

&lt;p&gt;The snapshot is bounded and excludes hidden entries, common dependency directories, generated output, symlinks, binary files, and credential-like filenames. It still cannot guarantee that source files contain no sensitive values, so reviewing the snapshot remains important.&lt;/p&gt;

&lt;h2&gt;
  
  
  Dynamic verification detection
&lt;/h2&gt;

&lt;p&gt;Loop Engine detects common build systems from files in the target directory:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Repository marker&lt;/th&gt;
&lt;th&gt;Verification&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Cargo.toml&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;cargo test&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;go.mod&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;go test ./...&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;package.json&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Available &lt;code&gt;test&lt;/code&gt;, &lt;code&gt;typecheck&lt;/code&gt;, and &lt;code&gt;build&lt;/code&gt; scripts&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Pytest configuration&lt;/td&gt;
&lt;td&gt;&lt;code&gt;python -m pytest&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;You can preview the selected checks without running them:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="n"&gt;loop-engine&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;--repo&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"C:\projects\my-app"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;--inspect-checks&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Example output for a Go repository:&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;"source"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"detected"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"checks"&lt;/span&gt;&lt;span class="p"&gt;:&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;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"program"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"go"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"args"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"test"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"./..."&lt;/span&gt;&lt;span class="p"&gt;]&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;span class="p"&gt;]&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;Explicit checks override detection:&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;"execution"&lt;/span&gt;&lt;span class="p"&gt;:&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;span class="nl"&gt;"max_tool_calls"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"timeout_seconds"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;180&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"checks"&lt;/span&gt;&lt;span class="p"&gt;:&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;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"program"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"go"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"args"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"test"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"./..."&lt;/span&gt;&lt;span class="p"&gt;]&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;span class="p"&gt;]&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;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 model cannot invent arbitrary shell commands. It can request verification, but the engine only executes commands resolved from trusted configuration or fixed detection rules.&lt;/p&gt;

&lt;h2&gt;
  
  
  Running an objective
&lt;/h2&gt;

&lt;p&gt;To run the engine against a repository:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="n"&gt;loop-engine&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;`
&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;--repo&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"C:\projects\my-app"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;`
&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;--env-file&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"C:\tools\loop-engine\.env"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;`
&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;--config&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"C:\tools\loop-engine\loop-engine.json"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;`
&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="s2"&gt;"Remove the deprecated endpoint and update its tests"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;`
&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;--iterations&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;3&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;During implementation, the coding agent can request these operations:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;List repository files.&lt;/li&gt;
&lt;li&gt;Read a text file.&lt;/li&gt;
&lt;li&gt;Create a text file.&lt;/li&gt;
&lt;li&gt;Replace an existing text file.&lt;/li&gt;
&lt;li&gt;Run the configured checks.&lt;/li&gt;
&lt;li&gt;Finish the implementation.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The engine always runs authoritative verification again after the final edit.&lt;/p&gt;

&lt;p&gt;Exit code &lt;code&gt;0&lt;/code&gt; means the work passed the completion rules. Exit code &lt;code&gt;2&lt;/code&gt; means the iteration or tool budget ended before verified completion. Other nonzero codes indicate execution errors.&lt;/p&gt;

&lt;h2&gt;
  
  
  Prompt logs and recovery journals
&lt;/h2&gt;

&lt;p&gt;Every run creates a &lt;code&gt;.loop-engine&lt;/code&gt; directory inside the target repository:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;.loop-engine/
  run-&amp;lt;id&amp;gt;.jsonl
  run-&amp;lt;id&amp;gt;/
    iteration-001/
      0001-plan.jsonl
      0002-implement.jsonl
      0003-implement.jsonl
      0004-review.jsonl
      0005-reflect.jsonl
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each prompt log records:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The iteration and phase.&lt;/li&gt;
&lt;li&gt;The selected model.&lt;/li&gt;
&lt;li&gt;The system and user prompts.&lt;/li&gt;
&lt;li&gt;The response or error.&lt;/li&gt;
&lt;li&gt;Request timing.&lt;/li&gt;
&lt;li&gt;Tool results associated with the prompt.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The request is written before the API call starts. If the process is interrupted or the provider times out, the input remains available for diagnosis.&lt;/p&gt;

&lt;p&gt;The main journal records original and replacement file content before every write. This provides a manual recovery path if a run fails after modifying files.&lt;/p&gt;

&lt;p&gt;Logs may contain source code and model output, so &lt;code&gt;.loop-engine/&lt;/code&gt; should remain excluded from version control.&lt;/p&gt;

&lt;h2&gt;
  
  
  Handling incomplete runs
&lt;/h2&gt;

&lt;p&gt;An incomplete result includes a &lt;code&gt;stop_reason&lt;/code&gt;, 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;verification_failed
tool_budget_exhausted
unresolved_tool_error
no_changes
invalid_review_response
review_requires_changes
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It also contains:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The changed file list.&lt;/li&gt;
&lt;li&gt;Verification commands and their output.&lt;/li&gt;
&lt;li&gt;The latest repository snapshot.&lt;/li&gt;
&lt;li&gt;Every model response.&lt;/li&gt;
&lt;li&gt;Prompt-log paths.&lt;/li&gt;
&lt;li&gt;The recovery-journal path.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Edits remain in the target repository after an incomplete run. This makes the result inspectable, but it also means the tool should preferably be used in a clean branch or disposable checkout.&lt;/p&gt;

&lt;h2&gt;
  
  
  Current limitations
&lt;/h2&gt;

&lt;p&gt;Loop Engine is still experimental.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;Replaces complete file contents instead of applying structured patches.&lt;/li&gt;
&lt;li&gt;Does not delete or rename files.&lt;/li&gt;
&lt;li&gt;Does not automatically roll back failed runs.&lt;/li&gt;
&lt;li&gt;Does not resume interrupted prompts.&lt;/li&gt;
&lt;li&gt;Detects build systems only from the selected repository root.&lt;/li&gt;
&lt;li&gt;Depends on model compliance with its JSON action protocol.&lt;/li&gt;
&lt;li&gt;Runs verification commands with the current user's operating-system permissions.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The verification process is not an operating-system sandbox. Project build scripts may access the network, environment variables, and files available to the user.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I learned
&lt;/h2&gt;

&lt;p&gt;The interesting part of an AI coding agent is not the initial code response. It is the control loop around that response.&lt;/p&gt;

&lt;p&gt;The model needs constrained tools, real observations, explicit verification, durable logs, and a completion rule that cannot be satisfied by confidence alone.&lt;/p&gt;

&lt;p&gt;Loop Engine is my attempt to make that loop small enough to understand while still useful against real repositories.&lt;/p&gt;

&lt;p&gt;The source is available on GitHub:&lt;/p&gt;

&lt;p&gt;👉 &lt;a href="https://github.com/anggadb/loop-engine" rel="noopener noreferrer"&gt;github.com/anggadb/loop-engine&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Feedback, issues, and contributions are welcome.&lt;/p&gt;

</description>
      <category>rust</category>
      <category>ai</category>
      <category>opensource</category>
      <category>programming</category>
    </item>
    <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>
