{"slug": "my-agents-ran-in-parallel-i-didn-t", "title": "My Agents Ran in Parallel. I Didn't", "summary": "Arnold Wang, chief software architect at Ingeniux Corporation, built a review queue for developers running many parallel AI coding agents, in which agents raise cards (done, question, permission, paused) instead of waiting silently in separate sessions. The design follows a rule that the model proposes but code can veto recognizable risky actions such as force-pushes, writes outside the workspace, or reads of .env files, keeping those cards in front of the human. Over five days of logs from the DSH harness, Wang reported at least one unanswered card in 73% of minutes between 8am and 6pm, with a peak of fifteen at once.", "body_md": "*A review queue for people who run many AI agents, and the rule that keeps it safe: the model proposes, code can veto.*\n\n*By Arnold Wang, Chief Software Architect at Ingeniux Corporation.*\n\n*The examples and numbers come from DSH (DeepSeek Harness), an agent harness with a plugin system that I use every day. I have only run this pattern there.*\n\n*Disclosure: the ideas, the data and the design are mine, taken from my own logs and design notes. An AI agent wrote the draft text under my direction, several fresh AI reviewers critiqued it, and I reviewed and approved the result.*\n\nOn November 2 at 10:18 in the morning, ten of my coding-agent sessions were waiting for my next instruction, eleven messages in all (one session had two), the oldest 16 minutes old. The last one was not cleared until 84 minutes later. Without a queue, I would find that out by opening each session in turn. With one, I saw all eleven on a single screen.\n\nOver November 2 to 6 I had at least one unanswered card in 73% of the minutes between 8am and 6pm, and fifteen at once at the peak.\n\nThe agents ran in parallel. I did not. Starting another session makes you faster only until the sessions begin waiting for you. After that the bottleneck is your attention, not the model.\n\nThe problem is not that the agents are slow. Every agent that needs me stops and waits, silently, in its own window, and I cannot see all of them at once. Three things follow. I find out late. I open sessions only to learn what they want. And most of what they want is small: a \"done\" message, or a question with one obvious answer.\n\nMy approach has three parts:\n\nThe rest of the article defines the terms, describes the queue, explains why a model must not decide alone, reports what five days of real logs show, and lists where the design breaks.\n\nThe rule that limits the damage: **the model proposes, code can veto what it can recognize.**\n\nAgents push, you pull. A session that needs something raises a card instead of waiting silently, and you work one list instead of visiting N sessions. Four kinds of card cover most of the traffic:\n\n| Card | What it means | What you do | \n|---|---|---|\n| Done | A session finished a turn | Read a short summary, reply or mark reviewed | \n| Question | The agent asks you to choose | Pick an option or type your own | \n| Permission | A tool call needs approval | See the full command, approve or deny | \n| Paused | A session stopped mid-task | Resume it | \n\nYou answer on the card. Opening the session is the expensive step the queue exists to avoid. Every card appears in the queue first, and triage may resolve or hide it afterwards, so a triage outage cannot lose a card on its way in.\n\nThe obvious design lets a language model read each card and decide whether to bother you. It is the wrong design for one reason: **the text on a card was written by the agent being judged**, or by something the agent read, such as a web page or an issue comment. If the model is what grants permission, the queue is only as safe as the weakest prompt injection. So code does the work that matters for safety:\n\n`git push --force`, a write outside the workspace, a read of `.env`: any one keeps the card in front of you, and those cards never reach the model.`git diff --stat` passes. `git diff --ext-diff` is refused. If the model says \"read-only\" and the parser disagrees, you see the card.\nTwo parts of this are weaker than they sound. For questions and done cards, the \"low risk\" call is the model's own, and code can only veto it with a word scan over the card and a scan of the end of the message. Code can say no. It cannot verify yes.\n\n**A permission that should never reach you.** An agent runs `git log --oneline -20` in its own workspace. The parser accepts it, the path is inside the workspace, no protected file is touched. It is approved and listed. `rg token .env` is vetoed in code and lands in your queue with a risk warning and the exact command.\n\n**A question that is a real decision.** \"Approve this tab-based design for the settings page?\" The triage model flags that it needs a decision. It stays in your queue.\n\n**A question that is not.** \"Rename the helper to `parseConfig`? (Recommended) Yes.\" One option is marked, nothing risky in the wording, the action is reversible. Auto mode picks exactly that label and nothing else.\n\nAnd a done card. \"Renamed the helper, 120 tests pass, committed\" is hidden, with a *Reopen* button. \"Tests pass, but you must run the migration by hand\" never is.\n\n**Code alone, with no model call, kept 552 of 1,183 triaged cards (47%) for me. Auto mode ran for about a day and a half of the five days, and in that time code acted on one card the model proposed to handle.**\n\nOne person, one workload, 168 sessions, no control group, November 2 to 6, 2026 (the last day partial). The queue received 1,645 cards. 1,183 of them went through triage: 895 done cards, 287 questions and one permission request. That is 72% of everything received. The rest were other card types (paused sessions, archive offers, todos) and some done cards and questions that never went through triage; I did not break that down. The \"held by code\" cards are the ones a veto stopped or no rule covers; I did not separate those two.\n\nEverything below comes from one snapshot of the log, the first 1,183 triaged cards (the log kept growing while I wrote this).\n\n| Triage funnel | Cards | \n|---|---|\n| **Reached triage (1,183 = 552 + 5 + 626)** | **1,183** | \n| Kept for me by code vetoes alone, no model call | 552 (47%) | \n| Skipped, or held back by a past-correction lesson | 5 | \n| **Judged by the triage model** | **626** | \n| ...of which unusable output, shown to me | 62 | \n| ...of which answered \"ask the human\" | 511 | \n| ...of which answered \"I can handle this\" | 53 | \n| ...of those 53, acted on automatically | 1 | \n\nI switched all three rules to auto on the morning of November 5. Before the switch, 647 cards reached triage: 282 were held by code, 2 were skipped and 363 were judged, and the model proposed handling 52 of them (14%). After it, 536 cards reached triage: 270 were held by code, 3 were skipped and 263 were judged, and the model proposed handling one (under 0.4%). That one was acted on, by code, on the model's proposal.\n\nSo \"one card acted on\" says mostly that the auto rules almost never fire. All 51 done-card proposals came before the switch and none after. I do not know why the rate fell (a prompt change, a stricter evidence rule, or drift), and it does not show that auto mode is safe.\n\n**Unusable output.** 62 of 626 judged cards, one in ten, came back unusable: 61 of them in the first three days and one later. The log only records \"the model answered something unusable\", so I cannot split them into broken JSON and timeouts. Each one fell back to showing me the card, so the cost was an extra card, not a wrong action.\n\n**Waiting.** Over all 1,645 cards, done cards I reviewed by hand waited a median of 1.6 minutes, and the slowest 10% waited 16 minutes or more (n = 438). Questions I answered on the card waited a median of 1.9 minutes, the slowest 10% 22 or more (n = 433). These exclude cards that were never resolved, such as two questions left overnight for about 13 hours, so they are biased low, and I have no before-and-after baseline. They describe life with the queue, not a comparison.\n\n**Agreement.** Only 11 done cards gave a usable comparison, and that is too few to mean anything. The log compared the model's \"I can handle this\" proposal with what happened on 49 done cards. 38 of them had already been hidden by an older \"nothing for you\" filter, which the queue records as resolved automatically, so they say nothing about me. (Two more feedback records mark a lesson as too strict, which is why 51 done-card proposals give 49 comparisons here.) Of the 11 I handled myself, I marked 9 reviewed, which matched, and I replied to 2 that the model would have cleared. I did not read back what those replies said. The model's note was visible while I decided, so those matches are anchored, not independent. Of the 53 proposals, 51 were done cards and 2 were questions. I overruled one question proposal. The other is the story below.\n\n**Questions.** Of the 287 questions that reached triage, code held or skipped 64 and the triage model judged 223. It called only 2 safe to answer. That measures its caution at least as much as the questions.\n\n**Permissions.** One permission request reached triage in five days (and only one reached the queue at all), so the parser and the vetoes were exercised by one live card. They are unit-tested, not field-tested.\n\nMost of the benefit I feel does not come from automation (unmeasured, one user). It comes from one list, from answering in place and from not seeing the empty cards.\n\n`Stop` and `Notification` hooks). Send yourself a count only: never the command, the message text or the session title, which can carry secrets or an agent-written phishing line. Make the hook exit 0 so it cannot re-trigger the agent.`git diff:*` still allows flags like `--output=file`, and repo config can make even `git status` run code. Skip git allow-lists in repos you do not trust.`(Recommended)`. For anything risky, decide before you look at the mark, because it anchors you. Do not let anything pick it for you without a check.`git log` or `git diff` run code, and a read such as `rg token .` or `git log -p` can surface secrets without naming a protected file.\nThe only card the queue ever acted on for the triage model (code acted on its proposal) happened while I was writing this article with an agent. The agent asked me a confirmation, \"Use this prompt as shown above?\", with a recommended option. I had just asked to see the full prompt before deciding. Five seconds after the card appeared, the queue answered with the recommended option and filed a receipt. As evidence it quoted an earlier instruction of mine: \"improve the above prompt first with a prompt tuner skill\". Code confirmed that the quote existed word for word. Nothing confirmed that it answered that question.\n\nThe answer was harmless, and probably the one I would have given. It was still not my decision, and *Mark wrong* cannot unsend an answer the agent has already read. My earlier words authorized the topic, not that choice. The model can propose. Only something that checks the specific decision, in code, should be allowed to act.\n\nThe queue has a second use. With a companion plugin, `dsh-session-link`, one agent session can find another session, ask it to do something, start a new session, or offer to archive itself. Each of those requests lands in the same queue as a card, and nothing happens until I confirm it.\n\nThat puts me in the important decisions. Another session starting work, one agent giving instructions to another, and a session closing itself are critical operations, and they go through me on a card that names the session asking. Agent frameworks put hurdles in the way of agent-to-agent messages, because a message from one agent into another is a way to push instructions past the user. This design goes around those hurdles instead of removing them: the agents can cooperate, but the person grants each cross-agent action, and a started session gets the queue's default access, not a copy of the sender's.\n\nIt has the same weakness as the rest of the queue. The label that marks a message as coming from another agent can be imitated, so the card is the only safeguard, and a tired person tapping through cards is the risk. Reading another session's last reply needs no approval and covers every workspace of the app, which I accepted knowingly.\n\nThe queue shows cards oldest first, and that is its biggest weakness: a flood of small cards can bury the one that matters. Two changes are next.\n\n**Priority.** Code, not the card text, will rank the cards, because an agent can write \"urgent\" in a summary just as it can plant \"(Recommended)\". The signals are the card type, the risk flags from the vetoes, whether the card needs a real decision, and how long it has waited, with a slow age boost so nothing starves. Each card will show why it ranks where it does, and a switch will bring back oldest first.\n\n**Timeline.** The queue does not yet record when I first saw a card, or how long an agent sat blocked waiting for me. I plan to add an append-only event log for cards and sessions, storing ids, types and timestamps but never card text. From it I can measure wait time, time to first look and blocked minutes, and use them to weigh priority by the cost of waiting.\n\nThe rollout follows the same rule as the triage rules: observe first. Record the events with no change in behavior. Show a computed priority as a badge while the list stays oldest first, and replay the 1,645 cards behind this article to see where the risky ones would have ranked. Reorder only after that looks right, behind a switch. The test is simple: the cards that block an agent or carry risk should wait less than they do today, and none should starve.\n\nThe numbers above are the logs of `dsh-review-queue`, an open-source plugin for DSH. The code is in the [dsh-plugins repository](https://github.com/jitsmaster/dsh-plugins), and the [v0.15.0 tag](https://github.com/jitsmaster/dsh-plugins/tree/v0.15.0/dsh-review-queue) is the version described here.", "url": "https://wpnews.pro/news/my-agents-ran-in-parallel-i-didn-t", "canonical_source": "https://dev.to/arnold_wang_dfbbb991be9ff/my-agents-ran-in-parallel-i-didnt-2enh", "published_at": "2026-11-07 01:17:57+00:00", "updated_at": "2026-11-07 01:22:23.442059+00:00", "lang": "en", "topics": ["ai-agents", "ai-tools", "ai-safety", "developer-tools"], "entities": ["Arnold Wang", "Ingeniux Corporation", "DSH", "DeepSeek Harness"], "also_reported_by": [], "alternates": {"html": "https://wpnews.pro/news/my-agents-ran-in-parallel-i-didn-t", "markdown": "https://wpnews.pro/news/my-agents-ran-in-parallel-i-didn-t.md", "text": "https://wpnews.pro/news/my-agents-ran-in-parallel-i-didn-t.txt", "jsonld": "https://wpnews.pro/news/my-agents-ran-in-parallel-i-didn-t.jsonld"}}