GitHub - DuongAnh1201/tomville: Retro game view of a Claude + Codex agent team running in herdr · GitHub
Skip to content
DuongAnh1201Public

About

Retro game view of a Claude + Codex agent team running in herdr

Resources

Stars

2 stars

Watchers

1 watching

Forks

Repository files navigation

Proposal: Tomville, a retro game view of the agent team

2026-10-16 · Tom Nguyen

Status (2026-10-20): 0.2.1 is released on PyPI (see CHANGELOG.md). Part 1 milestones M0–M6 are done and M7 is merged. M17 Phone (Telegram) is merged; its live check is pending. See milestones.md.

Summary

Build a terminal app where each agent (Claude, Codex) is a pixel pet walking around a retro office. Its movement shows what the agent is doing. The app reads live status from herdr, which is already installed, and the task board from GitHub Issues (or TASKS.md), so v1 needs no change to the agent setup.

  • Look: a retro game in a character grid, with movement one cell at a time.
  • Screen: designed for an 11-inch tablet in Termux, landscape first.
  • Options: frame rate, sprite style (emoji, ASCII, block pixels), and a pet for each agent.
  • Built with: Python + Textual, running in its own herdr tab.

Two ways to use it, built in this order:

Phase What When
1. CLI tomville / tmv: the office in the terminal, plus one-shot commands The two-day weekend build
2. Publish One-line install, like herdr: curl -fsSL …/install.sh | sh, plus PyPI Right after the weekend
3. Web UI The same office in a browser: tmv web Later

Both the CLI and the web UI use one shared core (event reader, world model, sprites, settings), so the web UI adds a new front end, not a second app. See Roadmap.

Prerequisites

All in place on the development tablet (Ubuntu 26.04 in Termux PRoot, aarch64):

  • herdr 0.9.5 in ~/.local/bin, on PATH
  • herdr integrations for Claude and Codex (herdr integration install claude|codex)
  • herdr agent skill in ~/.claude/skills/herdr/SKILL.md
  • Claude Code and Codex CLI, both logged in
  • gh logged in, with access to this repo

Still needed at build time: Python 3.11+ and uv (install steps in Install).

Running agents in Termux PRoot

Things learned on the tablet that every setup there needs:

Problem What you see Fix
npm Codex starts through a Node launcher, so herdr sees node (process name MainThread), not codex herdr agent start … --kind codex times out; the agent never gets its name A ~/.local/bin/codex wrapper that execs the real binary (tmv setup launcher codex); ~/.local/bin first on PATH
Codex's background server can't run in PRoot Codex panics with Function not implemented (os error 38) and says to rerun with --no-daemon Start Codex with --no-daemon (also for resume and fork)
Codex's sandbox can't start in PRoot Every command fails with app-server socket directory has an unsupported host mount Start Codex with --sandbox danger-full-access; approvals then follow --ask-for-approval
A crashed Codex leaves its session locked "This conversation is open in another app" codex --no-daemon fork <session> keeps the context in a new, unlocked session
Claude Code asks whether to trust a new folder herdr agent start reports the agent blocked during startup Answer it yourself in that pane; Tomville never answers it
A shell that already ran codex remembers the old path The launcher is still used after the wrapper is installed hash -r, or start in a fresh pane (tmv up always does)
Agents start slowly herdr agent start times out at its 30 s default --timeout 120000; Tomville uses start_timeout (120 s)

What it looks like

Landscape on an 11-inch tablet, about 140 × 40 cells:

┌ Tom's ville · myproject ────────────────────────────────────────┬ Team ──────────────────────────┐
│ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ │ 🐱 claude  boss    working     │
│ ▓ ┌ BOARD ───────────────────┐          🐱                      ▓ │ 🐶 codex1  worker  BLOCKED !   │
│ ▓ │ Todo   Doing   Review    │      ┌──────────┐                ▓ │ 🐑 codex2  worker  idle        │
│ ▓ │ #14    #12     #9        │      │   BOSS   │                ▓ ├ Events ────────────────────────┤
│ ▓ │ #15                      │      └──────────┘                ▓ │ 10:42 codex1 needs approval    │
│ ▓ └──────────────────────────┘                                  ▓ │ 10:40 claude → codex1: #12     │
│ ▓                                                               ▓ │ 10:40 #12 → Doing (codex1)     │
│ ▓        ┌────────┐        ┌────────┐        ┌────────┐         ▓ │ 10:31 #9 → Review (codex2)     │
│ ▓        │ codex1 │        │ codex2 │        │        │         ▓ │                                │
│ ▓        └────────┘        └────────┘        └────────┘         ▓ │                                │
│ ▓           🐶❗                                                 ▓ │                                │
│ ▓                                                               ▓ │                                │
│ ▓  LOUNGE 🛋  🐑 z z        REVIEW ▭                     DOOR ▯  ▓ │                                │
│ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ │                                │
└─────────────────────────────────────────────────────────────────┴────────────────────────────────┘
 [F]ps 10  [S]prites emoji  [G] board check 60s  [Tab] next agent  [Enter] open pane  [?] help

The boss sits at the top, behind the boss desk, facing the workers. The task board hangs on the wall next to the boss. Each worker has a desk with its name on it, facing the boss. Empty desks wait for new workers.

In portrait, the sidebar moves under the office floor.

How agent status becomes pet behaviour

herdr status Pet goes to Animation Extra
working Its desk Typing: 2 frames, keys flicker Task ID in a speech bubble
blocked Stays where it is Jumps, ❗ blinks Name turns red in the Team list; event logged
done Review zone Walks over, drops its card Card appears on the board's Review column
idle Lounge Wanders slowly, then sleeps (z z) Sleeps after 60 s idle
unknown Stays where it is Stands still, ? bubble
Agent gone Leaves through the door Walks off-screen Removed from the Team list

When the lead assigns a task (an issue gets status:doing and a worker's agent: label), the lead pet walks to the worker's desk and hands over a card.

Team shapes

herdr has no idea of a "team": any agent can prompt any other. Tomville supports two common shapes in v1 and draws a different office for each. You pick the shape in settings.toml or with --team.

1. Lead + workers (default)

One lead (the boss) hands tasks to workers. The office in What it looks like is this shape: the boss at the top behind the boss desk with the board beside it, and a named desk per worker, facing the boss.

The handover, step by step:

Step What happens How Tomville knows What you see
1 Boss puts a task on GitHub for a worker Issue created or labelled agent:codex1 (from tmv task at once; from elsewhere on the next board check) Card appears in Todo on the board
2 Boss tells the worker tmv send codex1 "…" in the message log Boss pet stands up, walks to codex1's desk; bubble: "#12 is yours"
3 Worker checks the board Worker reads GitHub Issues (or TASKS.md) Boss walks back; worker bubble: "checking issues…"
4 Worker takes it tmv task take 12: status:doing Card moves to Doing; bubble: "on it: #12"; worker types
5 Worker finishes Worker done; issue status:review Worker walks to the boss desk; bubble: "#12 ready for review"; card to Review
6 Boss reviews and closes Issue closed (Fixes #12) Card to Done; boss bubble: "merged ✓"

Changes made through tmv task and tmv send move pets at once, because Tomville logs them locally. Changes made elsewhere (the GitHub website or app) show up on the next board check.

Team rules (in each project's AGENTS.md):

  • Only the lead prompts other agents, with tmv send.
  • Workers never use herdr, not even to read: no herdr commands, no tmv up, tmv down or tmv send, no creating or closing panes, tabs, workspaces or worktrees. Only the lead does, after the user approves. A Check that needs real herdr goes in the PR for the lead to run.
  • An optional secretary (Claude Haiku) writes no code. When the lead asks, it files GitHub issues and emails the user at each step (PR opened for review, PR merged, worker blocked, milestone done), always with what changed: the changes in plain words, each file with its added and removed lines, and the worker's full summary comment. It emails only the one address in its [agents.secretary] table.

2. Pipeline

Each agent does one step and passes the work on, for example planner → coder → tester. The office is a row of stations joined by a conveyor belt:

 ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓
 ▓   PLAN            CODE             TEST             DONE  ▓
 ▓   ┌──┐            ┌──┐             ┌──┐             ┌──┐  ▓
 ▓   └──┘            └──┘             └──┘             └──┘  ▓
 ▓   🐱 z z          🐶⌨               🐑❗                     ▓
 ▓  ═════▶══════════[T1]▶═════════════▶════════════════▶═══  ▓
 ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓
What happens How Tomville knows What you see
A step finishes and the next starts Stage N goes done and stage N+1 goes working within 30 s The card rides the belt to the next station
A step is stuck That stage is blocked Its pet jumps with ❗; the belt stops behind it
The last step finishes Last stage goes done Card drops into DONE

The status table above still applies to each pet; the shape only changes where desks are and how cards move.

Settings

[team]
shape = "lead"                          # lead | pipeline
lead = "claude"                         # lead shape: who sits at the front desk

# pipeline shape: stage order, left to right
# shape = "pipeline"
# stages = ["planner", "coder", "tester"]

If [team] is missing, Tomville uses the lead shape with the first agent it sees as the lead. Agents not listed in stages wait in the lounge.

Other shapes (pair/reviewer, parallel fan-out with worktrees, peer group, across machines) are in Later.

Task board

The board's main source is GitHub Issues: each issue is a card. The board is a plug-in, like team shapes, so a project can use TASKS.md instead (offline, instant) by changing one setting.

Board idea GitHub Issues (default) TASKS.md
Card An open issue, #12 Fix login test A row
Column Label status:todo, status:doing, status:review; closed = done The row's status
Who has it Label agent:codex1 (agents aren't GitHub users, so no assignees) The row's agent
Finished A commit or PR with Fixes #12 closes it Row set to done
Notes Issue comments (gh issue comment) None
Updates Checked every check_seconds (you choose) Instant, on file change
[board]
source = "github"        # github | tasks-md
check_seconds = 60       # how often Tomville checks GitHub; you choose (minimum 10)
repo = ""                # owner/name; empty = the project's git remote
  • check_seconds is the user's trade-off: lower moves cards sooner, higher saves battery and network. Change it in the app (G key cycles 15/30/60/120 s), with --board-check SEC, or tmv config set board.check_seconds 30.
  • Each check is one GitHub API call for issues updated since the last check, sent with the last ETag; an unchanged board returns 304 and does not count against GitHub's rate limit.
  • When GitHub can't be reached, the board keeps the last cards and shows offline since 10:42; pets keep moving from herdr data.
  • Agents use tmv task instead of long gh commands: tmv task new "Fix login test", tmv task take 12 (adds status:doing and the agent's own agent: label), tmv task move 12 review, tmv task list. They call gh underneath, and the change shows on the next check.
  • tmv setup creates the labels in the repo and adds the board rules to CLAUDE.md / AGENTS.md.

For the offline source, set [board] source = "tasks-md" or run tmv --board tasks-md. The project file uses this table (extra columns are allowed):

| id | title | status | agent |
| --- | --- | --- | --- |
| T1 | Fix login test | doing | codex1 |
| T2 | Review changes | review | claude |

Status is todo, doing, review, or done; use - for no agent. The reader checks the file's modified time every poll_seconds and keeps the last cards if it becomes unreadable. --board PATH selects another Markdown file.

Shared docs

Agents and Tom share Markdown docs. Project docs (README, docs/, AGENTS.md, any .md in the repo) change through the normal branch, PR and review. Team notes live in notes/ of the project's main checkout, outside branches; every worktree finds it through git rev-parse --git-common-dir, and notes are edited live.

tmv doc rules (safety):

  • Only .md files, only under the folders in [docs] roots (default [".", "notes"]: the repo and notes/), never outside the project, and never through a symlink that points out of it.
  • tmv doc read PATH --section "Setup > Linux" picks a heading path; a section ends at the next heading of the same or higher level. # lines inside fenced code blocks are not headings. A missing section or a name that matches several headings is an error that lists the choices.
  • Every read prints a version tag for the whole file; tmv doc edit --base TAG says which version you edited from.

Safe edits (nobody silently overwrites anybody):

  • tmv doc read notes/plan.md --section Beta, then printf '## Beta\nnew\n' | tmv doc edit notes/plan.md --section Beta --base TAG. Without --section the whole file is edited; --append adds to the end of the section or file.

  • The write takes a lock (flock on a file in .tomville/doc-locks/), writes a temp file, fsyncs and os.replaces it. The text of every version you read or write is kept by tag in .tomville/doc-objects/ (also the history for the change log).

  • File unchanged since TAG: written. Changed by someone else (an agent, or Tom saving in any editor, which needs no lock): your edit is merged against the new content when the changes touch different lines. Same lines: refused with exit code 3 and the other change shown; read again and redo it.

  • --merge writes git-style markers (<<<<<<< ours / ======= / >>>>>>> theirs) instead and marks the doc conflicted. Every edit is refused until the markers are fixed and tmv doc resolve PATH passes. Change log:

  • Every doc new, edit, resolve and revert appends a line to .tomville/doc-log.jsonl: time, who, path, section, op, version before and after. Who is the agent whose herdr pane runs the command (as tmv task finds it), else --as NAME, else tom.

  • A save made outside tmv (Tom's editor) moves the hash with no log line. The next doc command that sees it logs it as tom (editor); the first doc read of a doc records a starting seen line so later outside edits can be told apart.

  • tmv doc list takes last editor and last change from the log (git, then file time, for docs not in it yet).

  • Old versions live in .tomville/doc-objects/. The last [docs] keep_versions (default 50) versions of each doc are kept; versions that were read but never logged expire after a week.

  • Project docs (outside notes/) are written in your own worktree, so the PR flow still applies; only notes/ is shared in the main checkout. .tomville/doc-* and /notes/ are git-ignored.

The team sees doc changes:

  • tmv watch also exits when the doc log gets a new entry or someone saves a file in notes/ outside tmv (new files too). It prints Doc notes/plan.md changed by codex1 · section Rollout (created by, reverted by, resolved conflicts in for the other operations). The lead handles it like a board change. Changes the watching agent made itself are skipped. The first run is a silent baseline; its state is .tomville/watch-docs.json.
  • Office: the Events panel shows codex1 edited notes/plan.md › Rollout, and a 📄 slides from the writer's desk (or the door, for Tom) to the shelf, the review area, and disappears.
  • Secretary: its emails get a Doc changes part (see AGENTS.md, "The secretary").
  • Telegram (when on): /doc read PATH [section] sends the text, trimmed to 3000 characters; /doc watch PATH pings 📄 codex1 edited notes/plan.md › Rollout when that doc changes; /doc unwatch PATH, /doc watching. It reads only what tmv doc read may read.
[docs]
roots = [".", "notes"]
keep_versions = 50   # versions of each doc kept for diff and revert

Who talks to whom

herdr shows what each agent is doing, but not who sent a prompt to whom: when the lead runs herdr agent prompt codex1 "...", only the lead gets a reply, and herdr's event stream has no "prompt sent" event or sender field (checked against the herdr 0.9.5 API schema). Tomville records messages itself, in a log both shapes use:

Way How it works Reliability
tmv send Agents send work with tmv send codex1 "Fix the login test". Tomville finds the sender from HERDR_PANE_ID (herdr sets it in every pane), calls herdr agent prompt itself and appends the message to the log Exact, when agents use it. tmv setup adds a short rule to the project's CLAUDE.md and AGENTS.md telling agents to use it
Claude Code hook A PreToolUse hook on Bash catches any herdr agent prompt … Claude runs directly and logs it with the sender's pane Exact for Claude as sender, even if it forgets tmv send. tmv setup installs it. Codex: check whether it has an equivalent hook
Timing guess Sender was working, then the target became working within a few seconds Fallback only; shown as ? → codex1

The log is one JSON line per message in .tomville/messages.jsonl in the project:

{"time": "2026-10-17T10:42:03Z", "from": "claude", "to": "codex1", "text": "Fix the login test", "via": "send"}

What you see:

  • The sender's pet walks to the target's desk carrying ✉; a speech bubble shows the start of the prompt.
  • The Events panel shows 10:42 claude → codex1: Fix the login test.
  • In the pipeline shape, a logged handoff moves the card at once; the timing guess is used only when there is no log line.
  • An unknown sender shows as ?, so the office never claims more than it knows.

Architecture

 herdr events (status changes) ─┐
 messages.jsonl (tmv send, hook)├─▶  Event reader  ─▶  World model  ─▶  Renderer (Textual)  ─▶  screen
 GitHub Issues / TASKS.md      ─┘                      pets, zones,       sprite frames,
                                                        targets, paths     frame timer
                                                              ▲
                                          settings.toml ──────┘  (fps, sprite style, pets)
Part Job
Event reader Runs herdr agent list once at start, then listens to herdr's live event stream (pane.agent_status_changed, pane.agent_detected, pane.closed) instead of polling. Also follows the message log and reads the board (GitHub Issues every check_seconds, or TASKS.md when it changes). Turns them into events: "codex1 became blocked", "claude → codex1: Fix the login test", "T1 moved to review". Falls back to polling herdr agent list every poll_seconds if the stream drops
World model Holds each pet's position, target zone and current animation. Events change targets; it does not draw anything
Movement Each tick, a pet steps one cell toward its target: first along x, then along y, around walls
Renderer A Textual widget that draws the office map, then each pet's current frame at its position. Textual only redraws changed cells
Settings settings.toml, changed in the app with hotkeys or from the shell with tmv config set

The renderer never calls herdr directly, and the event reader never draws. That keeps the game part testable with fake events and no agents running.

Frame rate option

Movement speed is set in cells per second and does not depend on fps. Changing fps only changes how smooth it looks, not how fast pets move.

Option Feel Use when
5 fps Choppy, very retro Battery saver
10 fps (default) Classic handheld game Normal use
15 fps Smooth walking Tablet plugged in
30 fps Smoothest Testing; heavy on battery under PRoot

Two automatic savings:

  • When every agent is idle and every pet is asleep, the app drops to 2 fps.
  • When the app's tab is not visible, it stops animating and only keeps polling.

Press F to cycle through the options; the choice is saved.

Emoji width on an 11-inch screen

The core rule: every map tile is 2 cells wide. Emoji are 2 cells wide in most terminals, so one emoji fills one tile. ASCII and block sprites also use 2-cell tiles. All three styles share the same map and stay aligned.

How it stays aligned:

  1. Fixed pet list. Pets come only from a tested list of 2-cell emoji (🐱 🐶 🐑 🐸 🦊 🐼 🤖 👾). Skip emoji built from several parts (skin tones, 🧑‍💻), because they render at different widths on different fonts.
  2. Width check at start. The app measures each sprite with Rich's cell_len. Anything that is not exactly 2 cells is replaced by its ASCII version, with a note in the Events panel.
  3. First-run calibration screen. It shows a ruler and a row of every pet:
    |..|..|..|..|..|..|..|..|
    |🐱|🐶|🐑|🐸|🦊|🐼|🤖|👾|
    
    If the bars line up, press Enter. If not, the app switches to ASCII sprites. This catches fonts that measure differently from what Python expects.
  4. Screen size. The map is 64 tiles × 24 rows (128 × 24 cells) on an 11-inch landscape screen at a normal Termux font size. Smaller windows get a 40 × 20 tile map, and the sidebar moves below it. Pinch-zoom in Termux changes the cell count, and the app re-lays out on resize.

Sprite styles

Press S to cycle through styles. Each pet has the same frames in each style.

Style Idle Walk frames Typing Notes
Emoji 🐱 🐱 / 🐱 with a dust · behind it 🐱⌨ Default on the tablet
ASCII ^^ ^^ / ^- ^^# Works on every font
Block pixel ▟▙ ▟▙ / ▙▟ ▟▙▒ Most "retro game"

Sprites are plain text files, one folder per pet. Built-in pets ship inside the package; your own go in ~/.config/tomville/sprites/ (see Custom pets and colours):

src/tomville/data/sprites/
  cat/
    emoji.txt     # frames separated by a line with ---
    ascii.txt
    blocks.txt
  dog/ ...

~/.config/tomville/settings.toml:

fps = 10
style = "emoji"          # emoji | ascii | blocks
idle_sleep_seconds = 60
poll_seconds = 1

[pets.claude]
sprite = "cat"

[pets.codex1]
sprite = "dog"

[pets.codex2]
sprite = "sheep"

Colours and your own pet designs are covered in Custom pets and colours.

Interaction

Key or touch Action
Tap a pet, or Tab Select it; the Team list highlights it
Enter on a selected pet Switch herdr to that agent's pane (to approve, read output)
F Cycle frame rate
S Cycle sprite style
B Show the board full-screen
T Cycle colour themes
G Cycle how often the GitHub board is checked (15/30/60/120 s)
? Help

v1 only watches. Approvals still happen in the agent's own pane: the blocked pet tells you where to go, and Enter takes you there. Sending prompts from the app comes later (see Later).

Custom pets and colours

You can design both the pet and its colours. How much depends on the sprite style:

Style Shape Colour
Emoji 🐱 Pick from the tested emoji list Fixed by the font. Only the name label takes a colour
ASCII ^^ Any characters One colour per pet
Block pixel ▟▙ Draw pixel by pixel Any colour, per pixel. Best for your own designs

Termux supports 24-bit colour, so any hex colour works.

Sprite file format

Each frame has two grids side by side: the shape on the left, colour letters on the right. A [palette] maps each letter to a hex colour. Frames are separated by ---.

~/.config/tomville/sprites/fox/blocks.txt:

# shape        # colours
 ▄▀▀▄           OOOO
 █▄▄█           OWWO
 ▀  ▀           B  B
---
 ▄▀▀▄           OOOO
 █▄▄█           OWWO
  ▀▀             BB

[palette]
O = "#ff8800"   # orange fur
W = "#ffffff"   # white face
B = "#5a3a1a"   # brown feet

Rules:

  • A pet is a whole number of 2-cell tiles wide; this fox is 2 tiles (4 cells).
  • Every frame of one pet has the same size.
  • A space in the colour grid means "no colour".
  • The first frame is used when standing still; all frames play in a loop when moving or working.

Your own pets go in ~/.config/tomville/sprites/<name>/ and override a built-in pet with the same name. Run tomville pets list to preview them all, and tomville pets check <name> to find mistakes in a pet file.

Colours and themes

In ~/.config/tomville/settings.toml:

theme = "night"          # night | game-boy | green-screen

[pets.claude]
sprite = "cat"
color  = "#f5a97f"       # the agent's colour: ASCII pet, name, board cards, events

[pets.codex1]
sprite = "fox"           # your own design

Three built-in themes. Press T in the app to cycle them.

Start your team

You answer a few questions once; Tomville builds the team in herdr. Nobody types herdr commands. Lead + workers shape only for now.

tmv new      # the questions, saved in .tomville/ville.toml
tmv up       # builds the team (no ville yet? it asks the tmv new questions first)
tmv down     # closes only the office; agents keep working
tmv down --agents  # confirms, then stops all configured agents and the office

Questions. For each agent: its name, its kind (claude, codex, gemini, … any kind herdr agent start --kind knows), its model (free text passed as --model; empty means the CLI's own default, so the list never goes stale) and its place:

Place What Tomville makes
here The pane you are in (the lead only)
worktree A git worktree on its own branch, in a new tab (default for workers)
tab A new tab in the project folder
pane A split of the current pane

Then: "Add a secretary (Claude Haiku) to file issues and email you when work is finished?" and the email address it may write to (default: git config user.email). The answers become [agents.NAME] tables:

[agents.claude]
kind = "claude"
model = "claude-opus-5-5"
place = "here"

[agents.codex1]
kind = "codex"
place = "worktree"

[agents.secretary]
kind = "claude"
model = "claude-haiku-5-5"
place = "tab"
role = "secretary"
email = "tom@example.com"

tmv new ends with "Start the team now?". Answering yes starts the saved team immediately; --yes saves the defaults without starting agents. With an existing ville, tmv up asks no questions and starts agents sequentially, then opens the office. Running it again reuses running agents and the office. Pane workers stack beside the lead: the first splits right from the lead, and each later worker splits down from the previous pane worker.

Who is the lead. Run tmv up inside an agent's pane and that agent is the lead; no second lead is started. Run it in a plain shell and Tomville starts the lead too, in a new tab.

If herdr can't detect an agent, Tomville names the pane, the running process and the last lines, with the known fix: a script launcher in front of the real binary (tmv setup launcher codex), a startup prompt such as folder trust (answer it yourself), a CLI that is not installed, or a shell that remembers an old path. tmv doctor runs the same checks without starting anything. It also reports the installed herdr version and warns outside the tested range. A herdr usage or argument error reports a CLI version mismatch instead of suggesting agent detection fixes.

tmv down and tmv down --office close only the office tab recorded in .tomville/team.json; every agent keeps working. tmv down NAME stops one agent owned by tmv up and keeps everything else.

tmv down --agents stops every running agent configured in the ville, including agents that were already running before tmv up, then closes the office. It lists their statuses first, marks working agents as busy and blocked agents as waiting for an answer, and asks once Stop them? [y/N]. Pressing Enter declines. Use --yes to skip confirmation; non-interactive use requires it. Workers stop first, then the secretary, then the lead. The caller's pane is reported as kept: lead/caller and left running.

Owned panes are closed after their agent quits. Pre-existing panes stay at their shell, reported as stopped (pane kept). Worktrees and agents outside the ville always stay. The git-ignored ownership record is updated for successful stops; panes with replaced or uncertain identities are retained for inspection. --office, --agents and NAME are mutually exclusive.

If startup stops at a trust or login question, answer it in the named pane yourself and retry tmv up. The pending first message is sent once the agent is ready. A failed start whose pane is still open is retained for inspection; use tmv down NAME before starting a replacement. Failed agents do not prevent the rest of the team from starting.

tmv setup launcher codex supports npm Codex's native binary and preserves its package environment. It asks before writing ~/.local/bin/codex, keeps an existing file as codex.bak, and refuses to overwrite an existing backup. --yes accepts the write. Other launcher kinds report that automatic resolution is unsupported instead of guessing a binary or its environment. Put ~/.local/bin first on PATH and use a fresh pane afterwards. Startup uses start_timeout seconds (default 120); herdr requires at least 3001 milliseconds, so smaller configured values use that minimum for the agent-start call.

Phone (Telegram)

Talk to your ville from your phone. It is optional: with Telegram off, nothing about it runs, asks or shows (tmv doctor only says "Telegram: not set up (optional)").

Lead + workers only, for now. For any other team shape, tmv new skips the question and says Sorry, we haven't done Telegram for the <shape> team yet.

One bot per ville. Each ville gets its own bot, so it is its own chat on your phone, named after the ville. Why not one shared bot: Telegram lets only one program at a time receive a bot's messages, so two villes on one tablet would steal each other's messages. With one bot per ville each runs on its own, the chat is never mixed up with another project's, and stopping one ville never touches another.

Set it up

  1. Last question of tmv new ("Connect a Telegram chat to this ville? (no)"), or any time later tmv telegram setup in the project.
  2. In Telegram, talk to @BotFather: /newbot, a name (Tomville suggests <project> ville), a username ending in bot. It gives you a token.
  3. Paste the token when asked (hidden input). Tomville checks it and shows the bot's username.
  4. Send the 6-digit code Tomville shows to that bot from your phone. Only your private chat with it is paired.
  5. Choose who may message you here (from, default secretary and lead) and whether to allow approvals from the chat.

The ville keeps no secret: [telegram] enabled, bot, id, approvals, from in .tomville/ville.toml. The token and your chat ID live in ~/.config/tomville/telegram/<id>.toml, mode 600. tmv up opens a telegram tab with the bot. tmv telegram off stops it and deletes the token.

What you can say

You type in the ville's chat Goes to
Plain text, e.g. start issue #12 The lead
/claude2 check the board layout That agent (one /<agent> per agent; codex-1 becomes /codex_1)
/status, /board, /pending Team, board and waiting agents
/pause, /resume, /help Stop and restart forwarding and pings; help

The Telegram command menu lists them. Every message is logged as from tom (via Telegram); only the lead answers, with tmv reply.

What you get

  • Answers from the lead (tmv reply "text").
  • Summaries from the secretary: the same content as its emails (what changed, files, the worker's summary), in bold and italic; when code changed, tmv reply --diff PR sends the diff per file in code blocks. Email stays bold and italic only.
  • Pings from the bot itself when an agent is blocked, or a card moves to review or done.
  • Only agents in [telegram] from can post; tmv reply from anyone else says so.

Approvals (off by default)

With approvals = true, a blocked agent's exact question arrives with ✅ Approve and ❌ Decline. Before the key is sent, the bot reads the pane again and checks it is still the same question; if anything changed the tap does nothing and says so. Buttons expire after 10 minutes, and only your paired chat counts. A question it cannot read clearly gets no buttons.

In the office

  • Your message shows as a 📱 coming in at the door and walking to the target's desk, with a bubble of the start of the text. You have no pet.
  • A reply shows as the sender's bubble 📱 → tom: …; an approval as a ✅ or ❌ bubble over the agent.
  • The Events panel shows 📱 tom → codex1: ….

Safety

Only your private chat is answered. The bot has no shell: it forwards text to agents and presses a key for an approval, nothing else. It uses long polling from the tablet, so there is no server, no open port and nothing to host.

Install and setup from the terminal

One command installs it, and tomville doctor shows the whole setup in the terminal: what is ready, what is missing, and the command to fix it.

Install

After publishing (phase 2), one line, the same way herdr installs:

curl -fsSL https://raw.githubusercontent.com/DuongAnh1201/tomville/main/install.sh | sh

Or, for people who already use Python tools:

uv tool install tomville     # or: pipx install tomville

Update with tmv update; remove with tmv uninstall. Settings in ~/.config/tomville/ stay.

During the weekend build (phase 1), install straight from GitHub:

curl -LsSf https://astral.sh/uv/install.sh | sh
uv tool install git+https://github.com/DuongAnh1201/tomville

While the repo is private, this works only where gh is logged in with access, as on the development tablet.

CLI

tomville works like claude and codex: run it bare for the interactive app, add options to change how it runs, or use a command for a one-shot job that prints and exits. The installer also adds a short alias, tmv.

tomville [options]              open the office (interactive)
tomville <command> [options]    run one command and exit
tomville <command> --help       help for one command

Every option is optional. With none, settings come from settings.toml, then built-in defaults.

Global options (any command)

Option Default What it does
-C, --project DIR current folder Project whose board is shown
--config FILE ~/.config/tomville/settings.toml Use another settings file
--json off Machine-readable output, for scripts and agents
--no-color off Plain text; also on when NO_COLOR is set
-q, --quiet / -v, --verbose normal Less or more output
-h, --help Show help
-V, --version Show the version

Office options (bare tomville)

Option Values What it does
--fps N 5 10 15 30 Frame rate for this run
--style S emoji ascii blocks Sprite style
--theme T night game-boy green-screen Colour theme
--pet AGENT=PET e.g. codex1=fox Pet for one agent; repeat for more
--board SOURCE github tasks-md FILE Board source for this run; a path means a TASKS.md-style file
--board-check SEC number How often to check GitHub Issues (default 60)
--layout L auto landscape portrait Force a layout
--team SHAPE lead pipeline Team shape for this run (see Team shapes)
--poll SEC number Polling interval if herdr's event stream is unavailable (default 1)
--demo Fake agents and tasks; works without herdr. Good for trying pets and themes
--still No animation; pets jump straight to their zone

Commands

Command What it does Useful options
send AGENT TEXT Prompt another agent and log who sent it (see Who talks to whom). For agents to use instead of herdr agent prompt --wait, --timeout MS (passed to herdr)
status Print the team and board once, then exit. Like claude -p: no app opens --json, --watch [SEC] (reprint every N s), --agent NAME
doctor Check the setup and print fixes --json
new [NAME] Create a new ville for this project: name, team shape, agents (kind, model, place), secretary, pets, board, emoji check, and last "Connect a Telegram chat?" (default no). Saved in .tomville/ville.toml. Bare tomville offers this when the project has no ville yet --yes (accept defaults), --claude-settings FILE (hook file, default ~/.claude/settings.json)
telegram setup / run / status / pause / resume / off Optional phone chat for this ville (see Phone (Telegram)). off stops the bot and deletes its token
reply TEXT Send a reply to Tom's chat. For the lead and the agents in [telegram] from --diff PR, --diff-file FILE (diff per file in code blocks)
doc list [DIR] List docs with title, last editor and last change (default notes/) --json
doc read PATH Print a doc and its version tag (a short content hash of the whole file) --section "A > B" (that heading down to the next one of the same or higher level), --json
doc new PATH Create a doc from a small template; refuses if it exists --title TEXT (required), --json
doc edit PATH Edit a doc or one section, new text on stdin. --base TAG is the version you read; if the file changed meanwhile, the edit is 3-way merged. Overlapping changes are refused (exit 3, shows the other change). tmv doc new and edit are the only write paths --section "A > B", --base TAG (required), --replace (default; give the section's heading line too) / --append, --merge (write conflict markers instead of refusing), --as NAME, --json
doc resolve PATH Clear the conflicted mark once no <<<<<<< / >>>>>>> lines are left --json
doc log PATH Who changed what: time, who, operation, section, version before → after -n N (last N, default 20; 0 = all), --json
doc diff PATH Unified diff from a stored version to the file now --since TAG (default: the version before the latest change), --json
doc revert PATH TAG Restore an old version as a new edit: same lock, merge and conflict rules as edit, logged --base TAG, --merge, --as NAME
up Build the team in herdr from the ville (see Start your team); with no ville it asks the new questions first. Safe to run again
down [NAME] [--office | --agents] [--yes] Default/--office: close only the office. NAME: one owned agent. --agents: confirm, then stop all ville agents and the office; pre-existing panes and the caller stay
setup launcher KIND Write a ~/.local/bin wrapper that runs the real binary, so herdr can detect it (asks first)
setup Redo parts of setup for this ville: emoji calibration, labels, agent rules, hook --yes (no questions; skips the emoji test unless --style is given), --style, --theme, --claude-settings FILE
pets list Show every pet, animated --style
pets show NAME Show one pet's frames and palette --style, --fps
pets new NAME Create a pet file to edit --from cat (copy a built-in), --size 2x3 (tiles × rows)
pets check NAME Check a pet file: sizes, palette letters, emoji width
theme list Preview all themes
config path / config show Print the settings file location or contents
config get KEY / config set KEY VALUE Read or change one setting, e.g. config set fps 15
update (phase 2) Upgrade to the latest release, like herdr update --check (only report)
uninstall (phase 2) Remove the app; keep or delete settings --purge (also delete ~/.config/tomville/)
web (phase 3) Serve the office in a browser --port 8787, --host 127.0.0.1
completion SHELL (later) Print tab-completion for bash, zsh or fish

Where settings come from

The highest line wins:

  1. Command-line options (--fps 15)
  2. Environment variables: TOMVILLE_FPS, TOMVILLE_STYLE, TOMVILLE_THEME, TOMVILLE_CONFIG
  3. ~/.config/tomville/settings.toml
  4. Built-in defaults

So --fps 30 tries 30 fps once, and tomville config set fps 30 keeps it.

Exit codes

Code Meaning
0 OK
1 Problem found (doctor, a broken pet file) or runtime error
2 Wrong command or option

Same convention as herdr, so scripts can check the result.

Examples

tomville                                      # open the office
tmv --demo --theme game-boy                   # try it with fake agents
tmv --style blocks --pet codex1=fox --fps 15  # one-off look
tmv status                                    # quick text summary
tmv status --watch 5                          # live summary in a small pane
tmv status --json                             # for scripts and agents
tmv watch                                     # wait for a GitHub issue/PR change, then exit
tmv watch --json                              # the same changes as a JSON array
tmv doctor
tmv pets new fox --from cat && tmv pets check fox
tmv config set theme night

tmv status output:

myproject · 3 agents · 3 tasks
  🐱 claude   lead    working   T2 Write tests for login
  🐶 codex1   worker  blocked   T1 Add login form   ← needs approval
  🐑 codex2   worker  idle
  Todo 1 · Doing 1 · Review 1 · Done 0

Agents can use it too

Because status --json prints the team and board as JSON, Claude can read it with one command. An optional line for the duo skill:

- To see the whole team at once, run `tomville status --json`.

Lead GitHub watcher

GitHub watching is opt-in, with [board] watch = false by default. tmv new asks whether to enable it (default no); tmv config set board.watch true enables it later for the current ville and refreshes its generated rules. When enabled, the lead keeps tmv watch running in the background. Manual tmv watch works even when the setting is off. Telegram blocked/review/done pings are unchanged. It reads issues and PRs every [board] check_seconds (the same interval chosen by tmv new), exits with changes, and should be restarted after the lead handles them. The first successful read creates a silent baseline in the ignored .tomville/watch.json. Later runs compare with it, including new/edited/closed items, reviews and CI red/green. GitHub failures print to stderr and retry at the next interval; --json keeps stdout machine-readable.

The watcher makes only read-only gh issue list / gh pr list calls. It includes closed/merged records so a closure is explicit, never guessed from a missing row. If its default history limit of 1,000 items per kind is reached, it reports this instead of saving a partial baseline; restart with a larger --limit. Forks and authors other than the repository owner are marked outside/unverified because the list API does not expose collaborator membership. The lead reviews and reports those PRs to Tom, and never merges them without his approval. Team PRs still require conflict checks, tests and review before merging; new issues need labels/assignment or a question to Tom. tmv doctor shows the watcher interval.

Doctor

Tomville doctor · v0.2.1

 Environment
  ✔ Python 3.12.3
  ✔ Terminal 142 × 41 cells, 24-bit colour
  ✔ Emoji width calibrated (emoji style)

 herdr
  ✔ herdr 0.9.5 at /root/.local/bin/herdr
  ✔ Running inside herdr (HERDR_ENV=1)
  ✔ Claude integration installed
  ✔ Codex integration installed

 Agents
  ✔ claude   (cat, #f5a97f)
  ✖ codex1   not running → herdr agent start codex1 --kind codex --pane <pane>

 Board
  ✔ GitHub Issues: DuongAnh1201/tomville (3 open, labels ready)

 Settings  ~/.config/tomville/settings.toml
 Sprites   ~/.config/tomville/sprites/ (1 custom pet)

 1 problem found.

Required: Python, herdr, and running inside herdr. Everything else is a warning; the office still starts and shows what it can.

Tech stack

Kept small so it fits in two days.

Layer Choice Why
Language Python 3.11+ Already on Ubuntu; tomllib built in
Terminal UI Textual + Rich Layouts, touch, animation timers, 24-bit colour, cell_len for emoji width
CLI argparse (standard library) with subcommands; two entry points, tomville and tmv Options and commands with built-in --help; no extra dependency
Settings TOML: tomllib to read, tomli-w to write Human-editable, same format as Codex and herdr
Agent status herdr agent list (JSON via subprocess) Uses the existing setup; no extra server
Board GitHub Issues via gh api (with ETag) or TASKS.md by modified time Uses the existing gh login; no extra library
Packaging pyproject.toml + hatchling; uv One command to install, update, remove
Tests pytest for the world model, sprite loader and doctor The game logic is tested with fake events, no agents needed

Dependencies: textual and tomli-w only.

Project layout

tomville/
  pyproject.toml
  README.md
  install.sh         # one-line installer (phase 2)
  CHANGELOG.md
  src/tomville/
    cli.py           # argparse: office options + status, doctor, setup, pets, theme, config
    core/            # shared by CLI and web; no UI code here
      events.py      # herdr event stream + messages.jsonl + board -> events
      board/         # board plug-ins: github.py (default), tasks_md.py
      messages.py    # tmv send, message log, hook entry point
      world.py       # pets, zones, targets, movement
      sprites.py     # load sprite files, palette, width check
      settings.py    # read/write settings.toml
      doctor.py      # checks shared by doctor and setup
    tui/             # phase 1: terminal front end
      app.py         # Textual app, layout, keys
      office.py      # renderer: office map + sprites widget
      status.py      # status output, text and JSON
    web/             # phase 3: browser front end
    data/
      sprites/       # built-in pets: cat, dog, sheep
      themes/        # night, game-boy, green-screen
      office.txt     # the floor plan, drawn as text
  tests/

User files live outside the package, so updates never overwrite them: ~/.config/tomville/settings.toml and ~/.config/tomville/sprites/.

Two-day plan

The build is now tracked in two parts in milestones.md: Part 1 (fundamentals, lead + workers and pipeline) and Part 2 (other team shapes, custom pets, themes, publishing). The table below is the original plan; where they differ, milestones.md wins.

Day 1: a live, moving office

Block Build Done when
Morning Project skeleton, pyproject.toml, office map, Textual layout (office + Team + Board + Events) tomville opens the empty office
Midday Event reader: herdr agent list + herdr event stream + GitHub Issues board into events Team list and board show real data
Afternoon World model: zones, status-to-behaviour table, cell-by-cell movement, fps option (F); lead + workers shape Pets walk to their desk when you start Claude or Codex
Evening Emoji and ASCII sprites, built-in cat, dog and sheep Style switch (S) works; tests for the world model pass

Day 2: your own pets, setup and install

Block Build Done when
Morning Pipeline shape: conveyor office map, [team] settings, --team; tmv send, message log, Claude Code hook; handoff detection tmv send moves an envelope and a card from planner to coder to tester
Midday Block-pixel sprite format with palettes; custom pets folder; pets list, pets show, pets new, pets check Your own fox walks around in its colours
Afternoon Themes (T), agent colours, emoji width check and calibration screen Map stays aligned on the 11-inch screen
Evening CLI: office options, status (with --json, --watch), doctor, setup, config, theme list; tmv alias tmv status --json and tmv doctor work; --help lists every option
Late Tap to select, Enter to open the pane, idle power saving; install test with uv tool install from GitHub Fresh install works end to end

If day 1 runs late, drop block-pixel sprites from day 2 first: emoji and ASCII are enough to use the app.

Each day ends with a usable app.

Roadmap

Phase 2: publish the CLI (right after the weekend)

Goal: anyone installs it with one line, updates with tmv update, the same experience as herdr.

Step What Done when
1. Name tomville is free on PyPI and GitHub (checked 2026-10-16); pick a licence (MIT suggested) Name and LICENSE in the repo
2. Package Version 0.1.0 in pyproject.toml, CHANGELOG.md, project links uv build makes a wheel that installs cleanly
3. Installer install.sh: checks OS and arch, installs uv if missing, runs uv tool install tomville, prints the PATH line if needed Works on Ubuntu (x86_64 and aarch64), macOS, Termux PRoot
4. Update and uninstall tmv update and tmv uninstall call uv tool upgrade/uninstall Update from 0.1.0 to 0.1.1 works
5. CI GitHub Actions: lint and tests on every push Green check on main
6. Release Push tag v0.1.0 → Actions builds, publishes to PyPI (trusted publishing, no token) and creates a GitHub Release with the changelog uv tool install tomville works on a clean machine
7. Go public Make the repo public; README starts with the one-line install The install line works for anyone

How it compares to herdr:

herdr Tomville
One-line install curl -fsSL https://herdr.dev/install.sh | sh curl -fsSL https://raw.githubusercontent.com/DuongAnh1201/tomville/main/install.sh | sh
What it installs A Rust binary per platform A Python app via uv, any platform with Python 3.11+
Self-update herdr update tmv update
Releases GitHub Releases GitHub Releases + PyPI

A short install domain (like herdr.dev) and standalone binaries without Python are optional extras for later.

Phase 3: web UI (later)

The same office in a browser, for a bigger screen or to watch from another device.

Step What
1. Quick version tmv web runs the existing Textual app in the browser with textual serve. Almost no new code
2. Real web UI A small server (FastAPI) streams world events from core/ over a WebSocket; the page draws pets on a pixel canvas, with real pixel art instead of characters
3. Extras Drag-and-drop board, pet editor in the browser, view from a phone on the same Wi-Fi

Rules for phase 1 that keep phase 3 easy:

  • All logic lives in core/; tui/ only draws and handles keys.
  • The world model emits plain events (dicts), so the web server can send the same events as JSON.
  • status --json uses the same event format, so it doubles as the web API's data shape.

Later (any phase)

  • Pet editor: paint pets by tapping cells.
  • Pixel-art pets from online sets: tmv pets import cat.png --frames 4 --size 8x8 --name ginger turns a sprite sheet into a block-pixel pet (Pillow + half-block ▄ rendering, as in Rich Pixels); a "big mode" office for full-size sprites; a community pet gallery repo (tmv pets install fox). Bundle only CC0 art (e.g. OpenGameArt "Cat & Dog Free Sprites", itch.io CC0 cat and dog sheets); not vscode-pets art, whose licence is unclear.
  • Prompt box that sends tasks to the lead (herdr agent prompt claude "...").
  • GitHub Projects (kanban) view on top of the Issues board; needs the project scope on gh.
  • Full setup wizard with live previews; more themes.
  • Shell tab-completion (tomville completion bash|zsh|fish).
  • More team shapes: pair/reviewer (two facing desks), parallel fan-out with git worktrees, peer group, and agents on other machines (herdr --machine).

Risks

Risk Effect Fallback
herdr agent list output changes in a herdr update Pets freeze or vanish The event reader is one small file; test after herdr update
Emoji width differs on the tablet font Map tiles shift Calibration screen; ASCII style
Agents call herdr agent prompt directly instead of tmv send Sender unknown (?) Rule in CLAUDE.md/AGENTS.md; Claude hook logs it anyway; timing guess as fallback
Battery drain under PRoot Tablet gets warm 10 fps default; auto 2 fps when idle; 5 fps saver
herdr event stream drops or changes Pets stop updating Fall back to polling herdr agent list; raise poll_seconds to 2–3 (--poll 3)
Repo stays private Others cannot install it Phase 2, step 7: make it public
install.sh runs on unknown machines Install fails or surprises users Script stays short and readable; it only installs uv and the app

Decided

  • Name: Tomville. Command tomville, short alias tmv; settings in ~/.config/tomville/.
  • First run: the user creates a new ville (tmv new), like starting a new game. One ville per project.
  • Lead + workers layout: boss at the top behind the boss desk, facing named worker desks; the board on the wall beside the boss.
  • The task board is GitHub Issues by default, checked every check_seconds (user's choice); TASKS.md is the offline plug-in.
  • Messages are logged by Tomville (tmv send + Claude Code hook), because herdr does not record senders.
  • Status comes from herdr's event stream, with polling as a fallback.
  • v1 team shapes: lead + workers, and pipeline. Other shapes come later.
  • Tomville builds on top of herdr, not as a replacement. herdr still runs the panes, sessions and agent status; Tomville reads that status and draws it, running in its own herdr tab.
  • v1 is watch-only; the prompt box comes later.
  • Built-in pets: cat, dog, sheep. Built-in themes: night, game-boy, green-screen.
  • Landscape first; portrait stacks the sidebar under the office.

Open questions

  • Which pet and colour for each agent?
  • Licence: MIT, or another?

Lead questions on Telegram

With the lead + workers team shape, Telegram enabled and paired, tmv setup installs Claude Code hooks for the lead’s permission requests, auto-mode safety blocks, structured questions and plain-text questions at the end of a turn. Workers and subagents are ignored. Enable telegram.approvals in the ville to answer permission and structured question dialogs; the bot must be running. Workers’ old screen-based approval buttons have been removed; Tomville never sends approval keys to a pane.

Permission requests show the exact tool input and cwd, with edits also shown as diff blocks. Choose Allow once or Deny; replying to that message denies with your reason. Structured questions have option buttons, multi-select, and Other via a typed reply to the particular question message. Tap Submit answers after answering every question. Unanswered permission requests are denied after ten minutes; unanswered questions are cancelled. There is no persistent allow option. Pausing the bot prevents delivery and answers; accepted hook requests still reach their ten-minute deny deadline. The installed hook timeout is 660 seconds so the 600-second decision can return first.

Safety blocks show the action and reason. Approve, retry sends Tom’s message to the lead; the safety check still decides on the retry. Leave it sends no prompt. Plain-text final messages with a question in any prose line or sentence are forwarded in full, with question lines bold and basic italic/code formatting preserved. Question marks in code, URLs and quoted text are ignored; replying reaches the lead normally.

The hooks exchange requests and answers through private, ignored .tomville/approvals/ files. Only the bot polls Telegram. Request IDs are random, answers are consumed once, late taps are ignored, and decisions/questions are logged in messages.jsonl as telegram-approval / telegram-question. Only the paired private chat can answer. Hooks look up their pane in the private, mode-600 ~/.config/tomville/leads.json registry, then verify that the ville’s own state names this pane as lead and Telegram is enabled. A stale or invalid registry entry gets no answer. If the pane is not registered, hooks search cwd and its parents for the first matching ville. Lead startup/adoption and Telegram pairing register the pane; confirmed tmv down --agents and tmv telegram off remove that ville’s entries. Registered leads therefore keep using their ville from any folder, including outside its directory tree. Workers and other panes remain excluded.

Other team shapes are not supported: setup stops before token entry; a running bot replies with the unsupported-shape message and sends no notifications or questions. Telegram can be set up again after switching back to lead + workers.

Coverage follows Claude Code’s hook events: PermissionDenied observes auto-mode denials, not manual denials, deny rules or another PreToolUse hook’s block. Sandboxed network permission prompts do not emit PermissionRequest and still require an in-pane answer. The implementation uses the documented hook contracts, checked against installed Claude Code 2.1.305; tests use synthetic contract fixtures, not live captures. Live hook/UI validation belongs to the lead after Tom’s approval.

Telegram polling and approval decisions run separately from slow herdr commands, notifications and request sends. Callback taps are acknowledged first; acknowledgement errors do not discard a still-pending answer. Request locks cover only local state changes, with a one-second lock deadline; a busy request is skipped by the scan and retried later. Slow lanes have bounded waits and one in-flight worker each, so an outstanding send cannot occupy the polling/decision workers. Bot API requests use 10-second I/O timeouts (30 for the 20-second long poll, 3 for callback acknowledgement), and Telegram-to-agent prompt subprocesses have a 10-second deadline. Request reads no longer rewrite unchanged queue files.

tmv telegram status shows the last successful poll in UTC and its age. tmv doctor warns when no successful poll has been recorded or it is over two minutes old, with a hint to inspect the bot log/restart it. An active loop iteration taking over 30 seconds logs a heartbeat warning. These diagnostics do not expose the token.

About

Retro game view of a Claude + Codex agent team running in herdr

Resources

Stars

2 stars

Watchers

1 watching

Forks

Releases

Packages

Contributors

Languages