{"slug": "ai-in-action-first-time-using-herdr-collaborating-with-claude-code-and-agy-for", "title": "[AI in Action] First Time Using Herdr: Collaborating with Claude Code and agy for Go Backend Code Review, Issue Fixes, and Cloud", "summary": "A developer rewrote the song-lingo Japanese song-learning tool's backend from Python and Next.js API routes to Go 1.26, then used Herdr—a tool that runs multiple AI agents in separate panes of one window—to have Claude Code act as reviewer and the Antigravity CLI (agy) as implementer, carrying the work from code review through Cloud Run deployment. The two agents coordinated through Herdr's terminal core commands (run, send, wait, read, keys, get), with agy reporting back into Claude Code's input via `herdr-core send t4`. The developer highlighted Herdr's `--skill` flag, which prints an instruction manual for AI agents, as its smartest design.", "body_md": "**song-lingo** is a small tool I made for learning Japanese songs: paste a YouTube MV link, and Gemini helps me transcribe the lyrics, label Romanization (Romaji), provide Traditional Chinese translation and grammar notes, and then uses Gemini TTS to generate a \"teacher demonstration audio.\" After I record myself shadowing the song, Gemini transcribes my pronunciation and provides a word-by-word score.\n\nA few days ago, in a single Claude Code session, I rewrote the entire backend from **Python + Next.js API routes to Go 1.26** (Issues #44–#49, Tracking Issue #50, PR #51–#56). The architecture after the rewrite looks like this:\n\n`/api` to the Go server in the same container (`127.0.0.1:8081`).` asia-east1`)`/data`` genai` SDK, Gemini REST (voices / interactions), Firestore transactions, `kagome` for tokenization and conversion to Hiragana, a custom Hepburn Romanization table, and a port of Python's `difflib.SequenceMatcher` to Go (aligned with The rewrite was done, it worked, and tests passed. But \"writing and reviewing your own code\" always has blind spots. I happened to want to try **Herdr**—a tool that allows multiple AI Agents to collaborate in the same window, each occupying a Pane. So, I conducted an experiment:\n\n**Let Claude Code be the controller and Reviewer on the left, and the Antigravity CLI (`agy`) be the implementer on the right. The two Agents communicate via Herdr, taking the song-lingo Go backend from Code Review all the way to Cloud Run deployment.**\n\n`herdr-app` and `herdr-core`\nHerdr is divided into two layers:\n\n| Component | Role | Description | \n|---|---|---|\n| **`herdr-app`** | Desktop App | Manages the layout of **spaces > tabs > panes** . You can split panes left/right and run programs directly in a new pane. | \n| **`herdr-core`** (CLI called` hc` ) | Terminal Core | Responsible for running \" **terminals that don't die when the window is closed** ,\" providing commands like run / send / wait / read / keys / get for Agents to operate other terminals. | \n\nA small side note: I initially told Claude Code to \"use `herdr --skill` to learn first,\" but the `herdr` command couldn't be found—the actual executables are `~/.local/bin/herdr-app` and `~/.local/bin/herdr-core`.\n\n`--skill`: An Instruction Manual for AI Agents\nI think Herdr's smartest design is that **both CLIs have a `--skill` parameter**. Running `herdr-app --skill` and `herdr-core --skill` prints an \"instruction manual for AI Agents.\"\n\nSo, Claude Code's first step wasn't to blindly try commands, but to **read both manuals before starting**. The manuals contain many sensible guidelines, such as: background Agents must use `--bg`, provide a name and clear scope, don't let sub-agents spawn more sub-agents, and use `hc wait` instead of **sleep polling** when waiting.\n\n| Command | Purpose | \n|---|---|\n| `$HC_TERMINAL_ID` | Every terminal has an address; Claude Code itself is **`t4`** . | \n| `hc run --bg --name xxx <agent> \"<task>\"` | Start a named Agent in the background and assign a task. | \n| `herdr-app do pane.split --direction right --pane <id> --argv [...]` | Split the pane to the right on the screen and run a program in the new pane. | \n| `hc send <t> \"...\"` | Paste text and press Enter; used to assign tasks or provide Review feedback. | \n| `hc wait <t>` /`hc wait --children all` | Wait for the other party to finish (replaces sleep polling). | \n| `hc read <t> --lines 0` | Read the other party's entire current screen. | \n| `hc keys <t> enter` | Send a keystroke, such as answering a permission dialog. | \n| `hc get <t>` | Check status: `idle` /`working` /`blocked` /`done` ; when`blocked` , it includes a`msg` , e.g.,`Approve RunCommand: ...` . | \n\nThis is the most critical piece of the collaboration. When assigning a task, I would require agy: \"When finished, please run `herdr-core send t4 '...'` to report back.\" After agy finishes, that message **appears directly in Claude Code's input**, looking like this:\n\n```\n<hc from=\"t7\">ready for review #57 ...</hc>\n```\n\nIn other words, Claude Code doesn't need to keep staring at the right screen; agy will \"knock on the door\" when done. Reporting is asynchronous, and every message carries a source address for tracking.\n\nThe first time I had Claude Code open agy, it used `hc run --bg` to run it in the background—it worked functionally, but I couldn't see what agy was doing. So, the second time, I specifically requested \"**in a pane on the right**,\" and Claude Code used `herdr-app do pane.split` to open agy on the right. The final configuration was:\n\n```\nflowchart LR\n    subgraph Herdr[\"Herdr Window (Same Tab)\"]\n        direction LR\n        CC[\"Left Pane: Claude Code (t4)<br/>Controller / Reviewer<br/>Opens Issues, views diffs, runs go test -race, approves\"]\n        AGY[\"Middle Pane: agy (t7)<br/>Implementer<br/>Code Review, modifies code, runs tests, commit/push\"]\n        BLOG[\"Right Pane: Claude Code<br/>Blog Writer<br/>(The one writing this article)\"]\n    end\n    Human([\"Me (Human)<br/>Can see every Pane, can intervene anytime\"])\n\n    CC -->|\"hc send: Assign tasks / Review feedback\"| AGY\n    AGY -->|\"herdr-core send t4: ready for review #N\"| CC\n    CC -->|\"hc keys: Grant read-only permissions\"| AGY\n    CC -->|\"hc send: Write blog\"| BLOG\n    BLOG -->|\"herdr-core send t4: blog draft ready\"| CC\n    Human -.-> CC\n    Human -.-> AGY\n```\n\nThe first task was simple: ask agy to review the Go code under `backend/`, **read-only, no subagents**, and write the results to a file, reporting back with `hc send t4` when finished.\n\nagy's interface showed it was running **Claude Sonnet 5.5 (plan mode)**, and it eventually produced **17 issues, sorted by importance**. Claude Code helped me organize them into three categories:\n\n| Category | Issues found by agy | \n|---|---|\n| **Concurrency and Data Integrity** | During Re-annotate (one Gemini call can take up to 5 mins), if I modify lyrics on the proofreading page, the write-back will be overwritten—the two sides use **different mutexes** ;`EditLine` 's global lock spans across Firestore calls;`WriteAtomic` 's temporary filename is fixed as`.tmp` . | \n| **Resource Limits and Leaks** | TTS generation has no concurrency limit (this is a paid API); the failure cache only grows and stores duplicates; 429 errors without `Retry-After` have no rate limiting at all;`TeacherVoice` re-reads`voices.json` every time and holds a global lock during network calls; request bodies lack`MaxBytesReader` ; background jobs use`context.Background()` , so they aren't cancelled on shutdown. | \n| **Performance and Structure** | `listSongs` parses all songs sequentially and reads progress for each; demonstration audio is read entirely into memory before being returned; the same Error type exists in 3–4 places; CLI`speak` and`tts.Service` logic are redundant. | \n\nHere is a detail I really liked: **Claude Code did not accept everything blindly**. Before opening an Issue, it first used grep to verify the most important one—\"re-annotate and review use different mutexes\"—and only after confirming it was true did it start opening Issues. The Reviewer's conclusions also need to be Reviewed.\n\nMy instructions to Claude Code were:\n\n\"Open issues and fix them sequentially. Mainly let the agy pane on the right do the modifications. You are responsible for reviewing and giving feedback. Only after you approve should agy commit and push.\"\n\nClaude Code consolidated the 17 items into **5 Issues (#57–#61)**, all performed on the same branch `go-review-fixes`, with **one commit per Issue**, and commit messages including `Fixes #N`.\n\n```\nsequenceDiagram\n    participant CC as Claude Code (t4)<br/>Reviewer\n    participant AGY as agy (t7)<br/>Implementer\n    participant GH as GitHub\n\n    CC->>AGY: hc send: Issue #N task + implementation points + \"do not commit\"\n    AGY->>AGY: Implementation + gofmt -l . && go vet ./... && go test ./...\n    AGY->>CC: herdr-core send t4 'ready for review #N'\n    CC->>CC: git diff + run go test -race personally\n    alt Has issues\n        CC->>AGY: hc send: Review feedback\n        AGY->>CC: Report back again after fix\n    end\n    CC->>AGY: Approved, you may commit & push\n    AGY->>GH: git commit (Fixes #N) + push\n    AGY->>CC: Report commit sha\n```\n\nThis is the part I want to share the most. Out of the 5 Issues, **3 required a second round of fixes, and 2 passed on the first try**:\n\n| Issue | Topic | Review Result | Commit | \n|---|---|---|---|\n| **#57** | Per-song lock | Design correct, but **no tests written** → requested them | `e5df5f4` | \n| **#58** | TTS concurrency limit and failure cache | Caught **error message regression** → requested fix and tests | `d67ec5a` | \n| **#59** | Request limits, timeout, graceful shutdown | Caught **shutdown sequence and time budget errors** → redesigned | `de38741` | \n| **#60** | Song list concurrency, demo audio streaming | ✅ Passed first try | `bc1615a` | \n| **#61** | Unified error types, CLI refactor, signal handling | ✅ Passed first try | `deb1fb7` | \n\nagy's per-song lock design was correct. Claude Code also specifically confirmed one thing: manual translations and the `reviewed` flag are both written into the transcript, so \"comparing the entire transcript before writing back\" indeed catches all edits.\n\nBut agy didn't write tests. So Claude Code requested extracting the logic into `saveAnnotation` and adding two cases: **normal write when unchanged**, and **return conflict and no write when edited**.\n\nWhile cleaning up the failure cache, agy deleted the server-side `recentErrors`. All tests passed, but Claude Code caught a regression:\n\nWhen the TTS quota is exhausted, **\"other sentences that have never been generated\"** checking `?error` would get `{error: null}`—the frontend then couldn't display the reason \"quota exhausted.\"\n\nThe fix was to make `LastError` **return a still-valid quota error when there is no failure record for that sentence**, and add a test.\n\nThis was the most valuable bug catch in my opinion. The graceful shutdown agy wrote was: `srv.Shutdown` waits 10s, then background jobs wait another 10s. There were two problems:\n\n`srv.Shutdown` would wait for it to finish, but its context hadn't been cancelled yet—it was essentially waiting for itself.\nClaude Code requested changing it to a **single 9-second deadline** and adjusting the sequence:\n\n```\n// Single deadline, leaving a bit of buffer for Cloud Run's SIGTERM grace period\nctx, cancel := context.WithTimeout(context.Background(), 9*time.Second)\ndefer cancel()\n\nrunner.Cancel() // 1. Cancel background and ongoing analysis work first\nsrv.Shutdown(ctx) // 2. Then shut down HTTP server (sync analysis already cancelled, won't hang)\nrunner.Wait(ctx) // 3. Finally wait for jobs to wrap up within the same deadline\n```\n\n(The above is illustrative of the sequence and single-deadline concept.)\n\n`*os.File` + `ModTime` handed to `http.ServeContent` for streaming (getting Range and Last-Modified for free).`apierr.Error`, CLI `speak` now uses `tts.Service.GenerateAll`, switched to `signal.NotifyContext`, and daily quota checks now use type fields instead of string comparisons.\nLooking back at these three rejected Issues, there's a common theme: **they were all \"tests pass, but it hurts in production\" problems**—test gaps, frontend error message regressions, and shutdown sequences. This is exactly the greatest value of \"one writing, one reviewing.\"\n\nAfter all 5 commits passed, **PR #62** was opened and merged, and #57–#61 were automatically closed. Then, a new revision was deployed using `gcloud run deploy --source .`.\n\nPost-deployment verification was completed by Claude Code running `fetch` directly through a **Chrome instance logged into IAP**:\n\n| Verification Item | Result | Corresponding Change | \n|---|---|---|\n| Homepage `/api/songs` | `200` (10 songs) | #60 Concurrent reading | \n| Learning page | `200` | — | \n| Generated demo audio | `200 audio/wav` | #60 Streaming | \n| Demo audio Range request | `206` | #60 `http.ServeContent` | \n| With `If-Modified-Since` | `304` (**New feature** : now has`Last-Modified` ) | #60 `ModTime` | \n| PATCH with 70 KB | `413` \"Request content too large.\" | #59 `MaxBytesReader` | \n| Cloud Logging | Only two WARNINGs, both from the test requests themselves | — | \n\nThe previous revision was also kept, allowing for an immediate rollback if needed.\n\n`agy \"prompt\"` will exit with code 2\nIntuitively, you might start agy like this:\n\n```\nagy \"Please review the Go code in backend/...\"\n```\n\nThe result is an immediate error and exit (exit 2):\n\n```\nPrompts are read only from -p/--print, -i/--prompt-interactive, or stdin\n```\n\nThe correct way is **`agy -i \"...\"`**—interactive startup with an initial task. The Herdr manual also warns **not to use `-p` one-off mode**, because the Agent needs to remain \"observable and follow-upable\" to conduct the Review back-and-forth.\n\nThe first time you start agy in a folder, it pops up \"**Do you trust the contents of this project?**\". At this point, the `hc get` status becomes `blocked (auth)`. Claude Code just needs to use `hc keys go-review enter` to confirm.\n\nAfter opening agy with `pane.split`, the screen looks idle, and the initial prompt seems unsent. Claude Code tried to resend it with `hc send`, but was blocked by `hc`:\n\n```\nt7 is blocked (permission)... typing would answer its dialog; read its screen first\n```\n\nIt turns out the task had already arrived, and agy was just stuck waiting for permission. **This protection mechanism in `hc` prevents accidentally typing a task description into a permission dialog**—if that actually happened, the result could be an unintended approval or rejection of a command.\n\nWhen agy is in plan mode (read-only research), it can only analyze. To have it start modifying code, you must press `shift+tab` to switch modes; Claude Code used `hc keys t7 shift+tab` to switch it. The first round of Code Review was perfect for plan mode, and it only switched out when modifications were needed in the second round.\n\nEvery shell command in agy requires permission. Pressing Enter for each one is too tedious, so Claude Code wrote a small driver script with the following logic:\n\n``` php\nflowchart TD\n    A[\"hc wait t7\"] --> B[\"hc get t7 Check status\"]\n    B -->|\"idle / done\"| Z[\"End, wait for agy report\"]\n    B -->|\"blocked\"| C[\"hc read Read full permission screen\"]\n    C --> D{\"Contains dangerous command?<br/>rm -rf / git reset / git checkout<br/>--force / curl / gcloud write operations…\"}\n    D -->|\"Yes\"| H[\"Stop, let human decide\"]\n    D -->|\"No\"| E{\"Is it git commit / push?\"}\n    E -->|\"Yes, and no ALLOW_GIT=1\"| H\n    E -->|\"No, or Reviewer approved\"| F[\"hc keys t7 enter Grant permission\"]\n    F --> A\n```\n\nThis script was refined several times during the process, each time providing a practical lesson:\n\n`msg` gets truncated`hc get`, but it would be truncated with \"...\". The truncated part might hide a second command. Changed to `hc read` the entire permission screen`gcloud A; gcloud deploy` single-line grep misses`describe` / `list` / `read` / `ls` / `du`).` gcloud storage ls` and `gcloud storage du` are actually read-only; they were blocked initially but later added to the allowlist.\nThe line for automated granting was drawn like this:\n\n`git commit` / `push`` ALLOW_GIT=1` after Claude Code Review passes\nAgents can trust each other enough to say \"you can run tests,\" but \"writing to git, writing to the cloud, making external requests\" must go through a Reviewer or a human.\n\nIf you also want to use Herdr (or similar tools) to have two Agents write and review, you can refer to this checklist:\n\n`herdr-app --skill` and `herdr-core --skill` first to confirm actual executable paths.`pane.split` to open the implementer Agent alongside, so a human can see and intervene at any time.`agy -i \"...\"`, don't use positional arguments, and don't use `-p` one-off mode.`herdr-core send <controller address>` when finished.`hc wait`, not sleep polling` blocked`, `go test -race`, don't just rely on the implementer's report.\nAs I finish writing this, agy on the right is already running the next task: using its GCP skill to analyze song-lingo's performance on Cloud Run—service settings, cold starts, gcsfuse, Firestore, etc.—proposing 5–8 solutions, each with evidence, expected benefits, monthly costs, and risks, for Claude Code to decide which to implement. This part is still ongoing; I'll share more later.\n\nMy biggest takeaway from using Herdr for the first time is: **Multi-Agent collaboration is finally \"visible.\"**\n\n`herdr-core send t4 'ready for review #N'` lets the implementer \"knock on the door,\" and the Reviewer uses And this article itself was written by Claude Code in the rightmost Pane of Herdr—after finishing, it will use `herdr-core send t4` to report back to the controller on the left.", "url": "https://wpnews.pro/news/ai-in-action-first-time-using-herdr-collaborating-with-claude-code-and-agy-for", "canonical_source": "https://dev.to/evanlin/ai-in-action-first-time-using-herdr-collaborating-with-claude-code-and-agy-for-go-backend-code-4jga", "published_at": "2026-11-12 14:44:41+00:00", "updated_at": "2026-11-12 14:52:55.467509+00:00", "lang": "en", "topics": ["ai-agents", "ai-tools", "developer-tools", "ai-products"], "entities": ["Herdr", "Claude Code", "Antigravity CLI", "agy", "Go 1.26", "Gemini", "Cloud Run", "song-lingo"], "also_reported_by": [], "alternates": {"html": "https://wpnews.pro/news/ai-in-action-first-time-using-herdr-collaborating-with-claude-code-and-agy-for", "markdown": "https://wpnews.pro/news/ai-in-action-first-time-using-herdr-collaborating-with-claude-code-and-agy-for.md", "text": "https://wpnews.pro/news/ai-in-action-first-time-using-herdr-collaborating-with-claude-code-and-agy-for.txt", "jsonld": "https://wpnews.pro/news/ai-in-action-first-time-using-herdr-collaborating-with-claude-code-and-agy-for.jsonld"}}