Skip to content

Repository files navigation

Secondhand

Talk to one agent. Ship with a crew.

Secondhand is a single Go CLI binary, hand, that orchestrates a fleet of coding agents across projects. A supervisory agent runs in a fleet home - a standalone directory anywhere on disk, or the secondhand checkout itself - records tasks in a markdown backlog, and calls hand to spawn autonomous workers into isolated git worktrees. The CLI owns lifecycle correctness, state management, and process supervision. It was born from firstmate, an agent fleet supervisor built as 34K lines of shell, and rebuilds that concept as a clean CLI.

Quick start

Install hand (see "Installation" below for every option), then create a fleet home and register a project:

mkdir ~/fleet
cd ~/fleet
hand init
hand project add https://github.com/org/repo

hand init asks nothing. It creates runtime directories, skeleton files, AGENTS.md and a .claude/settings.json session hook under the current directory, and reports which worker defaults are still unset. Those defaults are settled in the first supervising session you open in the home: it reads the same report at session start and asks you for each missing value, then persists your answer with hand config set <key> <value>. Nothing is guessed on your behalf, so a fleet home is never configured with a value you did not choose.

A fleet home is a plain directory, anywhere on disk, unrelated to any project's own repo. hand init never places a hand binary in it, so install hand on PATH first.

The worker lifecycle commands are available, including hand spawn, hand status, hand send, and hand teardown.

Set up a fleet home

The maintainers dogfood a fleet home inside the secondhand checkout itself; see CONTRIBUTING.md for that setup.

Every hand command resolves its fleet home the same way: the HAND_HOME environment variable if set, otherwise the current directory or the nearest ancestor holding state/hand.db. state/hand.db is the marker because only hand ever writes it, so a project clone under projects/ carrying its own generic top-level data/ and state/ never captures the walk up. Set HAND_HOME to run hand from outside the fleet home, for example from a script or a different working directory; pointed at a directory that is not a fleet home it refuses rather than falling back.

Core concepts

  • Projects: git repositories cloned under projects/, registered in hand's machine state and projected to data/projects.md. Each has a delivery mode: no-mistakes, direct-pr, or local-only.
  • Tasks: units of work identified by a unique ID. Ship tasks produce a branch and PR; scout tasks investigate and produce data/<id>/report.md.
  • Briefs: task instructions at data/<id>/brief.md, written by the supervisory agent before spawning a worker.
  • herdr tabs: each worker runs in its own herdr tab. herdr provides semantic agent state (working/idle/blocked/done/unknown) and push events, so no terminal scraping. herdr's state says whether a pane is busy, not whether a task finished - see SPECS.md's "Agent state" section.
  • Report channel: state/<id>.status is an append-only file the worker writes and hand only reads. It carries the task outcome herdr cannot (working/paused/blocked/needs-decision/done/failed), surfaces in hand status and hand watch, and auto-records a PR URL the worker reports - see SPECS.md's "Report channel" section.
  • treehouse worktrees: workers operate in isolated git checkouts acquired from a treehouse pool, never in the project clone itself.
  • Backlog: data/backlog.md is a plain markdown task queue, read and edited directly by the supervisory agent. Finished entries roll off into data/done-archive.md, dropped ones into data/note-archive.md.
  • Operator context and learnings: data/operator.md is written by the operator for the agent to read first - identity, authority, hard constraints - and data/learnings.md is the agent's own curated record of operational facts that cost real time to discover. The agent reads data/operator.md and never rewrites it, which is what lets its constraints outrank the agent's judgment. hand init seeds both, hand update seeds whichever an older home is missing, and neither ever overwrites one that exists; nothing under data/ is maintained by hand for the operator to read, since hand status and the issue tracker are their view of the fleet.
  • Ambient context: hand init and hand update install hand as a Claude Code SessionStart hook in the home's .claude/settings.json, so a supervising session opens with the fleet overview already in context instead of spending a turn asking for it. The file is merged, never overwritten: an operator's own hooks and permissions survive every refresh, and hand owns at most one entry - see SPECS.md's "Ambient context" section.
  • Agent-shaped output: every command prints TOON on stdout - key: value fields, name[N]{f1,f2}: row blocks with pre-computed aggregates above them, and a help[N]: list of what to run next - because the consumer is an LLM agent rather than a human terminal, with hand watch's per-line event stream as the one exception. --fields narrows a row block to the columns you name, --json still returns the same object it always did, and a failure renders its own document on stderr carrying error, kind and exit so a caller branches on a word instead of a number - see SPECS.md's "Output shape" section.
  • Machine state vs. the prose corpus: machine state - tasks, PR state, pane ids, the project registry, holds - is authoritative in sqlite at state/hand.db. The prose under data/ stays authoritative in files, with a derived full-text index at state/index.db that hand search reads and that is safe to delete at any time. When the database and a state/<id>.status file disagree about what a worker said, believe the file: it is readable without a working hand, which is what recovery has actually needed - see SPECS.md's "Machine state and the prose corpus" section.

CLI overview

Command Description Status
hand With no subcommand: name the binary that answered, its version and the fleet home it resolved, followed by the fleet overview hand status prints Available
hand init Initialize runtime directories, skeleton files and the session hook; asks nothing and chooses no worker default Available
hand config Report the fleet's worker defaults and which of them are still missing; hand config set <key> <value> validates and persists one Available
hand project add Clone and register a repository Available
hand project list List registered projects Available
hand project remove Unregister a project, keeping its clone Available
hand project sync Fast-forward project clones to their remote default branch Available
hand project upstream Declare the repo a fork project opens its PRs against, so hand pr accepts a PR living there and gate-opened-PR detection looks for one there Available
hand spawn Spawn a worker agent in an isolated worktree Available
hand status Show fleet overview or single-task detail Available
hand send Send a message to a running worker, from an argument or --file; waits out a busy composer up to --wait instead of failing, and records the message as undelivered when it never reaches the pane Available
hand hold set Record that an id is waiting on a human or on another id; survives the task's teardown, so hand spawn refuses to reuse a held id Available
hand hold clear Clear the hold on an id Available
hand watch Blocking watcher that prints actionable fleet events, including a worker gone silent with no herdr transition at all (parked), and steers a worker whose harness stopped on a usage limit back into its task once the quota plausibly returned, instead of leaving it dead until someone notices; --until-event exits on the first one - narrowed to chosen kinds with --event - so the exit itself wakes the supervisory agent, and exits 5 naming any worker it can't reach before arming; one watcher per fleet home, so a second exits 3 naming the incumbent unless it passes --takeover Available
hand merge Merge a task's completed work Available
hand pr Record a task's pull request URL Available
hand search Full-text search the prose corpus under data/ Available
hand doctor Report perishable content and generated-block drift in the fleet home's AGENTS.md; fixes nothing Available
hand deliver Record that a task's work is handed off and landing it is someone else's decision, so hand teardown accepts it without --force and the completion says delivered, not merged Available
hand teardown Clean up a completed task, fail-closed on unlanded work, recording it in state/completions.jsonl first Available
hand promote Promote a completed scout task into a ship task Available
hand notify Send an out-of-band notification via a configured command; hand watch also calls it in-process for events worth reaching the operator Available
hand update Update the installed binary from the latest GitHub Release; --check reports availability without installing Available

Run hand --help for details on currently available commands.

Architecture

flowchart TD
    user[User] -->|"requests, decisions, merge it"| supervisor["Supervisory agent<br/>reads AGENTS.md and data/<br/>edits data/backlog.md<br/>calls hand commands"]
    supervisor --> task1["Task 1 worker<br/>herdr tab"]
    supervisor --> task2["Task 2 worker<br/>herdr tab"]
    supervisor --> taskN["Task N worker<br/>herdr tab"]
    task1 --> worktrees["Treehouse worktrees<br/>isolated git checkouts"]
    task2 --> worktrees
    taskN --> worktrees
    worktrees --> ship["Ship: branch to PR to merge to teardown"]
    worktrees --> scout["Scout: investigate to report.md to teardown"]
Loading

Requirements

hand itself is a static binary with no runtime dependencies. It shells out to these tools, and reports the ones it cannot find on PATH when you run hand init or hand doctor:

  • herdr - terminal multiplexer with semantic agent state; every worker runs in a herdr pane, so spawning needs it
  • treehouse v2.1.0 or newer - git worktree pool manager; workers are given worktrees leased from it
  • gh - GitHub CLI, used for every PR and release operation
  • no-mistakes - validation pipeline, needed only by projects registered in no-mistakes mode
  • qmd - semantic search over historical task data, beyond hand search's keyword matching; optional

A worker also needs its own agent harness installed - claude, codex, grok, pi or opencode. hand config lists the supported ones, which of them are on PATH, and which accept a model or effort at launch.

Building from source additionally needs Go 1.26.5 or newer.

hand never installs or configures qmd, and every command works without it. To point it at a fleet home's corpus by hand:

qmd collection add data/ --name secondhand
qmd context add qmd://secondhand "Task briefs, scout reports, decisions, and backlog history"
qmd embed

qmd search "login auth decision" --json
qmd vsearch "how did we handle the deploy failure" -c secondhand

Installation

From a release, the way most installs should go:

curl -fsSLO https://github.com/atqamz/secondhand/releases/latest/download/hand-linux-amd64.tar.gz
tar xzf hand-linux-amd64.tar.gz
install -m755 hand ~/.local/bin/hand

Releases carry hand-linux-amd64, hand-linux-arm64, hand-darwin-amd64 and hand-darwin-arm64 as .tar.gz, alongside a checksums.txt to verify against. The releases page lists every asset.

From nix, either into your profile or for a single command:

nix profile install github:atqamz/secondhand
nix shell github:atqamz/secondhand -c hand --version

The flake covers aarch64-darwin, aarch64-linux and x86_64-linux. On Intel macOS, use a release binary or go install.

From Go:

go install github.com/atqamz/secondhand@latest

That produces a binary named secondhand, not hand, and embeds no version, so it prints dev and never reports available updates. Rename it or prefer a release asset.

To build a checkout - the path for working on secondhand itself - see CONTRIBUTING.md.

To update an installed binary, run hand update. It downloads the release asset for the current OS and architecture, verifies its SHA256 checksum, and replaces the running binary in place. When run inside a fleet home it then refreshes the generated part of that home's AGENTS.md, leaving your own additions untouched, and prints the new release's notes. hand update --check reports whether an update is available without installing it. Every other command run in a fleet home prints a one-line notice to stderr when a newer release exists, checked at most once a day and cached in state/.version-check. Builds without an embedded version never print the notice.

Configuration

Preferences live as plain files under config/, one value per file; SPECS.md's "Directory layout" section lists every key hand reads.

The three worker defaults - harness, model and effort - are owned by hand config, which validates a value and writes it atomically:

hand config                             # what is set, what is missing, what each harness can carry
hand config set harness claude
hand config set model claude-opus-5

The harness comes first because it decides whether the other two exist at all: hand config reports model and effort as pending-harness until one is chosen, then as unsupported for a harness that takes no such launch flag, and hand config set refuses to write one rather than storing a value nothing can dispatch. model and effort are stored per harness (config/model.claude), so switching harnesses re-asks instead of handing a worker an identifier chosen for a different tool.

Nothing sets these for you. hand init reports their state, every supervising session's opening document repeats the report, and the answer is yours to give in that session - the fleet home's own AGENTS.md carries the instructions the agent follows to ask.

A brief can declare its own model and effort for one task, which win over these defaults and lose only to a hand spawn/hand promote flag - see SPECS.md's "Brief format" section.

Workers run their harness interactively so they can be steered and watched. For Claude Code that means first-run dialogs, and hand spawn and hand promote answer the workspace-trust and bypass-permissions ones for you, then confirm the worker is actually running before reporting success. A worker that never comes up fails the spawn instead of being reported as started. Claude Code's managed-settings approval prompt and Codex's directory-trust prompt are exceptions: each enables settings or policies that can run project or host code, so hand refuses to accept the security decision for you and tells you to run the harness yourself once before respawning.

Contributing

See CONTRIBUTING.md.

License

MIT

About

Talk to one agent. Ship with a crew. CLI: hand

Resources

Contributing

Stars

4 stars

Watchers

0 watching

Forks

Releases

Used by

Contributors

Languages