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.