DEV Community

vishalmysore
vishalmysore

Posted on

CLI Based AI Agent : Tool Calling with CLI

A Technical Deep Dive into CLI Chaining, Pipelines, and Workflow Patterns

πŸ“‹ Stop Over-Engineering Your AI Agents

The industry is rushing to wrap every simple tool in a heavyweight service protocol, a sidecar container, or a JSON-RPC handshake. Meanwhile, the most performant orchestration layer for local workflows sits ignored: direct process execution.

Code for this article is here https://github.com/vishalmysore/cli-vs-mcp

This article explores CLI Orchestration as a complement to distributed protocols like MCP. Not as a replacementβ€”as a boundary-aware decision. Use CLI for local, stateless operations. Use protocols for remote, stateful services.

When CLI Makes Sense:

  • All tools run on the same machine
  • Stateless data transformations
  • Latency-sensitive workflows (<50ms)
  • Existing command-line tools need integration

When It Doesn't:

  • Tools span multiple machines
  • Stateful connections (databases, long-lived sessions)
  • Dynamic service discovery required
  • Network authentication necessary

The problem isn't MCP. It's protocol over-applicationβ€”wrapping trivial file operations in HTTP calls because that's what we know.


🎯 Motivation: The Tool Orchestration Spectrum

The Three Paradigms

Approach Best For Trade-offs
Direct CLI Execution Single-purpose tasks, legacy integration Limited composability
CLI Chaining & Pipelines Multi-stage data workflows, batch processing Requires careful output formatting
Service Protocols (MCP/REST) Distributed systems, long-running services Network overhead, complexity

This project explores the middle groundβ€”CLI composition patternsβ€”demonstrating that significant architectural sophistication can be achieved without crossing the network boundary.


πŸ—οΈ Architecture Overview

System Components

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                  AI AGENT (Tools4AI)                        β”‚
β”‚  β€’ Intent Recognition  β€’ Workflow Orchestration             β”‚
β”‚  β€’ Error Handling     β€’ Parallel Execution                  β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
             β”‚
             β”œβ”€β”€β”€ [Data Source CLIs] ─────────────┐
             β”‚    β€’ fetch_customers.cmd            β”‚
             β”‚    β€’ fetch_transactions.cmd         β”‚
             β”‚    β€’ fetch_metrics.cmd              β”‚
             β”‚                                     β”‚
             β”œβ”€β”€β”€ [Processor CLIs] ────────────────
             β”‚    β€’ filter_by.cmd (filtering)     β”‚
             β”‚    β€’ transform_data.cmd (enrich)   β”‚
             β”‚                                     β”‚
             β”œβ”€β”€β”€ [Aggregator CLIs] ───────────────
             β”‚    β€’ count_by.cmd (grouping)       β”‚
             β”‚    β€’ calculate_stats.cmd (stats)   β”‚
             β”‚                                     β”‚
             └─── [Workflow CLIs] β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                  β€’ workflow_customer_analysis
                  β€’ workflow_transaction_analytics
Enter fullscreen mode Exit fullscreen mode

Design Principles

  1. Single Responsibility: Each CLI does one thing exceptionally well
  2. Structured Output: Consistent data formats (pipe-delimited) for chainability
  3. Composition Over Monoliths: Complex workflows emerge from simple building blocks
  4. Fail Fast: Clear error messages, non-zero exit codes for failures
  5. Observability: Each stage logs its operations for debugging

πŸ”— Pattern: Sequential Pipeline

Concept

Chain CLIs by connecting stdout to stdin. Data flows through transformation stages without intermediate network calls.

Example Workflow:

fetch_customers β†’ filter(TIER=GOLD) β†’ count_by(TIER)
Enter fullscreen mode Exit fullscreen mode

Linux/macOS:

fetch_customers | filter_by TIER EQUALS GOLD | count_by TIER
Enter fullscreen mode Exit fullscreen mode

Windows (Tools4AI ProcessBuilder):

ProcessBuilder stage1 = new ProcessBuilder("fetch_customers");
String data = captureOutput(stage1.start());

ProcessBuilder stage2 = new ProcessBuilder("filter_by", "TIER", "EQUALS", "GOLD");
stage2.redirectInput(Redirect.from(createTempFile(data)));
String filtered = captureOutput(stage2.start());
Enter fullscreen mode Exit fullscreen mode

Reality Check: This uses temp files, not true piping. On Windows, batch scripts don't support stdin redirection cleanly. It works, but it's not elegant.

When This Makes Sense

Good fit:

  • Local data transformations (parsing logs, filtering CSVs)
  • Existing CLI tools you can't modify
  • Latency matters (no network round-trip)

Bad fit:

  • Need distributed execution
  • Stateful operations (database connections)
  • Requires sophisticated error recovery

🌊 Pattern: Conditional Workflows

Concept

Dynamic workflow branching based on CLI output analysisβ€”implementing decision trees at the orchestration layer.

Implementation: DevOps Log Monitoring

// Stage 1: Fetch recent application logs
ProcessBuilder logFetch = new ProcessBuilder("fetch_logs", "--last=1h");
String logs = captureOutput(logFetch.start());

// Stage 2: Extract error patterns
ProcessBuilder errorExtract = new ProcessBuilder("extract_errors");
errorExtract.redirectInput(Redirect.from(createTempFile(logs)));
String errors = captureOutput(errorExtract.start());

// Stage 3: Count critical vs warning errors
int criticalCount = countOccurrences(errors, "CRITICAL");
int warningCount = countOccurrences(errors, "WARNING");

// Stage 4: Branch on severity thresholds
if (criticalCount > 10 || warningCount > 100) {
    // Alert path: Page on-call engineer
    executeCli("alert_oncall", "--severity=HIGH");

    // Deep dive: Root cause analysis
    ProcessBuilder analysis = new ProcessBuilder("analyze_error_patterns");
    analysis.redirectInput(Redirect.from(createTempFile(errors)));
    String rootCause = captureOutput(analysis.start());

    // Escalate with context
    // WARNING: In production, sanitize rootCause before passing to CLI
    // (shell injection risk). Use temp files or proper escaping.
    executeCli("create_incident", "--details=" + rootCause);
} else {
    // Normal path: Generate routine report
    executeCli("generate_health_report", "--status=healthy");
}
Enter fullscreen mode Exit fullscreen mode

Key Insight

The orchestrator acts as a decision engineβ€”analyzing intermediate outputs and routing execution flow without LLM intervention for every branch. This keeps latency low while preserving intelligent behavior.


�️ The Layered Intelligence Model: CLI + MCP Hybrid

The Nuanced Position

The goal isn't to kill MCPβ€”it's to use the right tool for the right boundary.

Most agent architectures suffer from protocol over-application: wrapping trivial file operations in HTTP calls, or forcing stateless transforms through stateful service layers. This creates artificial bottlenecks.

Proposed Architecture: Boundary-Aware Orchestration

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚  AI AGENT (Tools4AI LLM Orchestrator)             β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚                                                     β”‚
β”‚  πŸ”Ή THE CORE (CLI Layer)                           β”‚
β”‚     β€’ High-speed stateless transformations         β”‚
β”‚     β€’ Local file I/O and system calls              β”‚
β”‚     β€’ Data parsing, filtering, aggregation         β”‚
β”‚     β€’ Process orchestration (parallel execution)   β”‚
β”‚     ⚑ Latency: 1-50ms per operation               β”‚
β”‚                                                     β”‚
β”‚  πŸ”Έ THE EDGE (MCP/Protocol Layer)                  β”‚
β”‚     β€’ Stateful database connections (pooling)      β”‚
β”‚     β€’ Authenticated third-party APIs (OAuth)       β”‚
β”‚     β€’ Long-running distributed services            β”‚
β”‚     β€’ WebSocket streams and pub/sub                β”‚
β”‚     ⚑ Latency: 50-500ms per operation             β”‚
β”‚                                                     β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
Enter fullscreen mode Exit fullscreen mode

Decision Framework

If it runs on the same machine and needs no persistent state, CLI. Otherwise, MCP.

Real-World Hybrid Workflow

Example: Log Analysis Agent

1. CLI: fetch_logs β†’ extract_errors (local, <10ms)
2. MCP: query_database β†’ check_known_issues (remote, 150ms)
3. CLI: generate_report β†’ write_to_disk (local, <5ms)
Enter fullscreen mode Exit fullscreen mode

Why This Works:

  • Heavy lifting stays local: Parsing 100MB of logs via CLI avoids network transfer
  • Stateful queries stay remote: Database connection pooling requires long-lived MCP server
  • LLM context preserved: Only final analysis sent to LLM, not raw logs

The Strategic Insight

By offloading the "fast" operations to CLI chains, you:

  1. Preserve LLM context window (less data serialization)
  2. Reduce failure surface area (fewer network hops)
  3. Achieve lower tail latencies (no protocol handshake tax)

MCP isn't the enemyβ€”protocol over-application is.


�️ Implementation Example

Project Structure

cli-vs-mcp/
β”œβ”€β”€ pom.xml                          # Maven configuration
β”œβ”€β”€ cli/
β”‚   β”œβ”€β”€ datasource/
β”‚   β”‚   β”œβ”€β”€ fetch_customers          # Customer data source
β”‚   β”‚   β”œβ”€β”€ fetch_transactions       # Transaction data source
β”‚   β”‚   └── fetch_metrics            # Metrics data source
β”‚   β”œβ”€β”€ processors/
β”‚   β”‚   β”œβ”€β”€ filter_by                # Generic filter processor
β”‚   β”‚   └── transform_data           # Data enrichment processor
β”‚   β”œβ”€β”€ aggregators/
β”‚   β”‚   β”œβ”€β”€ count_by                 # Counting aggregator
β”‚   β”‚   └── calculate_stats          # Statistical aggregator
β”‚   └── workflows/
β”‚       β”œβ”€β”€ workflow_customer_analysis
β”‚       └── workflow_transaction_analytics
β”œβ”€β”€ src/main/
β”‚   β”œβ”€β”€ java/.../AdvancedCliOrchestrator.java
β”‚   └── resources/
β”‚       β”œβ”€β”€ shell_actions.yaml       # CLI action registry
β”‚       β”œβ”€β”€ skills.json              # LLM skill definitions
β”‚       └── tools4ai.properties      # Framework configuration
Enter fullscreen mode Exit fullscreen mode

Core Technologies

Framework: Tools4AI 1.1.9.9

  • Provides LLM-driven CLI selection
  • Manages process lifecycle
  • Handles parameter extraction

Language: Java 18 (orchestrator), Batch scripts (CLIs)

LLM Integration: OpenAI GPT-4 (configurable)

Execution Model: Process-per-CLI with output capture

Sample Code: Executing CLI Chain

// Execute a 3-stage pipeline (cross-platform)
ProcessBuilder stage1 = new ProcessBuilder("fetch_customers");
Process p1 = stage1.start();
String data = captureOutput(p1);

// Stage 2: Filter
ProcessBuilder stage2 = new ProcessBuilder("filter_by", "TIER", "EQUALS", "GOLD");
stage2.redirectInput(ProcessBuilder.Redirect.from(createTempFile(data)));
Process p2 = stage2.start();
String filtered = captureOutput(p2);

// Stage 3: Aggregate
ProcessBuilder stage3 = new ProcessBuilder("count_by", "TIER");
stage3.redirectInput(ProcessBuilder.Redirect.from(createTempFile(filtered)));
Process p3 = stage3.start();
String result = captureOutput(p3);

System.out.println(result);
Enter fullscreen mode Exit fullscreen mode

πŸ§ͺ Running the Demo

Prerequisites

# Java 18+
java -version

# Maven 3.8+
mvn -version

# OpenAI API Key (or use Ollama for local LLM)
export OPENAI_API_KEY="your-key-here"
Enter fullscreen mode Exit fullscreen mode

Build & Run

# Clone and navigate
cd cli-vs-mcp

# Build project
mvn clean compile

# Run agent (interactive mode)
mvn exec:java

# Or run demo showcase
cmd /c cli\demo_showcase.cmd
Enter fullscreen mode Exit fullscreen mode

οΏ½ Summary

CLI orchestration isn't a replacement for distributed protocols. It's a boundary-aware choice.

Use it for: Local, stateless operations where latency matters and existing tools work.

Don't use it for: Distributed systems, stateful services, or when you're forcing it.

The fastest code is often the simplest code. But only when it actually solves your problem.


🎯 The Question

Before you reach for a service protocol, ask:

  • Does this need to run on a different machine?
  • Does it require stateful connections?
  • Is the overhead justified?

If "no" to all three, consider CLI.


Built with Tools4AI by Vishal Mysore

March 2026


Top comments (0)