AI Agents#
The agents package (pkgs/agents/) provides a suite of CLI tools for
personal data archeology — making sense of decades of accumulated files
(.txt, .rtf, .eml) using local Ollama inference.
All agents are available as flake apps and as installable binaries.
Running#
# Via flake app (no install needed)
nix run .#triage -- ~/Documents > results.jsonl
nix run .#summarize -- somefile.txt
# Or install via home-manager / nix profile
nix profile install .#agents
Environment variables#
| Variable | Default | Description |
|---|---|---|
OLLAMA_HOST |
http://localhost:11434 |
Ollama base URL |
OLLAMA_MODEL |
qwen2.5:3b |
LLM model to use |
Agents#
triage — batch analysis#
The primary tool for working through large collections. One LLM call per file, outputs JSONL (one JSON object per line).
triage ~/Dropbox/Personal > results.jsonl
Each line contains:
{
"file": "/path/to/file.txt",
"class": "financial",
"score": 4,
"reason": "W-2 tax document from 2003",
"summary": "Annual wage statement from Acme Corp...",
"people": ["John Doe"],
"dates": ["2003-01-31"],
"orgs": ["Acme Corp"],
"amounts": ["$52,400"],
"sensitive": false
}
Score scale: 1=junk/spam · 2=low · 3=moderate · 4=important · 5=critical
Useful jq filters on the output:
# Files worth keeping
jq -r 'select(.score >= 4) | .file' results.jsonl
# Files that may contain PII
jq -r 'select(.sensitive) | .file' results.jsonl
# All financial documents
jq -r 'select(.class == "financial") | .file' results.jsonl
# TSV for spreadsheet import
jq -r '[.file, .class, (.score|tostring), .reason] | @tsv' results.jsonl > triage.tsv
# Deletion candidates
jq -r 'select(.score <= 2) | .file' results.jsonl
summarize — human-readable summary#
3–5 sentence summary of one or more files. Accepts files and/or directories
(scans for .txt/.rtf/.eml at depth 1).
summarize letter.txt
summarize ~/Dropbox/Personal/Correspondence > summaries.txt
classify — single category word#
Outputs one of: legal · financial · medical · employment ·
correspondence · technical · junk
classify document.txt # → "financial"
Useful for routing in shell pipelines:
case $(classify "$file") in
legal) process-legal "$file" ;;
financial) process-financial "$file" ;;
*) summarize "$file" ;;
esac
extract — structured entity extraction#
Outputs JSON with people, organizations, dates, places, amounts, and a
sensitive flag.
extract contract.txt
{
"people": ["Jane Smith", "Robert Jones"],
"organizations": ["First National Bank"],
"dates": ["2001-03-15"],
"places": ["Indianapolis, IN"],
"amounts": ["$150,000"],
"sensitive": true
}
importance — archival score#
Outputs a 1–5 score and a one-line reason. Useful for deciding what to keep before a bulk triage run.
importance old-receipt.txt
{"score": 2, "reason": "Retail receipt for minor purchase, no legal significance"}
Supported file formats#
| Extension | Handling |
|---|---|
.txt, .TXT |
cat |
.rtf, .RTF |
unrtf --text |
.eml |
Python email module (extracts From/Date/Subject + text body) |
All content is truncated to ~3000 characters before sending to the LLM.
Adding a new agent#
- Add a
writeShellScriptBinblock inpkgs/agents/default.nix - Source
"$AGENTS_LIB"at the top — this gives youread_file,collect_files, andollama_chat - Add the binary name to the
for bin in …loop inextraSetup - Add it to
allScripts - Add a flake app entry in
modules/flake/packages/default.nix
Minimal template:
my-agent = pkgs.writeShellScriptBin "my-agent" ''
set -euo pipefail
source "$AGENTS_LIB"
SYSTEM='Your persona / instructions here. Output format constraints go here.'
[[ $# -lt 1 ]] && { echo "usage: my-agent <file>" >&2; exit 1; }
content=$(read_file "$1" | head -c 3000)
ollama_chat "$SYSTEM" "$content"
'';
Prompt design at 3b scale#
qwen2.5:3b is capable but small. Reliability comes from constraint:
- Force output format — single word, JSON schema, or CSV. Never open-ended prose.
- One task per call — don’t ask for classification AND extraction in the same
prompt unless you’re OK with occasional failures (as
triagedoes for speed). - Short context — 3000 chars (~750 words) is enough for metadata extraction. Fighting the context limit produces worse results than working within it.
- Explicit schema — include the JSON schema in the system prompt, not the user message. Models follow system-prompt constraints more reliably.