Skip to main content
C carlos.enredando.me CTO · Advisor · Builder

Mastering Agentic AI: Goal Setting and Monitoring

·1351 words·7 mins
Carlos Prados
Author
Carlos Prados
Telecommunications Engineer, Entrepreneur, CTO & CIO, Team Leader & Manager, IoT-M2M-Big Data Consultant, Pre-sales Engineer, Product-Service Manager & Strategist.

In my previous post, we wired agents into the outside world with MCP — giving them a standard way to discover and call tools. That gives an agent capability. But capability without direction is just an expensive way to wander.

Here’s the uncomfortable truth about most agent loops in production: they don’t stop when the job is done. They stop when they hit a max_iterations cap, or a token budget, or a timeout. Nobody ever told the agent what “done” actually means, so it can’t possibly know when it’s there.

That’s the gap the Goal Setting and Monitoring pattern closes. It’s the difference between an agent that generates and an agent that delivers.

Pattern #11: Goal Setting and Monitoring
#

The Problem
#

Take a deceptively simple task: “Write Python code to find the binary gap of a positive integer.”

A naive agent does one LLM call, returns whatever comes back, and declares victory. Maybe the code works. Maybe it crashes on zero. Maybe it has no example usage. Maybe it’s clever but unreadable. You won’t know until you run it — and the agent certainly doesn’t know, because it was never asked to check.

The core problem is that “write the code” is not a goal. It’s an instruction. A goal is a measurable definition of success:

  • Simple to understand
  • Functionally correct
  • Handles edge cases (zero, negatives)
  • Includes example usage

Once you state success in those terms, two things become possible that weren’t before: the agent can monitor its own output against the goals, and it can decide whether to ship or to iterate. Without explicit goals, there’s nothing to monitor and nothing to decide — so the agent just runs until something external pulls the plug.

The Solution
#

The pattern is a closed loop: set goals → generate → evaluate against goals → judge if met → stop or refine. The book implements it as an iterative code generator, and the structure is worth studying because it separates three roles that are usually mashed into one prompt.

First, goals are first-class inputs, not buried in the instruction. The generator prompt takes them explicitly, plus the previous attempt and the feedback on it — so each iteration is genuinely informed by the last:

def generate_prompt(use_case: str, goals: list[str], previous_code: str = "", feedback: str = "") -> str:
    base_prompt = f"""
You are an AI coding agent. Your job is to write Python code based on the following use case:

Use Case: {use_case}

Your goals are:
{chr(10).join(f"- {g.strip()}" for g in goals)}
"""
    if previous_code:
        base_prompt += f"\nPreviously generated code:\n{previous_code}"
    if feedback:
        base_prompt += f"\nFeedback on previous version:\n{feedback}\n"

    base_prompt += "\nPlease return only the revised Python code. Do not include comments or explanations outside the code."
    return base_prompt

Second, monitoring is a separate LLM call with a separate role. The reviewer doesn’t write code; it critiques the latest version against the same goals, naming exactly where it falls short:

def get_code_feedback(llm, code: str, goals: list[str]) -> str:
    feedback_prompt = f"""
You are a Python code reviewer. A code snippet is shown below. Based on the following goals:

{chr(10).join(f"- {g.strip()}" for g in goals)}

Please critique this code and identify if the goals are met. Mention if improvements are needed for clarity, simplicity, correctness, edge case handling, or test coverage.

Code:
{code}
"""
    return llm.invoke(feedback_prompt).content.strip()

Third — and this is the part people skip — the stop condition is itself a judgment, not a string match. A dedicated “are we done?” call reads the feedback and returns a hard boolean. This is the LLM-as-judge doing the monitoring: deciding whether the goals are objectively met.

def goals_met(llm, feedback_text: str, goals: list[str]) -> bool:
    review_prompt = f"""
You are an AI reviewer.

Here are the goals:
{chr(10).join(f"- {g.strip()}" for g in goals)}

Here is the feedback on the code:
\"\"\"
{feedback_text}
\"\"\"

Based on the feedback above, have the goals been met?

Respond with only one word: True or False.
"""
    response = llm.invoke(review_prompt).content.strip().lower()
    return "true" in response

Wire those three together and the control loop almost writes itself. Generate, monitor, judge — break the moment the goals are met, otherwise feed the code and feedback forward and try again. The max_iterations cap is a safety net, not the primary exit:

def run_code_iteration_agent(use_case: str, goals_list: list[str], max_iterations: int = 5):
    llm = get_llm(temperature=0.3)
    previous_code = ""
    feedback_text = ""

    for i in range(max_iterations):
        prompt = generate_prompt(use_case, goals_list, previous_code, feedback_text)
        raw_response = llm.invoke(prompt).content
        code = clean_code_block(raw_response)

        feedback_text = get_code_feedback(llm, code, goals_list)

        if goals_met(llm, feedback_text, goals_list):
            print("\n✅ Goals met! Stopping iteration.")
            break

        previous_code = code  # carry context into the next attempt

Notice the discipline here. The generator never gets to grade its own homework — the reviewer and the judge are distinct calls with distinct prompts. That separation is what keeps the loop honest. An agent that both produces and approves its own output will happily approve garbage; splitting the roles forces the monitoring step to actually adversarially probe the work.

Why This Matters
#

Goal Setting and Monitoring is the pattern that turns a one-shot generator into something you can trust to converge. Where it earns its keep:

  • Code generation that self-corrects — exactly the example above: generate, review against a checklist, refine until it passes.
  • Document and report drafting — goals like “covers all three sections,” “cites at least two sources,” “under 800 words,” monitored each pass.
  • Data extraction / ETL agents — goal: “every record has a non-null id and a valid date.” The monitor rejects partial extractions instead of silently shipping them.
  • Long-running autonomous tasks — periodically re-check progress against the goal so the agent can detect it’s stuck or drifting and change strategy, rather than grinding to the iteration cap.

The honest trade-offs. Cost and latency multiply. This isn’t one LLM call — each iteration is three (generate, critique, judge), and you may run several iterations. For the binary-gap toy that’s nothing; for a 4k-token document refined five times, you’re paying for fifteen calls. Budget for it.

Your goals are only as good as you write them. Vague goals (“make it good”) give the judge nothing to measure, and it’ll either rubber-stamp everything or loop forever. Goals must be specific and, ideally, individually checkable.

LLM-as-judge is fallible. The goals_met call can be wrong in both directions — declaring victory too early or never being satisfied. For anything high-stakes, back the judge with a deterministic check where you can: run the actual tests, validate the schema, execute the code. Use the LLM for the fuzzy judgments and code for the crisp ones.

Rule of thumb: reach for this pattern whenever “done” is more demanding than “produced output,” and you can articulate what done means. If you can write the checklist, you can build the loop. If you can’t write the checklist, that’s a sign the task isn’t ready to be automated yet.


The Bigger Picture
#

This is post #11 in my series documenting Antonio Gulli’s Agentic Design Patterns. All credit for the conceptual work goes to him — I’m focused on producing clean, runnable Python implementations that you can clone, modify, and ship.

Goal Setting and Monitoring is the connective tissue between patterns you’ve already seen. It’s Reflection (Chapter 4) with teeth: where Reflection critiques, this pattern critiques against an explicit, measurable target and uses that target as the stop condition. It’s also the natural home for the monitoring half — the “are we still on track?” check that long-horizon Planning (Chapter 6) agents need to avoid drifting off-mission.

All the code from this post is in my repository: carlosprados/Agentic_Design_Patterns, specifically under 11_Goal_Setting_and_Monitoring/. It runs with uv run, supports Gemini and Ollama via the shared get_llm() abstraction, and is ready to fork.


What’s Next
#

In the next post we’ll tackle Exception Handling and Recovery — what an agent does when a tool call fails, an API times out, or the model returns garbage. Goal Setting tells the agent where it’s going; Exception Handling keeps it moving when the road is broken. It’s the difference between a demo and a system that survives contact with production.

Stay tuned.