Back to blog
FILE 0xA0·A ONE-SHOT CLI SO 'ADD IT TO MY TODO LIST' ISN'T A RESEARCH

A one-shot CLI so 'add it to my todo list' isn't a research project

May 7, 2026 · cli, python, todos, agents

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:

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.