Prompting to Programming

Introduction

The agentic revolution was initially framed as an escape from the rigid syntax of traditional programming, offering the creative freedom of natural language (NL). The promise was compelling: simply articulate a requirement in plain English, and the system would execute it. However, as we strive for greater reliability, this unconstrained freedom often results in systemic failure.

To mitigate hallucinations and ensure enterprise-grade robustness, we have begun to formalize our natural language prompts. By incorporating data grounding, few-shot examples, session state management, and strict role definitions, the once freeform instruction set has evolved into a sophisticated, structured artifact. Ironically, these modern AI instructions increasingly resemble the very programming languages they were intended to replace.

This post examines the emerging “structured prompting” paradigm, drawing parallels between traditional software engineering and modern AI orchestration. We must ask: have we inadvertently developed a new, highly verbose programming language? The answer here is for the reader to decide based on the details that follow.

1. The Genesis of NL: The Unstructured Phase

Initially, interacting with LLMs appeared revolutionary. Early adoption relied on simple, unstructured NL requests where users provided a single sentence and received a direct response.

The Traditional Programming Way:

To get a program to summarize text, you’d need libraries, statistical models, or complex regex:

# Traditional approach (Pseudo-code)
import nlp_library
text = "..."
summary = nlp_library.summarize(text, max_length=100, algorithm="textrank")
print(summary)

The Early AI Way:

The most basic and simple way to interact with AI was to just ask it a question. For instance to summarize the text one would provide a simple prompt to AI like below.

Summarize this text for me: [Insert Text]

While this simplicity was elegant, it proved insufficient for production-level applications. There are multiple pitfalls that could stem because of ambiguity in natural language instructions. The lack of syntax can lead to many pitfalls with inconsistent outputs. Programming/software engineering on the other hand is the antithesis of ambiguity and there is only one way to interpret the commands provided to the machine. Let’s learn how some of the pitfalls are handled in the following sections

2. Conversations: State Management in a Stateless World

What about when there was more than one request, and it had to become part of a continuous conversation between the user and the LLM?

Because LLMs are inherently stateless, maintaining a continuous dialogue requires a sophisticated approach to state management. To preserve the logical flow of a conversation, developers must explicitly define the actor, provide historical context, and structure each turn within the prompt.

The Traditional Programming Way (Managing State):

// State management in JS
let conversationHistory = [];

function chat(userInput) {
    conversationHistory.push({ role: "user", text: userInput });
    let response = generateResponse(conversationHistory);
    conversationHistory.push({ role: "system", text: response });
    return response;
}

The Recommended AI Agent Way:

We had to define a structure in the prompt to provide the actor’s identity and previous conversations.

[
  {"role": "system", "content": "You are a helpful assistant."},
  {"role": "user", "content": "I bought a TV yesterday."},
  {"role": "assistant", "content": "That's great! What kind of TV did you get?"},
  {"role": "user", "content": "I need help returning it."}
]

The emergence of a hybrid syntax: We transitioned from plain text to utilizing JSON arrays and formal role definitions simply to maintain conversational continuity.

3. RAG: Injecting Variables into the Runtime

RAG became necessary to provide models with real-time, external data. This requirement introduced further complexity, as retrieved information—or context—must be integrated into the prompt alongside the user’s request.

To prevent model confusion, developers designed specific structural markers to isolate retrieved knowledge from the primary instruction. This effectively created a separation between data and logic within the prompt itself.

The Traditional Programming Way (Variable Assignment):

context_data = database.query("return policies")
user_query = "How do I return my TV?"
result = process(user_query, context_data)

The AI Agent Way:

More structure was added to the prompt to hold this information, creating designated “memory blocks.”

<context>
  <chunk id="1">Return policy: Items can be returned within 30 days with a receipt.</chunk>
  <chunk id="2">Electronics must be in original packaging.</chunk>
</context>

<question>
  How do I return my TV?
</question>

4. Accuracy: The “Unit Tests” of Prompting

Even with sufficient context, LLMs can deviate from expected formatting or logic. In traditional development, we use unit tests and strict typing to ensure validity. In the AI domain, we utilize Few-Shot Prompting to guide the model toward the desired output pattern.

We had to provide example data to the LLM to “nudge” the model into always responding in the desired format. This led to even more structure in the prompt.

The Traditional Programming Way (Formatting):

# --- The Method to Test ---
def process_user_intent(user_input):
    """Takes user input and returns the corresponding action/response."""
    if user_input == 'Show me watches':
        return 'Here are 5 watches you will like'
    # We can add more conditions here later
    return 'I am not sure how to help with that'

# --- The Unit Tests ---
class TestUserIntentProcessor(unittest.TestCase):
    def test_show_watches(self):
        """Tests if the specific watch query returns the correct watch response."""
        user_input = 'Show me watches'
        expected_action = 'Here are 5 watches you will like'
        actual_action = process_user_intent(user_input)
        self.assertEqual(actual_action, expected_action)

    def test_unknown_input(self):
        """Tests how the method handles unexpected input."""
        user_input = 'Show me cars'
        expected_action = 'I am not sure how to help with that'
        actual_action = process_user_intent(user_input)
        self.assertEqual(actual_action, expected_action)

The Recommended AI Agent Way:

#Examples of Agent responding  
<examples>
  <example>
    <input>Show me watches</input>
    <output>Here are 5 Watches you will like</output>
  </example>
  <example>
    <input>Do you have Shoes</input>
    <output>We do not sell shoes</output>
  </example>
</examples>

#User input 
<input>Do you have any watches</input>

5. Agentic Frameworks: Defining Class-like Behaviors

As we moved toward autonomous agents, it became clear that a single task description was insufficient. Agents require a defined persona, an overarching objective, and a specific scope of operations.

Just as Object-Oriented Programming uses Classes to define properties and methods, structured prompting utilizes tags like and to encapsulate agent behavior.

The Traditional Programming Way:

public class SupportAgent {
    private String role = "Support Team Member";
    private String goal = "Assist guests with damaged item orders.";
    // Methods follow...
}

The Recommended AI Agent Way:

<role>
   You are a Support Team Member. Your goal is to assist guests with their orders, specifically handling support issues regarding damaged or dented items.
</role>

6. Constraints: The If/Else and Error Handling of AI

Operational safety requires boundary conditions. Without them, an agent might perform unauthorized actions, such as granting a refund for a non-refundable service. This necessitated the introduction of “negative constraints” or guardrails.

The resulting block serves as the AI equivalent of conditional logic and error handling, ensuring the agent operates within prescribed parameters.

The Traditional Programming Way:

def handle_damaged_item(item):
    empathize()
    options = [" Instant replacement", " Full refund"]
    present_options(options)
    max_discount = 0.10
    # Strict enforcement via code logic

The Recommended AI Agent Way:

<constraints>
   1. **Empathy First:** Always start by empathizing immediately when a user reports a negative experience (e.g., damaged items).
   2. **Strict Option Presentation:** When offering solutions for a damaged item, you must present the options exactly as follows:
       * 1. Instant replacement - available for pickup at the local store today.
       * 2. Full refund - processed immediately.
   3. **Compensation Limit:** You are authorized to offer a discount coupon of up to 10% off their next purchase as a consolation.
</constraints>

7. Tools: API Integrations and Function Calling

To make agents genuinely useful, they needed to take actions and fetch real time data. This required tool calling. However, to explain what a tool does and how to call it, we needed more structured formatting.

The Traditional Programming Way:

import { getRecentOrders } from './api/orders';

async function processUser(userId) {
    console.log("I'm sorry to hear that.");
    let orders = await getRecentOrders(userId);
    return orders;
}

The Recommended AI Agent Way:

1. Immediately express empathy for the issue.
2. Do not repeat yourself.
3. Respond with "Please give me a moment"
4. Call the `{@TOOL: get_recent_orders}` tool to retrieve the guest's order history immediately. Do NOT ask for permission. Just do it.
5. Synthesize the tool's response back to the user

8. Complex Workflows: Control Flow and Orchestration

Now we want agents to do complex things that require multiple steps. Sometimes these steps are sequential, sometimes parallel. For an LLM to follow this logic correctly, we have to provide explicit control flow.

We essentially reinvented the switch statement and while loops using XML tags.

The Traditional Programming Way:

switch(state) {
    case 'REPORT_DAMAGE':
        empathize();
        getRecentOrders();
        state = 'CONFIRM_ITEM';
        break;
    case 'CONFIRM_ITEM':
        askUserToConfirm();
        state = 'PRESENT_OPTIONS';
        break;
    case 'PRESENT_OPTIONS':
        presentResolutionOptions();
        break;
    case 'DEFAULT':
        askAgainToClarify();
        break;
}

The Recommended AI Agent Way:

<step name="Acknowledge and Lookup">
    <trigger>The user reports a damaged or dented item.</trigger>
    <action>
        1. Immediately express empathy for the issue.
        2. Call the `{@TOOL: get_recent_orders}` tool to retrieve the guest's order history immediately. Do NOT ask for permission or say "Please give me a moment". Just do it.
    </action>
</step>

<step name="Confirm Item">
    <trigger>The `{@TOOL: get_recent_orders}` tool returns the order list.</trigger>
    <action>
        1. Ask the user to confirm exactly which item from the list is damaged or requires processing.
    </action>
</step>

<step name="Present Resolution Options">
    <trigger>The user confirms the specific item.</trigger>
    <action>
        1. Acknowledge the specific item.
        2. Present the two mandatory resolution options:
            - "1. Instant replacement - available for pickup at the Palo Alto store today"
            - "2. Full refund - processed immediately"
    </action>
</step>

We have defined triggers (events/conditions), actions (functions), and states (steps). This is almost becoming indistinguishable from a declarative programming language like YAML for CI/CD pipelines or HTML for document structure.

9. Multi-Agents: The Microservices Architecture

Finally, we want agents to delegate to other agents. In the software engineering world, this is the equivalent of moving from a monolithic application to a microservices architecture.

In the instruction set, we must now define which agents are available, what their endpoints (capabilities) are, and how to route traffic (user intents) to them. All this leads to highly structured master-prompts that define networks of sub-agents.

The Recommended AI Agent Way:

<delegation_rules>
   <rule intent="technical_support" target_agent="@tech_bot" />
   <rule intent="billing_issue" target_agent="@finance_bot" />
</delegation_rules>

Conclusion: The Transition to Declarative AI

What began as a conversational interface has evolved into a rigorous engineering discipline. As our requirements for memory, logic, and multi-agent coordination grew, so too did the complexity of our instructions.

To achieve reliability, we have wrapped our natural language in XML tags, strict JSON schemas, and logical constraints. We are no longer merely “prompting”; we are architecting systems using a declarative hybrid language.

While the syntax utilizes English terms, the underlying semantic reality is clear: we have developed a pseudo-structured programming language. In this paradigm, the compiler is a neural network, but the requirements for architectural planning and logical rigor remain as critical as they are in C++ or Python.

The agentic revolution doesn’t eliminate coding: it just changed the syntax to be more human friendly, that still requires clarity and rigor, while development requires low/no technical background. And in doing so, it gave birth to the most powerful, flexible, and verbose programming paradigm we have ever seen.

Can’t wait to learn more!

That’s very clever

It is a true reflection of how predictability matters for consistent predictable output

Great read. The progression you’ve mapped — from a single sentence to XML-tagged workflows with delegation rules — mirrors exactly how software architecture evolved from scripts to microservices. What strikes me most is the few-shot = unit tests analogy. Both are essentially behavioral contracts: you’re not explaining how to compute something, you’re showing what correct looks like and trusting the system to generalize. The difference is that unit tests are deterministic validators, while few-shot examples are probabilistic nudges. That gap is where a lot of production failures still hide.

This was a great read. I never thought of prompting as a form of declarative programming, but the comparison with XML tags and system roles makes a lot of sense for building reliable agents.

loving the developer evolution

I don’t understand much in the agent but I think RAG avoid agent to Hallucination I looking forward to studying the agent to know more about it

thanks for this explanation

Interesting, I needed this information. I’m going to revisit this on a weekly basis for more understanding.

That’s what I’m looking for is how the agents are built-in and then a customer can use them. I’m going to read into this more and try to understand how much it will effectively be a win.

plus 100. Thats why we are now we are also building deterministic agents using workflows

Correct. Fuethermore,

by introducing structured frameworks like adk.dev/workflows/, the Agent building is actively shifting the burden of control flow away from the LLM’s raw reasoning and onto rigid, predictable state machines.

I am glad that this was helpful for you Tim. Appreciate the comment

You are welcome Noha .

Thanks

A very nice way to say its still programming but the format and syntax has changed just like when migrating from c → java → python . Where folks said more friendly and easy way to program the application in a faster and better understanding. So now using AI we would be able to do the same but providing the code in as instruction with more communicative way.

Great post :heart_hands:

The halucination can you please help me with fixing a halcination agent that I believe i have a problem with i have been fighting with this whole time trying to fix these problems and getting run in circles here i was hoping to reach out to someone who voukd help with this if I could have a chance to explain the situation I gave had and am facing with this i was hoping to find some support that I could hopefully explain this situation of my code gone bonkers and having to start from the beginning is seems to be unfair when I truly was hoping to just create and big development seems to be were i am stuck and don’t want to be in truly not like this anyways thank you …please if anybody could help i would be very great full ty

There are 3 ways in which you can reduce hallucination

  1. Directly via instructions of the agent by providing examples and contraints
    Best practices and patterns  |  CX Agent Studio  |  Google Cloud Documentation
    Instructions  |  CX Agent Studio  |  Google Cloud Documentation
  2. By using call backs to have more determinism . Callbacks: Observe, Customize, and Control Agent Behavior - Agent Development Kit (ADK)
  3. By not invoking LLM and creating workflows where necessary Graph routes - Agent Development Kit (ADK)

Hope this helps

Compiler architecture for our times: the runtime takes up a $500000 GPU cluster.

Great article! Thank you for explaining how prompting works and the stages it goes through.

I was wondering, how should we use meta-prompting in practice, and should we use it at all?