Terminal AI Agents Workshop

Table of Contents

Overview

Terminal AI agents represent a paradigm shift in how developers interact with command-line tools. Rather than memorizing flags and piping outputs, developers describe intent and let agents orchestrate tool execution. This research explores patterns for building effective terminal-based AI agents.

Repository: terminal-ai-agents-workshop

Tool Use Patterns

The Tool Abstraction

Terminal agents succeed when tools are well-defined with clear contracts:

  • Input schema: JSON Schema defining expected parameters
  • Output format: Structured responses agents can parse
  • Error handling: Predictable failure modes with actionable messages
  • Idempotency: Safe to retry without side effects where possible

Common Tool Categories

Category Examples Agent Considerations
File System read, write, glob, grep Path validation, permission aware
Process bash, background tasks Timeout handling, output limits
Version Control git status, diff, commit State awareness, conflict detection
Network fetch, API calls Rate limiting, caching
Analysis LSP, linting, testing Context aggregation

Composition Patterns

Tools compose through several patterns:

  1. Sequential: Output of one tool feeds input of next
  2. Parallel: Independent tools run concurrently
  3. Conditional: Tool selection based on prior results
  4. Iterative: Repeated tool invocation until condition met

Model Context Protocol (MCP)

MCP standardizes how AI applications connect to external tools and data sources.

Core Concepts

  • Servers: Expose tools, resources, and prompts
  • Clients: AI applications that connect to servers
  • Transports: Communication mechanisms (stdio, HTTP+SSE)

Server Implementation Pattern

from mcp.server import Server
from mcp.types import Tool, TextContent

server = Server("terminal-tools")

@server.tool()
async def run_command(command: str, timeout: int = 30) -> str:
    """Execute a shell command with timeout."""
    result = await asyncio.create_subprocess_shell(
        command,
        stdout=asyncio.subprocess.PIPE,
        stderr=asyncio.subprocess.PIPE
    )
    stdout, stderr = await asyncio.wait_for(
        result.communicate(),
        timeout=timeout
    )
    return stdout.decode() if result.returncode == 0 else stderr.decode()

Tool Definition Best Practices

{
  "name": "file_search",
  "description": "Search for files matching a glob pattern",
  "inputSchema": {
    "type": "object",
    "properties": {
      "pattern": {
        "type": "string",
        "description": "Glob pattern (e.g., '**/*.py')"
      },
      "path": {
        "type": "string",
        "description": "Root directory to search from"
      }
    },
    "required": ["pattern"]
  }
}

Agent Architecture

Planning vs Execution

Effective terminal agents separate planning from execution:

  1. Understand: Parse user intent, gather context
  2. Plan: Determine tool sequence, identify dependencies
  3. Execute: Run tools, handle errors, adapt
  4. Synthesize: Combine results, present to user

Context Management

Terminal agents manage several context types:

  • Conversation: Prior messages and decisions
  • Codebase: File structure, dependencies, patterns
  • Environment: Working directory, available tools
  • Task: Current objective and progress

Error Recovery Strategies

Error Type Recovery Strategy
Tool not found Search for alternatives, ask user
Permission denied Explain limitation, suggest workaround
Timeout Retry with longer timeout or decompose
Parse failure Request clarification, show raw output
Rate limit Backoff, queue, or switch providers

Implementation Examples

Claude Code

Anthropic's Claude Code demonstrates production-grade terminal agent patterns:

  • Persistent shell sessions with state management
  • Parallel tool execution with dependency tracking
  • Summarization for context window management
  • Hook system for customization

Custom Agents with Agent SDK

from claude_code_sdk import Agent, Tool

class TerminalAgent(Agent):
    def __init__(self):
        self.tools = [
            Tool("bash", self.run_bash),
            Tool("read_file", self.read_file),
            Tool("write_file", self.write_file),
        ]

    async def run_bash(self, command: str) -> str:
        # Execute with sandboxing
        pass

    async def process(self, user_input: str):
        # Planning and execution loop
        pass

Security Considerations

Terminal agents require careful security design:

  • Sandboxing: Limit file system and network access
  • Command validation: Prevent injection attacks
  • Secrets management: Never expose credentials in logs
  • Audit logging: Track all tool invocations
  • Rate limiting: Prevent runaway execution

Related Research

Author: Jason Walsh

j@wal.sh

Last Updated: 2025-12-22 21:24:59

build: 2025-12-23 09:12 | sha: e32f33e