Hum runs your dev processes in the background. You and your coding agent can start, check, and stop them from any terminal.
you (CLI) ββββ βββΆ db
ββββΆ hum daemon ββββββΌββΆ api starts after db is ready
agent (MCP) ββ keeps the logs βββΆ web starts after api is ready
Processes keep running when you close the terminal that started them.
![]() Start and stop a stack in order |
![]() An agent finds why a process failed |
brew trust --formula brettinternet/tap/hum
brew install brettinternet/tap/hum| Other ways | Command |
|---|---|
| Script (macOS, Linux) | curl --proto '=https' --tlsv1.2 -fsSL https://raw.githubusercontent.com/brettinternet/hum/main/install.sh | sh |
| mise | mise use -g github:brettinternet/hum |
| From source | See development setup |
The script verifies checksums and installs to ~/.local/bin. Set HUM_VERSION=0.9.0 to pin a release or HUM_INSTALL_DIR to change the location. Homebrew and release archives include the hum(1) man page.
Windows (amd64)
Download the release zip, check its SHA-256, and unpack it:
$version = '1.2.3' # replace with the release version
$asset = "hum-$version-windows-x64.zip"
$base = "https://github.com/brettinternet/hum/releases/download/v$version"
Invoke-WebRequest "$base/$asset" -OutFile $asset
Invoke-WebRequest "$base/checksums.txt" -OutFile checksums.txt
$line = Get-Content checksums.txt | Where-Object { $_ -match "^([0-9a-fA-F]{64}) \./$([regex]::Escape($asset))$" }
if (@($line).Count -ne 1) { throw 'hum checksum entry missing or duplicated' }
if ((Get-FileHash $asset -Algorithm SHA256).Hash -ine ($line -split ' ')[0]) { throw 'hum checksum mismatch' }
$installDir = Join-Path $env:LOCALAPPDATA 'Programs\hum'
Expand-Archive $asset -DestinationPath $installDir -Force
$env:Path += ";$installDir" # add this directory to your user PATH for future shells
& (Join-Path $installDir 'hum.exe') --versionEverything works except hum signal. In hum attach, Ctrl+] detaches; other keys, including Ctrl+C, Ctrl+D, and Ctrl+Z, go to the process. See Windows behavior.
Save this as hum.yaml in a Git repository:
version: 1
processes:
db:
argv: [sh, -c, "sleep 1; echo ready; exec sleep 3600"]
ready: {match: ready}
api:
argv: [sh, -c, "echo listening; while :; do date; sleep 2; done"]
after: [db]
ready: {match: listening}Start it, check it, read its logs, and stop it:
$ hum up --detach
hum up: db: started; waiting for readiness
hum up: db: ready
hum up: api: started; waiting for readiness
hum up: api: ready
NAME RESULT STATE PID
api started running 28813
db started running 27456
$ hum status
NAME STATE PID READINESS RESTART FOLLOWERS
api running 28813 ready never 0
db running 27456 ready never 0
$ hum logs api --tail 2
Thu Sep 24 15:48:03 MDT 2026
Thu Sep 24 15:48:05 MDT 2026
next cursor: 7
$ hum down
api stopped
db stoppedWithout --detach, hum up follows the logs. Ctrl+C stops following; the processes keep running.
hum init writes a starter hum.yaml. Editors with SchemaStore check it against hum.schema.json. See hum.example.yaml for every option and examples/ for small runnable setups.
| To | Run |
|---|---|
| Start everything and follow logs | hum up |
| Start in the background | hum up --detach |
Start api and what it needs |
hum up api |
| See what's running | hum status |
| See one process in detail | hum status api |
| See every project | hum list --all |
| Find who holds a port | hum list --port 3000 |
| Read recent logs | hum logs api --tail 50 |
| Read only errors | hum logs api --stream stderr |
| Follow all logs | hum logs --follow |
| Wait for a log line | hum wait api --match ready --timeout 30s |
| Restart and reload config | hum restart api |
| Stop one process | hum stop api |
| Stop everything | hum down |
| See starts, exits, and failures | hum events --failed |
| Check setup without starting anything | hum doctor |
Add --json for machine-readable output (format). hum start api starts only api; hum up api also starts everything listed in its after. Full command reference: design or man hum.
A process listed in after waits until this check passes:
| Ready when | Config |
|---|---|
| a log line matches | ready: {match: "Listening"} |
| a command exits 0 | ready: {exec: [pg_isready, -h, localhost]} |
| an HTTP GET returns 2xx | ready: {http: "http://127.0.0.1:3000/readyz"} |
| a TCP port accepts | ready: {tcp: "127.0.0.1:5432"} |
| the process itself exits 0 | ready: {exit: 0} |
exec, http, and tcp checks retry every interval (1s); every check gives up after timeout (30s). They run only at startup; Hum does not monitor health. HTTP and TCP targets must use an IP address or localhost.
Use exit: 0 for setup steps such as migrations:
processes:
migrate:
argv: [bun, run, migrate]
ready: {exit: 0}
api:
argv: [bun, run, api]
after: [migrate]If migrate fails, api does not start.
processes:
api:
argv: [bun, run, api]
restart: on-failure # retry after 1s, 2s, 4s, 8s, 16s, then give up
stop_grace: 5s # wait between SIGTERM and SIGKILL (default 10s; 0s kills at once)hum stop always wins over automatic restarts.
version: 1
environment:
files: [.env, {path: .env.local, required: false}]
processes:
api:
argv: [bun, run, api]
env:
PORT: "3001"
LEGACY_DATABASE_URL: null # remove ityour shell env β .env β .env.local β processes.api.env (later wins)
Files are required by default; a mapping with required: false skips only a missing file. Set environment.inherit: false to skip your shell env. Hum never expands $VAR, $(...), or backticks. Changes apply on hum restart. Limits and file syntax: design.
hum run preview -- bun run preview # run in the foreground
hum run preview --detach -- bun run preview # run in the background
hum attach preview # watch it
hum logs preview --follow
hum stop preview # stop, keep logs
hum remove preview # stop and forgetEach Git root is its own project, so every worktree gets its own processes:
hum -C .worktrees/agent-a up --detach
hum -C .worktrees/agent-b up --detach
hum list --all
hum -C .worktrees/agent-a downHum does not assign ports. Commit a default PORT in .env, then override it in a Git-ignored .env.local listed as {path: .env.local, required: false}. If the override is missing, the committed default applies; if that port is already bound, the server fails loudly at bind. Use a plain string entry (.env.local) when a missing local file should fail launch preflight instead.
| Pick a project or manifest | Example |
|---|---|
| Another project | hum -C ../other status |
| Another manifest | hum -F hum.test.yaml up |
| Machine-wide, no project | hum -g run proxy -- caddy run |
Hum uses --file if given, else .hum.yaml, else hum.yaml. It never merges them. Put a personal .hum.yaml in .gitignore to use Hum without changing shared config.
processes:
console:
argv: [./console]
tty: truehum attach console # type into it; Ctrl+] detaches
hum input console --text 'yes' # answer a prompt without attachingOnly one client can type at a time. Input is sent once and never queued.
$ hum status web
...
listening: 127.0.0.1:8123 (pid 67051)Port taken? Find which project holds it:
$ hum list --port 8123
Project: /src/app/.worktrees/agent-a (hum --project /src/app/.worktrees/agent-a)
NAME STATE PID LISTENING
web running 67051 127.0.0.1:8123 (pid 67051)Named status shows TCP listeners held by the process and its descendants. list --port searches every project; inspections that could not finish print as Incomplete: lines. Plain list and status never inspect sockets. Non-Hum processes and container port-forwards are not seen. See port observation.
Install a plugin, or point any MCP client at hum mcp:
claude plugin marketplace add brettinternet/hum && claude plugin install hum@hum # Claude Code
codex plugin marketplace add . && codex plugin add hum@hum # Codex, from a checkout
herdr plugin install brettinternet/hum/plugins/herdr --yes # Herdr process picker{ "mcpServers": { "hum": { "command": "hum", "args": ["mcp"] } } }Scripts can check the JSON format version first:
$ hum version --json
{"schema_version":1,"version":"<version>","build_time":"<time>"}See coding-agent setup and the Herdr plugin.
source <(hum completion bash) # bash
source <(hum completion zsh) # zsh
hum completion fish > ~/.config/fish/completions/hum.fish # fish| Hum | pitchfork | Overmind | Hivemind | mprocs | |
|---|---|---|---|---|---|
| Config | YAML, exact argv | TOML, shell | Procfile | Procfile | YAML or Procfile |
| Ready checks | match, exec, HTTP, TCP, exit | match, command, HTTP, TCP, delay | β | β | β |
| Log history | bounded output + event history | SQLite with search | tmux | β | TUI, files |
| Resume reads from a cursor | yes: --after-cursor returns next |
β | β | β | β |
| Type into a process | attach, one-shot input | β | tmux | stdin | TUI |
| MCP tools | 13 | 5 | β | β | β |
| UI | none (Herdr panes) | TUI, web | tmux | terminal | TUI |
β means not documented.
Pick pitchfork for port allocation, a proxy, boot start, cron, file-watch restarts, hooks, or a UI. Pick Hum for exact argv, per-worktree isolation, and a small agent API where reads resume without missing or repeating a line:
$ hum logs api --tail 2
...
next cursor: 7
$ hum logs api --after-cursor 7 # only lines written sinceScripts pair the cursor with the session_id from --json output, so a cursor from a removed or replaced process fails instead of silently skipping lines:
$ hum logs api --after-cursor 7 --session-id 5361f4a2...
output cursor session "5361f4a2..." is stale; current session is "9c0e17b4..."; read without a cursorHum does not provide a UI, port allocation, $PORT injection, a reverse proxy, URL inference, scheduling, boot start, file-watch restarts, health monitoring, resource limits, log queries, or shell templating. See decision-001.


