Skip to content

Agent Code Mode

cli-to-py is useful for normal Python applications, but the main design target is agent code that needs to coordinate CLI tools.

The Problem

Traditional tool calling is sequential. An agent asks for one tool call, waits for the result, reasons in text, then asks for another tool call.

That gets clumsy when the task needs:

  • loops over many files or records
  • arithmetic or aggregation
  • branching based on command output
  • retries and filtering
  • many calls to the same CLI
  • coordination across several CLIs

Python is a better control plane for that work.

The Pattern

Use cli-to-py to convert a CLI into a Python object:

from cli_to_py import convert

git = await convert("git")

Then expose selected operations to the agent's code runtime:

async def changed_python_files(base: str = "HEAD~1") -> list[str]:
    paths = await git("diff", name_only=True, _=[base]).lines()
    return [path for path in paths if path.endswith(".py")]

async def last_commit_for_file(path: str) -> str:
    return await git("log", oneline=True, n=1, _=["--", path]).text()

An agent can then write ordinary Python to coordinate those functions:

files = await changed_python_files("HEAD~5")
for path in files:
    print(path, await last_commit_for_file(path))

The result is fewer round trips and less text reasoning for work that is really program control flow.

Interpreter Fit

Interpreters such as Monty are designed for agent-written Python with host-provided external functions. cli-to-py fits as the adapter layer between command-line tools and those host functions.

The split is:

  • cli-to-py converts CLI help and process execution into Python callables.
  • The host application chooses which functions are exposed.
  • The interpreter runs agent-written Python against that controlled function set.

cli-to-py is not a sandbox. Use the interpreter or host application to enforce resource limits, filesystem policy, network policy, and authorization.

Why Validation Matters

Agent-written code can typo a flag or invent an option. Validation lets the host check calls before spawning a subprocess:

errors = git.validate("commit", massage="fix")
assert errors[0].suggestion == "message"

That makes retries cheaper: the agent can repair structured validation errors instead of parsing arbitrary CLI stderr.