Agent Integration Guide
frm is designed to be driven by AI agents. Every command supports
--json for structured output and --dry-run for
safe previews. This guide covers the patterns and workflows that make
agent integration straightforward.
The --json flag
The --json flag is a persistent root flag, meaning it works on
every command. When set, commands output structured JSON to stdout instead of
human-readable text.
Read commands
Commands that list data return JSON arrays or objects:
$ frm check --json
[
{
"name": "Alice Smith",
"frequency": "2w",
"last_seen": "2026-02-01",
"ago": "3w",
"email": "alice@example.com",
"org": "Acme Corp",
"group": "friends",
"last_note": "caught up over coffee"
}
]
$ frm context "Alice Smith" --json
{
"name": "Alice Smith",
"frequency": "2w",
"group": "friends",
"ignored": false,
"last_contact": "2026-02-01T00:00:00Z",
"last_note": "caught up over coffee",
"days_since": 27,
"days_until_due": -13
}
$ frm stats --json
{
"total_contacts": 142,
"tracked": 45,
"ignored": 80,
"untriaged": 17,
"coverage_pct": 88.03,
"overdue": 3,
"total_interactions": 127,
"most_contacted": "Alice Smith",
"least_contacted": "Zara Jones"
}
Write commands
Commands that modify data return a confirmation object:
$ frm track "Alice" --every 2w --json
{
"action": "track",
"name": "Alice Smith",
"frequency": "2w",
"accounts": 1
}
$ frm log "Alice" --note "had coffee" --json
{
"action": "log",
"contact": "Alice Smith",
"time": "2026-02-28T12:00:00Z",
"note": "had coffee"
}
$ frm ignore "Spam Contact" --json
{
"action": "ignore",
"name": "Spam Contact",
"accounts": 1,
"skipped": 0
}
The --dry-run flag
The --dry-run flag prevents any writes. Combined with
--json, the output includes "dry_run": true
so your agent can confirm intent before committing:
$ frm track "Alice" --every 2w --json --dry-run
{
"action": "track",
"name": "Alice Smith",
"frequency": "2w",
"accounts": 1,
"dry_run": true
}
Use --dry-run during development and testing to avoid
accidentally modifying contacts. Your agent can do a dry-run pass first,
present the plan, then re-run without the flag to apply changes.
Structured JSON errors
When --json is set and a command fails, frm outputs a
structured error to stdout (not stderr) so JSON parsers can handle it:
$ frm track "Nonexistent Person" --every 2w --json
{
"error": "contact \"Nonexistent Person\" not found"
}
The exit code is still non-zero on failure, so your agent can check both the exit code and the JSON body.
The triage loop
The core agent workflow is the triage loop. The idea is simple: ask frm for untriaged contacts, decide what to do with each one, then repeat until the list is empty.
Step 1: Get untriaged contacts
$ frm triage --json --limit -1
[
{
"name": "Alice Smith",
"email": "alice@example.com",
"org": "Acme Corp"
},
{
"name": "Bob Jones",
"email": "bob@example.com",
"phone": "+1-555-0100"
}
]
The list only includes contacts that have no frequency set and are not
ignored. The --limit -1 flag returns all of them.
Step 2: Categorize each contact
For each contact, run one of:
# Set a tracking frequency
frm track "Alice Smith" --every 2w --json
# Or ignore them permanently
frm ignore "Bob Jones" --json
Step 3: Repeat
Call frm triage --json again. Contacts you just tracked or
ignored will no longer appear. Repeat until the list is empty.
Complete triage script
Here is a shell script that demonstrates the loop:
#!/bin/bash
# Example: triage all contacts using an LLM to decide frequencies.
# Replace the "decide" function with your actual LLM call.
decide() {
local name="$1" email="$2" org="$3"
# Your LLM logic here. Return a frequency string or "ignore".
echo "1m"
}
while true; do
contacts=$(frm triage --json --limit 20)
count=$(echo "$contacts" | jq 'length')
if [ "$count" -eq 0 ]; then
echo "All contacts triaged."
break
fi
echo "$contacts" | jq -c '.[]' | while read -r contact; do
name=$(echo "$contact" | jq -r '.name')
email=$(echo "$contact" | jq -r '.email // empty')
org=$(echo "$contact" | jq -r '.org // empty')
result=$(decide "$name" "$email" "$org")
if [ "$result" = "ignore" ]; then
frm ignore "$name" --json
else
frm track "$name" --every "$result" --json
fi
done
done
The daily check workflow
Beyond triage, an agent can run your daily check and prepare context:
#!/bin/bash
# Daily workflow: check overdue contacts and prepare context for each.
overdue=$(frm check --json)
count=$(echo "$overdue" | jq 'length')
if [ "$count" -eq 0 ]; then
echo "All caught up!"
exit 0
fi
echo "$overdue" | jq -c '.[]' | while read -r contact; do
name=$(echo "$contact" | jq -r '.name')
freq=$(echo "$contact" | jq -r '.frequency')
ago=$(echo "$contact" | jq -r '.ago // "never"')
note=$(echo "$contact" | jq -r '.last_note // "no notes"')
echo "--- $name (every $freq, last: $ago) ---"
echo "Last note: $note"
# Get full context including email threads
frm context "$name" --json | jq '.providers // empty'
echo ""
done
Adding and editing contacts
Agents can create new contacts and update existing ones:
# Add a new contact
frm add "Jane Doe" --email jane@example.com --org "Startup Inc" --json
# Update an existing contact
frm edit "Jane Doe" --phone "+1-555-0200" --json
# Group them
frm group set "Jane Doe" professional --json
Managing overdue contacts
Agents can snooze contacts and use spread to manage imports:
# Snooze someone for 2 months
frm snooze "Alice" --until 2m --json
# After a big import, spread contacts across their intervals
frm spread # preview
frm spread --apply # execute
Summary of useful commands for agents
| Command | Purpose |
|---|---|
frm triage --json |
Get untriaged contacts |
frm check --json |
Get overdue contacts |
frm context <name> --json |
Get full context for a contact |
frm track <name> --every <freq> --json |
Set tracking frequency |
frm ignore <name> --json |
Permanently hide a contact |
frm log <name> --note <text> --json |
Record an interaction |
frm stats --json |
Get tracking dashboard data |
frm add <name> --json |
Create a new contact |
frm edit <name> --json |
Update contact fields |
All write commands support --dry-run for safe previews.
Use this during development to test your agent logic without modifying
real contacts.