AI Agents·8 min read·

ReAct vs Plan-Then-Act Loops for Ops Bots

Choose ReAct when the next check depends on live output, and use plan-then-act when an ops bot on a Linux VPS must not improvise during writes.

NB

Netbay Cloud Team

Netbay Engineering

On this page

Ops bots fail in two opposite ways. They either stare at one metric and never look at the next, or they invent a twelve-step plan and execute step seven against a machine that already changed. ReAct and plan-then-act are the two loop shapes that show up in production. Neither is magic. They trade adaptivity for predictability, and for systemd work that trade is the whole design.

ReAct interleaves reasoning and tools: observe, think, act, observe again. Plan-then-act writes a full plan, optionally asks a human, then executes without asking the model to reinvent the plan after every command. You can implement both with the same tool table on a Linux VPS.

When ReAct Is the Right Shape

Use ReAct when the next tool depends on data you do not have yet. Is nginx down, or is the upstream app returning 502? You cannot know until you read ActiveState and then hit /health. A frozen plan that always restarts nginx is how you paper over an application bug at 3 a.m.

ReAct also fits read-only investigation: gather journal lines, compare a hash, quote the observation in a final message. The model should not commit to a restart before it has seen the facts.

The cost is token spend and drift. Each step re-prompts the model. A confused model can ping-pong between two tools. Cap steps. Detect cycles: if the same tool and the same argument hash appear three times, stop.

When Plan-Then-Act Is Safer

Use plan-then-act for writes. Restarting a unit, rotating a log, toggling a maintenance flag — these should be a short, reviewable list, not an improvisation. The model proposes a plan object. Your code validates each step against the tool schema. A human or a policy engine approves. Then a dumb executor runs the list and stops on the first failure.

The model is out of the loop during execution. That is the feature. Mid-run creativity is how a bot decides to "also clear the cache" with a command you never allowlisted.

python
import json

PLAN_SCHEMA_KEYS = {"goal", "steps", "stop_on_error"}

def parse_plan(raw):
    plan = json.loads(raw)
    extra = set(plan) - PLAN_SCHEMA_KEYS
    if extra:
        raise ValueError("plan has extra keys")
    steps = plan.get("steps") or []
    if not isinstance(steps, list) or not steps or len(steps) > 8:
        raise ValueError("steps must be a list of 1 to 8 items")
    for step in steps:
        if step.get("tool") not in DISPATCH:
            raise ValueError("unknown tool in plan")
        if not isinstance(step.get("args"), dict):
            raise ValueError("args must be an object")
    return plan

def execute_plan(plan, dry_run=True):
    results = []
    for index, step in enumerate(plan["steps"]):
        if dry_run:
            results.append({"index": index, "tool": step["tool"], "skipped": True})
            continue
        out = DISPATCH[step["tool"]](step["args"])
        results.append({"index": index, "tool": step["tool"], "out": out[:2000]})
        if plan.get("stop_on_error", True) and out.startswith("error:"):
            break
    return results

The plan is data. You can print it, store it, and re-run it tomorrow without calling the model. That is closer to Ansible than to a chatbot, which is what you want for writes.

A Hybrid That Ops Teams Actually Ship

The hybrid that works: ReAct for diagnose, plan-then-act for change. First loop is read-only tools and a MAX_STEPS of six. Its output is a plan JSON, not a restart. Second loop never sees the journal. It sees the plan and the approval record.

python
READ_TOOLS = {"read_unit_status", "http_get_health", "tail_journal"}
WRITE_TOOLS = {"restart_unit", "reload_unit"}

def diagnose(user_text):
    # ReAct with READ_TOOLS only. Returns a plan JSON string or a final note.
    return run_agent(user_text, tools=read_tool_defs, dispatch=read_dispatch)

def apply_approved(plan, approval_id, dry_run=False):
    if not approval_is_valid(approval_id, plan):
        raise PermissionError("missing or stale approval")
    for step in plan["steps"]:
        if step["tool"] not in WRITE_TOOLS:
            raise ValueError("write plan referenced a non-write tool")
    return execute_plan(plan, dry_run=dry_run)

Keep the two dispatches as separate dicts. A diagnose process that cannot import restart_unit cannot restart nginx even if the model asks. That process split is worth more than a clever prompt.

Do not let ReAct "helpfully" call a write tool because the prompt said to fix the issue. The diagnose system prompt should name the read tools and say that producing a plan JSON is the only successful finish. If the model emits restart_unit anyway, the dispatch miss becomes an error observation and the step still counts against MAX_STEPS. That is how you train the loop without widening the blast radius.

Plan-then-act also plays well with systemd timers. Generate a plan during office hours, apply it from a timer with dry_run false only after the approval file exists. Store the plan on High-Speed SSD next to the approval fingerprint so you can prove what ran. The VPS in Lucknow does not need a GPU; it needs a clock, Intel Xeon Platinum idle enough to JSON-parse eight steps, and L3/L4 DDoS protection on whatever webhook receives the human click. If you cannot print the plan as a list of argv, it is not a plan. It is another chat.

ReAct vs plan-then-act ReAct (diagnose) observe - tool - observe - tool read-only tools, cycle detection output: plan JSON or notes Plan-then-act (change) validate plan, approve, execute model is out of the write loop output: per-step results Hybrid ops bot Separate dispatch dicts: READ_TOOLS vs WRITE_TOOLS Writes require approval_id and optional dry_run

Takeaway

ReAct gathers facts. Plan-then-act spends them. Split the processes so a diagnose loop cannot import a restart tool. You can run both halves on a Netbay Linux VPS in Lucknow — netbayhosts.in.

Keep reading

Follow along on a real VPS

Deploy Linux in under 60 seconds

These guides are written against Ubuntu, Debian, and RHEL-family images — the same ones on NetBay.

Deploy an instance