PromptABCD
FeaturesLearnHow it worksUse casesFAQGuideBlogContext Blocks
Sign inGet started free
Sign inSign up
PromptABCD

A calm home for your best AI prompts. Save them once, find them in seconds, reuse them forever.

Product

  • Features
  • Chrome Extension
  • Free Courses
  • How it works
  • Use cases
  • Blog
  • Context Blocks
  • Export Anywhere
  • FAQ

Resources

  • User guide
  • Learn prompting
  • Sign in
  • Get started free

© 2026 PromptABCD. All rights reserved.

Privacy PolicyTerms and Conditions
Home/Blog/AI Harness/Building a CLI Around Your Agent Harness
AI Harness

Building a CLI Around Your Agent Harness

A useful agent nobody could run became one everyone uses — the fix was a real agent harness cli with stdin, clean stdout, and meaningful exit codes.

September 10, 2026·8 min read
ShareShare
⚡Featured Prompt— copy and use right now
def main():
    prompt = os.environ.get("PROMPT", "")
    result = agent.run(prompt)
    print(result.text)

if __name__ == "__main__":
    main()

Picture this: you're a developer on a team that built a genuinely useful agent, but every time someone wants to run it, they open a Python file, edit some variables at the top, and run the script — copying output out of the terminal by hand. It works, technically. It's also why nobody outside the original two engineers ever uses it. The thing standing between a useful agent and an adopted one is often just a good agent harness cli — a proper command-line interface that makes the agent feel like a real tool. This is the story of a team that built one and what they learned about the details that matter.

A CLI seems like a thin wrapper, and a bad one is. A good one is the difference between "run this script the way I showed you" and "it's on everyone's path and it composes with their other tools." The gap between those is entirely in the details this case study is about.

The Problem This Team Faced

Their agent was strong and its ergonomics were terrible. Running it meant editing source, there was no way to pass input except by changing code, output was an undifferentiated blob printed at the end, and there was no way to tell success from failure except by reading the text. It couldn't be scripted, couldn't be piped, couldn't be scheduled — it could only be babysat by someone who knew the code.

The result was predictable: adoption stalled at the people who wrote it. Every potential user bounced off the friction, and the agent's real capability went unused because the interface to it was a source file.

⚠️ Common mistake: Treating the CLI as an afterthought wrapper around a

main()
you already wrote. A CLI is a product surface — it's how most people will ever experience the agent — and an interface that requires reading source or editing variables guarantees the agent stays a two-person tool no matter how good the underlying work is.

The Wrong Approach

Their first pass at "adding a CLI" was to read a couple of environment variables and print the result. It removed the source-editing but little else.

hljs python
[object Object], ,[object Object],():
    prompt = os.environ.get(,[object Object],, ,[object Object],)
    result = agent.run(prompt)
    ,[object Object],(result.text)

,[object Object], __name__ == ,[object Object],:
    main()

What this does: Pulls the prompt from an environment variable and prints the final text. It's marginally better than editing source, but it has no real argument parsing, no streaming (the user still stares at nothing until the end), no way to signal success or failure to a calling script, and no help text. It's a script wearing a CLI costume.

The deeper issue was that it composed with nothing. A real command-line tool participates in the shell ecosystem — it reads from stdin, writes to stdout, sets an exit code, and behaves differently when piped than when run interactively. This version did none of that, so it couldn't be dropped into a pipeline or a cron job or another script.

The Correct Approach

The rebuilt agent harness cli was designed as a first-class command-line citizen: real argument parsing, streaming output to a terminal, plain output when piped, and meaningful exit codes.

hljs python
[object Object], argparse, sys

,[object Object], ,[object Object],():
    p = argparse.ArgumentParser(description=,[object Object],)
    p.add_argument(,[object Object],, nargs=,[object Object],, ,[object Object],=,[object Object],)
    p.add_argument(,[object Object],, action=,[object Object],, ,[object Object],=,[object Object],)
    p.add_argument(,[object Object],, action=,[object Object],, ,[object Object],=,[object Object],)
    args = p.parse_args()

    prompt = args.prompt ,[object Object], sys.stdin.read()      ,[object Object],
    ,[object Object],:
        run = agent.run(prompt, dry_run=args.dry_run,
                        stream=sys.stdout.isatty())  ,[object Object],
        emit(run, as_json=args.json)
        sys.exit(,[object Object], ,[object Object], run.succeeded ,[object Object], ,[object Object],)          ,[object Object],
    ,[object Object], HarnessError ,[object Object], e:
        ,[object Object],(,[object Object],, file=sys.stderr)
        sys.exit(exit_code_for(e))

What this does: Parses real arguments including a positional prompt (or reads it from stdin), supports

--dry-run
and
--json
, streams output only when connected to a terminal, and exits with a code that reflects the outcome. Now the agent reads piped input, writes clean output a script can consume, and tells its caller what happened via the exit code. It behaves like
grep
or
curl
— a tool, not a script.

Results and What Changed

Adoption moved. Once the agent read stdin, wrote clean stdout, and set exit codes, people started composing it into their own workflows — piping data in, capturing output, wiring it into scripts and scheduled jobs. The capability that had been trapped behind a source file became something teammates reached for daily, because it now fit the tools they already used.

The exit codes turned out to matter more than expected. Because a failed run exited non-zero and different failure classes had different codes, people could script around the agent reliably — retry on a transient failure code, alert on a hard one, branch on success. An agent that only ever printed text couldn't support any of that; one that spoke the shell's language of exit codes slotted into automation naturally. The team's takeaway was that the boring plumbing — stdin, stdout, stderr, exit codes — did more for adoption than any feature they could have added to the agent itself.

⚡ Pro tip: Detect whether you're attached to a terminal with

sys.stdout.isatty()
and change behavior accordingly. Stream a rich, live view when a human is watching; emit plain, parseable output when piped into another program. The same command serving both audiences well is what separates a real CLI from a script that only works when a person is staring at it.

How to Apply This to Your Situation

Start with the shell contract, not the flags. A good agent harness cli reads input from arguments or stdin, writes results to stdout and errors to stderr, and sets an exit code that reflects the outcome. Get those three right and the tool composes with everything else in the shell; get them wrong and no amount of flags will make it feel native.

Then add the flags that map to your harness features you already built:

--dry-run
to plan without executing,
--json
for machine-readable output,
--replay
to run a recorded tape,
--verbose
for the operator-level detail. Each of these is exposing an existing capability through the interface, which is cheap and high-value.

Finally, invest in help text and errors.

--help
that actually explains usage, and error messages that say what went wrong and what to do, are what let someone use the tool without reading its source — which was the whole problem you set out to solve.

There's a subtle sequencing lesson here too: the team got adoption not by making the agent smarter but by making it reachable. The underlying capability hadn't changed between the version nobody used and the version everyone used — only the interface had. It's worth remembering whenever adoption of a good agent stalls, because the instinct is usually to improve the model or the prompt when the actual barrier is that people can't easily run the thing.

⚡ Pro tip: Ship a single-line install and a single-line first run in your README, and test that exact pair on a fresh machine. The distance between "I heard about this agent" and "I ran it once" is where most potential users are lost, and every extra setup step in that gap sheds a fraction of them. A tool someone can try in two commands gets tried; one that needs a page of setup gets bookmarked and forgotten.

Beyond the Basics: Config, Interactivity, and Errors

Once the shell contract is solid, three refinements turn a functional agent harness cli into one people genuinely enjoy using.

Configuration layering matters more than it sounds. Users want to set defaults once and override them per invocation, so support a config file, environment variables, and flags — with flags winning over env vars, which win over the config file. This lets someone set their common options in a file and override just one for a particular run, instead of retyping everything every time.

hljs python
[object Object], ,[object Object],(,[object Object],):
    ,[object Object], flag_val ,[object Object], ,[object Object], ,[object Object],:            ,[object Object],
        ,[object Object], flag_val
    ,[object Object], env_key ,[object Object], os.environ:           ,[object Object],
        ,[object Object], os.environ[env_key]
    ,[object Object], config.get(env_key, default) ,[object Object],

What this does: Resolves each setting by precedence — an explicit flag beats an environment variable beats the config file beats the built-in default. Users get the convenience of persistent defaults and the flexibility of per-run overrides from the same setting, which is the behavior every well-loved CLI tool shares. Predictable precedence is what makes configuration feel obvious instead of surprising.

An interactive mode is worth adding for agents people converse with. When run with no piped input and attached to a terminal, drop into a REPL-style loop where the user can send follow-up messages and watch the agent respond, rather than exiting after one turn. The same binary then serves both one-shot scripting and interactive exploration.

⚡ Pro tip: Write errors to stderr and results to stdout, always, and never mix them. A user piping your agent's output into another program wants only the result on stdout; if error messages and progress chatter leak into stdout, they corrupt the piped data. Keeping the two streams clean is what lets

your-agent "..." | jq
actually work — and it's the detail that most separates a real tool from a script that prints everything to one place.

Next Steps

A CLI that sets meaningful exit codes depends on the harness having a meaningful classification of failures underneath it — you can't map errors to exit codes if all errors look the same. That error taxonomy is the natural next piece to build, and it makes the CLI's exit codes trustworthy — a distinct code per failure class is only possible when the harness already knows how to tell those classes apart.

Keep your CLI's argument conventions and exit-code map versioned alongside your prompts in a library like PromptABCD, so every agent you ship presents the same familiar interface — and someone who's used one of your agents already knows how to drive the next.

ai-harnessclideveloper-experienceexit-codesstdintooling

Continue Reading

Managing Prompt Templates Across a Harness Codebase
AI Harness

Managing Prompt Templates Across a Harness Codebase

Four divergent copies of one prompt caused a two-day bug. Harness prompt templates management makes prompts versioned, tested, single-source artifacts instead of scattered strings.

September 10, 2026·8 min read
How to Open-Source Your Agent Harness
AI Harness

How to Open-Source Your Agent Harness

An agent harness isn't an ordinary library — it's security-sensitive infra tangled with your secrets. Release an open source agent harness without leaking a key or shipping unusable code.

September 10, 2026·8 min read
Error Taxonomy: Classifying Harness Failures
AI Harness

Error Taxonomy: Classifying Harness Failures

When every failure looks the same, you can't retry, route, or alert correctly. Agent harness error classification gives failures types that drive real behavior.

September 10, 2026·8 min read

Save the prompts from this post

PromptABCD is a free prompt manager. Paste, organize, and reuse your best AI prompts — no more hunting through chat history.

Start free →
← PreviousHandling Streaming Tool Calls in the HarnessNext →Error Taxonomy: Classifying Harness Failures
Share this post:
ShareShare