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.7

To 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:

  1. Parse Python class hierarchies
  2. Follow import statements
  3. Read configuration files
  4. Understand the relationship between components
  5. 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:

  1. Readable as a coherent whole
  2. In a minimal, clear syntax
  3. 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:

  1. Read its own .tac file
  2. Analyze its performance (via logs, evaluations, user feedback)
  3. Identify improvements to make
  4. Rewrite its own definition
  5. Test the new version
  6. 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:

  1. Tool definitions with typed schemas
  2. An agent with its complete system prompt
  3. Orchestration logic with safety limits
  4. Input/output contracts
  5. 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.