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.