3 Everything as Code
We’ve seen what Tactus is and how transparent durability keeps workflows from losing their place. Now let’s explore another fundamental design principle: everything as code.
In Tactus, an agent’s entire definition—its prompts, tools, orchestration logic, and even its tests—is readable as a coherent whole, not scattered across abstraction layers. This isn’t just a convenience; it enables something profound: agents that can understand and improve themselves.
3.1 The Problem with Scattered Definitions
In traditional frameworks, agent logic is scattered:
# agents/researcher.py
class ResearcherAgent:
def __init__(self, model="gpt-4"):
self.model = model
self.system_prompt = load_prompt("prompts/researcher.txt")
self.tools = [SearchTool(), AnalyzeTool()]
async def run(self, query):
# Complex orchestration logic here
pass
# prompts/researcher.txt
You are a research assistant...
# tools/search.py
class SearchTool:
name = "search"
description = "Search the web"
# ...
# tests/test_researcher.py
def test_researcher_calls_search():
# Test implementation
pass
# config/agents.yaml
researcher:
model: gpt-4
temperature: 0.7To understand this agent, you need to read five different files across four directories. The definition is fragmented.
Now imagine an AI trying to understand this agent—perhaps to improve it or adapt it for a new task. The AI would need to:
- Parse Python class hierarchies
- Follow import statements
- Read configuration files
- Understand the relationship between components
- Synthesize a coherent picture
It’s possible, but awkward. The agent’s definition isn’t legible as a whole.
3.2 The Tactus Approach: Readable, Complete Definitions
Here’s a complete, single-file Tactus program (copied directly from the main Tactus repo):
--[[
Example: Individual Lua Function Tools
Demonstrates defining tools using the tool() function.
Each tool() declaration creates a single-tool toolset that can be
referenced by name in agent configurations.
To run this example:
tactus run examples/18-feature-lua-tools-individual.tac --param task="Calculate 15% tip on a $50 bill"
]]--
-- Mock configuration for testing (only active in mock mode)
Mocks {
calculator = {
tool_calls = {
{tool = "calculate_tip", args = {bill_amount = "50", tip_percentage = "20"}},
{tool = "done", args = {reason = "Bill: $50.00, Tip (20%): $10.00, Total: $60.00"}}
},
message = "I'll calculate the 20% tip on $50."
}
}
-- Define completion tool
local done = require("tactus.tools.done")
-- Define individual tools using the Tool function
calculate_tip = Tool {
description = "Calculate tip amount for a bill",
input = {
bill_amount = field.number{required = true, description = "Total bill amount in dollars"},
tip_percentage = field.number{required = true, description = "Tip percentage (e.g., 15 for 15%)"},
},
function(args)
local tip = args.bill_amount * (args.tip_percentage / 100)
local total = args.bill_amount + tip
return string.format("Bill: $%.2f, Tip (%.0f%%): $%.2f, Total: $%.2f",
args.bill_amount, args.tip_percentage, tip, total)
end
}
split_bill = Tool {
description = "Split a bill total among multiple people",
input = {
total_amount = field.number{required = true, description = "Total amount to split"},
num_people = field.integer{required = true, description = "Number of people to split among"},
},
function(args)
local per_person = args.total_amount / args.num_people
return string.format("Split $%.2f among %d people = $%.2f per person",
args.total_amount, args.num_people, per_person)
end
}
calculate_discount = Tool {
description = "Calculate price after discount",
input = {
original_price = field.number{required = true, description = "Original price"},
discount_percent = field.number{required = true, description = "Discount percentage"},
},
function(args)
local discount_amount = args.original_price * (args.discount_percent / 100)
local final_price = args.original_price - discount_amount
return string.format("Original: $%.2f, Discount (%.0f%%): $%.2f, Final: $%.2f",
args.original_price, args.discount_percent, discount_amount, final_price)
end
}
-- Agent with access to individual Lua tools
calculator = Agent {
model = "openai/gpt-4o-mini",
tool_choice = "required",
system_prompt = [[You are a helpful calculator assistant.
IMPORTANT: You MUST call the appropriate tool for EVERY calculation. Never calculate directly.
After calling the calculation tool, call done with the result.]],
initial_message = "{input.task}",
tools = {calculate_tip, split_bill, calculate_discount, done}
}
-- Main workflow
Procedure {
input = {
task = field.string{description = "Calculation task to perform", default = "Calculate 20% tip on $50"}
},
output = {
result = field.string{required = true, description = "The calculation result"},
completed = field.boolean{required = true, description = "Whether the task was completed successfully"}
},
function(input)
local max_turns = 5
local turn_count = 0
local result
repeat
result = calculator()
turn_count = turn_count + 1
-- Log tool usage
if Tool.called("calculate_tip") then
Log.info("Used tip calculator")
end
if Tool.called("split_bill") then
Log.info("Used bill splitter")
end
if Tool.called("calculate_discount") then
Log.info("Used discount calculator")
end
until done.called() or turn_count >= max_turns
-- Get final result
local answer
if done.called() then
local call = done.last_call()
answer = (call and call.args and call.args.reason) or "Task completed"
else
answer = (result and result.output) or ""
end
return {
result = answer,
completed = done.called()
}
end
}
-- BDD Specifications
Specifications([[
Feature: Individual Lua Function Tools
Demonstrate tool() function for defining individual tools
Scenario: Calculator calculates 20% tip on $50
Given the procedure has started
When the procedure runs
Then the procedure should complete successfully
And the output completed should be True
And the calculate_tip tool should be called
And the calculate_tip tool should be called with bill_amount=50
And the calculate_tip tool should be called with tip_percentage=20
And the output result should match pattern "\$10\.00"
]])Run the copy in this book repo:
tactus run code/chapter-03/18-feature-lua-tools-individual.tac --param task="Calculate 15% tip on a $50 bill"A complete, readable definition:
- Tools: Defined inline with their schemas and handlers
- Agent: Configuration, system prompt, tool access
- Orchestration: The procedure that drives the agent
- Tests: BDD specifications embedded alongside code
Notice the Specifications block at the end. Testing isn’t separate from development in Tactus—it’s embedded in the same file. This reflects years of experience with TDD and BDD in mission-critical systems: when tests live alongside the code they verify, they stay in sync and actually get maintained.
This matters even more for agent-based code. Since agents are non-deterministic, you need both behavioral specifications (does it do the right thing?) and evaluations (how reliably?). Keeping both in the same file means you’re always thinking about reliability, not just correctness.
An AI can read this definition and understand the complete agent.
3.3 Why Readable Definitions Matter
3.3.1 For Humans: Easier Understanding
When debugging or modifying an agent, you don’t chase imports across a codebase. Everything relevant is visible in one place. You can see how the system prompt relates to the available tools, how the orchestration loop works, and what behavior the tests expect.
3.3.2 For Version Control: Clean Diffs
When an agent changes, the diff shows exactly what changed. Compare:
Python framework diff:
--- a/agents/researcher.py
+++ b/agents/researcher.py
@@ -15,7 +15,7 @@ class ResearcherAgent:
- self.max_iterations = 10
+ self.max_iterations = 15
--- a/prompts/researcher.txt
+++ b/prompts/researcher.txt
@@ -3,6 +3,7 @@
+Be thorough but efficient.Two files changed. Readers must mentally combine changes to understand the modification.
With Tactus, the prompt, tool definitions, orchestration loop, and specifications live together. A single-file diff usually contains the full story of a behavioral change.
3.3.3 For AI: Self-Evolution Potential
This is the profound implication. When an agent’s definition is:
- Readable as a coherent whole
- In a minimal, clear syntax
- Compact enough to fit in a context window
…then an AI can read, understand, and modify it.
3.4 The Self-Evolution Vision
Imagine an agent that can:
- Read its own
.tacfile - Analyze its performance (via logs, evaluations, user feedback)
- Identify improvements to make
- Rewrite its own definition
- Test the new version
- Deploy if tests pass
This isn’t science fiction. The architecture makes it possible: a Tactus procedure is a compact, readable artifact that another agent can load, reason about, rewrite, and then test. We’ll return to this idea later in the book after we’ve covered tools, state, and testing.
3.5 Token Efficiency: Fitting in Context Windows
Tactus is deliberately minimal. Compare token counts for equivalent functionality:
Python framework (across multiple files):
Typical agent: 800-1500 tokens
Tactus (complete definition):
Typical agent: 300-600 tokens
This efficiency matters when you want an AI to:
- Read multiple agent definitions for comparison
- Include agent definitions in prompts
- Generate or modify agent code
More agents fit in the context window. Modifications produce smaller diffs. The syntax overhead is minimal.
There’s another advantage: Tactus is intentionally aligned with DSPy’s design (signatures, modules, history). Because many coding assistants have already seen DSPy patterns during pre-training, they can often transfer that knowledge when reading or generating Tactus—while you still get a higher-level, durable DSL.
3.6 Safe Embedding: Why Lua Matters
You might wonder: why Lua instead of Python?
Python cannot be safely sandboxed. A Python agent can: - Access the file system - Make network requests - Import arbitrary modules - Execute shell commands
Lua was designed for embedding. Tactus runs agents in a restricted Lua VM where:
- File system access is disabled by default
- Network access is disabled by default
- Only explicitly provided tools grant capabilities
This makes Tactus safe for:
- Multi-tenant platforms: User A’s agent can’t affect User B
- User-contributed agents: Run untrusted code safely
- AI-generated code: Let agents write and execute orchestration logic
The tools you provide are the only capabilities the agent has. Everything else is sandboxed away.
3.7 A Complete, Self-Contained Example
The runnable example shown above lives at code/chapter-03/18-feature-lua-tools-individual.tac.
This definition contains:
- Tool definitions with typed schemas
- An agent with its complete system prompt
- Orchestration logic with safety limits
- Input/output contracts
- BDD specifications for testing
A human can understand it. An AI can read and modify it. It runs in a sandbox. And it’s durable—if the process crashes mid-processing, it resumes.
3.8 Summary: The Everything-as-Code Advantage
Tactus makes agent definitions readable and complete:
| Benefit | Why It Matters |
|---|---|
| Comprehension | Understand agents without chasing abstraction layers |
| Clean diffs | Version control shows meaningful changes |
| Token efficiency | More agents fit in LLM context windows |
| Self-evolution | AIs can read, modify, and improve agents |
| Secure execution | Sandboxed Lua prevents escape |
Later in the book, we’ll combine this “everything-as-code” approach with durability, human-in-the-loop, and tests/evaluations to build workflows you can trust unattended.
Next, we’ll get hands-on: install Tactus and write your first agent.