Posted on
Artificial Intelligence

Artificial Intelligence Agent Planning Strategies

Author
  • User
    linuxbash
    Posts by this author
    Posts by this author

Artificial Intelligence Agent Planning Strategies for Bash Users: From “Think” to “Do” Safely

What if your nightly maintenance tasks, log triage, or “fix it and page me if it fails” routines could plan their own steps, verify each move, and back out safely? That’s the promise of agent planning: bringing structure and reliability to AI-driven or semi-automated operations. In this article, we’ll demystify agent planning strategies, explain why they matter, and give you practical Bash-friendly patterns and scripts you can use today—no exotic frameworks required.

Why Planning Matters (Even If You’re “Just” Scripting)

  • LLMs can draft steps, but naïve “one-shot” prompts are brittle. Planning provides scaffolding: goals, subgoals, tool calls, checks, and fallbacks.

  • In ops/SRE and DevSecOps, correctness beats charisma. Structured plans reduce blast radius by verifying intermediate states and encoding rollbacks.

  • Bash is already your orchestration layer. With a little structure (JSON plans + jq + checks), you can make your scripts behave like disciplined agents that think before they act.

Prerequisites (Install Once, Reuse Everywhere)

We’ll use standard CLI tools:

  • jq (for JSON plans)

  • graphviz (for plan visualization)

  • curl (for simple checks)

Install them with your package manager:

  • Debian/Ubuntu (apt):

    sudo apt update
    sudo apt install -y jq graphviz curl
    
  • Fedora/RHEL/CentOS (dnf):

    sudo dnf install -y jq graphviz curl
    
  • openSUSE (zypper):

    sudo zypper install -y jq graphviz curl
    

Core Strategies You Can Use Today

1) Choose the Right Planning Pattern for the Job

Pick a strategy that matches problem complexity and risk:

  • Reactive (ReAct-style): Alternate “think” (decide next step) and “act” (run a tool), verifying after each step. Great for linear troubleshooting and CLI automation.

  • Task Decomposition: Break a goal into ordered subgoals and dependencies (a DAG). Ideal for maintenance playbooks and pipelines.

  • Tree Search (Tree-of-Thought/MCTS-inspired): Explore multiple candidate steps, score, and pick the best. Useful when ambiguity is high (e.g., root-cause analysis with multiple hypotheses). You can approximate this with branching plans and checks.

If you’re not sure, start with task decomposition. It’s easy to audit and integrates naturally with Bash.

2) Make Plans Data, Not Just Code

Represent each plan as JSON so it can be:

  • Generated by a human, a script, or an AI model

  • Visualized

  • Executed predictably and safely

Here’s a minimal plan for “Fix 502 from nginx” with dependencies, checks, and an optional rollback:

{
  "goal": "Restore web service returning 502 from nginx",
  "tasks": [
    {
      "id": "t1",
      "desc": "Check nginx is running",
      "cmd": "systemctl is-active --quiet nginx",
      "check": "systemctl is-active --quiet nginx",
      "deps": []
    },
    {
      "id": "t2",
      "desc": "Inspect nginx error log for upstream failures",
      "cmd": "grep -m1 -E 'upstream.*(fail|timeout)' /var/log/nginx/error.log || true",
      "check": ":",
      "deps": []
    },
    {
      "id": "t3",
      "desc": "Probe upstream on port 8080",
      "cmd": "curl -fsS http://127.0.0.1:8080/health -o /dev/null",
      "check": "curl -fsS http://127.0.0.1:8080/health -o /dev/null",
      "deps": ["t2"]
    },
    {
      "id": "t4",
      "desc": "Reload nginx if config is valid",
      "cmd": "nginx -t && systemctl reload nginx",
      "check": "sleep 1 && curl -fsS http://127.0.0.1/ -o /dev/null",
      "rollback": "systemctl restart nginx || true",
      "deps": ["t1", "t3"]
    }
  ]
}
  • cmd: what to do

  • check: a verifiable condition that must pass

  • deps: task IDs that must complete first

  • rollback: an optional safe fallback if cmd/check fail

3) Execute with a Safe Planner-Executor Loop in Bash

The script below:

  • Resolves dependencies

  • Runs tasks with timeouts

  • Verifies checks

  • Optionally runs rollback

  • Logs all actions

  • Supports DRY_RUN and TIMEOUT

Save as plan-run.sh and chmod +x plan-run.sh:

#!/usr/bin/env bash
# plan-run.sh: Execute a JSON plan with deps, checks, and optional rollback.
# Usage: DRY_RUN=1 TIMEOUT=30 ROLLBACK_ON_FAIL=1 ./plan-run.sh plan.json

set -uo pipefail

PLAN_FILE="${1:-plan.json}"
LOG_FILE="run.log"
STATE_FILE="plan_state.json"
DRY_RUN="${DRY_RUN:-0}"
TIMEOUT_SECS="${TIMEOUT:-45}"
ROLLBACK_ON_FAIL="${ROLLBACK_ON_FAIL:-1}"

command -v jq >/dev/null || { echo "jq is required"; exit 1; }
command -v timeout >/dev/null || { echo "timeout is required (usually coreutils)"; exit 1; }

ts() { date -Is; }
log() { echo "[$(ts)] $*" | tee -a "$LOG_FILE" >&2; }

# Load task IDs
mapfile -t TASK_IDS < <(jq -r '.tasks[].id' "$PLAN_FILE")

# Initialize statuses (pending/done/failed)
declare -A STATUS
if [[ -f "$STATE_FILE" ]]; then
  while IFS="=" read -r k v; do
    [[ -n "$k" ]] && STATUS["$k"]="$v"
  done < <(jq -r 'to_entries[] | "\(.key)=\(.value)"' "$STATE_FILE" 2>/dev/null || true)
fi
for id in "${TASK_IDS[@]}"; do
  : "${STATUS[$id]:=pending}"
done

get_field() {
  local id="$1" field="$2"
  jq -r --arg id "$id" --arg f "$field" '.tasks[] | select(.id==$id) | .[$f] // ""' "$PLAN_FILE"
}

deps_done() {
  local id="$1" dep
  mapfile -t DEPS < <(jq -r --arg id "$id" '.tasks[] | select(.id==$id) | (.deps // [])[]' "$PLAN_FILE")
  for dep in "${DEPS[@]}"; do
    [[ "${STATUS[$dep]}" == "done" ]] || return 1
  done
  return 0
}

save_state() {
  {
    echo "{"
    local first=1
    for id in "${TASK_IDS[@]}"; do
      [[ $first -eq 1 ]] || echo ","
      first=0
      printf '  "%s": "%s"' "$id" "${STATUS[$id]}"
    done
    echo
    echo "}"
  } > "$STATE_FILE"
}

progress_made=1
while [[ $progress_made -eq 1 ]]; do
  progress_made=0
  for id in "${TASK_IDS[@]}"; do
    [[ "${STATUS[$id]}" == "pending" ]] || continue
    if deps_done "$id"; then
      desc="$(get_field "$id" desc)"
      cmd="$(get_field "$id" cmd)"
      check="$(get_field "$id" check)"
      rollback="$(get_field "$id" rollback)"
      log "READY: [$id] $desc"

      if [[ "$DRY_RUN" == "1" ]]; then
        log "DRY_RUN: would run: $cmd"
        [[ -n "$check" ]] && log "DRY_RUN: would verify: $check"
        STATUS[$id]="done"
        save_state
        progress_made=1
        continue
      fi

      ok=0
      if [[ -n "$cmd" ]]; then
        log "RUN: $cmd"
        if timeout "$TIMEOUT_SECS" bash -lc "$cmd" >>"$LOG_FILE" 2>&1; then
          ok=1
        else
          ok=0
        fi
      else
        ok=1
      fi

      if [[ $ok -eq 1 && -n "$check" ]]; then
        log "CHECK: $check"
        if timeout "$TIMEOUT_SECS" bash -lc "$check" >>"$LOG_FILE" 2>&1; then
          ok=1
        else
          ok=0
        fi
      fi

      if [[ $ok -eq 1 ]]; then
        log "SUCCESS: [$id]"
        STATUS[$id]="done"
      else
        log "FAIL: [$id]"
        if [[ "$ROLLBACK_ON_FAIL" == "1" && -n "$rollback" ]]; then
          log "ROLLBACK: $rollback"
          timeout "$TIMEOUT_SECS" bash -lc "$rollback" >>"$LOG_FILE" 2>&1 || true
        fi
        STATUS[$id]="failed"
      fi

      save_state
      progress_made=1
    fi
  done
done

# Summary
failed_any=0
for id in "${TASK_IDS[@]}"; do
  st="${STATUS[$id]}"
  log "TASK [$id]: $st"
  [[ "$st" == "failed" ]] && failed_any=1
  if [[ "$st" == "pending" ]]; then
    log "BLOCKED: [$id] has unmet deps; check for cycles or failures"
    failed_any=1
  fi
done

exit $failed_any

Run it:

./plan-run.sh plan.json
# or safely test first
DRY_RUN=1 ./plan-run.sh plan.json

This is already an “agent”: it respects dependencies, verifies intermediate outcomes, and contains failure policy (rollback).

4) Visualize Your Plan (DAG) to Review Risk

Before you run, visualize the dependency graph to catch surprises. Save as plan-viz.sh:

#!/usr/bin/env bash
# Usage: ./plan-viz.sh plan.json | dot -Tpng -o plan.png
set -euo pipefail
PLAN="${1:-plan.json}"
echo "digraph Plan {"
jq -r '.tasks[] | @base64' "$PLAN" | while read -r row; do
  obj() { echo "$row" | base64 --decode | jq -r "$1"; }
  id="$(obj '.id')"
  desc="$(obj '.desc' | @sh)"
  echo "  \"$id\" [label=$desc, shape=box, style=rounded];"
  jq -r "$row | @base64d | fromjson | .deps // [] | .[]" | while read -r dep; do
    echo "  \"$dep\" -> \"$id\";"
  done
done
echo "}"

Then:

./plan-viz.sh plan.json | dot -Tpng -o plan.png

Open plan.png to visually validate ordering and fan-out/fan-in. This doubles as documentation for code review.

5) Build Safety In: Limits, Dry-Runs, and Telemetry

  • Dry-run first: DRY_RUN=1 ./plan-run.sh plan.json

  • Limit blast radius:

    • Use timeout (already included).
    • Run destructive steps behind explicit flags in your plan (e.g., require ALLOW_RESTART=1).
    • Consider sudo -n so plans never hang on a password prompt.
  • Log everything: the runner appends to run.log and writes plan_state.json for postmortem.

  • Idempotence wins: Make check idempotent and side-effect-free so reruns are safe.

Real-World Examples You Can Adapt

  • Log Triage: Plan lists log locations, greps key error signatures, counts frequencies, and emits a suggested next action. Checks verify logs exist and aren’t empty.

  • Disk Pressure Remediation: Decompose into “identify big dirs,” “archive or purge old artifacts,” “verify free space,” and “notify.” Rollback reverts a mount or restores a tar.

  • Blue/Green Deploy NGINX: Validate config, warm up upstream, flip symlink or config block, reload, and health-check. Rollback flips back on failure.

Each of these is just a different JSON plan feeding the same runner.

Bonus: Prompt Template to Auto-Draft Plans

If you do use an LLM, hand it this template and ask for valid JSON matching our schema:

You are a planning agent. Produce a JSON object with fields:

- goal: string

- tasks: array of {id, desc, cmd, check, rollback?, deps[]}

Constraints:

- Each id is unique and simple (e.g., t1, t2).

- cmds are Bash-safe and non-interactive.

- checks succeed if and only if the step achieved its goal.

- Use only tools available on a typical Linux server.

- Keep it minimal, safe, and idempotent.

User goal: "Restore web service returning 502 from nginx"

Paste the result into plan.json, review it, visualize, dry-run, then execute for real.

Conclusion and Call to Action

Agent planning isn’t just for research labs—it maps cleanly onto the way Bash users already work: goals, ordered steps, checks, and fallbacks. You can start small:

1) Install jq, graphviz, and curl (see commands above).
2) Save the runner and visualizer scripts.
3) Wrap one repetitive task as a JSON plan.
4) Visualize it, dry-run it, then execute.
5) Iterate: add rollbacks, better checks, and branchier plans as needed.

Next step: pick one maintenance routine you dread and convert it to a plan. Your future self (and your pager) will thank you.