nod

module
v0.1.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Nov 8, 2026 License: MIT

README

nod

ci release license

A human approval queue for AI agents, kept in a plain markdown file.

Agents ask. You nod (or don't) from your phone, your editor, or the terminal. The agent's blocked command picks up the answer and keeps going.

nod ask "Ship v2.3 to prod" --action "make deploy" --wait && make deploy

nod demo

Why

Once you run more than a couple of agents, most of what they do is fine to just do. A few things are not: sending email as you, deploying prod, spending money, deleting data. The usual answer is that each agent pings you in its own chat. You lose track, the agent either stalls or guesses, and there's no record of what you said yes to.

nod gives every agent one place to ask and gives you one list to answer:

  • One file, readable anywhere. The queue is nod.md, a markdown checklist. Read it on your phone through Obsidian or Syncthing, git diff it, cat it over ssh.
  • Safe with many writers. Every write takes an exclusive flock and replaces the file atomically. Twenty agents can call nod ask at the same moment and get twenty distinct IDs. That's a test, not a promise.
  • Agents block on your answer. nod ask --wait exits 0 on approve and 1 on decline, so && does the gating. No SDK, no webhook server.
  • Built for agents first. No prompts, --json everywhere, stable exit codes, idempotency keys for retries, and real examples in every --help.
  • MCP built in. nod mcp serves the same queue to Claude Code, Cursor, or any MCP client.
  • One static binary, no dependencies. Standard library Go only.

30-second quickstart

go install github.com/8thlyr/nod/cmd/nod@latest     # or grab a binary from Releases

# an agent asks (prints the id, N-1)
nod ask "Send invoice to Acme" --action "send drafts/acme.eml" --agent mailer

# you look
nod digest
#   1 waiting on you
#   N-1 mailer · Send invoice to Acme (2m)
#     → send drafts/acme.eml

# you answer
nod approve N-1 --note "looks right"

The queue lives at ~/.local/share/nod/nod.md unless you say otherwise (see Where the queue lives).

Recipes

Gate a shell step. The agent blocks until you answer, then runs only on a yes:

nod ask "Rotate the Stripe key" --action "./rotate.sh stripe" --wait --timeout 2h && ./rotate.sh stripe

Exit codes from --wait and nod wait: 0 approved or done, 1 declined or dropped, 3 timed out (still open), 4 no such request.

Retry safely. Agents retry. Pass a key and asking twice returns the same request instead of queueing a duplicate:

id=$(nod ask "Merge PR 412" --action "gh pr merge 412" --key pr-412)
nod wait "$id"

Push new asks to your phone. nod watch runs a command on every change, with the details in env vars. Point it at ntfy, Slack, or anything with a URL:

nod watch --exec '[ "$NOD_EVENT" = asked ] && curl -s -d "$NOD_ID $NOD_SUMMARY → $NOD_ACTION" ntfy.sh/my-approvals'

Events are asked, approved, declined, done, dropped. Env vars: NOD_EVENT, NOD_ID, NOD_AGENT, NOD_SUMMARY, NOD_ACTION, NOD_REF, NOD_BY, NOD_NOTE.

A morning digest. nod digest fits in a push notification or a chat message (capped at about 1000 characters, oldest first):

nod digest --format ntfy | curl -s -d @- ntfy.sh/my-approvals      # plain text
nod digest --format markdown                                         # for chat or a daily note

Give an MCP agent the queue.

claude mcp add nod -- nod mcp

Or in any MCP client config:

{ "mcpServers": { "nod": { "command": "nod", "args": ["mcp"] } } }

The server exposes nod_ask, nod_status and nod_list, and its instructions tell the model not to act until the state is approved. There's no blocking wait tool on purpose: approvals can take hours, so agents poll nod_status. Requests are tagged with the client's name (for example claude-code) unless you pass --agent.

Answer in bulk.

nod list                          # what's open
nod approve all --dry-run         # what would change
nod approve N-4 N-7 --note ok
nod decline N-5 --note "not today"

How it works

The whole queue is one markdown file:

# nod queue

Approvals waiting on a human. Edit with `nod`, read with anything.

## Open
- [ ] N-7 · deployer · Ship v2.3 to prod → ACTION: make deploy ENV=prod · REF: PR#412 · asked 2026-11-08 01:12

## Resolved
- [x] N-6 · mailer · Send invoice to Acme → ACTION: send drafts/acme.eml · REF: - · asked 2026-11-07 22:01 · APPROVED 2026-11-07 23:40 by jc (looks good)
  • Writes. Each command takes an exclusive flock on a sidecar .nod.md.lock, reads the file, changes it, writes a temp file, fsyncs it, and renames it over the original. Readers never see a half-written file, and concurrent writers queue up on the lock.
  • IDs are N-<n>, one more than the highest number anywhere in the file, so they never get reused.
  • Your edits survive. Lines nod doesn't recognize (notes, headings, prose) are kept verbatim and in place. Tick a box by hand (- [x]) and nod reads it as done. The parser is fuzz-tested for round-trip stability.
  • Fields stay one line. Newlines collapse, and the separators · and → inside a field are replaced, so a weird summary can't break the format.
  • Waiting polls the file's mtime and size (1s by default, --interval to change it). There's no daemon to run.

Where the queue lives

First match wins:

  1. --file PATH
  2. $NOD_FILE
  3. ./nod.md if it exists in the current directory (a per-project queue)
  4. $XDG_DATA_HOME/nod/nod.md
  5. ~/.local/share/nod/nod.md

Other env vars: NOD_AGENT (who's asking, defaults to $USER) and NOD_USER (who's answering, defaults to $USER).

Commands

Command What it does
nod ask <summary> --action <cmd> Queue a request. --wait to block, --key for idempotency, --ref for context.
nod wait <id> Block until resolved. Exit 0 yes, 1 no, 3 timeout.
nod list Open requests (--all for history, --agent to filter).
nod show <id> One request in full.
nod approve / decline / done / drop <id...|all> Resolve. --note, --by, --dry-run.
nod digest Short summary for a phone or chat.
nod watch Print, or --exec a command, on every change.
nod mcp MCP server on stdio.

Every command takes --json and --file, and nod <command> --help shows examples. Exit codes: 0 ok, 1 declined (wait), 2 usage error, 3 timeout, 4 not found, 5 conflict (already resolved differently), 10 I/O error.

Non-goals

No server, no web UI, no auth. Access to the queue is access to the file, so use filesystem permissions, or sync it somewhere private. Cross-process locking needs flock, so Linux and macOS are supported. Windows builds only lock within one process.

License

MIT

Directories

Path Synopsis
cmd
nod command
Command nod is a human-in-the-loop approval queue for AI agents that lives in a plain markdown file.
Command nod is a human-in-the-loop approval queue for AI agents that lives in a plain markdown file.
internal
cli
Package cli implements the nod command line: argument parsing, help text, output formats and exit codes.
Package cli implements the nod command line: argument parsing, help text, output formats and exit codes.
mcp
Package mcp serves a nod queue over the Model Context Protocol on stdio: newline-delimited JSON-RPC 2.0, one response object per request line.
Package mcp serves a nod queue over the Model Context Protocol on stdio: newline-delimited JSON-RPC 2.0, one response object per request line.
queue
Package queue reads and writes a nod queue: a markdown file of approval requests that agents add to and humans resolve.
Package queue reads and writes a nod queue: a markdown file of approval requests that agents add to and humans resolve.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL