# tinyagent **Repository Path**: uchenily/tinyagent ## Basic Information - **Project Name**: tinyagent - **Description**: No description available - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: main - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2026-06-21 - **Last Updated**: 2026-06-21 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # tinyagent > **Modular Bash AI Coding Agent** — A clean, layered architecture for CLI-based LLM agents. Bash-first, with a small Python helper for reliable JSON and stream parsing. [![Bash](https://img.shields.io/badge/bash-4.0%2B-green?logo=gnubash)](https://www.gnu.org/software/bash/) [![Platform](https://img.shields.io/badge/platform-Linux%20%7C%20macOS%20%7C%20WSL-blue)]() [![License](https://img.shields.io/badge/license-Apache%202.0-blue)](./LICENSE) --- ## What is tinyagent? tinyagent is a **modular core** of a pure-bash AI coding agent that works anywhere bash does. It's a self-contained implementation of the agent loop, API communication, tool execution, and terminal UI, all built around a clean **transcript/renderer** architecture. You can use tinyagent as: - A **standalone CLI coding agent** with interactive REPL and oneshot modes - A **reference implementation** of a modular bash agent architecture - A **building block** for your own AI-powered shell tools --- ## Architecture ``` ┌──────────────────────────────────────────┐ │ Logic Layer │ │ api.sh │ tool.sh │ agent_loop.sh │ │ message.sh │ config.sh │ └──────────────┬───────────────────────────┘ │ transcript events ▼ ┌──────────────────────────────────────────┐ │ Transcript Layer │ │ transcript.sh (structured events) │ └──────────────┬───────────────────────────┘ │ render_new() ▼ ┌──────────────────────────────────────────┐ │ Renderer Layer │ │ renderer.sh (ANSI terminal output) │ └──────────────┬───────────────────────────┘ │ ▼ Terminal ``` **Key design principles:** - **One module, one responsibility** — Each `.sh` file does exactly one thing - **Logic and UI are separated** via the transcript pattern — modules emit events, the renderer consumes them - **Logs go to files, UI goes to terminal** — No debug spam in your conversation - **Any Linux tool can be used per module** — `curl` for HTTP, `python3` for JSON handling and SSE parsing where bash would be brittle --- ## Requirements - **bash 4.0+** (associative arrays, `shopt lastpipe`) - **curl** — HTTP client - **python3** — JSON helper and SSE parser That's it. No Node. No pip/npm. Just bash, curl, and python3, which are already present on nearly every Unix-like system this project targets. --- ## Quick Start ```bash # 1. Set your API key export TINYAGENT_API_KEY="sk-..." # 2. Interactive REPL mode ./tinyagent/tinyagent # 3. Single-query (oneshot) mode ./tinyagent/tinyagent --oneshot "explain main.py" # 4. Custom model / API endpoint ./tinyagent/tinyagent --model gpt-4o \ --api-url https://api.openai.com/v1/chat/completions # 5. Safe mode (confirm before destructive operations) ./tinyagent/tinyagent --safe-mode ``` --- ## Directory Structure ``` tinyagent/ ├── tinyagent # Entry point (CLI parsing, module loading, main dispatch) ├── lib/ │ ├── logger.sh # File-only logging (6 levels, buffered, rotation) │ ├── config.sh # Configuration (defaults → env → file) │ ├── json.sh # JSON helpers (Python-backed) │ ├── message.sh # Conversation history (persistence, compression) │ ├── transcript.sh # Structured event layer (12 event types) │ ├── renderer.sh # Terminal rendering (ANSI colors, spinner) │ ├── api.sh # API client (Anthropic + OpenAI protocols) │ ├── tool.sh # Tool framework (registration, dispatch, safe mode) │ ├── input.sh # Custom line editor (readline, history, CJK support) │ ├── agent_loop.sh # REPL loop + single-turn execution + hot-reload │ ├── format.sh # Text formatting utilities │ ├── markdown_renderer.sh # Incremental markdown parsing and rendering │ ├── diff_ui.py # Rich unified diff UI renderer │ ├── sse_parser.py # Server-Sent Events stream parser │ ├── table_renderer.py # Table rendering helper │ └── json_helper.py # Python JSON helper (avoids ARG_MAX issues) ├── tools/ │ ├── read_file.sh # Read files with offset/limit │ ├── write_file.sh # Write new files (won't overwrite existing, with diff output) │ ├── edit_file.sh # Edit files by exact string replacement (with diff output) │ ├── delete_file.sh # Delete files/directories (file deletions include diff output) │ ├── list_files.sh # List directory contents with pattern matching │ ├── grep.sh # Search code (ripgrep preferred, grep fallback) │ ├── bash.sh # Execute arbitrary shell commands │ └── ask_user.sh # Prompt user for input or confirmation ├── logs/ │ ├── tinyagent.log # Application log (all levels) │ └── access.log # API call log └── .data/ └── history.json # Persisted conversation history ``` --- ## Configuration Priority (highest to lowest): **CLI flags → Environment variables → `tinyagent.json` → Defaults** ### Environment Variables | Variable | Default | Description | |---|---|---| | `TINYAGENT_API_KEY` | *(required)* | API key (also checks `ANTHROPIC_API_KEY`, `OPENAI_API_KEY`) | | `TINYAGENT_MODEL` | `deepseek-v4-pro[1m]` | Model name | | `TINYAGENT_API_URL` | DeepSeek endpoint | API endpoint (auto-detects protocol) | | `TINYAGENT_LOG_LEVEL` | `DEBUG` | File log level (TRACE/DEBUG/INFO/WARN/ERROR) | | `TINYAGENT_SAFE_MODE` | `false` | Confirm destructive operations before executing | | `TINYAGENT_MAX_TOKENS` | `32768` | Max tokens per API response | | `TINYAGENT_THINKING_BUDGET` | `16384` | Thinking budget for models that support it | | `TINYAGENT_CONNECT_TIMEOUT` | `15` | HTTP connect timeout (seconds) | | `TINYAGENT_REQUEST_TIMEOUT` | `120` | HTTP request timeout (seconds) | ### CLI Flags | Flag | Description | |---|---| | `--help, -h` | Show usage | | `--version, -v` | Show version | | `--model MODEL` | Set model name | | `--api-url URL` | Set API endpoint | | `--api-key KEY` | Set API key | | `--log-level LEVEL` | Set log level | | `--safe-mode` | Enable safe mode | | `--oneshot "PROMPT"` | Run single query and exit | | `--project-dir DIR` | Set project directory | ### JSON Config File Place a `tinyagent.json` in your project directory to persist settings: ```json { "model": "claude-sonnet-4-20250514", "safe_mode": "true" } ``` --- ## Interactive REPL ### Slash Commands | Command | Description | |---|---| | `/help` | Show available commands | | `/exit`, `/quit`, `/q` | Exit the agent | | `/clear` | Clear conversation history | | `/save` | Save conversation to a timestamped JSON file | | `/status` | Show session stats (turns, tokens, elapsed time) | | `/tools` | List all available tools with descriptions | | `/compact` | Compress older conversation into a summary | | `/model NAME` | Switch model mid-session | | `/log-level L` | Set terminal log verbosity (TRACE/DEBUG/INFO/WARN/ERROR) | | `/debug` | Dump current configuration (API key masked) | ### Session Workflow Each turn follows a tool-use loop (up to 20 iterations): 1. User enters a prompt 2. System prompt + conversation + tools sent to LLM 3. If the LLM responds with text → displayed, turn ends 4. If the LLM calls tools → execute all tools, feed results back, go to step 2 --- ## Module Reference ### `logger.sh` — File-Only Logging Zero terminal output. All logs go to files. - **6 levels**: TRACE, DEBUG, INFO, WARN, ERROR, FATAL - **Buffered writes**: 32 entries per flush for performance - **Daily rotation**: Auto-rotates, keeps 7 days of history - **Crash context**: ERR trap captures stack trace on failure - **Performance timing**: `log_perf_start` / `log_perf_end` for profiling - **Separate access log**: API calls logged to `access.log` ```bash # View logs in real-time tail -f tinyagent/logs/tinyagent.log # Search for errors grep ERROR tinyagent/logs/tinyagent.log # View API call history cat tinyagent/logs/access.log ``` ### `config.sh` — Configuration Management 3-tier config system: defaults → environment variables → JSON file. All configuration stored in an associative array (`CONFIG`), accessible via `config_get` and `config_set`. ### `json.sh` — JSON Helpers Provides safe JSON operations using `python3` as a backing parser. Handles large payloads via temp files to avoid shell `ARG_MAX` limits. ### `message.sh` — Conversation History - Full conversation stored as a JSON array in memory - Auto-persist to `.data/history.json` with dirty tracking - Token estimation (chars ÷ 4) - Context compression: summarizes older messages, keeps recent ones intact - Supports Anthropic-native content blocks (text + tool_use + tool_result) ### `transcript.sh` — Structured Event Layer The decoupling layer between logic and UI. All modules emit events here; only the renderer reads from it. **12 event types:** | Event | Emitted by | Description | |---|---|---| | `log` | All modules | Log message (level + message) | | `text` | `agent_loop.sh` | Assistant text response | | `thinking` | `agent_loop.sh` | Model thinking/reasoning | | `tool_call` | `agent_loop.sh` | Tool invocation (name + summary) | | `tool_result` | `agent_loop.sh` | Tool result (name + status + detail) | | `diff` | `agent_loop.sh` | Diff output from file-mutating tools | | `access` | `api.sh` | API request (method + url + status + latency) | | `error` | Any | Error message | | `status` | `agent_loop.sh` | Status updates (e.g. "Thinking...") | | `usage` | `agent_loop.sh` | Token usage per iteration | | `stream_text_delta` | `agent_loop.sh` | Streaming text chunk | | `stream_thinking_delta` | `agent_loop.sh` | Streaming thinking chunk | ### `renderer.sh` — Terminal Rendering The *only* module that writes to stdout/stderr for UI purposes. - Reads events from transcript incrementally (`render_new()`) - Filters log events by configured level - ANSI color-coded output (cyan = assistant, yellow = tool calls, red = errors) - Spinner animation for "Thinking..." state - Tool call braille spinner animation during execution - Per-tool detail display for commands, file read parameters, and result counts/byte sizes - Rich diff UI for file creation, edits, and file deletions ### `api.sh` — API Client - **Dual protocol support**: Anthropic Messages API and OpenAI Chat Completions - **Auto-detection**: Determines protocol from URL path - **Protocol conversion**: Translates between Anthropic and OpenAI message formats - **Retry with backoff**: Up to 3 retries for 5xx/429 errors - **Request body via temp files**: Avoids `ARG_MAX` on large message histories - **Thinking budget**: Configurable for models that support extended thinking ### `tool.sh` — Tool Framework - **Registration**: Tools auto-register when `tools/*.sh` files are sourced - **Dispatch**: `dispatch_tool ` executes any registered tool - **Safe mode**: Intercepts destructive tools (`write_file`, `edit_file`, `delete_file`, `bash`) for user confirmation - **Performance tracking**: Each tool call is timed ### `agent_loop.sh` — Agent Loop - **REPL**: Read → Evaluate → Print loop for interactive use - **Turn execution**: Manages the tool-use loop (API call → parse → execute tools → repeat up to 20 iterations) - **Session stats**: Tracks turns, token usage (input/output), elapsed time - **System prompt**: Auto-generates from registered tools - **Hot-reload**: Auto-detects changed `lib/` and `tools/` scripts, re-sources without restart - **Tool call animation**: Braille spinner during tool execution with elapsed time display --- ## Adding a Custom Tool Create a new file in `tools/`, e.g. `tools/fetch_url.sh`: ```bash #!/usr/bin/env bash # Tool: fetch_url — Fetch content from a URL tool_fetch_url() { local input="$1" local url url=$(json_get "$input" "url" | python3 -c "import json,sys; print(json.load(sys.stdin) or '')" 2>/dev/null) local content content=$(curl -sL --max-time 10 "$url" 2>&1) || { python3 -c 'import json,sys; print(json.dumps({"error": "Failed to fetch: " + sys.argv[1]}, ensure_ascii=False))' "$url" return 1 } python3 -c ' import json, sys content = sys.argv[2] print(json.dumps({"url": sys.argv[1], "content": content, "length": len(content)}, ensure_ascii=False)) ' "$url" "$content" } register_tool "fetch_url" "tool_fetch_url" \ "Fetch content from a URL via HTTP GET." \ '{ "type": "object", "properties": { "url": {"type": "string", "description": "URL to fetch content from"} }, "required": ["url"] }' ``` The tool will be automatically loaded on next startup and available to the LLM. --- ## API Protocol Details tinyagent supports both Anthropic and OpenAI chat APIs, with automatic protocol detection: | URL contains | Protocol | |---|---| | `/anthropic` or `/messages` | Anthropic Messages | | `/chat/completions` or `/openai` | OpenAI Chat Completions | | Other | Defaults to Anthropic | All internal message handling uses **Anthropic format** (content as array of typed blocks). OpenAI responses are converted on-the-fly via `_convert_openai_to_anthropic`. --- ## Troubleshooting | Issue | Solution | |---|---| | `"No API key configured"` | Set `TINYAGENT_API_KEY` environment variable | | `curl: (28) Connection timed out` | Check `TINYAGENT_API_URL` or network connectivity | | `Argument list too long` | Should not happen — large payloads use temp files. Report if it does. | | Empty/frozen response | Check `tinyagent/logs/access.log` for HTTP status codes | --- ## License Apache 2.0 — see [LICENSE](./LICENSE).