- Posted on
- • Artificial Intelligence
Learning AI Agents
- Author
-
-
- User
- linuxbash
- Posts by this author
- Posts by this author
-
Learning AI Agents from the Linux Shell: Build One You Can Use Today
If you live in Bash, you already think in pipelines, composability, and automation. Imagine adding a tireless teammate who can reason about your goals, propose commands, and help you research, triage logs, and script boilerplate—without leaving your terminal. That’s the promise of AI agents from a Linux perspective: reliable, auditable task automation driven by large language models (LLMs).
This post explains what AI agents are, why they map naturally to the Unix philosophy, and how to build a tiny, practical agent you can use from Bash. You’ll install only a couple of small tools, pick a model backend (local with Ollama or cloud with OpenAI), and try a “review-before-run” agent that suggests a single shell command for a given goal. Then we’ll suggest next steps to grow your agent into something bigger.
Why AI agents are worth your time (especially in Bash)
Agents look like the shell: loop through “observe → think → act → reflect,” just like you iteratively run commands and refine.
They compose tools: grep, curl, jq, git, find—your toolbox becomes the agent’s tools.
They’re auditable: logs of inputs/outputs keep automation explainable and reversible.
They can run locally: with a local model backend, you can keep data on your machine and work offline.
They’re incremental: start with a human-in-the-loop “suggest a command” agent, then add tools, memory, and planning when you’re ready.
Step 1 — Install prerequisites
We’ll use standard CLI tools. Install curl and jq (and git if you want to log to a repo). Choose the commands for your distro.
Debian/Ubuntu (apt):
sudo apt update
sudo apt install -y curl jq git
Fedora (dnf):
sudo dnf install -y curl jq git
openSUSE (zypper):
sudo zypper refresh
sudo zypper install -y curl jq git
Note:
timeout(used later) is in coreutils and usually preinstalled.If any commands are missing, install coreutils as well:
- apt:
sudo apt install -y coreutils - dnf:
sudo dnf install -y coreutils - zypper:
sudo zypper install -y coreutils
- apt:
Step 2 — Choose a model backend
You have two easy paths. Pick one (or configure both and switch via environment variables).
Option A: Local (Ollama)
- Install:
curl -fsSL https://ollama.com/install.sh | sh
- Pull a small, capable model:
ollama pull llama3.1
- Configure your shell to use it:
export OLLAMA_MODEL=llama3.1
Option B: Cloud (OpenAI)
- Set your API key:
export OPENAI_API_KEY="sk-..."
- Optionally set model (default below is gpt-4o-mini for economy/performance balance):
export OPENAI_MODEL="gpt-4o-mini"
The tiny agent below automatically chooses Ollama if OLLAMA_MODEL is set, otherwise falls back to OpenAI if OPENAI_API_KEY is present.
Step 3 — Build a 60-line “suggest-and-run” Bash agent
This agent:
Takes a natural-language goal.
Feeds your current directory context to the model.
Asks for a single safe command in JSON.
Shows you the proposal and asks for confirmation before running.
Logs everything to
~/.ai-agent/logs/.
Save as ai_cmd.sh and make it executable.
#!/usr/bin/env bash
set -euo pipefail
LOG_DIR="${HOME}/.ai-agent/logs"
mkdir -p "$LOG_DIR"
usage() {
echo "Usage: $0 \"your goal in plain English\""
echo "Backend: set OLLAMA_MODEL for local (ollama) or OPENAI_API_KEY for OpenAI."
exit 1
}
command -v jq >/dev/null 2>&1 || { echo "jq is required. Install it first."; exit 1; }
command -v curl >/dev/null 2>&1 || { echo "curl is required. Install it first."; exit 1; }
if [[ $# -lt 1 ]]; then usage; fi
GOAL="$*"
# Detect backend
BACKEND=""
if [[ -n "${OLLAMA_MODEL:-}" ]]; then
command -v ollama >/dev/null 2>&1 || { echo "ollama not found. Install or unset OLLAMA_MODEL."; exit 1; }
BACKEND="ollama"
elif [[ -n "${OPENAI_API_KEY:-}" ]]; then
BACKEND="openai"
else
echo "No backend configured. Set OLLAMA_MODEL or OPENAI_API_KEY."
exit 1
fi
PWD_INFO="$(pwd)"
LS_INFO="$(ls -1 2>/dev/null | head -n 200)"
DATE_STR="$(date -Is)"
RUN_ID="${DATE_STR//:/-}"
SYSTEM_RULES=$'You are a Linux CLI assistant. Based on a GOAL and context, propose ONE safe shell command.\n\
Rules:\n\
- Prefer read-only commands unless the GOAL explicitly asks to modify files.\n\
- Do not use sudo unless the GOAL explicitly says so.\n\
- Prefer portable POSIX tools available on most distros.\n\
- Output ONLY a JSON object inside a fenced code block like:\n\
```json\n\
{\"command\": \"...\", \"explanation\": \"...\"}\n\
```\n\
No extra prose outside the JSON block.'
USER_PROMPT=$(cat <<EOF
GOAL: $GOAL
Context:
- pwd: $PWD_INFO
- files (truncated):
$LS_INFO
Return only the JSON block with the fields:
- command: a single shell command line
- explanation: one-sentence why this is the right command
EOF
)
extract_json_block() {
# Extract content between ```json and ``` fences
awk '
/```json/ {inblock=1; next}
/```/ && inblock==1 {inblock=0; exit}
inblock==1 {print}
'
}
call_ollama() {
# Ollama: single prompt with system-style prefixing
local prompt="System:
${SYSTEM_RULES}
User:
${USER_PROMPT}
Assistant:"
ollama run "${OLLAMA_MODEL}" <<< "$prompt"
}
call_openai() {
curl -sS https://api.openai.com/v1/chat/completions \
-H "Authorization: Bearer ${OPENAI_API_KEY}" \
-H "Content-Type: application/json" \
-d @- <<JSON
{
"model": "${OPENAI_MODEL:-gpt-4o-mini}",
"temperature": 0,
"messages": [
{"role": "system", "content": ${SYSTEM_RULES@Q}},
{"role": "user", "content": ${USER_PROMPT@Q}}
]
}
JSON
}
RAW_OUT=""
if [[ "$BACKEND" == "ollama" ]]; then
RAW_OUT="$(call_ollama)"
else
RAW_OUT="$(call_openai | jq -r '.choices[0].message.content')"
fi
JSON_OUT="$(printf '%s' "$RAW_OUT" | extract_json_block)"
if ! echo "$JSON_OUT" | jq empty >/dev/null 2>&1; then
echo "Model response was not valid JSON. Full response below for debugging:"
echo "-----"
echo "$RAW_OUT"
exit 1
fi
CMD=$(echo "$JSON_OUT" | jq -r '.command // empty')
EXPL=$(echo "$JSON_OUT" | jq -r '.explanation // empty')
if [[ -z "$CMD" ]]; then
echo "No command found in model response."
echo "$JSON_OUT" | sed 's/^/ /'
exit 1
fi
echo "Suggested command:"
echo " $CMD"
echo "Because:"
echo " $EXPL"
echo
# Basic guardrails: refuse obviously dangerous operations unless explicitly requested
DANGEROUS_REGEX='(^|[[:space:]])(rm|mkfs|:>|dd[[:space:]]|shutdown|reboot|init[[:space:]]0|halt)([[:space:]]|$)'
if echo "$CMD" | grep -Eq "$DANGEROUS_REGEX" && ! echo "$GOAL" | grep -Eq '(delete|erase|format|wipe|remove permanently)'; then
echo "Refusing potentially destructive command without explicit GOAL permission."
exit 1
fi
read -rp "Run it now? [y/N] " ans
if [[ "${ans,,}" == "y" ]]; then
echo
echo "---- OUTPUT (timeout 60s) ----"
set +e
timeout 60s bash -o pipefail -c "$CMD"
RC=$?
set -e
echo "---- EXIT CODE: $RC ----"
else
RC="skipped"
fi
# Log run
{
echo "time: $DATE_STR"
echo "backend: $BACKEND"
echo "goal: $GOAL"
echo "pwd: $PWD_INFO"
echo "cmd: $CMD"
echo "explanation: $EXPL"
echo "exit_code: $RC"
echo "raw_model_output:"
echo "$RAW_OUT" | sed 's/^/ /'
} > "${LOG_DIR}/${RUN_ID}.log"
echo "Log written to ${LOG_DIR}/${RUN_ID}.log"
Make it executable:
chmod +x ai_cmd.sh
Try it:
./ai_cmd.sh "Find the 5 largest log files under /var/log"
./ai_cmd.sh "Show processes using the most memory"
./ai_cmd.sh "Summarize errors in the system journal from the last hour"
You’ll see a proposed command (with an explanation) and a prompt asking to run it. Confirm to execute, or decline and refine your goal.
Step 4 — Real-world examples (and what to expect)
Disk triage:
- Goal: “Find the 10 largest directories under my home folder.”
- Likely suggestion:
du -h ~ | sort -h | tail -n 10
Service health:
- Goal: “List the 10 most recent systemd errors.”
- Likely suggestion:
journalctl -p err -n 10 --no-pager
Codebase navigation:
- Goal: “Search recursively for uses of getenv in .c files.”
- Likely suggestion:
grep -R --line-number --include='*.c' 'getenv' .
Each run is logged so you can copy good commands into your personal toolbox (aliases, scripts).
Step 5 — Safety and ergonomics you should adopt
Keep a human-in-the-loop: always confirm before running.
Snap timeouts around commands (
timeout 60s) to avoid hangs.Treat “dangerous” verbs specially (rm, dd, mkfs, etc.). The example blocks them unless the goal is explicit.
Log everything to build trust and enable audits.
Start read-only; explicitly ask for destructive actions when needed.
What you just built is a (small) AI agent
It:
Observes (pwd, file list, your goal).
Thinks (LLM proposes a command).
Acts (shell executes after you approve).
Reflects (logs results to improve next time).
That’s the core agent loop, Unix-style. From here you can add:
More tools: a “web_search” tool via curl + DuckDuckGo HTML endpoint, a “read_file” tool for summarization, a “git_diff” tool for commit message generation.
Memory: save previous successful commands per repo or directory for reuse.
Multi-step planning: let the model propose a small plan, then execute steps with your confirmations.
If you later want full-featured frameworks (Python-based), look at LangChain, CrewAI, or Microsoft’s AutoGen—but you can go far with plain Bash, curl, and jq.
Conclusion and next step (CTA)
You don’t need a new stack to get value from AI agents. Your Linux shell is already the perfect substrate: transparent, composable, and scriptable. Start using ai_cmd.sh today as a smart suggestion engine for your daily tasks. Then:
Extend it with one new tool (e.g., a “web_search” function) this week.
Wire it into your dotfiles as an alias (e.g.,
alias aicmd="~/bin/ai_cmd.sh").Keep a “best-of” commands file from your logs.
When you’re ready, evolve it into a multi-step agent that can plan, act, and summarize across your system—still with you in control.