A one-shot CLI so 'add it to my todo list' isn't a research project
Whenever I said "add it to my todo list" to my homelab assistant, it would burn twenty tool calls poking around the codebase trying to remember how todos worked. New session, no context, same exploration every time. I wrote a one-shot CLI to short-circuit the whole loop.
What was happening
The assistant could in principle add a todo by reading the todos module, finding the function, importing it correctly with the right env, writing to DynamoDB. In practice it was:
ls /opt/assistant/backend/(where is the todos code?)cat /opt/assistant/backend/todos.py(what's the function called?)cat /opt/assistant/.env(what env vars do I need?)python -c "from todos import create_todo; ..."(cargo-cult the call)- Repeat half of that when the import errored
Every "add it to my todo list" interaction cost dozens of tool calls of context-window noise.
What I found
The right shape isn't "make the assistant smarter at exploration." It's "make the right action cheap to invoke." One executable on the PATH that takes a single string argument and just does the thing. The assistant calls it once, gets a one-line confirmation, and moves on.
The fix
A thin Python script at /opt/assistant/data/workspace/cass-todo,
symlinked into the venv's bin/ so it's on $PATH:
#!/usr/bin/env python3
"""cass-todo "the todo text" [--priority high] [--category finance]"""
from dotenv import load_dotenv
load_dotenv("/opt/assistant/.env")
import argparse, sys
sys.path.insert(0, "/opt/assistant/backend")
from todos import create_todo
from nlquickadd import parse # extracts due dates / recurrence
from categorize import categorize # picks a category via fast model
def main():
ap = argparse.ArgumentParser()
ap.add_argument("text")
ap.add_argument("-c", "--category")
ap.add_argument("--priority", choices=["low", "med", "high"])
ap.add_argument("--notes")
ap.add_argument("--due")
ap.add_argument("--recurrence")
ap.add_argument("--no-parse", action="store_true")
ap.add_argument("--no-auto", action="store_true")
ap.add_argument("--source", default="cli")
args = ap.parse_args()
text = args.text
due_at = args.due
recurrence = args.recurrence
if not args.no_parse:
parsed = parse(text)
text = parsed.text
due_at = due_at or parsed.due_at
recurrence = recurrence or parsed.recurrence
category = args.category
if not category and not args.no_auto:
category = categorize(text)
item = create_todo(
text=text,
category=category or "general",
priority=args.priority,
notes=args.notes,
due_at=due_at,
recurrence=recurrence,
source=args.source,
)
extras = []
if item.get("due_at"):
extras.append(f"due={item['due_at']}")
if item.get("recurrence"):
extras.append(f"recur={item['recurrence']}")
suffix = " " + " ".join(extras) if extras else ""
print(f"added [{item['id']}] ({item['category']}) {item['text']}{suffix}")
if __name__ == "__main__":
main()
What the assistant sees now:
$ cass-todo "rent due every month on the 1st"
added [01HF...] (finance) rent due every month on the 1st recur=FREQ=MONTHLY;BYMONTHDAY=1
One tool call, one line out. From twenty-plus tool calls to one.
What I'd do differently
The pattern generalizes: whenever I notice myself (or an agent acting on my behalf) doing the same exploratory dance twice, that's the cue to make a tiny binary for it. The assistant has a much better time when "add a todo," "log water," "send myself an SMS," and "post to the family group" are each one command, no research.
The other thing: I made the script print exactly one summary line because the assistant pipes tool output back into its context. Anything I print becomes future tokens. A noisy CLI costs me both speed and context budget. The boring rule "print the minimum that confirms success" is even more important when an agent is the one reading.