Introduction: Beyond Static Prompts
So far, you’ve learned how to define multi-stage AI agents using markdown within AIPack. These agents are powerful for sequential tasks, but what happens when your agent needs to make a decision? What if it needs to retry an action or branch its behavior based on an AI model’s output or an external condition? Pure markdown, while excellent for prompt templating, lacks the dynamic control flow needed for truly intelligent and resilient agents.
This is where Lua comes into play. AIPack leverages Lua, a lightweight, powerful, and embeddable scripting language, to introduce sophisticated logic and control flow into your agents. By embedding Lua code, you can empower your AI agents with conditional branching, loops, and custom data manipulation, transforming them from simple sequential processors into dynamic decision-makers.
In this chapter, we’ll dive into the world of Lua scripting within AIPack. We’ll explore why Lua is the perfect fit, learn its fundamental syntax for agent control, and walk through practical examples to build agents that can adapt and respond intelligently to varying circumstances. Get ready to add a new layer of intelligence to your AI Packs!
Core Concepts: Why Lua for Agent Control?
AI agents in real-world production environments often encounter non-deterministic situations. An AI model might respond in unexpected formats, an external API call might fail, or a user’s request might require different processing paths. To handle these scenarios gracefully, agents need explicit programming logic.
Lua’s Role in AIPack
Lua is a scripting language specifically designed to be easily embedded into applications. Its simplicity, small footprint, and high performance make it an ideal choice for adding custom logic without bogging down the core runtime.
Within AIPack, Lua serves as the agent’s “brain” for procedural decisions. It allows you to:
- Implement Conditional Logic: Use
if/elsestatements to choose different execution paths based on inputs, previous AI outputs, or external checks. - Create Loops: Perform retries, iterate over data, or wait for specific conditions to be met.
- Manipulate Data: Transform, parse, or validate data flowing between agent stages or AI model calls.
- Manage State: Store and update variables that persist across different parts of your agent’s execution.
This integration allows your markdown agents to delegate complex decision-making to a robust scripting environment, making them far more capable and reliable.
📌 Key Idea: Lua provides the “if this, then that” and “do this until” logic that markdown alone cannot offer, making AIPack agents truly adaptive.
How Lua Integrates with Markdown Agents
AIPack allows you to embed Lua code directly within your .aip markdown files using fenced lua code blocks. These blocks can access and manipulate the agent’s context (inputs, variables, previous outputs) and then set new outputs or variables that subsequent markdown stages can use.
Consider a simplified flow:
In this diagram, the Lua Block acts as a branching point, directing the agent’s flow based on specific conditions it evaluates.
Fundamental Lua Concepts for AIPack
Before we write some code, let’s quickly review the Lua essentials you’ll use most often:
- Variables:
local my_variable = "Hello" -- Local variable another_variable = 123 -- Global variable (use 'local' for good practice) - Data Types: Numbers, strings, booleans (
true/false), andnil(null). - Tables: Lua’s only data structuring mechanism, serving as arrays, dictionaries, or objects.🧠 Important: Lua arrays are 1-indexed, meaning the first element is at index
local my_table = { name = "Alice", age = 30, hobbies = {"reading", "coding"} } print(my_table.name) -- Access by key print(my_table.hobbies[1]) -- Access array-like elements (1-indexed!)1, not0like in many other languages. - Conditional Statements:
local score = 85 if score >= 90 then print("Excellent!") elseif score >= 70 then print("Good job.") else print("Keep trying.") end - Loops:
-- For loop (numeric) for i = 1, 3 do print("Iteration " .. i) end -- prints: -- Iteration 1 -- Iteration 2 -- Iteration 3 -- While loop local count = 0 while count < 2 do print("Counting: " .. count) count = count + 1 end -- prints: -- Counting: 0 -- Counting: 1 - Functions:
local function greet(name) return "Hello, " .. name .. "!" end print(greet("World")) -- Prints: Hello, World!
Step-by-Step Implementation: Building a Conditional Agent
Let’s create an AIPack agent that uses Lua to decide whether to generate a short summary or a detailed report based on the user’s input.
Scenario: Adaptive Summary Agent
Our agent will take a user’s request and a piece of text. If the user explicitly asks for a “short summary” or the text is very brief, it will provide a concise output. Otherwise, it will default to a more detailed report.
Step 1: Create a New AIPack File
Create a file named adaptive_summary.aip in your AIPack project directory.
# Adaptive Summary Agent
This agent dynamically decides between a short summary and a detailed report.
---
Step 2: Define Initial Input and a Lua Block
We’ll start by defining the agent’s inputs and then immediately use a Lua block to analyze them.
Add the following to adaptive_summary.aip:
# Adaptive Summary Agent
This agent dynamically decides between a short summary and a detailed report.
---
## Input
```text
{{.Input}}
Internal Logic - Determine Output Type
-- Access the agent's input from the context
local user_request = aipack.get_input()
-- Define a threshold for "short" text (example: 100 characters)
local TEXT_LENGTH_THRESHOLD = 100
-- Check if the user specifically asked for a short summary
local is_short_request = string.find(string.lower(user_request), "short summary", 1, true) ~= nil
-- Simulate the text content (in a real scenario, this would come from another stage)
-- For now, let's hardcode a sample text for demonstration
local text_to_process = "AIPack is an open-source agentic runtime for building, running, and sharing AI Packs. It uses multi-stage markdown agents and Lua logic for control flow. This makes it highly flexible for various AI-assisted workflows."
-- Check if the text itself is short
local is_text_short = string.len(text_to_process) < TEXT_LENGTH_THRESHOLD
-- Determine the desired output type
local output_type = "detailed_report" -- Default
if is_short_request or is_text_short then
output_type = "short_summary"
end
-- Store the determined output_type in the agent's context for later stages
aipack.set_output(output_type, "output_type")
aipack.set_output(text_to_process, "text_content")
-- Print for debugging (will show in AIPack console if debugging is enabled)
print("User Request: " .. user_request)
print("Is Short Request: " .. tostring(is_short_request))
print("Is Text Short: " .. tostring(is_text_short))
print("Determined Output Type: " .. output_type)
Explanation of the Lua Block:
local user_request = aipack.get_input(): This is how Lua accesses the primary input provided to the AIPack agent.aipackis a global object provided by the AIPack runtime.string.find(...): A Lua function to search for a substring. We convertuser_requestto lowercase for case-insensitive matching.~= nilchecks if the substring was found.string.len(text_to_process): Returns the length of the string.if is_short_request or is_text_short then ... end: Our core conditional logic. If either condition is true,output_typebecomes"short_summary".aipack.set_output(output_type, "output_type"): This is crucial! It stores theoutput_typevariable into the agent’s context under the key"output_type". Subsequent markdown stages can then access this using{{.output_type}}.aipack.set_output(text_to_process, "text_content"): We also store the text to process so later stages can use it.print(...): A standard Lua function for debugging. These messages will appear in your console when running the agent.
Step 3: Define Conditional Markdown Stages
Now, let’s create two different markdown stages, each guarded by a condition based on the output_type set by our Lua block.
Add the following after the Lua block in adaptive_summary.aip:
---
## Short Summary Output
{{ if eq .output_type "short_summary" }}
You asked for a short summary or the text was brief. Here's a concise overview:
Summarize the following text concisely:
```text
{{.text_content}}
{{ end }}
Detailed Report Output
{{ if eq .output_type “detailed_report” }} You requested a detailed report. Here is a comprehensive analysis:
Provide a detailed report on the following text, including key concepts and potential applications:
{{.text_content}}
{{ end }}
**Explanation of Conditional Markdown:**
* `{{ if eq .output_type "short_summary" }}`: This is a Go template conditional statement (AIPack uses Go templates for markdown processing). It checks if the `output_type` variable in the agent's context is equal to `"short_summary"`.
* `{{ end }}`: Closes the `if` block.
* Only one of these two markdown blocks will be processed and sent to an AI model, depending on the value of `.output_type` determined by the Lua script.
* `{{.text_content}}`: Accesses the `text_content` variable we set in Lua.
### Step 4: Run the Agent
Save `adaptive_summary.aip`. Now, open your terminal and run the agent with different inputs to see the Lua logic in action.
**Run 1: Requesting a short summary**
```bash
aipack run adaptive_summary.aip "Please provide a short summary."
You should see output indicating the “Short Summary Output” stage was activated. The prompt sent to the AI model will be for a concise summary.
Run 2: Requesting a detailed report
aipack run adaptive_summary.aip "I need a detailed analysis of this topic."
This time, the “Detailed Report Output” stage should be active, prompting for a comprehensive analysis.
Run 3: Short text (without explicit request)
Modify the text_to_process in your Lua block to be very short (e.g., "AIPack is cool."). Then run again:
aipack run adaptive_summary.aip "Analyze this."
Even without explicitly asking for a “short summary,” the Lua logic should detect the short text and activate the “Short Summary Output” stage.
⚡ Real-world insight: This pattern is incredibly useful for creating flexible agents. Imagine an agent that checks if a user’s query contains sensitive information before sending it to a cloud AI model, or an agent that retries a code generation task if the initial output fails a linter check.
Step-by-Step Implementation: Looping with Lua for Retries
Another common need in agentic workflows is the ability to retry actions, especially when dealing with external services or non-deterministic AI outputs. Lua’s looping constructs are perfect for this.
Scenario: Robust API Call Agent
Let’s imagine an agent that needs to call an external API. This API might occasionally fail or return an invalid response. We want our agent to retry the call up to a maximum number of times before giving up.
Step 1: Create a New AIPack File for Retries
Create retry_api_agent.aip.
# Robust API Call Agent
This agent attempts an API call multiple times if it fails.
---
Step 2: Implement Lua Logic with a Loop
Add a Lua block that simulates an API call and retries it.
# Robust API Call Agent
This agent attempts an API call multiple times if it fails.
---
## Internal Logic - API Call with Retries
```lua
local max_retries = 3
local current_retry = 0
local api_success = false
local api_response = "Initial state"
print("Attempting API call with retries...")
while current_retry < max_retries and not api_success do
current_retry = current_retry + 1
print("--- Attempt " .. current_retry .. " ---")
-- Simulate an API call
-- In a real scenario, this would be `aipack.call_tool("my_api_tool", { param = "value" })`
-- For demonstration, let's make it "fail" twice, then "succeed"
if current_retry < 3 then
print("Simulating API failure...")
api_response = "Error: Service Unavailable"
api_success = false
aipack.sleep(1) -- Wait for 1 second before retrying
else
print("Simulating API success!")
api_response = "Data received: { 'status': 'ok', 'data': 'Hello from API!' }"
api_success = true
end
end
-- After loop, set the final status and response
aipack.set_output(tostring(api_success), "api_call_success")
aipack.set_output(api_response, "api_final_response")
print("Final API Call Status: " .. tostring(api_success))
print("Final API Response: " .. api_response)
Explanation of the Retry Lua Block:
while current_retry < max_retries and not api_success do ... end: Thiswhileloop continues as long as we haven’t exceededmax_retriesAND theapi_successflag isfalse.current_retry = current_retry + 1: Increments the retry counter.aipack.sleep(1): A utility function provided by AIPack to pause execution for a specified number of seconds. This is crucial for real-world retries to avoid overwhelming the external service.- The
if current_retry < 3block simulates a failure for the first two attempts and then a success on the third. tostring(api_success): Converts the booleanapi_successto a string foraipack.set_output, asset_outputgenerally expects string values or values that can be easily serialized.
Step 3: Define Output Stages Based on Retry Result
Add markdown stages to show the outcome.
---
## API Call Result
{{ if eq .api_call_success "true" }}
The API call was successful! Here's the response:
```json
{{.api_final_response}}
{{ else }} The API call failed after multiple retries. Last response:
{{.api_final_response}}
{{ end }}
### Step 4: Run the Agent
Save `retry_api_agent.aip` and run it:
```bash
aipack run retry_api_agent.aip
Observe the console output. You’ll see the agent retrying twice, pausing, and then succeeding on the third attempt. If you change max_retries to 2 and keep the simulation logic, you’d see it fail after two attempts.
⚠️ What can go wrong: Without proper retry logic, an agent might fail on transient network issues or temporary service outages, leading to brittle workflows. Lua’s loops make your agents much more robust.
Mini-Challenge: Text Processing Decision Agent
Let’s combine what you’ve learned to build an agent that makes a more complex decision.
Challenge: Create an AIPack agent (text_processor.aip) that takes a user’s input request and a piece of text_content. Your Lua logic should:
- Check if the
text_contentis empty or very short (e.g., less than 20 characters). If so, set anaction_typeto"ask_for_more_text". - If the
text_contentis substantial, check if the user’s request contains keywords like “translate” or “summarize”.- If “translate” is found, set
action_typeto"translate". - If “summarize” is found, set
action_typeto"summarize". - If neither is found, default
action_typeto"analyze".
- If “translate” is found, set
- Have three distinct markdown stages, each guarded by
{{ if eq .action_type "..." }}:- One to prompt the user for more text.
- One to translate the
text_content. - One to summarize the
text_content. - One to analyze the
text_content.
Hint:
- Use
aipack.get_input()for the user’s request. - Define
text_contentas a hardcoded string in Lua for now, or pass it as a second input toaipack.get_input("text_content")if you define it as a named input in a previous markdown stage. For simplicity, hardcode it in Lua first. - Use
string.findfor keyword detection andstring.lenfor text length. - Remember Lua’s
elseiffor multiple conditions.
What to observe/learn: This challenge will solidify your understanding of combining multiple conditional checks, string manipulation, and branching execution paths in AIPack using Lua. It demonstrates how a single agent can perform diverse tasks based on dynamic input.
Common Pitfalls & Troubleshooting
Working with Lua in AIPack is powerful, but like any scripting, it has its quirks.
Lua Syntax Errors:
- Missing
end: Everyif,for,while, andfunctionblock in Lua must be closed with anend. This is a very common mistake. - Incorrect comparison: In Lua,
==is for equality comparison,=is for assignment. Using=in anifcondition is a syntax error. - Case sensitivity: Lua is case-sensitive.
myVaris different frommyvar. - 1-indexing vs. 0-indexing: Remember Lua tables (arrays) start at index
1. - Debugging: Use
print("My variable value: " .. tostring(my_var))statements generously within your Lua blocks. They will show up in your terminal when you run the agent.
- Missing
Incorrect Variable Access:
aipack.get_input()andaipack.set_output(): Ensure you’re using these functions correctly to pass data between the AIPack runtime and your Lua script, and then between Lua and subsequent markdown stages.- Named inputs: If you define named inputs in markdown (e.g.,
{{.text_content}}), you can access them in Lua usingaipack.get_input("text_content"). - Go template variables: Remember that variables set via
aipack.set_output()are accessed in markdown with{{.variable_name}}.
Infinite Loops:
- If your
whileloop condition never becomes false (e.g., you forget to increment a counter or modify a flag), your agent will hang indefinitely. - Debugging: If your agent hangs, stop it (Ctrl+C). Add
print()statements inside your loop to check if the loop variable or condition is changing as expected.
- If your
Security Concerns with Arbitrary Lua:
- While convenient, allowing arbitrary Lua execution means your agent has the power to do whatever Lua can do (e.g., read/write files, make network requests if the runtime allows).
- Best Practice: When sharing or deploying AIPacks, ensure you understand the Lua code’s implications. AIPack’s runtime might sandbox Lua, but it’s always good to be aware.
Summary: Mastering Agent Control
You’ve now taken a significant step in building more intelligent and robust AI agents with AIPack. By integrating Lua scripting, you can move beyond simple sequential prompts and into the realm of dynamic, decision-making workflows.
Here are the key takeaways from this chapter:
- Lua’s Purpose: It provides essential control flow (conditionals, loops), data manipulation, and state management capabilities to AIPack agents, complementing markdown’s templating power.
- Integration: Lua code is embedded directly into
.aipfiles using fencedluablocks. - AIPack Lua API: Use
aipack.get_input()to read data into Lua andaipack.set_output()to pass data from Lua back to the AIPack context for markdown stages.aipack.sleep()is useful for pauses. - Conditional Logic:
if/elseif/elsestatements in Lua allow agents to branch execution paths based on various conditions. - Looping for Robustness:
whileandforloops enable agents to retry operations, iterate over data, and build more resilient workflows. - Debugging:
print()statements are your best friend for understanding Lua’s execution flow.
With Lua in your toolkit, your AIPack agents are no longer just prompt wrappers; they are capable, adaptive programs that can handle complexity, errors, and diverse user intentions.
In the next chapter, we’ll explore how to connect your AIPack agents to the outside world by diving into various AI model provider integrations, from local models like Ollama to powerful cloud APIs.
References
This page is AI-assisted and reviewed. It references official documentation and recognized resources where relevant.