<?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: CodeKing</title>
    <description>The latest articles on DEV Community by CodeKing (@codekingai).</description>
    <link>https://dev.to/codekingai</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%2F3843914%2F002834b8-4fde-497a-b61e-521c08cc7017.jpg</url>
      <title>DEV Community: CodeKing</title>
      <link>https://dev.to/codekingai</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/codekingai"/>
    <language>en</language>
    <item>
      <title>"My Coding Agent Remembered Sessions, Not Work. That Was the Bug"</title>
      <dc:creator>CodeKing</dc:creator>
      <pubDate>Fri, 03 Jul 2026 08:26:28 +0000</pubDate>
      <link>https://dev.to/codekingai/my-coding-agent-remembered-sessions-not-work-that-was-the-bug-2fig</link>
      <guid>https://dev.to/codekingai/my-coding-agent-remembered-sessions-not-work-that-was-the-bug-2fig</guid>
      <description>&lt;p&gt;A coding agent can keep a thread alive and still feel forgetful.&lt;/p&gt;

&lt;p&gt;That was the annoying part I ran into after fixing session continuity in &lt;a href="https://github.com/codeking-ai/cligate" rel="noopener noreferrer"&gt;CliGate&lt;/a&gt;, my local control plane for a resident assistant, Claude Code, Codex CLI, model routing, channels, and scheduled work.&lt;/p&gt;

&lt;p&gt;The session was there. The follow-up worked. But repeated tasks were still slower than they should have been.&lt;/p&gt;

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

&lt;p&gt;Because the agent remembered the conversation, not the work.&lt;/p&gt;

&lt;h2&gt;
  
  
  A live session is not the same thing as usable memory
&lt;/h2&gt;

&lt;p&gt;Session continuity solves one real problem.&lt;/p&gt;

&lt;p&gt;It keeps follow-ups like these from turning into a fresh start:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;continue&lt;/li&gt;
&lt;li&gt;do the same for this file&lt;/li&gt;
&lt;li&gt;retry that&lt;/li&gt;
&lt;li&gt;explain the error&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That matters.&lt;/p&gt;

&lt;p&gt;But it does not solve another problem that shows up the second you repeat a workflow a few days later.&lt;/p&gt;

&lt;p&gt;If the agent previously figured out:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;which button actually works&lt;/li&gt;
&lt;li&gt;which step is a dead end&lt;/li&gt;
&lt;li&gt;which field needs special handling&lt;/li&gt;
&lt;li&gt;which rule the user always wants applied&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;then a still-open session is not enough.&lt;/p&gt;

&lt;p&gt;The useful thing is not that the thread exists.&lt;/p&gt;

&lt;p&gt;The useful thing is that the agent can recall what made the last run succeed.&lt;/p&gt;

&lt;h2&gt;
  
  
  The bug was “process amnesia”
&lt;/h2&gt;

&lt;p&gt;The first run usually contains the expensive part.&lt;/p&gt;

&lt;p&gt;That is when the agent explores, verifies, backtracks, and discovers the tiny details that are never in the idealized prompt:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;this page hides the action under a menu&lt;/li&gt;
&lt;li&gt;this editor is actually an iframe&lt;/li&gt;
&lt;li&gt;this project wants Chinese replies and concise status updates&lt;/li&gt;
&lt;li&gt;this environment URL is different from production&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Before I fixed this in CliGate, those details existed only as raw execution history.&lt;/p&gt;

&lt;p&gt;That meant the agent technically had logs, but not reusable memory.&lt;/p&gt;

&lt;p&gt;On the next similar task, it often had to rediscover too much.&lt;/p&gt;

&lt;p&gt;That is not intelligence.&lt;/p&gt;

&lt;p&gt;That is paying the same debugging cost twice.&lt;/p&gt;

&lt;h2&gt;
  
  
  I stopped treating memory like a chat transcript problem
&lt;/h2&gt;

&lt;p&gt;The wrong model was:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;save more history -&amp;gt; keep more context -&amp;gt; hope the next run uses it well
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That helps a little, but it gets noisy fast.&lt;/p&gt;

&lt;p&gt;What I actually needed was a smaller, more reusable layer:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;procedures&lt;/li&gt;
&lt;li&gt;facts&lt;/li&gt;
&lt;li&gt;directives&lt;/li&gt;
&lt;li&gt;references&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In other words, not “everything that happened,” but “what should be remembered from what happened.”&lt;/p&gt;

&lt;p&gt;That changed the shape of the system.&lt;/p&gt;

&lt;p&gt;Instead of forcing the next run to infer the lesson from a giant transcript, the assistant now has a file-based memory layer that can recall:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a procedure: the current best steps and known dead ends&lt;/li&gt;
&lt;li&gt;a fact: a URL, environment rule, or known setting&lt;/li&gt;
&lt;li&gt;a directive: how the user wants things done&lt;/li&gt;
&lt;li&gt;a reference: where the relevant doc lives&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That is much closer to how people actually work.&lt;/p&gt;

&lt;h2&gt;
  
  
  The important rule was verify-then-trust
&lt;/h2&gt;

&lt;p&gt;The trap with workflow memory is obvious.&lt;/p&gt;

&lt;p&gt;Interfaces change.&lt;/p&gt;

&lt;p&gt;Buttons move.&lt;/p&gt;

&lt;p&gt;Old steps decay.&lt;/p&gt;

&lt;p&gt;So I did not want “perfect replay.”&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;recall the previous best procedure
-&amp;gt; try it first
-&amp;gt; verify each important step
-&amp;gt; if it no longer works, fall back to exploration
-&amp;gt; update the memory after success
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That turned out to be the difference between brittle automation and useful operational memory.&lt;/p&gt;

&lt;p&gt;A remembered workflow should save exploration, not replace judgment.&lt;/p&gt;

&lt;h2&gt;
  
  
  This also fixed a second problem: user rules kept getting lost in the noise
&lt;/h2&gt;

&lt;p&gt;There is another kind of “memory” that does not belong in a runtime session at all.&lt;/p&gt;

&lt;p&gt;Things like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;always reply in Chinese&lt;/li&gt;
&lt;li&gt;keep answers concise&lt;/li&gt;
&lt;li&gt;do not touch production data&lt;/li&gt;
&lt;li&gt;this project uses this test environment&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Those are not just follow-up context.&lt;/p&gt;

&lt;p&gt;They are standing operating rules.&lt;/p&gt;

&lt;p&gt;Once I separated those from ordinary conversation history, the assistant became much more predictable.&lt;/p&gt;

&lt;p&gt;The model no longer had to rediscover the same user preference in the middle of a task. It could start from it.&lt;/p&gt;

&lt;p&gt;That sounds small, but it reduces a lot of friction.&lt;/p&gt;

&lt;h2&gt;
  
  
  The real improvement was skipping re-discovery
&lt;/h2&gt;

&lt;p&gt;The best sign that this change mattered was not that the memory layer looked elegant.&lt;/p&gt;

&lt;p&gt;It was that repeat tasks got shorter.&lt;/p&gt;

&lt;p&gt;The assistant could move faster because it was no longer starting with an empty tactical model every time.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;inspect again&lt;/li&gt;
&lt;li&gt;guess again&lt;/li&gt;
&lt;li&gt;retry again&lt;/li&gt;
&lt;li&gt;rediscover the same dead end&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;it could do this:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;recall the best-known path&lt;/li&gt;
&lt;li&gt;verify it&lt;/li&gt;
&lt;li&gt;continue from there&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That is a better loop for both desktop-style workflows and non-desktop tasks.&lt;/p&gt;

&lt;p&gt;And it scales better than pretending one long-lived session can stand in for real memory.&lt;/p&gt;

&lt;h2&gt;
  
  
  The rule I am keeping
&lt;/h2&gt;

&lt;p&gt;If you are building a coding agent, do not confuse “the thread is still attached” with “the system learned something useful.”&lt;/p&gt;

&lt;p&gt;A session helps with continuity.&lt;/p&gt;

&lt;p&gt;Memory helps with repeated work.&lt;/p&gt;

&lt;p&gt;Those are different layers.&lt;/p&gt;

&lt;p&gt;The session should keep the conversation alive.&lt;/p&gt;

&lt;p&gt;The memory layer should keep the useful lessons alive.&lt;/p&gt;

&lt;p&gt;Once I separated those two, CliGate started feeling less like a chat system with a very long buffer and more like an assistant that can actually learn how work gets done.&lt;/p&gt;

&lt;p&gt;If you are building agent workflows, is your system remembering the thread, or remembering the successful procedure?&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>javascript</category>
      <category>ai</category>
      <category>opensource</category>
    </item>
    <item>
      <title>Three habits that make AI coding agents actually useful</title>
      <dc:creator>CodeKing</dc:creator>
      <pubDate>Wed, 01 Jul 2026 13:46:42 +0000</pubDate>
      <link>https://dev.to/codekingai/three-habits-that-make-ai-coding-agents-actually-useful-3ei1</link>
      <guid>https://dev.to/codekingai/three-habits-that-make-ai-coding-agents-actually-useful-3ei1</guid>
      <description>&lt;p&gt;Most of the friction people hit with AI coding agents isn't the model — it's how we hand work to it. After pairing with agents daily for months, three small habits have made the biggest difference for me.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Give the agent a way to verify itself
&lt;/h2&gt;

&lt;p&gt;An agent that can run the tests, read the output, and try again is worth far more than one that only emits code. Before asking for a change, make sure there's a command it can run to know whether it succeeded. "Make the test pass" beats "write this function" almost every time, because success becomes something the agent can check instead of something you have to.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Keep the task smaller than you think it should be
&lt;/h2&gt;

&lt;p&gt;Large, vague asks produce large, vague diffs that are hard to review. Scoping a request to a single file or a single behavior keeps the change reviewable and the feedback loop tight. You can always chain small wins into a big one — but you can't easily un-tangle one sprawling change.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Write down the intent, not just the instruction
&lt;/h2&gt;

&lt;p&gt;Telling an agent &lt;em&gt;what&lt;/em&gt; to do gets you one result. Telling it &lt;em&gt;why&lt;/em&gt; — the constraint you actually care about, the thing that must not break — lets it make the dozens of small decisions you never spelled out. Intent travels further than instructions.&lt;/p&gt;

&lt;p&gt;None of this is tied to a particular tool. It's about treating the agent like a fast, literal collaborator that does its best work when the goal is concrete and checkable.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>productivity</category>
      <category>programming</category>
    </item>
    <item>
      <title>"My AI Assistant Stopped Working the Moment I Closed the Chat"</title>
      <dc:creator>CodeKing</dc:creator>
      <pubDate>Tue, 30 Jun 2026 07:17:56 +0000</pubDate>
      <link>https://dev.to/codekingai/my-ai-assistant-stopped-working-the-moment-i-closed-the-chat-2j5c</link>
      <guid>https://dev.to/codekingai/my-ai-assistant-stopped-working-the-moment-i-closed-the-chat-2j5c</guid>
      <description>&lt;p&gt;A lot of AI assistants only feel helpful while the chat window is still open.&lt;/p&gt;

&lt;p&gt;You ask for something, watch it think, maybe answer one follow-up, and then you leave.&lt;/p&gt;

&lt;p&gt;That is the moment the illusion breaks.&lt;/p&gt;

&lt;p&gt;If the assistant cannot wake itself up later, run something in the background, and only come back to you when it actually needs you, it is not much of an assistant. It is just a chat box with good manners.&lt;/p&gt;

&lt;p&gt;That was one of the more practical lessons while building &lt;a href="https://github.com/codeking-ai/cligate" rel="noopener noreferrer"&gt;CliGate&lt;/a&gt;, my local AI control plane for a resident assistant, scheduled work, channels, Claude Code, Codex, and model routing behind one localhost service.&lt;/p&gt;

&lt;h2&gt;
  
  
  The real bug was requiring the user to babysit the assistant
&lt;/h2&gt;

&lt;p&gt;The old mental model was too chat-shaped.&lt;/p&gt;

&lt;p&gt;A user message arrived. The assistant did some work. The answer came back into the same visible thread.&lt;/p&gt;

&lt;p&gt;That works for interactive tasks, but it falls apart for the boring real ones:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;send me a morning summary&lt;/li&gt;
&lt;li&gt;publish a post tonight&lt;/li&gt;
&lt;li&gt;check something while I am offline&lt;/li&gt;
&lt;li&gt;keep going later and ping me only if you get stuck&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In other words, the assistant needed a way to work &lt;em&gt;without&lt;/em&gt; pretending the user was still sitting there.&lt;/p&gt;

&lt;h2&gt;
  
  
  A reminder was not enough
&lt;/h2&gt;

&lt;p&gt;The first naive version of scheduled work is just:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;at 8 PM -&amp;gt; send message
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That is fine for "drink water" reminders.&lt;/p&gt;

&lt;p&gt;It is useless for anything that needs actual reasoning or tools.&lt;/p&gt;

&lt;p&gt;What I wanted instead was:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;at 8 PM -&amp;gt; wake the assistant with an instruction
-&amp;gt; let it run in the background
-&amp;gt; send me the result
-&amp;gt; if it needs input, route my reply back into that paused run
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That sounds small, but it changes what a scheduled task really is. It stops being a timer and starts being a delayed assistant run.&lt;/p&gt;

&lt;h2&gt;
  
  
  The assistant needed its own hidden workspace for each fire
&lt;/h2&gt;

&lt;p&gt;One subtle bug showed up fast.&lt;/p&gt;

&lt;p&gt;If every recurring run reused the same hidden conversation forever, the assistant started treating later fires like duplicates.&lt;/p&gt;

&lt;p&gt;That is especially bad for recurring work. A scheduled publishing task should not say "already done" just because a previous run earlier that day used the same scope and saw similar history.&lt;/p&gt;

&lt;p&gt;So I ended up separating two modes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;fresh context by default&lt;/strong&gt; for recurring work&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;shared context only when explicitly useful&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That means a recurring task can wake the assistant in its own background scope conversation, without polluting the user's visible chat and without inheriting the wrong assumptions from the last run.&lt;/p&gt;

&lt;p&gt;In CliGate, that also made the scheduling model much easier to trust: each fire can be a clean round, while things like a running diary can still opt into shared context.&lt;/p&gt;

&lt;h2&gt;
  
  
  The tricky part was not starting the run. It was pausing honestly.
&lt;/h2&gt;

&lt;p&gt;Background runs do not always finish cleanly.&lt;/p&gt;

&lt;p&gt;Sometimes the assistant needs the user.&lt;/p&gt;

&lt;p&gt;Maybe a credential is missing. Maybe a choice matters. Maybe a risky action needs confirmation.&lt;/p&gt;

&lt;p&gt;If that happens inside a hidden scheduled-task scope, the run cannot just hang there forever.&lt;/p&gt;

&lt;p&gt;So the assistant needed a bridge back to the user-facing conversation.&lt;/p&gt;

&lt;p&gt;The model that finally felt right was:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;scheduled run happens in a hidden scope conversation&lt;/li&gt;
&lt;li&gt;visible conversations only get notifications&lt;/li&gt;
&lt;li&gt;if the run pauses on the user, remember which scheduled task is waiting&lt;/li&gt;
&lt;li&gt;when the user replies in the visible conversation, route that reply back to the paused background run&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That was the difference between "background automation" and "a silent dead end."&lt;/p&gt;

&lt;h2&gt;
  
  
  The UI had to explain the tradeoff clearly
&lt;/h2&gt;

&lt;p&gt;One thing I like about this setup is that the user-facing choice is simple.&lt;/p&gt;

&lt;p&gt;A scheduled task can either:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;notify user&lt;/strong&gt; with a static message, or&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;invoke assistant&lt;/strong&gt; with a real instruction&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And when invoking the assistant, two more controls matter a lot:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;optional working directory&lt;/li&gt;
&lt;li&gt;whether runs share context or start fresh&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That is enough surface area to be useful without turning scheduling into a mini workflow engine.&lt;/p&gt;

&lt;p&gt;Most people do not want cron trivia. They want to say:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;every morning, summarize this project&lt;/li&gt;
&lt;li&gt;tonight, publish the draft&lt;/li&gt;
&lt;li&gt;every weekday, check this thing and message me if it failed&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That is the level an assistant should operate at.&lt;/p&gt;

&lt;h2&gt;
  
  
  The result was a more believable assistant
&lt;/h2&gt;

&lt;p&gt;The biggest win was not technical elegance.&lt;/p&gt;

&lt;p&gt;It was behavioral honesty.&lt;/p&gt;

&lt;p&gt;The assistant no longer had to pretend every task was a live chat exchange.&lt;/p&gt;

&lt;p&gt;Now it can:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;wake up later&lt;/li&gt;
&lt;li&gt;run unattended&lt;/li&gt;
&lt;li&gt;keep background work out of the main conversation&lt;/li&gt;
&lt;li&gt;push a result back when it is done&lt;/li&gt;
&lt;li&gt;ask for help only when it actually needs help&lt;/li&gt;
&lt;li&gt;continue from the user's reply instead of losing the thread&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That made CliGate feel a lot less like "chat with extra steps" and a lot more like an actual local operator.&lt;/p&gt;

&lt;p&gt;If you are building AI assistants, this is the line I would not blur: a scheduled reminder is not the same thing as a scheduled assistant run, and users feel the difference immediately.&lt;/p&gt;

</description>
      <category>tutorial</category>
      <category>webdev</category>
      <category>node</category>
      <category>ai</category>
    </item>
    <item>
      <title>"My Two AI Tasks Kept Fighting for the Same Mouse"</title>
      <dc:creator>CodeKing</dc:creator>
      <pubDate>Sun, 21 Jun 2026 12:03:06 +0000</pubDate>
      <link>https://dev.to/codekingai/my-two-ai-tasks-kept-fighting-for-the-same-mouse-1hij</link>
      <guid>https://dev.to/codekingai/my-two-ai-tasks-kept-fighting-for-the-same-mouse-1hij</guid>
      <description>&lt;p&gt;Parallel agent demos look great right up until two tasks both try to use the same mouse.&lt;/p&gt;

&lt;p&gt;One task is logging into a site. Another one opens a browser window. A third thing is only supposed to answer a status question, but now the whole system is clicking in the wrong place, cancelling the wrong run, or reporting a failure that is really just resource contention.&lt;/p&gt;

&lt;p&gt;That was one of the more useful reliability lessons while building &lt;a href="https://github.com/codeking-ai/cligate" rel="noopener noreferrer"&gt;CliGate&lt;/a&gt;, my local control plane for a resident assistant, Claude Code, Codex, channels, scheduled work, and desktop automation.&lt;/p&gt;

&lt;h2&gt;
  
  
  The real bug was not "parallelism"
&lt;/h2&gt;

&lt;p&gt;Parallelism was fine.&lt;/p&gt;

&lt;p&gt;The bug was pretending every task could use every resource at the same time.&lt;/p&gt;

&lt;p&gt;Code tasks can run in parallel. A weather lookup can happen while a runtime session is still working. A background summary does not need to block anything.&lt;/p&gt;

&lt;p&gt;But the desktop is different.&lt;/p&gt;

&lt;p&gt;There is only one physical keyboard, one mouse, and one visible screen. If two agent runs both think they own that surface, the result is not intelligent multitasking. It is sabotage.&lt;/p&gt;

&lt;h2&gt;
  
  
  I first made the classic bad fix
&lt;/h2&gt;

&lt;p&gt;My first instinct was the lazy one:&lt;/p&gt;

&lt;p&gt;if a new task shows up and something else is already running, cancel the old run and let the new one take over.&lt;/p&gt;

&lt;p&gt;That sounds simple. It is also wrong.&lt;/p&gt;

&lt;p&gt;A user asking "how far did it get?" should not cancel a login flow. A user asking for the weather should not kill a desktop task. And the worst version of this bug is when an agent sees another active run and accidentally cancels itself.&lt;/p&gt;

&lt;p&gt;That was the moment I stopped treating concurrency as a prompt problem.&lt;/p&gt;

&lt;p&gt;It was a resource problem.&lt;/p&gt;

&lt;h2&gt;
  
  
  The fix was to separate task concurrency from resource ownership
&lt;/h2&gt;

&lt;p&gt;The rule I wanted turned out to be much simpler than the behavior I had before:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;independent tasks should run in parallel&lt;/li&gt;
&lt;li&gt;tasks that need the desktop should queue&lt;/li&gt;
&lt;li&gt;cancellation should only happen when the user clearly asks for it&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That meant I needed the assistant to know something more concrete than "there are active runs."&lt;/p&gt;

&lt;p&gt;It needed to know:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;which run currently holds the desktop&lt;/li&gt;
&lt;li&gt;whether the new request is only a status query&lt;/li&gt;
&lt;li&gt;whether the new task can run without the desktop&lt;/li&gt;
&lt;li&gt;whether the user is correcting the current task or replacing it&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That sounds obvious in hindsight, but it changes the control flow a lot.&lt;/p&gt;

&lt;h2&gt;
  
  
  I ended up treating the desktop like a leased resource
&lt;/h2&gt;

&lt;p&gt;In CliGate, desktop input is now handled more like a lease than a best-effort tool call.&lt;/p&gt;

&lt;p&gt;A task that starts using the mouse and keyboard becomes the current desktop holder. Another task can still exist, but it does not get to click on top of the first one. It waits.&lt;/p&gt;

&lt;p&gt;The mental model is 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;new task arrives
-&amp;gt; does it need the desktop?
-&amp;gt; if no, run in parallel
-&amp;gt; if yes and desktop is free, acquire it
-&amp;gt; if yes and desktop is busy, queue it
-&amp;gt; only cancel when the user explicitly says stop
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That was the missing boundary.&lt;/p&gt;

&lt;p&gt;Before that, I had a lot of behavior that looked concurrent in logs but felt random to the user.&lt;/p&gt;

&lt;h2&gt;
  
  
  The queue mattered more than another retry loop
&lt;/h2&gt;

&lt;p&gt;One of the subtle failures in desktop automation is that retries can make things worse.&lt;/p&gt;

&lt;p&gt;If a second task keeps trying to grab the mouse while the first task is still typing or waiting for a window, more retries do not increase reliability. They just increase interference.&lt;/p&gt;

&lt;p&gt;So the better fix was not:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;retry the click harder&lt;/li&gt;
&lt;li&gt;guess a different coordinate&lt;/li&gt;
&lt;li&gt;keep asking the model what to do&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The better fix was to make the assistant say the honest thing:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;the desktop is busy, I am queued behind the current task, and I will start automatically when it is released&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That turns a confusing failure into predictable behavior.&lt;/p&gt;

&lt;h2&gt;
  
  
  I also had to block the dumbest cancellation path
&lt;/h2&gt;

&lt;p&gt;There was another bug hiding inside the same area.&lt;/p&gt;

&lt;p&gt;If the assistant is allowed to cancel any active run it sees, it needs a hard rule against cancelling the run it is currently inside.&lt;/p&gt;

&lt;p&gt;So I treated that as an invariant rather than a suggestion:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;do not list the current run as a cancel target&lt;/li&gt;
&lt;li&gt;reject self-cancel if it is somehow attempted anyway&lt;/li&gt;
&lt;li&gt;check for cancellation continuously so cancelled runs actually stop cleanly&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That part is not glamorous, but it is the difference between a trustworthy scheduler and an agent that panic-clicks its own off switch.&lt;/p&gt;

&lt;h2&gt;
  
  
  The UX got simpler once the system became less "smart"
&lt;/h2&gt;

&lt;p&gt;This is the pattern I keep running into with agent tooling.&lt;/p&gt;

&lt;p&gt;A lot of bad behavior comes from trying to be clever too early.&lt;/p&gt;

&lt;p&gt;What users actually need is often plainer than that:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;if a task does not conflict, let it run&lt;/li&gt;
&lt;li&gt;if it conflicts over one physical resource, queue it&lt;/li&gt;
&lt;li&gt;if the user asks for status, answer from status&lt;/li&gt;
&lt;li&gt;if the user says stop, stop&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That is less magical than a giant orchestration layer that tries to infer everything from every message.&lt;/p&gt;

&lt;p&gt;It is also much easier to trust.&lt;/p&gt;

&lt;p&gt;That resource-aware queue ended up making parallel agent work feel more human, not less. The assistant stopped acting like every incoming message was a fight for control, and started acting more like an operator that understands the difference between a question, a correction, and a second pair of hands trying to grab the same mouse.&lt;/p&gt;

&lt;p&gt;If you are building AI tooling that touches the desktop, this is the part I would not fake: parallel tasks are fine, but physical resources still need ownership.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>javascript</category>
      <category>ai</category>
      <category>opensource</category>
    </item>
    <item>
      <title>"I Stopped Pretending Every AI Provider Was the Same"</title>
      <dc:creator>CodeKing</dc:creator>
      <pubDate>Sat, 20 Jun 2026 12:10:47 +0000</pubDate>
      <link>https://dev.to/codekingai/i-stopped-pretending-every-ai-provider-was-the-same-18k8</link>
      <guid>https://dev.to/codekingai/i-stopped-pretending-every-ai-provider-was-the-same-18k8</guid>
      <description>&lt;p&gt;The easiest way to make an AI gateway feel flaky is to pretend every upstream model works the same way.&lt;/p&gt;

&lt;p&gt;On paper, a lot of tools look compatible.&lt;/p&gt;

&lt;p&gt;They all take a prompt. They all return text. Some of them even share an OpenAI-shaped API.&lt;/p&gt;

&lt;p&gt;In practice, the differences show up exactly where users stop forgiving you:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a tool-specific field gets dropped&lt;/li&gt;
&lt;li&gt;an image payload works on one route and breaks on another&lt;/li&gt;
&lt;li&gt;a model switch silently changes behavior&lt;/li&gt;
&lt;li&gt;the request succeeds, but the wrong capability set was assumed&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That was one of the most useful lessons while building &lt;a href="https://github.com/codeking-ai/cligate" rel="noopener noreferrer"&gt;CliGate&lt;/a&gt;, my local control plane for Claude Code, Codex CLI, Gemini CLI, OpenClaw, a resident assistant, and multiple model/account sources behind one localhost entrypoint.&lt;/p&gt;

&lt;h2&gt;
  
  
  The bug was not “routing failed”
&lt;/h2&gt;

&lt;p&gt;The bug was subtler than that.&lt;/p&gt;

&lt;p&gt;Routing often &lt;em&gt;did&lt;/em&gt; succeed. A request got sent somewhere. A response came back. Nothing obviously crashed.&lt;/p&gt;

&lt;p&gt;But that did not mean the gateway was correct.&lt;/p&gt;

&lt;p&gt;If you route different tools and providers as if they were interchangeable, you get a class of failures that are hard to spot from logs alone:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Claude-style payloads that need translation, not passthrough&lt;/li&gt;
&lt;li&gt;Codex-compatible flows that should degrade unsupported fields instead of forwarding them blindly&lt;/li&gt;
&lt;li&gt;Gemini paths that need their own capability assumptions&lt;/li&gt;
&lt;li&gt;local or fallback routes that are reachable but not feature-equivalent&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That is not just transport routing.&lt;/p&gt;

&lt;p&gt;That is capability routing.&lt;/p&gt;

&lt;h2&gt;
  
  
  I had to separate “where to send it” from “what this destination can really do”
&lt;/h2&gt;

&lt;p&gt;At first, it is tempting to think routing is just:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;pick provider -&amp;gt; send request
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That model is too small.&lt;/p&gt;

&lt;p&gt;What actually mattered in CliGate was 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;identify caller/tool
-&amp;gt; identify protocol shape
-&amp;gt; resolve provider/model source
-&amp;gt; apply capability profile
-&amp;gt; translate or degrade fields safely
-&amp;gt; send upstream
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A provider being reachable is not enough.&lt;/p&gt;

&lt;p&gt;It also needs to be treated according to the features it really supports.&lt;/p&gt;

&lt;h2&gt;
  
  
  Translation turned out to be part of routing
&lt;/h2&gt;

&lt;p&gt;One of the more useful internal lessons in this project is that protocol translation is not a separate cleanup step after routing.&lt;/p&gt;

&lt;p&gt;It &lt;em&gt;is&lt;/em&gt; part of routing.&lt;/p&gt;

&lt;p&gt;Some paths can accept a richer request shape. Some need fields normalized or stripped before the request becomes a silent bug.&lt;/p&gt;

&lt;p&gt;That changed the safe mental model from:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“upstream did not complain, so the route must be fine.”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;to:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“this route supports a specific capability profile, so normalize on purpose.”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That sounds small, but it prevents a lot of “works sometimes” behavior.&lt;/p&gt;

&lt;h2&gt;
  
  
  A “compatible API” is not the same thing as compatible behavior
&lt;/h2&gt;

&lt;p&gt;This is the trap.&lt;/p&gt;

&lt;p&gt;Lots of systems advertise compatibility because they accept a familiar endpoint shape.&lt;/p&gt;

&lt;p&gt;But compatibility at the HTTP layer is only the beginning.&lt;/p&gt;

&lt;p&gt;If one tool expects richer reasoning or metadata semantics and another backend treats those fields differently, the gateway has three bad choices:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;pass everything through and let undefined behavior happen&lt;/li&gt;
&lt;li&gt;reject too aggressively and feel brittle&lt;/li&gt;
&lt;li&gt;normalize by capability and keep behavior predictable&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Only the third one scales.&lt;/p&gt;

&lt;p&gt;That is why I now prefer capability-aware routing over a universal passthrough design.&lt;/p&gt;

&lt;h2&gt;
  
  
  Caller identity matters more than I expected
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;claude-code&lt;/code&gt;, &lt;code&gt;codex&lt;/code&gt;, &lt;code&gt;gemini-cli&lt;/code&gt;, &lt;code&gt;openclaw&lt;/code&gt;, and generic OpenAI/Anthropic-compatible clients may hit similar-looking routes, but they are not interchangeable from an operator’s perspective.&lt;/p&gt;

&lt;p&gt;The user is often really asking for one of these:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;keep Claude Code on the provider/model path that fits Claude-style flows&lt;/li&gt;
&lt;li&gt;bind Codex to a specific account or key&lt;/li&gt;
&lt;li&gt;let Gemini use its own capability profile&lt;/li&gt;
&lt;li&gt;fall back safely when a source is unavailable&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That is why app-aware routing and capability-aware translation ended up being complementary, not separate concerns.&lt;/p&gt;

&lt;p&gt;One decides &lt;strong&gt;who this request is for&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The other decides &lt;strong&gt;how to make it truthful on the way through&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Degrade intentionally, never accidentally
&lt;/h2&gt;

&lt;p&gt;The worst failures are the accidental ones.&lt;/p&gt;

&lt;p&gt;If a gateway quietly forwards a field that the destination ignores, the user may never know why results became inconsistent.&lt;/p&gt;

&lt;p&gt;So I started preferring explicit degradation rules.&lt;/p&gt;

&lt;p&gt;If a route cannot honor a field, normalize it on purpose.&lt;/p&gt;

&lt;p&gt;If a provider cannot match a capability, map it honestly.&lt;/p&gt;

&lt;p&gt;If a model source is rate-limited or invalid, skip it instead of pretending all active-looking credentials are equal.&lt;/p&gt;

&lt;p&gt;That gives me a much better operator story:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;why this source was chosen&lt;/li&gt;
&lt;li&gt;which capability profile was applied&lt;/li&gt;
&lt;li&gt;which fields were transformed or removed&lt;/li&gt;
&lt;li&gt;why a different route would behave differently&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Reliability improved when I stopped chasing “perfect abstraction”
&lt;/h2&gt;

&lt;p&gt;A good gateway should hide repetitive setup work.&lt;/p&gt;

&lt;p&gt;It should not lie about capability differences.&lt;/p&gt;

&lt;p&gt;Once I accepted that, the architecture became cleaner:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;route by app and protocol&lt;/li&gt;
&lt;li&gt;map by provider/model source&lt;/li&gt;
&lt;li&gt;translate by capability profile&lt;/li&gt;
&lt;li&gt;expose the differences clearly in logs and settings&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That is less magical, but much more dependable.&lt;/p&gt;

&lt;h2&gt;
  
  
  The rules I would keep
&lt;/h2&gt;

&lt;p&gt;If I were designing another AI gateway tomorrow, I would keep these rules:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;do not equate API shape with feature equivalence&lt;/li&gt;
&lt;li&gt;make caller identity a first-class routing input&lt;/li&gt;
&lt;li&gt;treat translation as part of routing&lt;/li&gt;
&lt;li&gt;degrade unsupported fields deliberately&lt;/li&gt;
&lt;li&gt;expose capability decisions so operators can explain failures&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;That is the direction I have been pushing with CliGate.&lt;/p&gt;

&lt;p&gt;The project still aims to give me one local place for model routing, accounts, API keys, local runtimes, channels, runtime sessions, and an assistant layer.&lt;/p&gt;

&lt;p&gt;But the system became much more trustworthy once I stopped pretending every upstream provider was the same.&lt;/p&gt;

&lt;p&gt;If you run multiple AI tools through one gateway, are you doing plain endpoint routing, or routing by actual capability too?&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>javascript</category>
      <category>ai</category>
      <category>opensource</category>
    </item>
    <item>
      <title>"My Two AI Tasks Kept Fighting for the Same Mouse"</title>
      <dc:creator>CodeKing</dc:creator>
      <pubDate>Thu, 18 Jun 2026 12:01:22 +0000</pubDate>
      <link>https://dev.to/codekingai/my-two-ai-tasks-kept-fighting-for-the-same-mouse-593g</link>
      <guid>https://dev.to/codekingai/my-two-ai-tasks-kept-fighting-for-the-same-mouse-593g</guid>
      <description>&lt;p&gt;The second my local AI assistant learned how to operate Windows apps, I ran into a new bug.&lt;/p&gt;

&lt;p&gt;Not a model bug. Not a prompt bug.&lt;/p&gt;

&lt;p&gt;A traffic bug.&lt;/p&gt;

&lt;p&gt;One task was logging into a site through the desktop. Another task wanted to open a different app. Both requests were valid. Both were "in progress." And both were trying to use the same physical mouse, keyboard, and screen.&lt;/p&gt;

&lt;p&gt;That is when an assistant stops feeling like software and starts feeling like two interns fighting over one laptop.&lt;/p&gt;

&lt;h2&gt;
  
  
  The first version knew there were multiple runs, but not one shared desktop
&lt;/h2&gt;

&lt;p&gt;In CliGate, I already had the shape of a resident assistant: background runs, task records, channel conversations, runtime delegation, and desktop automation on Windows.&lt;/p&gt;

&lt;p&gt;The problem was that "multiple tasks" and "multiple desktop tasks" are not the same thing.&lt;/p&gt;

&lt;p&gt;Most work can run in parallel just fine. A coding task can edit files while another task checks the weather or summarizes a document. But desktop control is a physical boundary. There is only one active pointer, one focused window, one keyboard target.&lt;/p&gt;

&lt;p&gt;If the system treats every run as equally parallel, desktop tasks do not feel concurrent. They feel destructive.&lt;/p&gt;

&lt;p&gt;One can steal focus. Another can click into the wrong window. A third can decide there is already a similar run and cancel the wrong thing entirely.&lt;/p&gt;

&lt;h2&gt;
  
  
  The bug was confusing activity with resource ownership
&lt;/h2&gt;

&lt;p&gt;The old mental model was too shallow:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;conversation -&amp;gt; active runs -&amp;gt; decide whether to cancel one
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That can work for status displays. It is not enough for desktop scheduling.&lt;/p&gt;

&lt;p&gt;What I actually needed was a separate truth the assistant could look at:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;resource: desktop
holder: run X
waiters: run Y, run Z
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That changed the question from:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;"Are there other active runs?"&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;into:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;"Is the desktop currently held by another run, and if so should this task wait, answer from state, or be cancelled because the user explicitly said stop?"&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That is a much more useful question.&lt;/p&gt;

&lt;h2&gt;
  
  
  The fix was resource-aware scheduling, not blanket cancellation
&lt;/h2&gt;

&lt;p&gt;I ended up moving toward a simple rule set.&lt;/p&gt;

&lt;p&gt;If two tasks do not need the same exclusive resource, let them run in parallel.&lt;/p&gt;

&lt;p&gt;If they both need the desktop, serialize them.&lt;/p&gt;

&lt;p&gt;If the user asks for status, answer from the existing run instead of touching the desktop again.&lt;/p&gt;

&lt;p&gt;If the user says "stop," cancel the target run explicitly.&lt;/p&gt;

&lt;p&gt;That sounds obvious in hindsight, but it changed the behavior a lot.&lt;/p&gt;

&lt;p&gt;Instead of seeing another run and trying to kill it, the assistant can now treat the desktop like a queueable resource. One task holds it. The next desktop task waits. When the holder finishes, the next one starts automatically.&lt;/p&gt;

&lt;p&gt;That is much closer to how a human assistant would behave. You would not cancel a login flow just because someone also asked you to open another app. You would say: "I'm in the middle of this one, I'll do the next desktop step right after."&lt;/p&gt;

&lt;h2&gt;
  
  
  It also cleaned up the UX around interruption
&lt;/h2&gt;

&lt;p&gt;The nice side effect is that the assistant becomes easier to talk to during long tasks.&lt;/p&gt;

&lt;p&gt;While a desktop job is running, I can still ask:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;what happened to the last run?&lt;/li&gt;
&lt;li&gt;how much is left?&lt;/li&gt;
&lt;li&gt;can you also write a script in the repo?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Those should not all be treated as reasons to interrupt the desktop flow.&lt;/p&gt;

&lt;p&gt;The assistant can answer status questions from run state, do non-desktop work in parallel, and only queue the things that truly need the same physical controls.&lt;/p&gt;

&lt;p&gt;That made the product feel less like a brittle automation demo and more like an actual operator with limited hands.&lt;/p&gt;

&lt;h2&gt;
  
  
  The rule I am keeping
&lt;/h2&gt;

&lt;p&gt;If an agent can touch the real desktop, it needs to understand the difference between:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;parallel work&lt;/li&gt;
&lt;li&gt;exclusive resources&lt;/li&gt;
&lt;li&gt;explicit cancellation&lt;/li&gt;
&lt;li&gt;simple status follow-ups&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Without that split, concurrency just becomes another word for collision.&lt;/p&gt;

&lt;p&gt;That is now part of how I am shaping CliGate, the local control plane I use for Claude Code, Codex CLI, Gemini CLI, channels, desktop automation, and a resident assistant layer on top.&lt;/p&gt;

&lt;p&gt;The project is open source here: &lt;a href="https://github.com/codeking-ai/cligate" rel="noopener noreferrer"&gt;CliGate&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;If you are building local agents, are you treating the desktop as just another tool call, or as a resource that needs scheduling?&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>javascript</category>
      <category>ai</category>
      <category>opensource</category>
    </item>
    <item>
      <title>"My Coding Agent Finished the Task. Why Did the Thread Die?"</title>
      <dc:creator>CodeKing</dc:creator>
      <pubDate>Wed, 17 Jun 2026 12:02:51 +0000</pubDate>
      <link>https://dev.to/codekingai/my-coding-agent-finished-the-task-why-did-the-thread-die-2jk2</link>
      <guid>https://dev.to/codekingai/my-coding-agent-finished-the-task-why-did-the-thread-die-2jk2</guid>
      <description>&lt;p&gt;The most annoying follow-up bug in a coding agent is not a crash.&lt;/p&gt;

&lt;p&gt;It is when the first task succeeds, and the next message still feels like amnesia.&lt;/p&gt;

&lt;p&gt;I would ask Claude Code or Codex to do something through my local assistant, wait for it to finish, and then send the most normal follow-up in the world:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;make the button green
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;From the user's side, that is obviously part of the same thread.&lt;/p&gt;

&lt;p&gt;From the system side, it was too easy to treat &lt;code&gt;completed&lt;/code&gt; as "this session is over, start from scratch next time."&lt;/p&gt;

&lt;p&gt;That was the bug.&lt;/p&gt;

&lt;h2&gt;
  
  
  The real problem was mixing up three different things
&lt;/h2&gt;

&lt;p&gt;While building CliGate, I realized I had let three layers blur together:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the chat conversation&lt;/li&gt;
&lt;li&gt;the runtime session&lt;/li&gt;
&lt;li&gt;the current execution turn&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Those are not the same object.&lt;/p&gt;

&lt;p&gt;A conversation is the long-lived place where the user keeps talking.&lt;/p&gt;

&lt;p&gt;A runtime session is the working thread attached to that conversation.&lt;/p&gt;

&lt;p&gt;A turn is just one run inside that session.&lt;/p&gt;

&lt;p&gt;Once I wrote it down that way, the mistake became obvious. &lt;code&gt;completed&lt;/code&gt; should mean the current turn is done. It should &lt;strong&gt;not&lt;/strong&gt; mean the whole thread has to be detached.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why the old behavior felt wrong so quickly
&lt;/h2&gt;

&lt;p&gt;The broken flow looked 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;user asks for task
-&amp;gt; runtime session starts
-&amp;gt; task completes
-&amp;gt; conversation clears active session
-&amp;gt; next follow-up starts a brand-new session
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That does not sound terrible until you use it from a phone or a busy web chat.&lt;/p&gt;

&lt;p&gt;Real follow-ups are short. People say things like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;retry that&lt;/li&gt;
&lt;li&gt;do the same for this file&lt;/li&gt;
&lt;li&gt;make the button green&lt;/li&gt;
&lt;li&gt;explain the error&lt;/li&gt;
&lt;li&gt;continue&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Those messages only work if the agent still knows what thread they belong to.&lt;/p&gt;

&lt;p&gt;If the system detaches the session the second one task finishes, the user is forced back into full restatement mode. The agent technically works, but the thread feels fake.&lt;/p&gt;

&lt;h2&gt;
  
  
  The fix was not more prompting
&lt;/h2&gt;

&lt;p&gt;My first instinct was to improve follow-up classification.&lt;/p&gt;

&lt;p&gt;That helped a little, but it was not the core fix.&lt;/p&gt;

&lt;p&gt;The real fix was to make the binding model explicit:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;conversation is persistent&lt;/li&gt;
&lt;li&gt;runtime session is sticky by default&lt;/li&gt;
&lt;li&gt;completed/failed only close the current turn&lt;/li&gt;
&lt;li&gt;a new session should be explicit, or caused by a real compatibility change&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In other words, I stopped treating task completion as session death.&lt;/p&gt;

&lt;p&gt;That tiny semantic change affects the whole product feel.&lt;/p&gt;

&lt;h2&gt;
  
  
  When should a new session actually start?
&lt;/h2&gt;

&lt;p&gt;Not every follow-up should reuse the old session forever.&lt;/p&gt;

&lt;p&gt;But the boundary needs to be meaningful.&lt;/p&gt;

&lt;p&gt;In CliGate, the cases that justify a new session are things like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;there is no bound session yet&lt;/li&gt;
&lt;li&gt;the user explicitly asks for a new one&lt;/li&gt;
&lt;li&gt;the provider or model changed in a way that makes reuse incompatible&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That is very different from saying the last turn completed, so the thread should be thrown away.&lt;/p&gt;

&lt;p&gt;The first rule matches how people think.&lt;/p&gt;

&lt;p&gt;The second rule matches how brittle plumbing thinks.&lt;/p&gt;

&lt;h2&gt;
  
  
  This mattered on both web chat and mobile channels
&lt;/h2&gt;

&lt;p&gt;The nice part is that the same model applies in more than one place.&lt;/p&gt;

&lt;p&gt;In a web chat window, the user expects one tab to behave like one ongoing thread.&lt;/p&gt;

&lt;p&gt;In Telegram, Feishu, or DingTalk, the expectation is even stronger. A conversation on the phone is already compressed. The user is relying on the system to preserve context across tiny, vague follow-ups.&lt;/p&gt;

&lt;p&gt;So the product behavior I wanted was simple:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;first message creates a runtime session&lt;/li&gt;
&lt;li&gt;later messages keep using that session by default&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;completed&lt;/code&gt; or &lt;code&gt;failed&lt;/code&gt; do not silently drop the thread&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;/new&lt;/code&gt; or a real config drift can start a fresh session&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That makes the agent feel much closer to a real working thread instead of a command launcher.&lt;/p&gt;

&lt;h2&gt;
  
  
  The subtle UX win was explaining the boundary clearly
&lt;/h2&gt;

&lt;p&gt;Once you separate conversation, session, and turn, the UI gets easier to reason about too.&lt;/p&gt;

&lt;p&gt;The user can understand:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;this conversation is still attached to a session&lt;/li&gt;
&lt;li&gt;the last turn is done&lt;/li&gt;
&lt;li&gt;I can keep asking follow-ups&lt;/li&gt;
&lt;li&gt;if I want a clean start, I should explicitly ask for a new session&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That is a much better mental model than making the user guess whether the tool still remembers anything.&lt;/p&gt;

&lt;p&gt;It also makes debugging easier. If a new session appears, it should be because the user asked for one or because something important changed, not because the state machine quietly treated success as disposal.&lt;/p&gt;

&lt;h2&gt;
  
  
  The rule I am keeping
&lt;/h2&gt;

&lt;p&gt;If you are building a coding agent, do not let one successful task kill the thread.&lt;/p&gt;

&lt;p&gt;The durable unit is the conversation.&lt;/p&gt;

&lt;p&gt;The reusable worker is the runtime session.&lt;/p&gt;

&lt;p&gt;The thing that ends on success or failure is the turn.&lt;/p&gt;

&lt;p&gt;Once I separated those three layers, follow-ups stopped feeling random and started feeling conversational.&lt;/p&gt;

&lt;p&gt;That is now part of how I am shaping &lt;a href="https://github.com/codeking-ai/cligate" rel="noopener noreferrer"&gt;CliGate&lt;/a&gt;, the local control plane I use for Claude Code, Codex CLI, Gemini CLI, channels, and a resident assistant layer on top.&lt;/p&gt;

&lt;p&gt;If you are building agent workflows, are you treating completion as the end of a turn, or the end of the whole thread?&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>javascript</category>
      <category>ai</category>
      <category>opensource</category>
    </item>
    <item>
      <title>"My Two AI Tasks Kept Fighting for the Same Mouse"</title>
      <dc:creator>CodeKing</dc:creator>
      <pubDate>Tue, 16 Jun 2026 12:02:21 +0000</pubDate>
      <link>https://dev.to/codekingai/my-two-ai-tasks-kept-fighting-for-the-same-mouse-17m5</link>
      <guid>https://dev.to/codekingai/my-two-ai-tasks-kept-fighting-for-the-same-mouse-17m5</guid>
      <description>&lt;p&gt;The second my local AI assistant learned how to operate Windows apps, I ran into a new bug.&lt;/p&gt;

&lt;p&gt;Not a model bug. Not a prompt bug.&lt;/p&gt;

&lt;p&gt;A traffic bug.&lt;/p&gt;

&lt;p&gt;One task was logging into a site through the desktop. Another task wanted to open a different app. Both requests were valid. Both were "in progress." And both were trying to use the same physical mouse, keyboard, and screen.&lt;/p&gt;

&lt;p&gt;That is when an assistant stops feeling like software and starts feeling like two interns fighting over one laptop.&lt;/p&gt;

&lt;h2&gt;
  
  
  The first version knew there were multiple runs, but not one shared desktop
&lt;/h2&gt;

&lt;p&gt;In CliGate, I already had the shape of a resident assistant: background runs, task records, channel conversations, runtime delegation, and desktop automation on Windows.&lt;/p&gt;

&lt;p&gt;The problem was that "multiple tasks" and "multiple desktop tasks" are not the same thing.&lt;/p&gt;

&lt;p&gt;Most work can run in parallel just fine. A coding task can edit files while another task checks the weather or summarizes a document. But desktop control is a physical boundary. There is only one active pointer, one focused window, one keyboard target.&lt;/p&gt;

&lt;p&gt;If the system treats every run as equally parallel, desktop tasks do not feel concurrent. They feel destructive.&lt;/p&gt;

&lt;p&gt;One can steal focus. Another can click into the wrong window. A third can decide there is already a similar run and cancel the wrong thing entirely.&lt;/p&gt;

&lt;h2&gt;
  
  
  The bug was confusing activity with resource ownership
&lt;/h2&gt;

&lt;p&gt;The old mental model was too shallow:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;conversation -&amp;gt; active runs -&amp;gt; decide whether to cancel one
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That can work for status displays. It is not enough for desktop scheduling.&lt;/p&gt;

&lt;p&gt;What I actually needed was a separate truth the assistant could look at:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;resource: desktop
holder: run X
waiters: run Y, run Z
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That changed the question from:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;"Are there other active runs?"&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;into:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;"Is the desktop currently held by another run, and if so should this task wait, answer from state, or be cancelled because the user explicitly said stop?"&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That is a much more useful question.&lt;/p&gt;

&lt;h2&gt;
  
  
  The fix was resource-aware scheduling, not blanket cancellation
&lt;/h2&gt;

&lt;p&gt;I ended up moving toward a simple rule set.&lt;/p&gt;

&lt;p&gt;If two tasks do not need the same exclusive resource, let them run in parallel.&lt;/p&gt;

&lt;p&gt;If they both need the desktop, serialize them.&lt;/p&gt;

&lt;p&gt;If the user asks for status, answer from the existing run instead of touching the desktop again.&lt;/p&gt;

&lt;p&gt;If the user says "stop," cancel the target run explicitly.&lt;/p&gt;

&lt;p&gt;That sounds obvious in hindsight, but it changed the behavior a lot.&lt;/p&gt;

&lt;p&gt;Instead of seeing another run and trying to kill it, the assistant can now treat the desktop like a queueable resource. One task holds it. The next desktop task waits. When the holder finishes, the next one starts automatically.&lt;/p&gt;

&lt;p&gt;That is much closer to how a human assistant would behave. You would not cancel a login flow just because someone also asked you to open another app. You would say: "I'm in the middle of this one, I'll do the next desktop step right after."&lt;/p&gt;

&lt;h2&gt;
  
  
  It also cleaned up the UX around interruption
&lt;/h2&gt;

&lt;p&gt;The nice side effect is that the assistant becomes easier to talk to during long tasks.&lt;/p&gt;

&lt;p&gt;While a desktop job is running, I can still ask:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;what happened to the last run?&lt;/li&gt;
&lt;li&gt;how much is left?&lt;/li&gt;
&lt;li&gt;can you also write a script in the repo?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Those should not all be treated as reasons to interrupt the desktop flow.&lt;/p&gt;

&lt;p&gt;The assistant can answer status questions from run state, do non-desktop work in parallel, and only queue the things that truly need the same physical controls.&lt;/p&gt;

&lt;p&gt;That made the product feel less like a brittle automation demo and more like an actual operator with limited hands.&lt;/p&gt;

&lt;h2&gt;
  
  
  The rule I am keeping
&lt;/h2&gt;

&lt;p&gt;If an agent can touch the real desktop, it needs to understand the difference between:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;parallel work&lt;/li&gt;
&lt;li&gt;exclusive resources&lt;/li&gt;
&lt;li&gt;explicit cancellation&lt;/li&gt;
&lt;li&gt;simple status follow-ups&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Without that split, concurrency just becomes another word for collision.&lt;/p&gt;

&lt;p&gt;That is now part of how I am shaping CliGate, the local control plane I use for Claude Code, Codex CLI, Gemini CLI, channels, desktop automation, and a resident assistant layer on top.&lt;/p&gt;

&lt;p&gt;The project is open source here: &lt;a href="https://github.com/codeking-ai/cligate" rel="noopener noreferrer"&gt;CliGate&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;If you are building local agents, are you treating the desktop as just another tool call, or as a resource that needs scheduling?&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>javascript</category>
      <category>ai</category>
      <category>opensource</category>
    </item>
    <item>
      <title>My Coding Agent Asked Permission for Every Tiny Step</title>
      <dc:creator>CodeKing</dc:creator>
      <pubDate>Tue, 16 Jun 2026 07:48:24 +0000</pubDate>
      <link>https://dev.to/codekingai/my-coding-agent-asked-permission-for-every-tiny-step-4a07</link>
      <guid>https://dev.to/codekingai/my-coding-agent-asked-permission-for-every-tiny-step-4a07</guid>
      <description>&lt;p&gt;The most annoying bug in my local AI assistant was not that it refused to ask for permission.&lt;/p&gt;

&lt;p&gt;It was that it asked too often.&lt;/p&gt;

&lt;p&gt;I would give it a normal task like "read this PDF and tell me what is inside." The assistant would make a reasonable first move, ask for approval, run the command, and then immediately ask again for the next tiny probe.&lt;/p&gt;

&lt;p&gt;One task turned into a permission treadmill.&lt;/p&gt;

&lt;h2&gt;
  
  
  The approval loop felt safe but unusable
&lt;/h2&gt;

&lt;p&gt;Approval prompts are good. I want a local assistant to stop before it runs commands, writes files, opens apps, or touches the desktop.&lt;/p&gt;

&lt;p&gt;The problem is that real work is rarely one tool call.&lt;/p&gt;

&lt;p&gt;Reading a PDF might require checking whether Python exists, finding a converter, running the converter, reading the text output, and then summarizing it. A desktop task might require focusing a window, entering text, pressing a button, and verifying the result.&lt;/p&gt;

&lt;p&gt;If every step asks the same kind of question, the user stops evaluating risk and starts clicking through interruptions.&lt;/p&gt;

&lt;p&gt;That is worse than unsafe. It trains the user to ignore the safety system.&lt;/p&gt;

&lt;h2&gt;
  
  
  The bug was treating approval as a single event
&lt;/h2&gt;

&lt;p&gt;The old flow in CliGate looked 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;assistant chooses tool
-&amp;gt; policy says confirmation required
-&amp;gt; user approves
-&amp;gt; tool runs
-&amp;gt; assistant continues
-&amp;gt; next tool asks again
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That was technically correct. Each tool call had its own confirmation.&lt;/p&gt;

&lt;p&gt;But it missed the shape of the user's intent. The user was not approving "run one tiny probe." They were approving the assistant to continue one bounded task.&lt;/p&gt;

&lt;p&gt;So I changed the behavior after the first approved action in a chat conversation.&lt;/p&gt;

&lt;p&gt;Once the user approves the first execution tool for the task, CliGate flips a conversation flag that lets later steps continue without another approval roundtrip. The assistant still feeds the real tool result back into the continuation run, so it can keep working from what actually happened instead of pretending the approval itself completed the task.&lt;/p&gt;

&lt;p&gt;The escape hatch matters too:&lt;br&gt;
&lt;/p&gt;

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

&lt;/div&gt;



&lt;p&gt;That turns the conversation back into explicit confirmation mode.&lt;/p&gt;

&lt;h2&gt;
  
  
  The continuation had to carry the real result
&lt;/h2&gt;

&lt;p&gt;There was another subtle part.&lt;/p&gt;

&lt;p&gt;When a user approves a pending action, the system cannot just say "confirmed" and stop. It has to continue the original job.&lt;/p&gt;

&lt;p&gt;The continuation prompt now tells the assistant that the previous tool call was approved, already executed, and produced a real result. That stops the assistant from asking for the same approval again or forgetting why the tool ran in the first place.&lt;/p&gt;

&lt;p&gt;This made the assistant feel much less like a form with a chatbot attached to it. The flow became:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;ask once for the risky boundary&lt;/li&gt;
&lt;li&gt;execute the first step&lt;/li&gt;
&lt;li&gt;continue the task with real observations&lt;/li&gt;
&lt;li&gt;only interrupt again when the user explicitly returns to safe mode or a genuinely different boundary appears&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The interesting part is that the fix is not "disable approvals." It is "remember what approval was for."&lt;/p&gt;

&lt;h2&gt;
  
  
  I also hit a language bug
&lt;/h2&gt;

&lt;p&gt;This same continuation path exposed a smaller but very visible bug.&lt;/p&gt;

&lt;p&gt;The continuation message was system-generated and written in English. When the original user task was Chinese, the assistant sometimes detected the system continuation as the latest "user" text and switched the next confirmation prompt to English.&lt;/p&gt;

&lt;p&gt;That produced an ugly mixed-language workflow: first approval in Chinese, later approval prompts in English.&lt;/p&gt;

&lt;p&gt;The fix was simple in principle: system-authored continuation turns should not decide the reply language. The assistant now looks back to the latest genuine user message in the conversation before choosing the response language.&lt;/p&gt;

&lt;p&gt;That is the kind of detail that seems minor until you use the tool for a multi-step task. Then it is the difference between "this assistant is tracking me" and "this assistant is reacting to its own plumbing."&lt;/p&gt;

&lt;h2&gt;
  
  
  The rule I am keeping
&lt;/h2&gt;

&lt;p&gt;I do not think agent approval should be all-or-nothing.&lt;/p&gt;

&lt;p&gt;For local tools, the useful middle ground is task-scoped trust:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;ask before crossing a meaningful boundary&lt;/li&gt;
&lt;li&gt;remember that approval for the current task&lt;/li&gt;
&lt;li&gt;keep an obvious way to return to strict mode&lt;/li&gt;
&lt;li&gt;do not let system continuation messages masquerade as user intent&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That is now how I am shaping approvals in CliGate, the local control plane I use for Claude Code, Codex CLI, Gemini CLI, desktop automation, channels, and model routing.&lt;/p&gt;

&lt;p&gt;The project is open source here: &lt;a href="https://github.com/codeking-ai/cligate" rel="noopener noreferrer"&gt;CliGate&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;If you are building local agents, how are you handling approval fatigue: per tool call, per task, per session, or something more granular?&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>javascript</category>
      <category>ai</category>
      <category>opensource</category>
    </item>
    <item>
      <title>"I Added a /yolo Button to My Local AI Assistant"</title>
      <dc:creator>CodeKing</dc:creator>
      <pubDate>Mon, 15 Jun 2026 09:32:38 +0000</pubDate>
      <link>https://dev.to/codekingai/i-added-a-yolo-button-to-my-local-ai-assistant-4klh</link>
      <guid>https://dev.to/codekingai/i-added-a-yolo-button-to-my-local-ai-assistant-4klh</guid>
      <description>&lt;p&gt;I like local AI assistants that ask before they do risky things.&lt;/p&gt;

&lt;p&gt;I do &lt;strong&gt;not&lt;/strong&gt; like approving the same task six times in a row.&lt;/p&gt;

&lt;p&gt;That was the failure mode I kept hitting while working on CliGate. A normal task would start with one reasonable command, then another, then another, and every tiny step wanted a fresh confirmation.&lt;/p&gt;

&lt;p&gt;So I added a blunt but useful switch:&lt;br&gt;
&lt;/p&gt;

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

&lt;/div&gt;



&lt;p&gt;It sounds reckless, but the real goal was the opposite: make the assistant feel faster &lt;strong&gt;without&lt;/strong&gt; training me to click through meaningless approval spam.&lt;/p&gt;

&lt;h2&gt;
  
  
  The bad version of safety was too chatty
&lt;/h2&gt;

&lt;p&gt;The old loop looked safe on paper:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;assistant picks a mutating tool
-&amp;gt; asks for confirmation
-&amp;gt; user approves
-&amp;gt; one tool runs
-&amp;gt; assistant continues
-&amp;gt; next tiny tool asks again
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That is technically correct.&lt;/p&gt;

&lt;p&gt;It is also miserable in practice.&lt;/p&gt;

&lt;p&gt;A real task is rarely one tool call. Reading a document, checking a project, or sending a result through a channel usually means several small steps in a row. If every step interrupts the user, the approval system stops communicating risk and starts communicating friction.&lt;/p&gt;

&lt;p&gt;That is when safety UI turns into background noise.&lt;/p&gt;

&lt;h2&gt;
  
  
  /yolo made the approval scope match the user's intent
&lt;/h2&gt;

&lt;p&gt;The fix was not "remove approvals." It was "remember what the user actually meant."&lt;/p&gt;

&lt;p&gt;In CliGate, &lt;code&gt;/yolo&lt;/code&gt; flips a conversation-level flag:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;later mutating tool calls in the same conversation auto-approve&lt;/li&gt;
&lt;li&gt;the assistant stops asking for every tiny follow-up step&lt;/li&gt;
&lt;li&gt;the user can turn strict mode back on with &lt;code&gt;/safe&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Under the hood, the flag lives on the conversation metadata as &lt;code&gt;assistantCore.autoApproveTools&lt;/code&gt;, and both web chat and channel conversations read the same source of truth.&lt;/p&gt;

&lt;p&gt;That detail mattered because I did not want one behavior in the web UI and a different one in DingTalk or Feishu.&lt;/p&gt;

&lt;h2&gt;
  
  
  Natural language mattered as much as the slash command
&lt;/h2&gt;

&lt;p&gt;The more interesting part is that users do not always type &lt;code&gt;/yolo&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;They say things like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;"后续都同意"&lt;/li&gt;
&lt;li&gt;"不要再问我了"&lt;/li&gt;
&lt;li&gt;"直接执行"&lt;/li&gt;
&lt;li&gt;"from now on, just do it"&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So I added sticky-approval phrase detection too.&lt;/p&gt;

&lt;p&gt;That means the assistant can recognize conversation-wide consent from normal language, not only from a command. But it still treats denial phrases separately, so "我不同意" does not accidentally enable auto-approve just because it contains the word "同意".&lt;/p&gt;

&lt;p&gt;This turned out to be one of those small pieces of product logic that matters more than the model prompt.&lt;/p&gt;

&lt;h2&gt;
  
  
  I still kept a real high-risk boundary
&lt;/h2&gt;

&lt;p&gt;The trap with a feature named &lt;code&gt;/yolo&lt;/code&gt; is obvious: if everything gets auto-approved, then safety is fake.&lt;/p&gt;

&lt;p&gt;So I kept one hard rule.&lt;/p&gt;

&lt;p&gt;Routine local work can flow through auto-approve mode, but genuinely destructive or external actions still need a fresh explicit confirmation.&lt;/p&gt;

&lt;p&gt;That means things like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;deleting files or directories&lt;/li&gt;
&lt;li&gt;overwriting data in a destructive way&lt;/li&gt;
&lt;li&gt;publishing outward&lt;/li&gt;
&lt;li&gt;sending messages to other people or other conversations&lt;/li&gt;
&lt;li&gt;submitting forms&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;still stop and ask.&lt;/p&gt;

&lt;p&gt;That boundary is what makes the mode usable. The assistant can stop being noisy about low-level execution steps while still pausing when the consequence is actually irreversible or external.&lt;/p&gt;

&lt;h2&gt;
  
  
  One confirmation can now cover a whole batch
&lt;/h2&gt;

&lt;p&gt;I also found a second bug while fixing this.&lt;/p&gt;

&lt;p&gt;Sometimes one pending confirmation represented &lt;strong&gt;multiple&lt;/strong&gt; queued tool calls. Historically, approving it only executed the first one, which silently dropped the rest.&lt;/p&gt;

&lt;p&gt;The confirmation service now expands a pending action into all captured tool invocations and runs each of them in order. One approve means the whole batch gets executed, not just item one.&lt;/p&gt;

&lt;p&gt;That sounds like an implementation detail, but it changes user trust a lot. If the UI says "confirmed," users expect the intended action to finish, not partially disappear.&lt;/p&gt;

&lt;h2&gt;
  
  
  The result feels less magical and more honest
&lt;/h2&gt;

&lt;p&gt;My favorite part of this change is that it did not make the assistant feel more autonomous.&lt;/p&gt;

&lt;p&gt;It made it feel more aligned.&lt;/p&gt;

&lt;p&gt;The assistant now behaves closer to how a human collaborator would interpret the conversation:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;if I said "just continue," it continues&lt;/li&gt;
&lt;li&gt;if I said &lt;code&gt;/yolo&lt;/code&gt;, it stops nagging me for every tiny step&lt;/li&gt;
&lt;li&gt;if the next move is truly risky, it still pauses&lt;/li&gt;
&lt;li&gt;if I want strict mode back, &lt;code&gt;/safe&lt;/code&gt; restores it immediately&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That balance is what I want from a local control plane: not maximum freedom, not maximum ceremony, just the right amount of friction in the right place.&lt;/p&gt;

&lt;p&gt;CliGate is the open-source local control plane I use to route Claude Code, Codex CLI, channels, desktop control, and assistant workflows through one place: &lt;a href="https://github.com/codeking-ai/cligate" rel="noopener noreferrer"&gt;CliGate&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;If you are building local agents, where do you draw the line between approval memory and real safety boundaries?&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>javascript</category>
      <category>ai</category>
      <category>opensource</category>
    </item>
    <item>
      <title>I Added WeChat to My Claude Code and Codex Workflow</title>
      <dc:creator>CodeKing</dc:creator>
      <pubDate>Thu, 11 Jun 2026 07:47:14 +0000</pubDate>
      <link>https://dev.to/codekingai/i-added-wechat-to-my-claude-code-and-codex-workflow-395b</link>
      <guid>https://dev.to/codekingai/i-added-wechat-to-my-claude-code-and-codex-workflow-395b</guid>
      <description>&lt;p&gt;The awkward part of local AI tooling is that "local" usually means "only useful while I am sitting at the keyboard."&lt;/p&gt;

&lt;p&gt;That is fine when I am deep in an editor. It is less fine when Claude Code is waiting for approval, Codex finished a task, or I just want to ask "what happened?" from my phone.&lt;/p&gt;

&lt;p&gt;I already had browser chat and a few external channels wired into CliGate. But for my daily workflow, one missing channel was obvious: WeChat.&lt;/p&gt;

&lt;h2&gt;
  
  
  The problem was not another bot
&lt;/h2&gt;

&lt;p&gt;Adding a chat provider is easy if all you want is text in and text out.&lt;/p&gt;

&lt;p&gt;That was not the workflow I wanted.&lt;/p&gt;

&lt;p&gt;The useful version needs to preserve the shape of an AI coding task:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;start a Codex or Claude Code run&lt;/li&gt;
&lt;li&gt;keep follow-up messages attached to the same task&lt;/li&gt;
&lt;li&gt;surface approvals when the runtime asks for permission&lt;/li&gt;
&lt;li&gt;send progress and final results back to the user&lt;/li&gt;
&lt;li&gt;remember whether the conversation is a new task, a status check, or a continuation&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Without that, a WeChat integration would just become another notification pipe. Nice to demo, not very useful under pressure.&lt;/p&gt;

&lt;p&gt;The painful case is familiar: a local agent starts doing real work, hits one approval prompt, and then silently waits in a terminal you are not looking at. The problem is not that the model cannot reason. The problem is that the control loop is trapped in the wrong place.&lt;/p&gt;

&lt;h2&gt;
  
  
  The fix was to treat channels as task surfaces
&lt;/h2&gt;

&lt;p&gt;In CliGate, I ended up treating WeChat the same way I treat Telegram, Feishu, and DingTalk: not as separate products, but as surfaces over the same local task system.&lt;/p&gt;

&lt;p&gt;The runtime is still local. Claude Code and Codex still run on my machine. The proxy, account routing, logs, approvals, and task records still live on &lt;code&gt;localhost&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The channel only changes where I talk to the workflow.&lt;/p&gt;

&lt;p&gt;That distinction matters. It means the WeChat provider does not need to know how to run Codex. It needs to know how to receive a message, map a user to a conversation, and hand that message to the same supervisor layer that the web chat uses.&lt;/p&gt;

&lt;p&gt;The mental model 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;WeChat message
-&amp;gt; channel gateway
-&amp;gt; supervisor conversation
-&amp;gt; Codex or Claude Code runtime
-&amp;gt; progress / approval / result
-&amp;gt; WeChat reply
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The interesting part is the middle. A message like "status?" should not be forwarded to Codex as a random new prompt. It should be answered from the remembered task state. A message like "also update the docs" should usually continue the current task, not start from scratch with the default provider.&lt;/p&gt;

&lt;p&gt;That task memory is what makes a channel feel like a real interface instead of a webhook.&lt;/p&gt;

&lt;h2&gt;
  
  
  What changed in the workflow
&lt;/h2&gt;

&lt;p&gt;Before this, I had a split-brain setup.&lt;/p&gt;

&lt;p&gt;The desktop dashboard was where the real task state lived. My phone was mostly for checking messages. If an AI coding task needed a decision, I had to return to the browser or terminal.&lt;/p&gt;

&lt;p&gt;Now the chat app can stay in the loop:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;start a coding task from WeChat&lt;/li&gt;
&lt;li&gt;receive progress as the local runtime works&lt;/li&gt;
&lt;li&gt;answer approval prompts without finding the terminal&lt;/li&gt;
&lt;li&gt;ask for a status update without interrupting the agent&lt;/li&gt;
&lt;li&gt;continue the same task with a short follow-up&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That does not make WeChat special. That is actually the point.&lt;/p&gt;

&lt;p&gt;The more useful design is that the channel is replaceable. Telegram, Feishu, DingTalk, WeChat, and the web dashboard should all speak to the same task model. Users can pick the surface that matches their day.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why I kept the tools unchanged
&lt;/h2&gt;

&lt;p&gt;I did not want to build a fake Claude Code or a fake Codex inside a chat app.&lt;/p&gt;

&lt;p&gt;Those tools already have their own strengths. The better layer is a local control plane around them: one place for routing, credentials, approvals, task records, logs, and channel delivery.&lt;/p&gt;

&lt;p&gt;That is what CliGate is trying to be.&lt;/p&gt;

&lt;p&gt;You still run it locally:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx cligate@latest start
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then the same machine that owns your code can also own the coordination loop. No hosted relay is required for the core workflow, and the AI coding tools do not need custom provider configs for every channel you add.&lt;/p&gt;

&lt;h2&gt;
  
  
  The lesson
&lt;/h2&gt;

&lt;p&gt;The main thing I learned is that "mobile support" for local agents is not just a smaller UI.&lt;/p&gt;

&lt;p&gt;It is a continuity problem.&lt;/p&gt;

&lt;p&gt;If the phone cannot see the task state, approvals, runtime choice, and final result, then it is only a remote text box. If it can see those things, it becomes a practical control surface for work that still runs safely on your own machine.&lt;/p&gt;

&lt;p&gt;That is the direction I want local AI tooling to go: the work stays local, but the conversation can meet you where you already are.&lt;/p&gt;

&lt;p&gt;The project is open source here: &lt;a href="https://github.com/codeking-ai/cligate" rel="noopener noreferrer"&gt;https://github.com/codeking-ai/cligate&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you use AI coding agents, where do you want their progress and approval prompts to show up: terminal, browser, Slack, Telegram, WeChat, or somewhere else?&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>javascript</category>
      <category>ai</category>
      <category>opensource</category>
    </item>
    <item>
      <title>My Coding Agent Asked Permission for Every Tiny Step</title>
      <dc:creator>CodeKing</dc:creator>
      <pubDate>Tue, 09 Jun 2026 07:09:30 +0000</pubDate>
      <link>https://dev.to/codekingai/my-coding-agent-asked-permission-for-every-tiny-step-26lg</link>
      <guid>https://dev.to/codekingai/my-coding-agent-asked-permission-for-every-tiny-step-26lg</guid>
      <description>&lt;p&gt;The most annoying bug in my local AI assistant was not that it refused to ask for permission.&lt;/p&gt;

&lt;p&gt;It was that it asked too often.&lt;/p&gt;

&lt;p&gt;I would give it a normal task like "read this PDF and tell me what is inside." The assistant would make a reasonable first move, ask for approval, run the command, and then immediately ask again for the next tiny probe.&lt;/p&gt;

&lt;p&gt;One task turned into a permission treadmill.&lt;/p&gt;

&lt;h2&gt;
  
  
  The approval loop felt safe but unusable
&lt;/h2&gt;

&lt;p&gt;Approval prompts are good. I want a local assistant to stop before it runs commands, writes files, opens apps, or touches the desktop.&lt;/p&gt;

&lt;p&gt;The problem is that real work is rarely one tool call.&lt;/p&gt;

&lt;p&gt;Reading a PDF might require checking whether Python exists, finding a converter, running the converter, reading the text output, and then summarizing it. A desktop task might require focusing a window, entering text, pressing a button, and verifying the result.&lt;/p&gt;

&lt;p&gt;If every step asks the same kind of question, the user stops evaluating risk and starts clicking through interruptions.&lt;/p&gt;

&lt;p&gt;That is worse than unsafe. It trains the user to ignore the safety system.&lt;/p&gt;

&lt;h2&gt;
  
  
  The bug was treating approval as a single event
&lt;/h2&gt;

&lt;p&gt;The old flow in CliGate looked 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;assistant chooses tool
-&amp;gt; policy says confirmation required
-&amp;gt; user approves
-&amp;gt; tool runs
-&amp;gt; assistant continues
-&amp;gt; next tool asks again
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That was technically correct. Each tool call had its own confirmation.&lt;/p&gt;

&lt;p&gt;But it missed the shape of the user's intent. The user was not approving "run one tiny probe." They were approving the assistant to continue one bounded task.&lt;/p&gt;

&lt;p&gt;So I changed the behavior after the first approved action in a chat conversation.&lt;/p&gt;

&lt;p&gt;Once the user approves the first execution tool for the task, CliGate flips a conversation flag that lets later steps continue without another approval roundtrip. The assistant still feeds the real tool result back into the continuation run, so it can keep working from what actually happened instead of pretending the approval itself completed the task.&lt;/p&gt;

&lt;p&gt;The escape hatch matters too:&lt;br&gt;
&lt;/p&gt;

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

&lt;/div&gt;



&lt;p&gt;That turns the conversation back into explicit confirmation mode.&lt;/p&gt;

&lt;h2&gt;
  
  
  The continuation had to carry the real result
&lt;/h2&gt;

&lt;p&gt;There was another subtle part.&lt;/p&gt;

&lt;p&gt;When a user approves a pending action, the system cannot just say "confirmed" and stop. It has to continue the original job.&lt;/p&gt;

&lt;p&gt;The continuation prompt now tells the assistant that the previous tool call was approved, already executed, and produced a real result. That stops the assistant from asking for the same approval again or forgetting why the tool ran in the first place.&lt;/p&gt;

&lt;p&gt;This made the assistant feel much less like a form with a chatbot attached to it. The flow became:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;ask once for the risky boundary&lt;/li&gt;
&lt;li&gt;execute the first step&lt;/li&gt;
&lt;li&gt;continue the task with real observations&lt;/li&gt;
&lt;li&gt;only interrupt again when the user explicitly returns to safe mode or a genuinely different boundary appears&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The interesting part is that the fix is not "disable approvals." It is "remember what approval was for."&lt;/p&gt;

&lt;h2&gt;
  
  
  I also hit a language bug
&lt;/h2&gt;

&lt;p&gt;This same continuation path exposed a smaller but very visible bug.&lt;/p&gt;

&lt;p&gt;The continuation message was system-generated and written in English. When the original user task was Chinese, the assistant sometimes detected the system continuation as the latest "user" text and switched the next confirmation prompt to English.&lt;/p&gt;

&lt;p&gt;That produced an ugly mixed-language workflow: first approval in Chinese, later approval prompts in English.&lt;/p&gt;

&lt;p&gt;The fix was simple in principle: system-authored continuation turns should not decide the reply language. The assistant now looks back to the latest genuine user message in the conversation before choosing the response language.&lt;/p&gt;

&lt;p&gt;That is the kind of detail that seems minor until you use the tool for a multi-step task. Then it is the difference between "this assistant is tracking me" and "this assistant is reacting to its own plumbing."&lt;/p&gt;

&lt;h2&gt;
  
  
  The rule I am keeping
&lt;/h2&gt;

&lt;p&gt;I do not think agent approval should be all-or-nothing.&lt;/p&gt;

&lt;p&gt;For local tools, the useful middle ground is task-scoped trust:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;ask before crossing a meaningful boundary&lt;/li&gt;
&lt;li&gt;remember that approval for the current task&lt;/li&gt;
&lt;li&gt;keep an obvious way to return to strict mode&lt;/li&gt;
&lt;li&gt;do not let system continuation messages masquerade as user intent&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That is now how I am shaping approvals in CliGate, the local control plane I use for Claude Code, Codex CLI, Gemini CLI, desktop automation, channels, and model routing.&lt;/p&gt;

&lt;p&gt;The project is open source here: &lt;a href="https://github.com/codeking-ai/cligate" rel="noopener noreferrer"&gt;CliGate&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;If you are building local agents, how are you handling approval fatigue: per tool call, per task, per session, or something more granular?&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>javascript</category>
      <category>ai</category>
      <category>opensource</category>
    </item>
  </channel>
</rss>
