HerdTag

Source: docs/assistant.md

Text assistant

Run the herd by text message. Ask "who's out?" or "move them to North Thursday 6am" from any phone; changes always wait for a YES. A plain summary arrives every morning.

The code is in src/herdtag/server/assistant.py. It loads automatically with the server.

How a message is handled

  1. Built-in commands first, no AI involved:

    Text Does
    STATUS or SUMMARY The herd summary (same as the morning text)
    YES (or the 4-digit code) Runs the change waiting for this phone, if it's under 15 minutes old
    NO / CANCEL Cancels the waiting change
    UNDO Reverses the last pause, resume, start or scheduled move from this phone, within an hour
    HELP What it can do
  2. Anything else goes to Claude with the last 10 messages from that phone and these tools:

    Tool Kind
    herd_status Read: counts by state, who's out, not heard from, low batteries, fences not confirmed
    recent_events Read: alerts from the last N hours
    grazing_status Read: paddocks, rotations, next move, moves scheduled by text
    list_fences Read: hand-drawn fences
    rotation_action Change: pause, resume, move-now, close-gate, stop
    start_rotation Change
    schedule_action Change: run pause / resume / move-now / stop at a future local time
    send_fence Change: send an existing fence to some or all tags

Safety rules

Setup

  1. Claude. Set ANTHROPIC_API_KEY. The SDK also picks up other credential sources, such as ant auth login. Without credentials the built-in commands still work.
  2. Allowlist. Add phones with the full country code: curl -X POST localhost:8000/api/assistant/phones -H 'content-type: application/json' \ -d '{"phone": "+15551234567", "name": "Dale"}' Or set HERDTAG_ALLOWED_PHONES=+15551234567,+15557654321.
  3. Twilio number. Buy an SMS-capable number. Under Messaging → A message comes in, set Webhook, HTTP POST to https://<your public host>/sms/twilio. Behind a tunnel or proxy, set HERDTAG_PUBLIC_URL=https://<your public host> so the signature check uses the URL Twilio signed.
  4. Outbound texts (morning summary, scheduled-action results): set TWILIO_ACCOUNT_SID, TWILIO_AUTH_TOKEN, TWILIO_FROM. Without them, texts are stored with status dry_run; see GET /api/assistant/outbox.
  5. US numbers need A2P 10DLC registration with Twilio before carriers deliver texts reliably.
Env var Default Meaning
HERDTAG_ASSISTANT_MODEL claude-sonnet-5-5 Model for free-text messages
HERDTAG_ASSISTANT_FALLBACKS 1 Server-side refusal fallback (fallbacks: "default") on models that support it; 0 turns it off
HERDTAG_SUMMARY_HOUR 6 Local hour for the morning text
HERDTAG_ALLOWED_PHONES Comma-separated E.164 numbers, added to the phones table
HERDTAG_PUBLIC_URL Public base URL, for the Twilio signature check
HERDTAG_NO_SCHEDULER Set to skip the background thread (tests)

The background thread runs every 30 s. It runs scheduled actions that are due, sends the morning summary once a day to every allowed phone, and expires stale confirmations.

API

Method Path Notes
POST /sms/twilio Twilio inbound webhook (form-encoded From, Body); replies with TwiML
POST /api/assistant/chat {phone?, text} → {phone, reply}; same logic as SMS, no allowlist
GET /api/assistant/summary {text}: the morning summary right now
GET / POST / DELETE /api/assistant/phones, /api/assistant/phones/{phone} Manage the allowlist
GET /api/assistant/outbox Outbound texts and their status (sent, dry_run, error)
GET /api/assistant/pending Recent confirmation requests and what happened to them
GET /assistant Web chat page for trying it without a phone

Cost

Each free-text message is one short Claude conversation: usually 2 calls (a lookup, then the answer) at low effort. With Claude Sonnet 5.5 ($2 / $10 per million input / output tokens), that's roughly a cent or two per message; check the usage page. STATUS, YES, NO, UNDO and the morning summary use no AI. Twilio charges per message segment; replies are capped at 480 characters.

Try it

uv run herdtag-server
uv run herdtag demo --rotation 0.05      # optional: a live herd to ask about
open http://127.0.0.1:8000/assistant

Not yet tested