You ask your AI copilot to capture a window. The copilot writes peek app "Xcode". The tool looks for a window owned by Xcode exactly. It doesn’t find one because the process is named Xcode-16.3. The tool responds with Error: application not found. The copilot, a large language model (LLM) with the memory span of a goldfish, then tries peek app "Xcode-16.3". This time it works—but it’s wasted a conversational turn, input tokens, output tokens, and the patience of the person paying the bill.
Now imagine a different version: the copilot writes peek app xcode. The tool normalizes the name, uses fuzzy matching, finds Xcode-16.3, captures the window, and returns /tmp/peek/Xcode-16.3-1712524800.png. One output token. Zero wasted turns.
The difference between the two versions isn’t a bug. It’s a design decision.
The User Who Doesn’t Read Your --help
In early 2025, Mathias Biilmann (CEO of Netlify) coined the term Agent Experience—AX—to describe the experience AI agents have when interacting with a product. Just as UX is for humans and DX is for developers, AX is for LLMs.
The concept feels abstract until you apply it to something concrete—like a CLI. For 40 years, CLIs have been designed for humans: descriptive messages, colors, progress bars, --help pages. All of that is just noise to an LLM. An LLM doesn’t read the help page—it infers flags from the command name. It doesn’t appreciate green “Success” messages—it processes plain text. It doesn’t look at a loading bar—it waits for the process to finish.
Traditional CLI design optimizes for humans to understand what’s happening. AX optimizes for agents to take action with minimal tokens and turns.
Five Principles, Three Tools
Over the past few months, I’ve built three CLIs following this philosophy: peek (macOS window capture), lql (issue management for Linear), and driftkit (agent harness auditing). All three are designed for LLMs to use without a manual, and from that experience come five principles.
1. Output Is a Contract, Not a Conversation
A traditional CLI for capturing a window might say:
✅ Screenshot saved successfully!
File: /tmp/peek/Xcode-1712524800.png
Size: 1920x1080
Format: PNG
It’s nice. Informative. And entirely useless to an LLM that just needs the file path to pass to the next tool. It has to parse the output, ignore the emoji, find the line starting with File:, and extract the path. Tokens wasted.
peek outputs one thing:
/tmp/peek/Xcode-1712524800.png
Just a path. Nothing else. The LLM reads it, uses it, and moves on. The stdout output is a contract: always a path, always parsable, always stable. If you change the format, you break the contract and every agent depending on it.
In other words: your stdout isn’t for decoration. It’s an API.
2. Tolerate Hallucinations—Don’t Punish Them
LLMs hallucinate names. It’s as inevitable as gravity or the WiFi breaking when you’re in a rush. If your tool demands exact names, you’re asking for precision from a probabilistic machine. It won’t work.
peek uses three search phases to find an app:
- Exact match (case-insensitive):
xcode→Xcode - Normalized match (removes spaces and dashes):
thinklocal→ThinkLocal - Fuzzy match:
xcode→Xcode-16.3
The LLM doesn’t need to know the exact process name. It gives an approximate name, and the tool handles the details. lql does the same with project and team names—resolving tokamak to Tokamak without requiring a UUID.
The principle is simple: if the human overseeing the agent can figure out what it meant, your tool should be able to as well.
3. Errors Should Tell You What to Do, Not What Went Wrong
Compare these two errors:
Error: application not found in window list
Error: "Xcode" is not running. Start it with: open -a "Xcode"
The first describes the problem. The second solves it. A human reads “application not found” and thinks “oh, it’s not open.” An LLM reads the same and… tries another name, or Googles, or invents a --force flag. The second error allows the LLM to execute open -a "Xcode", wait, and retry. Problem solved in one turn.
Another example: when peek doesn’t know which app you mean because the name matches multiple options:
Error: "code" matches multiple apps:
Visual Studio Code
Xcode
Be more specific.
The LLM gets the list of candidates, picks the right one, and retries. It doesn’t have to guess. It doesn’t have to search. The tool provides all the information needed to act.
The rule: every error message should include an actionable command or a list of valid options. If the agent has to think after reading your error, your error isn’t good enough.
4. Zero Mandatory Flags
A traditional CLI for capturing windows might require:
capture --app "Xcode" --format png --output /tmp/screenshot.png --window-id 12345
Four mandatory flags. An LLM has to remember (or guess) all four. Every missing flag adds a layer of error correction.
peek only needs:
peek app Xcode
The format is always PNG. The output has a sensible default (/tmp/peek/<app>-<timestamp>.png). The window ID resolves automatically by selecting the largest window. Customization is available—--output, --panel—but optional.
lql follows the same philosophy: lql create "Login fails with OAuth" automatically assigns type, priority, team, and project based on the working directory. Zero flags.
The principle: sensible defaults aren’t just a convenience—they’re resilience against hallucinations. The fewer parameters an agent needs, the fewer it can invent.
5. Silent Capture—Don’t Interrupt the Agent
This one is specific to peek but illustrates a general principle: your tool should not interfere with the agent’s workflow.
On macOS, using screencapture to grab a window requires bringing it to the foreground. This steals focus from the terminal where the agent is running. The agent then loses its active window. It’s like taking a screwdriver out of an electrician’s hand mid-repair.
peek uses ScreenCaptureKit—Apple’s screenshot framework—with a filter that selects a specific window by its ID (SCContentFilter(desktopIndependentWindow:)). The window is captured as-is, without activating it, without moving it, without touching the focus. The terminal remains the active window.
The general principle: a tool designed for agents should have no visible side effects. No opening dialogs, no confirmation prompts, no “Press Enter to continue.” The agent works in the background—your tool should too.
Before and After
Here’s a side-by-side comparison of traditional design versus AX-first design for the same tasks:
| Task | Traditional CLI Output | AX-First CLI Output |
|---|---|---|
| Capture output | ✅ Saved to /tmp/... + metadata | /tmp/peek/App-123.png |
| App not found | Error: not found | Error: "X" not running. Start: open -a "X" |
| Inexact name | Error | Automatic fuzzy match |
| Required flags | 3–4 mandatory | 0 (just the positional argument) |
| Create issue | --team T --type bug --priority 2 --project P | ` |
This article was originally published in Spanish and translated with the help of AI.