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

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.