cd /news/ai-agents/ai-in-action-first-time-using-herdr-… · home › topics › ai-agents › article
[ARTICLE · art-168382] src=dev.to ↗ pub= topic=ai-agents verified=true sentiment=↑ positive

[AI in Action] First Time Using Herdr: Collaborating with Claude Code and agy for Go Backend Code Review, Issue Fixes, and Cloud

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.

by read13 min views2 publishedNov 12, 2026

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.

A 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:

/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:

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.

herdr-app and herdr-core Herdr is divided into two layers:

Component Role Description
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.
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.

A 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.

--skill: An Instruction Manual for AI Agents I 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."

So, 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.

Command Purpose
$HC_TERMINAL_ID Every terminal has an address; Claude Code itself is t4 .
hc run --bg --name xxx <agent> "<task>" Start a named Agent in the background and assign a task.
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.
hc send <t> "..." Paste text and press Enter; used to assign tasks or provide Review feedback.
hc wait <t> /hc wait --children all Wait for the other party to finish (replaces sleep polling).
hc read <t> --lines 0 Read the other party's entire current screen.
hc keys <t> enter Send a keystroke, such as answering a permission dialog.
hc get <t> Check status: idle /working /blocked /done ; whenblocked , it includes amsg , e.g.,Approve RunCommand: ... .

This 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:

<hc from="t7">ready for review #57 ...</hc>

In 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.

The 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:

flowchart LR
    subgraph Herdr["Herdr Window (Same Tab)"]
        direction LR
        CC["Left Pane: Claude Code (t4)<br/>Controller / Reviewer<br/>Opens Issues, views diffs, runs go test -race, approves"]
        AGY["Middle Pane: agy (t7)<br/>Implementer<br/>Code Review, modifies code, runs tests, commit/push"]
        BLOG["Right Pane: Claude Code<br/>Blog Writer<br/>(The one writing this article)"]
    end
    Human(["Me (Human)<br/>Can see every Pane, can intervene anytime"])

    CC -->|"hc send: Assign tasks / Review feedback"| AGY
    AGY -->|"herdr-core send t4: ready for review #N"| CC
    CC -->|"hc keys: Grant read-only permissions"| AGY
    CC -->|"hc send: Write blog"| BLOG
    BLOG -->|"herdr-core send t4: blog draft ready"| CC
    Human -.-> CC
    Human -.-> AGY

The 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.

agy'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:

Category Issues found by agy
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 .
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-readsvoices.json every time and holds a global lock during network calls; request bodies lackMaxBytesReader ; background jobs usecontext.Background() , so they aren't cancelled on shutdown.
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; CLIspeak andtts.Service logic are redundant.

Here 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.

My instructions to Claude Code were:

"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."

Claude 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.

sequenceDiagram
    participant CC as Claude Code (t4)<br/>Reviewer
    participant AGY as agy (t7)<br/>Implementer
    participant GH as GitHub

    CC->>AGY: hc send: Issue #N task + implementation points + "do not commit"
    AGY->>AGY: Implementation + gofmt -l . && go vet ./... && go test ./...
    AGY->>CC: herdr-core send t4 'ready for review #N'
    CC->>CC: git diff + run go test -race personally
    alt Has issues
        CC->>AGY: hc send: Review feedback
        AGY->>CC: Report back again after fix
    end
    CC->>AGY: Approved, you may commit & push
    AGY->>GH: git commit (Fixes #N) + push
    AGY->>CC: Report commit sha

This 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:

Issue Topic Review Result Commit
#57 Per-song lock Design correct, but no tests written → requested them e5df5f4
#58 TTS concurrency limit and failure cache Caught error message regression → requested fix and tests d67ec5a
#59 Request limits, timeout, graceful shutdown Caught shutdown sequence and time budget errors → redesigned de38741
#60 Song list concurrency, demo audio streaming ✅ Passed first try bc1615a
#61 Unified error types, CLI refactor, signal handling ✅ Passed first try deb1fb7

agy'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.

But 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.

While cleaning up the failure cache, agy deleted the server-side recentErrors. All tests passed, but Claude Code caught a regression:

When 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."

The fix was to make LastError return a still-valid quota error when there is no failure record for that sentence, and add a test.

This 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:

srv.Shutdown would wait for it to finish, but its context hadn't been cancelled yet—it was essentially waiting for itself. Claude Code requested changing it to a single 9-second deadline and adjusting the sequence:

// Single deadline, leaving a bit of buffer for Cloud Run's SIGTERM grace period
ctx, cancel := context.WithTimeout(context.Background(), 9*time.Second)
defer cancel()

runner.Cancel() // 1. Cancel background and ongoing analysis work first
srv.Shutdown(ctx) // 2. Then shut down HTTP server (sync analysis already cancelled, won't hang)
runner.Wait(ctx) // 3. Finally wait for jobs to wrap up within the same deadline

(The above is illustrative of the sequence and single-deadline concept.)

*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. Looking 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."

After 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 ..

Post-deployment verification was completed by Claude Code running fetch directly through a Chrome instance logged into IAP:

Verification Item Result Corresponding Change
Homepage /api/songs 200 (10 songs) #60 Concurrent reading
Learning page 200 —
Generated demo audio 200 audio/wav #60 Streaming
Demo audio Range request 206 #60 http.ServeContent
With If-Modified-Since 304 (New feature : now hasLast-Modified ) #60 ModTime
PATCH with 70 KB 413 "Request content too large." #59 MaxBytesReader
Cloud Logging Only two WARNINGs, both from the test requests themselves —

The previous revision was also kept, allowing for an immediate rollback if needed.

agy "prompt" will exit with code 2 Intuitively, you might start agy like this:

agy "Please review the Go code in backend/..."

The result is an immediate error and exit (exit 2):

Prompts are read only from -p/--print, -i/--prompt-interactive, or stdin

The 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.

The 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.

After 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:

t7 is blocked (permission)... typing would answer its dialog; read its screen first

It 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.

When 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.

Every 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:

flowchart TD
    A["hc wait t7"] --> B["hc get t7 Check status"]
    B -->|"idle / done"| Z["End, wait for agy report"]
    B -->|"blocked"| C["hc read Read full permission screen"]
    C --> D{"Contains dangerous command?<br/>rm -rf / git reset / git checkout<br/>--force / curl / gcloud write operations…"}
    D -->|"Yes"| H["Stop, let human decide"]
    D -->|"No"| E{"Is it git commit / push?"}
    E -->|"Yes, and no ALLOW_GIT=1"| H
    E -->|"No, or Reviewer approved"| F["hc keys t7 enter Grant permission"]
    F --> A

This script was refined several times during the process, each time providing a practical lesson:

msg gets truncatedhc get, but it would be truncated with "...". The truncated part might hide a second command. Changed to hc read the entire permission screengcloud A; gcloud deploy single-line grep missesdescribe / 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. The line for automated granting was drawn like this:

git commit / push`` ALLOW_GIT=1 after Claude Code Review passes Agents 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.

If you also want to use Herdr (or similar tools) to have two Agents write and review, you can refer to this checklist:

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. As 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.

My biggest takeaway from using Herdr for the first time is: Multi-Agent collaboration is finally "visible."

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.

── more in #ai-agents 4 stories · sorted by recency
── more on @herdr 3 stories trending now
sponsored brought to you by zahid.host 4,200+ EU-deployed projects
reading about agents? ship yours in a single git push.

Run your AI side-project on zahid.host

EU-based hosting, git-push deploys, automatic HTTPS, no cold starts. Free tier with a custom domain — perfect for shipping the agent you just read about.

$git push zahid main
→ Live at https://your-agent.zahid.host ✓
Get free account → Pricing
from €0/mo · no card required
LIVE [news/ai-in-action-first-t…] indexed:0 read:13min 2026-11-12 · —