Skip to content

AI harness (opt-in)

fm's verdict and close recommendations are fully deterministic and need no LLM. The AI harness is an optional second opinion: an agent with a read-only tool loop that diagnoses why the Mac is hot and recommends what to close — on demand (a) and when thresholds fire.

It never kills anything. Close actions become rows in the AI tab; you select one and confirm through the same k → confirm → SIGTERM path as the deterministic recommendations.

Setup

30-second path

Press A in the TUI. The wizard lists providers detected in ~/.omp/agent/models.yml (e.g. dgx), prefills the endpoint and first model, and writes ~/.config/macos-fanMonitor/config.toml.

From the shell

fm ai <cmd> does everything without opening the TUI — handy on a fresh install or when an agent is configuring it for you:

fm ai providers    # what's available: omp providers, Ollama, LM Studio
fm ai setup       # writes config.toml (refuses to overwrite; --force to redo)
fm ai status       # parsed config + whether the key resolves (never prints it)
fm ai test         # one chat round-trip; --tools also verifies tool calling
fm ai disable|enable

Worked examples:

# OMP harness (key read from ~/.omp/agent/models.yml at runtime)
fm ai setup --from-omp dgx

# Ollama — model must support tool calling (e.g. qwen3, llama3.1)
fm ai setup --base-url http://localhost:11434/v1 \
            --model qwen3:8b --key-source none

# LM Studio (served model with tool support)
fm ai setup --base-url http://localhost:1234/v1 \
            --model your-model-id --key-source none

# OpenAI — key stays in your environment, never in the file
export OPENAI_API_KEY=fm ai setup --base-url https://api.openai.com/v1 \
            --model gpt-4o-mini --key-source env:OPENAI_API_KEY

Tuning goes on the same line: --fan-duty 70 --throttle 30 --mem 90 --swap 70 --high-samples 3 --cooldown 300 --max-tool-rounds 6.

Verify

fm ai status       # exit 0 when configured; shows thresholds, key state
fm ai test         # "OK · <model> · <latency>s"
fm ai test --tools # also proves tool-call round-trips work
fm --once --ai     # snapshot + AI diagnosis panel

Setting up with an AI agent

An agent can configure this end-to-end from the shell. Copy-paste block:

Set up the fm AI harness:
1. `fm ai providers` — pick a reachable provider.
2. `fm ai setup --from-omp <name>` if an omp provider exists, else
   `fm ai setup --base-url <url> --model <id> --key-source env:<VAR>`
   (or `--key-source none` for Ollama/LM Studio).
3. `fm ai status` then `fm ai test --tools` — both must exit 0.
RULES: never paste an API key into config.toml — use env:<VAR>; never use
--force on an existing config unless I asked; do not modify
~/.omp/agent/models.yml.

The config file itself:

[ai]
enabled = true
provider = "openai-compatible"
base_url = "https://…/v1"
model = "dgx-current"
key_source = "omp:dgx"        # omp:<name> | env:<VAR> | none (no key)
max_tool_rounds = 6
cooldown_s = 300

[update]
auto = true                 # fm self-updates on launch when PyPI is newer

[triggers]
fan_duty_pct = 70
throttle_pct = 30
mem_used_pct = 90
swap_used_pct = 70
high_severity_samples = 3     # verdict severity "high" for N samples

The API key is never stored by fm: key_source is a pointer resolved at runtime. FANMON_AI_CONFIG=/path/to.toml overrides the config path (used by tests).

Using it

key / flag action
a consult the AI on the latest snapshot (opens the AI tab)
A setup wizard
enter in the AI tab's input ask a follow-up (keeps last 12 messages of context)
enter on a follow-up option quick-reply: sends that question as a consult
space on a close row toggle it for a batch kill ()
enter on a close row same as space
enter on investigate/wait sends a "look closer with your tools" consult
k on the actions table kills toggled rows as one confirmed batch; without toggles, kills the highlighted row
fm --once --ai snapshot + diagnosis panel, then exit
fm --once --ai --ask "…" same, with your question

After a kill that came from the AI tab, fm automatically re-consults a few seconds later ("verifying…") so the AI can check whether its advice helped.

Triggers auto-start a consult when fan duty / throttle / memory / swap cross their thresholds, or the verdict stays high for high_severity_samples samples in a row. A fired rule cools down for cooldown_s and must drop below 85% of its threshold before it can fire again.

Tools the agent may call

All read-only, all capped:

tool what it does
proc_detail ps row for one PID (argv truncated to 120 chars), lsof open-file count, children, category
process_tree ancestors + direct children from the snapshot's ppids
resample a fresh full snapshot packet
recent_logs log show --last Nm (N ≤ 10) filtered by message text, ≤ 60 lines
thermal_state raw pmset -g therm + pmset -g batt
watchdog_events last 20 fan-activity events from the watchdog logs

Privacy — what is and isn't sent

Sent: machine facts (cores, RAM total, fanless), gauge values (fan RPM/duty, throttle, temps, CPU/memory/swap, load), the verdict text, deterministic recommendation rows, watchdog probe state and events, trend summaries, and the top 12 processes by CPU and by RSS — each as pid, comm, category, closeable, rss_mb, cpu_pct, age_h, group.

Never sent: full command lines / argv (Proc.command is excluded from the packet; proc_detail truncates argv to a basename + 3 args, 120 chars), usernames, paths beyond process basenames, file contents, and of course the API key itself (sent only in the Authorization header to your configured endpoint).

Recommend-only guarantee

The response contract is a single JSON object with diagnosis, confidence, actions, and follow_up_questions. Before an action reaches the table, fm drops any close action whose pids are not closeable or are category system, and records the drop. What survives is a row — the human still picks it and confirms the kill.