# Designing CLIs for Better Agent Experiences

Design a CLI as an agent interface: task flows, structured output, dry runs, and feedback loops.

Published: 2026-09-04
By [Nguyen Pham](https://x.com/nguyenfamj)

For a long time, the command line was an interface for people already closest
to the system, mostly engineers who knew the vocabulary and held the relevant context,
and could recover from a failed command without the interface explaining
itself.

That is changing because agents can work directly with a CLI. Their
training gives them a working familiarity with the language and conventions of command line , making it a natural execution surface.

In a sandbox, an agent can work through a task independently and involve a
person only where judgment is genuinely needed.

The human interaction with CLI is not disappearing, but that they move from
typing every command to deciding what should happen and reviewing the result.
As the CLI becomes where an agent turns human intent into product work, it is
part of the product experience rather than merely a developer convenience layer
over an API.

## Why sandboxes make the CLI important again

AI Product teams are adding sandboxes because many useful tasks cannot be reduced
to one tool call. A sandbox gives an agent room to understand the state of a
system, carry out work, and verify the outcome safely. People are saying CLI is more token efficient too :)).

MCP remains valuable because it gives agents curated, well-bounded access to a
product, but overall MCP and a CLI are not competing choices. MCP is often the right
interface for a focused capability, while a CLI is useful when work is
exploratory, stateful, or composed from several operations. In that setting,
the CLI becomes the durable, inspectable surface that the agent can reason over
while it works.

<table>
  <thead>
    <tr>
      <th>Choose MCP when</th>
      <th>Choose a CLI when</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>The agent needs one bounded product capability with a clear contract.</td>
      <td>The agent needs to carry a task through several steps.</td>
    </tr>
    <tr>
      <td>Inputs and expected outputs are known before the call.</td>
      <td>The agent needs to discover context as it works.</td>
    </tr>
    <tr>
      <td>You want a narrow permission boundary around an operation.</td>
      <td>The agent needs to inspect state, rehearse a change, or verify an outcome.</td>
    </tr>
    <tr>
      <td>The interaction is a product action such as creating a ticket or reading a record.</td>
      <td>The work involves a repository, an environment, or a sequence of related commands.</td>
    </tr>
    <tr>
      <td>The agent only has access to curated product tools.</td>
      <td>The agent has a sandboxed shell and the files or environment needed for the task.</td>
    </tr>
    <tr>
      <td>Tool schemas add a baseline context cost, while bounded responses keep each call predictable.</td>
      <td>Commands are compact, but discovery and unbounded terminal output can grow the transcript.</td>
    </tr>
  </tbody>
</table>

Neither interface is inherently cheaper in tokens. Compare the cost of the
whole execution loop: MCP tool definitions and responses on one side, and CLI
commands, help text, and output on the other. In both cases, concise,
structured responses are what keep an agent's context under control.

The useful design question is not which interface wins. It is where the
boundary belongs: use MCP to provide deliberate access to a capability, and a
CLI when the agent needs an execution surface for the surrounding work.

Having an API is therefore no longer enough: an API can be complete while still
offering a really poor agent experience. The real challenge is whether an agent can use
the interface to get from a request to a trustworthy result, just like how human navigating a product UI.

## Design for the task, not the endpoint

The easiest CLI to build mirrors API endpoints: `projects.create`,
`projects.list`, `members.add`. Although that can be useful, it forces the
agent to reconstruct the workflow and infer what a successful result looks
like.

A better CLI begins with the task and supports the full path to completion.

Creating a project, for example, should be one legible action with a clear
outcome and enough options to make the result useful in the next step, rather
than a raw transport call.

<table>
  <thead>
    <tr>
      <th>Bad: endpoint-shaped</th>
      <th>Good: task-shaped</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><code>product api post /projects --body '&#123;&quot;name&quot;:&quot;launch-plan&quot;&#125;'</code></td>
      <td><code>product project create --name launch-plan --owner team-platform --json</code></td>
    </tr>
    <tr>
      <td>The agent must discover the endpoint, construct a request body, and work out what to do next.</td>
      <td>The command expresses the outcome, accepts the context needed to achieve it, and returns a result for the next step.</td>
    </tr>
  </tbody>
</table>

The endpoint-shaped command still has a place as an escape hatch. It is a poor
default when the product already knows the task the agent is trying to finish.

The same standard applies to human UI: a good interface gives its user
orientation, clear states, and a path forward. Since a CLI is an agent's UI, it
deserves the same attention to flow and detail.

## Make the result easy to reason about

People can often read a loosely formatted response and work out what happened,
but agents should not have to rely on that interpretation. A stable result lets
an agent verify the outcome and continue without guessing.

Structured output is not only about JSON. It is an agreement about what an
agent can rely on, reducing ambiguity at the point where it decides what to do
next. The structured result should follow a concrete invocation.

```sh
product project create --name launch-plan --owner team-platform --json
```

```json
{
  "status": "created",
  "project": { "id": "prj_123", "name": "launch-plan" },
  "suggested_next_actions": ["product project show prj_123 --json"]
}
```

That final field is small but important because a command should not end a
workflow with a dead end. It should help an agent find the next reliable move.

## Let the agent rehearse before it changes anything

The most capable agent is not the one that acts fastest, but the one that knows
when to stop and inspect the consequence of an action.

Dry runs make that restraint possible by showing an agent the consequence of a
change before it alters a real system. They create a reviewable boundary
between planning and execution.

```sh
product access grant --project prj_123 --member alex@example.com --role editor \
  --dry-run --json
```

Although dry runs are useful for people too, they are essential for agents
because they make reasoning visible, expose assumptions, and reserve escalation
for decisions that genuinely need a person.

## Keep intent in the execution context

Every tool invocation should have a concise statement of intent in its
execution record when that context is available. This is not a request for an
agent's private reasoning. It is a durable account of the outcome being sought,
which gives a person reviewing the trace enough context to understand why an
action happened.

The product command should remain the same for people and agents. An agent
runtime can take the intent from its assigned task and attach it to every
command automatically. When a person runs the CLI directly, the system should
record a direct human action unless they deliberately provide intent in the
surrounding workflow.

```sh
product access grant --project prj_123 --member alex@example.com --role editor --json
```

Outside the CLI response, the execution system can record the agent's task
context alongside the result.

```json
{
  "actor_type": "agent",
  "intent": "Grant Alex editor access to launch-plan",
  "tool": "product access grant",
  "status": "failed"
}
```

## Treat failures as part of the interface

Most agent experiences fail not because the happy path is missing, but because
a command fails and the interface offers no way back.

An error should explain what happened in a form the agent can act on. "Request
failed" leaves it nowhere to go, whereas an actionable failure gives it a way
to correct itself instead of retrying blindly or handing an opaque problem to a
person.

```sh
product access grant --project prj_123 --member alex@example.com --role editor --json
```

```json
{
  "status": "failed",
  "error": {
    "code": "member_not_found",
    "message": "No member matches alex@example.com.",
    "suggested_next_actions": ["product member list --json"]
  }
}
```

Failures are also product research: they show where the CLI lacks context,
where an instruction is unclear, and where an evaluation should be added. A
feedback mechanism turns an agent's failed attempt into evidence for the next
improvement.

## Agents can provide feedback too

Agent-facing products should give agents a direct way to report when the
interface has failed them. A dedicated feedback tool can capture the context of
an error or a blocked task while it is still available, instead of relying on a
person to reconstruct the problem later.

The human equivalent is a "Report a problem" path inside a product UI. When a
person cannot complete a settings flow, the interface can let them describe the
problem while preserving the relevant state for the team. An agent needs the
same escape hatch, expressed as a tool rather than a button.

Keep the feedback tool simple. The agent already has the task it was attempting
and the observation that blocked it, so the command can accept one message
rather than require it to manufacture a product-specific taxonomy.

The agent needs guidance before it uses the tool. Its instructions should ask
for concise feedback that explains the intended outcome, the observed problem,
and the context needed to continue. That gives the model a standard for useful
feedback while leaving it to describe the situation in its own words.

```sh
product feedback report \
  --message "I could not grant Alex editor access to launch-plan because the member lookup returned member_not_found. Confirm the account email before retrying." \
  --json
```

The product can acknowledge that report in a form the agent can retain in its
own work log.

```json
{
  "status": "recorded",
  "feedback_id": "fbk_123",
  "suggested_next_actions": ["Ask the user to confirm Alex's account email"]
}
```

That feedback closes the loop between use and evaluation. It gives the product
team evidence for improving the CLI, its instructions, and the scenarios used
to test whether agents can complete real work.

## The practical test

When designing a CLI, the practical question is whether an agent with a clear
goal can carry the work safely through to completion, including recovery when
something fails.

When that sequence cannot happen, the interface is unfinished: it may be a
capable API wrapper, but it is not yet a reliable product surface for agents.
Teams that take this seriously will not only give agents more access. They will
give them interfaces that make good outcomes more likely.
