AIBites: Agentic AI

multi-step workflows, chaining calls, human-in-the-loop

1.0 - Introduction

An agentic workflow chains multiple agent calls together, with the output of one becoming the input of the next. Each call can have a different focus: analyze, plan, generate, test. This chapter covers how to structure multi-step workflows, how to pass state between calls, and where to add human-in-the-loop checkpoints. Autonomy is the defining property of agentic AI: the system decides the sequence of actions needed to reach a goal rather than waiting for each step to be explicitly requested. The developer defines the goal and the available tools; the AI plans and executes the steps.
When does autonomy help?
  1. When the task has multiple clearly-ordered steps and each step's output is the next step's input.
  2. When you want repeatability - the same workflow produces the same kind of output regardless of who runs it.
  3. When the individual steps are too small to justify a full CLI session but too many to do manually each time.

2.0 - Analyze → Plan → Generate → Test

A four-call workflow for producing a new module:
  1. Analyze - Read existing code; produce a JSON summary of types, functions, and dependencies.
  2. Plan - Feed the summary to a second call; ask for a numbered implementation plan as JSON.
  3. Generate - Feed the plan to a third call; ask for source code, one file at a time.
  4. Test - Run the generated code, capture stdout/stderr, feed errors back into a fourth call for a fix.
Each step is a separate API call. Separating the steps prevents context bloat: the analyze call only sees source files; the generate call only sees the plan. Each call gets exactly the context it needs and nothing more. The JSON boundary between steps is also a checkpoint boundary. You can inspect the analysis output before the plan runs, or inspect the plan before code is generated, without changing the architecture of the pipeline.

3.0 - Passing State Between Calls

Use plain Python data structures to carry results forward. A dict or dataclass per step is enough for most workflows. For long-running pipelines, serialize to a JSON file so a failed step can be retried without re-running the earlier ones. A pattern that works well:
  1. Write step output to step_N_output.json before starting step N+1.
  2. At startup, check whether step_N_output.json already exists and skip that step if it does.
  3. Delete all output files to force a full re-run from scratch.
This gives you idempotent steps at no cost: the pipeline resumes from the last successful step rather than starting over on every transient failure.

4.0 - Human-in-the-Loop Checkpoints

Pause after high-risk steps for user confirmation. A simple input("Continue? [y/N] ") is enough for a personal script. For production workflows, write the plan to a file and require an explicit approval file before the generate step runs. Common checkpoint positions: For CLI workflows, Claude Code's built-in permission prompts provide checkpoints at every file write and shell command. For API-driven pipelines, add explicit input() gates at the boundaries listed above.

5.0 - Example: Building CsTextFinder

CsTextFinder has three independent library projects - CommandLine, DirNav, and Output - plus EntryPoint that wires them together. Building each library first, in dependency order, catches failures close to their source before the integration build runs. The agentic element is Claude diagnosing build failures. When dotnet build fails on a library, the script passes the compiler output to Claude, prints the suggested fix, and pauses for a human-in-the-loop confirmation before retrying.
import subprocess, json, anthropic

client = anthropic.Anthropic()

LIBS = ["CommandLine", "DirNav", "Output"]
ROOT = "CsTextFinder"

def run(cmd, cwd=ROOT):
    r = subprocess.run(cmd, capture_output=True, text=True, cwd=cwd)
    return r.returncode, r.stdout + r.stderr

def diagnose(output, name):
    resp = client.messages.create(
        model="claude-opus-4-7",
        max_tokens=512,
        messages=[{"role": "user",
                   "content": f"Build failure in {name}:\n{output}\n"
                              "Suggest one concrete fix."}]
    )
    return resp.content[0].text

results = {}

for lib in LIBS:
    code, out = run(["dotnet", "build", "-nologo", "-v", "q"],
                    f"{ROOT}/{lib}")
    if code != 0:
        print(f"\n--- {lib} build failed ---\n{out}")
        print("Claude says:", diagnose(out, lib))
        input("Apply fix, then press Enter to retry: ")
        code, out = run(["dotnet", "build", "-nologo", "-v", "q"],
                        f"{ROOT}/{lib}")
    results[lib] = "pass" if code == 0 else "fail"

# build + smoke-test the full project
code, out = run(["dotnet", "build", "-nologo", "-v", "q"])
if code == 0:
    code, out = run(
        ["dotnet", "run", "--project", "EntryPoint", "--",
         "/P", ".", "/p", "cs", "/r", "class"]
    )
results["EntryPoint"] = "pass" if code == 0 else "fail"

print(json.dumps(results, indent=2))
State travels in the results dict. A library that fails to build leaves "fail" in its slot, so the final report pinpoints exactly which component broke without re-running the ones that already passed. The smoke test runs the full pipeline - command-line parsing, directory walk, regex match - with a single real query against the project's own source tree. A passing smoke test confirms every component composes correctly at runtime, not just at compile time.

6.0 - References

ResourceDescription
AIBites: Agentic AI Original CodeBites page (stub, in progress).
AIBites: Agent AI Single-agent patterns, tool use, and the demo agent.
AIBites: LLM API API-level patterns used to build agentic pipelines.
Anthropic: Agentic Frameworks Anthropic documentation on agentic and multi-agent patterns.