Headless CLI Agents
Build agentic systems using Claude Code CLI or the Claude Agent SDK.
CLI Headless Mode
Use -p flag for non-interactive execution:
Basic query
claude -p "Explain this code"
With JSON output for parsing
claude -p "Create a REST API" --output-format json
Streaming JSON for real-time output
claude -p "Build a CLI app" --output-format stream-json
Restrict tools
claude -p "Stage changes" --allowedTools "Bash,Read" --permission-mode acceptEdits
Output Formats
Format Flag Use Case
Text (default) Simple scripts
JSON --output-format json
Programmatic parsing
Stream JSON --output-format stream-json
Real-time streaming
JSON response includes: session_id , total_cost_usd , duration_ms , num_turns , result .
Multi-Turn Sessions
Get session ID
session_id=$(claude -p "Start review" --output-format json | jq -r '.session_id')
Continue conversation
claude -p --resume "$session_id" "Now implement the plan"
Or continue most recent
claude --continue "Add tests"
Key Flags
Flag Purpose
-p, --print
Non-interactive mode
--output-format
text/json/stream-json
--resume, -r
Resume by session ID
--continue, -c
Resume most recent
--allowedTools
Restrict available tools
--disallowedTools
Block specific tools
--append-system-prompt
Add custom instructions
--mcp-config
Load MCP servers from JSON
--verbose
Detailed logging
Python Agent SDK
pip install claude-agent-sdk
Basic Usage
import anyio from claude_agent_sdk import query
async def main(): async for message in query(prompt="What is 2 + 2?"): print(message)
anyio.run(main)
Custom Tools (In-Process MCP)
from claude_agent_sdk import tool, create_sdk_mcp_server, ClaudeSDKClient
@tool("greet", "Greet a user", {"name": str}) async def greet_user(args): return {"content": [{"type": "text", "text": f"Hello, {args['name']}!"}]}
server = create_sdk_mcp_server(name="my-tools", version="1.0.0", tools=[greet_user]) client = ClaudeSDKClient(mcp_servers=[server])
ClaudeSDKClient Options
from claude_agent_sdk import ClaudeSDKClient, ClaudeAgentOptions
options = ClaudeAgentOptions( system_prompt="You are a helpful assistant", working_directory="/path/to/project", allowed_tools=["Bash", "Read", "Write"], permission_mode="acceptEdits" )
client = ClaudeSDKClient(options=options)
Agent Loop Pattern
Agents follow a core feedback loop:
-
Gather Context - Read files, search, web fetch
-
Take Action - Execute tools, generate code
-
Verify Work - Evaluate output, iterate if needed
Subagents
Use subagents for parallel execution with isolated context. Each subagent has its own context window and returns only relevant data to parent.
Best Practices
-
Use JSON output for reliable parsing
-
Check exit codes and stderr for errors
-
Implement timeouts: timeout 300 claude -p "$prompt"
-
Use session management for multi-step workflows
-
Parse costs: echo "$result" | jq -r '.total_cost_usd'
Integration Examples
CI/CD Pipeline
#!/bin/bash
result=$(claude -p "Review PR diff for security issues"
--output-format json
--allowedTools "Read,Grep,WebSearch")
if echo "$result" | jq -e '.result | contains("vulnerability")' > /dev/null; then echo "Security issues found" exit 1 fi
Multi-Step Workflow
Step 1: Plan
plan_result=$(claude -p "Create implementation plan for: $TASK" --output-format json) session_id=$(echo "$plan_result" | jq -r '.session_id')
Step 2: Implement
claude -p --resume "$session_id" "Implement the plan"
Step 3: Test
claude -p --resume "$session_id" "Write tests for the implementation"
Rust CLI Integration
use std::process::Command;
async fn call_claude(prompt: &str) -> Result<String> { let output = Command::new("claude") .args(["-p", prompt, "--output-format", "json"]) .output()?;
let response: serde_json::Value = serde_json::from_slice(&output.stdout)?;
Ok(response["result"].as_str().unwrap_or("").to_string())
}
References
-
Headless Mode Docs
-
Agent SDK Overview
-
Claude Agent SDK Python