All posts
Use case

Dispatching a fleet of local coding agents from your phone:
Larkway × Herdr

Claude Code, Codex and pi run as long-lived agents inside Herdr on my desktop. When I leave the house I only have my phone. Without changing a line of Larkway, one Larkway agent learned the herdr CLI — now I @ it in Feishu and the agents on my desktop start working. This is the write-up: the setup, three real runs, the pitfalls, and the skill that came out of it.

Larkway 0.3.x Herdr Claude Code · Codex · pi 2026-09-06 · ~12 min

The problem: the agents are on my desk, I am not

Herdr is a terminal workspace manager for coding agents. It keeps interactive agents like Claude Code, Codex and pi each in their own pane, knows whether they are idle, working or blocked, and exposes a herdr CLI to control them. On my desktop I keep four of them alive: one Claude Code, two Codex, one pi on a cheap model. A Mac mini holds two more.

At the desk this is great. Away from it, I only have a phone, and:

Feishu is the app that lives on my phone anyway, and Larkway already connects my local agents to Feishu. So the question became: can a Larkway agent drive the agents inside Herdr?

The approach: no new backend, teach the agent the tool

My first instinct was to add a herdr backend to Larkway and bind Feishu threads to Herdr panes one-to-one. It would work, but it hard-codes the usage: one bot per pane, and every change in the Herdr line-up means a config change.

I went the other way: a Larkway agent can already run Bash, so let it learn the herdr CLI. It discovers which agents exist right now, who is idle, which directory each one is in; it decides whom to dispatch to; it waits and collects the result itself. Larkway stays untouched, and the Herdr roster can change any time.

@ the steward from Feishu mobile steward decomposes · picks an agent herdr agent prompt evidence · back to Feishu

So I redefined one Larkway bot as a "steward": understand the goal, break it into tasks, do the simple ones itself, dispatch the rest to Herdr agents via the CLI, follow up, and report back. Its role is essentially the Claude Code main conversation you would have at your desk — only the entry point is Feishu.

Setup: three config changes, zero code

The Larkway bot subprocess and the Herdr server live on the same machine, so the local socket is enough. Three small things needed attention:

  1. Install the herdr skill. Herdr ships an agent-readable skill; herdr --skill prints it. Put it in ~/.agents/skills/herdr/SKILL.md so both the Claude Code and Codex backends can read it.

  2. Get past the skill's guard. The official skill first checks for HERDR_ENV=1 and tells the agent to stop if it is missing. A Larkway bot is not inside a pane, but the CLI works perfectly well from outside. Add HERDR_ENV=1 to ~/.larkway/.env; the bridge passes it to child processes.

  3. Allow the commands. Larkway runs Claude-backed bots in acceptEdits mode with a Bash allowlist. Add Bash(herdr *) to permissions.allowExtra in ~/.larkway/config.json, plus Bash(ssh <host> *) if you want to reach a second machine.

~/.larkway — zsh
# 1. skill
$ mkdir -p ~/.agents/skills/herdr && herdr --skill > ~/.agents/skills/herdr/SKILL.md

# 2. guard variable (bridge passes it to bot subprocesses)
$ echo 'HERDR_ENV=1' >> ~/.larkway/.env

# 3. allowlist (~/.larkway/config.json)
"permissions": { "allowExtra": ["Bash(herdr *)", "Bash(ssh mac-mini *)"] }

$ larkway stop && larkway start

Then the steward's role memory spells out the principles: you are a dispatcher outside Herdr; run herdr agent list first every time; pick by status, directory and type; make dispatched tasks self-contained; never interrupt a working agent; bring blocked questions back to the human.

Run 1: prove the path works

The first test was deliberately tiny: ask the pi agent inside Herdr what 1+1 is.

Agents · Feishu group
me
me · phone
@steward Herdr test: run herdr agent list and check work-pi. If it is idle, have work-pi answer "what is 1+1, one sentence", wait for it to finish, and paste the answer back.
steward Larkway
Herdr test complete

1. herdr agent list → work-pi is idle

2. herdr agent prompt work-pi "what is 1+1, one sentence" --wait

3. herdr agent read work-pi → work-pi answered: "1+1 equals 2."

Meanwhile, in the pi pane on the desktop, the question and answer really did appear. The path works.

Run 2: let the steward choose

The second test named no agent. It exercised the whole loop — discover, choose, dispatch, report: see who is available in Herdr, pick the lightest one to ask what day it is, and explain the choice.

Agents · Feishu group
steward Larkway
Steward test passed — pi says: "Today is Saturday."

Agents in Herdr right now (4, all idle)

nametypestatus
work-claudeclaudeidle
work-codexcodexidle
work-codex-2codexidle
work-pipiidle

Why work-pi: a one-line question with no code and no project context. By the selection rules that goes to pi, keeping the heavier Claude / Codex agents free for real work.

One wrinkle worth noting: the first time I told it "do not run any commands" and pi replied that it cannot know the date. Once allowed to run date it answered correctly. Lesson: time questions require letting the agent read the system clock.

That last paragraph was discovered and written by the steward itself, and it later appended the lesson to its own knowledge base. That is exactly what I wanted: the skill gives the basics; the agent records the pitfalls as it hits them and avoids them next time.

Run 3: dispatch to Codex, read the evidence from a file

The third test was real dispatching: have an idle Codex write a hello.py in a temp directory (print the time and the Python version), run it, report the raw output, and say where the evidence came from.

Agents · Feishu group
steward Larkway
Done

Dispatched to work-codex (a Codex agent). It wrote and ran /tmp/herdr-out/hello.py; the output is a 2026-09-06 00:41:16 timestamp plus Python 3.11.4 version info, exit 0.

Evidence comes from the result file it wrote by convention, /tmp/herdr-out/work-codex-20260906-004050.md. I re-ran the script locally and got the same result.

The result file Codex left behind looks like this — source, command, raw output, exit code, nothing missing. The steward read this file, not the screen.

/tmp/herdr-out/work-codex-20260906-004050.md
# hello.py

## raw output
2026-09-06 00:41:16.087130
3.11.4 (main, Jul 25 2023, 17:07:07) [Clang 14.0.3 ...]

## exit code
0

From @ to card, under a minute.

Pitfalls

1. Waiting for the wrong state: done is not idle

The first version of the memory told it to use --wait --until idle. When pi finished, Herdr marked it done (finished in the background, not yet seen in the UI), so the wait never saw idle and the Bash tool timed out after two minutes. The right call is plain --wait: it returns on idle, done or blocked. Also raise the Bash tool timeout to ten minutes, otherwise the bot times out, not the agent.

2. A bot restarting its own bridge kills itself

Midway I asked the steward to upgrade Larkway. It installed the new version, asked whether to restart the bridge, and I chose "restart in the background". The moment it ran larkway stop, the bridge and the steward's own process died together; the following larkway start never ran. Four bots were offline for a few minutes until I started it by hand.

Anything that touches the bridge, the Herdr server or the network must not go through this path. It is now a hard rule: a bot never restarts its own bridge; it hands the command to a human.

3. Second-hand information gets rationalised

After the bridge came back, the alarm the steward had scheduled fired, it checked the version, and reported that "the restart script hit a launchd race and launchd relaunched the process on its own". The result was right; the cause was invented. It had no evidence but produced a plausible explanation. In this chain an agent relays an agent, and each layer may add a little inference, so "state the evidence source" became a hard rule.

4. Screen scraping is not a return value

herdr agent read gives you rendered terminal text: status bars, progress spinners, the input box and leftovers from the previous turn, all mixed together. Fine for short answers, unusable for long ones. If the agent runs on the alternate screen, old lines never enter scrollback, and a larger --lines cannot bring them back.

What came out of it: the herdr-operator skill

All of the above went into one skill, written from the viewpoint "I am outside Herdr, dispatching a group of agents", at ~/.agents/skills/herdr-operator/. The steward's and the backup operator's memories only point to it. The essentials:

What this is good for, and what it is not

After an evening with it, my verdict: this is an asynchronous dispatch system, not a remote terminal.

ScenarioUseWhy
Away from the desk, clear goal, willing to waitFeishu → Larkway → HerdrCovers every agent type, no SSH client, shares the desktop session so you can pick it up when you return.
Claude only, want native streaming and permission promptsClaude Code Remote ControlStructured and real-time, but Claude only.
Need to watch the screen and iterateSSH + TailscaleWhen the agent pops choices or asks for confirmation, one round trip through Feishu takes a minute or two; the flow breaks.

The three do not conflict. Daily dispatching goes through Feishu, deep Claude sessions through Remote Control, and SSH when I really need to watch.

Why I insisted on "teach the agent the tool"

Looking back, the key decision was not writing a Herdr backend for Larkway. A pipeline would be "more stable", but every new agent in Herdr, every change of project directory, every parallel dispatch would mean config or code changes. Once the agent knows the tool, those become its own judgement calls: who is free, who is in which directory, which one is cheapest — and it writes down what it learns when it hits a wall.

Larkway has always been a thin bridge: the bridge only does what the agent cannot (Feishu long connection, cards, process management); process and judgement belong to the agent. This time the "tool" just happened to be another group of agents instead of git and merge requests.


Build your own

  1. Install Herdr, start your usual agents in panes and name them (herdr agent rename).
  2. Install Larkway, create a bot, run larkway start.
  3. Apply the three config changes above: skill, HERDR_ENV=1, allowExtra.
  4. Write the bot's memory as a dispatcher, not an executor; leave command usage to the skill.
  5. @ it in a group, starting with "ask pi what 1+1 is".

Questions are welcome in the community group or on GitHub.