Self-compiling agent runtime — agents build their own interpreters, compilers, and runtimes
Part of the PLATO/LAU mathematical agent framework.
Most agent frameworks give agents a fixed set of tools and say "good luck." This crate takes a different approach: agents compile their own task-specific mini-runtimes, complete with bytecode instructions, builtin functions, energy budgets, and state snapshots.
The core loop:
- An agent receives a task description (e.g., "monitor sensors", "control motor speed")
- The
RuntimeCompilermatches it to a pre-built DSL program and compiles it into bytecode - A
MiniRuntimeVM executes the bytecode with energy tracking, stack operations, variable storage, and builtin function calls - The
AgentShellmanages multiple runtimes, tracks abstraction levels, and saves/restores state
Each compilation increases the agent's abstraction level (capped at 3), representing how many layers of self-improvement it has undergone. Agents that compile more runtimes save more tokens in future interactions because they operate at higher levels of abstraction.
An agent that can build its own runtime is an agent that can become specialized. Instead of carrying every capability at once, it compiles exactly what it needs for the current task — and gets better at compiling over time.
The runtime is energy-budgeted: every instruction has a cost, and execution halts if the budget is exhausted. This enforces conservation laws at the computational level.
[dependencies]
lau-agent-runtime = { git = "https://github.com/SuperInstance/lau-agent-runtime" }Or:
cargo add lau-agent-runtime- Rust 2021 edition
serdewithderivefeatureserde_json
use lau_agent_runtime::*;
// Create a shell for an agent with 1000 energy units
let mut shell = AgentShell::new("journeyman", 1000.0);
// Build a runtime for a task
let rt_id = shell.build_runtime("monitor sensors", "iot");
// Register a builtin function
fn read_sensors(_args: Vec<Value>) -> Value {
let mut m = std::collections::HashMap::new();
m.insert("temp".to_string(), Value::Number(22.5));
Value::Map(m)
}
shell.runtimes.get_mut(&rt_id).unwrap().register_builtin("read_sensors", read_sensors);
// Compile a custom program into the runtime
let rt = shell.runtimes.get_mut(&rt_id).unwrap();
rt.compile(r#"
call read_sensors 0
store sensor_data
observe temp
return
"#).unwrap();
// Activate and execute
shell.activate(&rt_id);
let result = shell.execute().unwrap();
println!("Success: {}", result.success);
println!("Output: {:?}", result.output);
println!("Energy consumed: {:.2}", result.energy_consumed);
println!("Instructions executed: {}", result.instructions_executed);| Type | Description |
|---|---|
RuntimeId(String) |
Unique identifier (auto-generated with atomic counter) |
Value |
Runtime value: Number, Text, Bool, List, Map, Null, Reference |
Instruction |
Bytecode op: Push, Pop, Call, Store, Load, JumpIf, Return, EnergyCheck, Delegate, Observe, Assert |
RuntimeResult |
Execution result: success flag, output value, energy consumed, instructions executed |
StepResult |
Single-step result with remaining energy and done flag |
RuntimeSnapshot |
Saveable state: memory, energy used, compilation level, timestamp |
MiniRuntime |
The VM: instructions, builtins, memory, stack, energy, program counter |
RuntimeCompiler |
Compiles DSL source text into instruction vectors |
AbstractionLayer |
Tracks compilation levels and token savings |
AgentShell |
Top-level workspace: manages multiple runtimes, activation, status |
pub enum Value {
Number(f64),
Text(String),
Bool(bool),
List(Vec<Value>),
Map(HashMap<String, Value>),
Null,
Reference(String),
}Methods: is_truthy(), as_f64(), as_str(), type_name(). Implements PartialEq, Default (=Null), Serialize/Deserialize.
The DSL is line-based, one instruction per line. Comments start with #. Blank lines are ignored.
push <number|text|true|false|null|"quoted">
pop
call <name> <argc>
store <variable>
load <variable>
jump_if <address>
return
energy_check <threshold>
delegate <agent> <task>
observe <field>
assert <description...>
| Instruction | Cost |
|---|---|
Push |
0.1 |
Pop |
0.05 |
Store / Load |
0.1 |
Call |
1.0 |
Delegate |
2.0 |
Observe |
0.3 |
Assert |
0.2 |
JumpIf / Return / EnergyCheck |
0.05 |
new(name, domain, budget) → Selfregister_builtin(name, fn)— registerfn(Vec<Value>) → Valuecompile(source: &str) → Result<Vec<Instruction>, String>— compile DSL, increment compilation levelexecute() → Result<RuntimeResult, String>— run all instructionsstep() → Result<StepResult, String>— single instructioncall(function, args) → Result<Value, String>— direct builtin callsave_state() → RuntimeSnapshot/load_state(&RuntimeSnapshot)is_conserved() → bool—energy_used ≤ budgetabstraction_level() → u32reset()— clear PC, stack, instruction count (keep energy)
compile_source(source: &str) → Result<Vec<Instruction>, String>— parse DSLcompile_task(task: &str, domain: &str, budget: f64) → MiniRuntime— pattern-match task to pre-built program
| Task keyword | Pattern |
|---|---|
| "sensor" / "monitor" | Load → read_sensors → store → check_thresholds → report |
| "motor" / "control" | Load target → compute_error → adjust_pwm → verify |
| "crew" / "manage" | assess_task → pick_archetype → assign → monitor |
| "analy" / "data" | load_data → compute_statistics → find_patterns → verify_conservation |
| "bridge" / "communic" | connect → handshake → send → receive → verify → disconnect |
| (default) | push null; return |
new() → Self— starts at level 0 ("Raw — no abstraction")promote(description)— add a new layeradd_savings(tokens)— track token savings at current levelrecord_runtime_built()— increment countercurrent_level() → u32/tokens_saved() → u64progress_report() → String
new(agent_id, budget) → Selfbuild_runtime(task, domain) → RuntimeId— compile and registeractivate(id)/deactivate()— switch active runtimeexecute() → Result<RuntimeResult, String>— run active runtimelist_runtimes() → Vec<(&RuntimeId, &str, u32)>— (id, domain, level)shell_status() → ShellStatus— agent ID, active runtime, count, abstraction level, tokens saved, energy remainingactive_runtime_mut() → Option<&mut MiniRuntime>
Value, Instruction, RuntimeResult, RuntimeSnapshot, ShellStatus, AbstractionLayer all derive Serialize + Deserialize. MiniRuntime does not (contains function pointers).
AgentShell
├── AbstractionLayer (tracks levels, savings)
├── MiniRuntime[0]: "monitor sensors" (iot domain)
│ ├── Instructions: [Load, Call, Store, ...]
│ ├── Builtins: {read_sensors → fn, ...}
│ ├── Memory: {sensor_data → Map{...}}
│ ├── Stack: [...]
│ └── Energy: used=3.2 / budget=100.0
├── MiniRuntime[1]: "control motor" (robotics)
└── MiniRuntime[2]: "analyze data" (data)
- Task description string →
RuntimeCompiler::compile_task()orcompile_source() - Pattern matching selects a DSL template (or raw DSL is provided)
- DSL is parsed line-by-line into
Vec<Instruction> - Compilation level increments (max 3)
- Instructions are stored in the
MiniRuntime
execute()resets PC and stack, enters main loop- Each
step()fetches instruction at PC, computes energy cost - If
energy_used + cost > budget→ error - Execute instruction (push/pop stack, call builtin, store/load memory, conditional jump)
- On
Returnor end-of-program → returnRuntimeResult
Every operation costs energy. The total energy consumed is bounded by the budget. EnergyCheck(threshold) explicitly tests remaining energy and fails if below threshold. Delegate costs the most (2.0 units) because sub-agent coordination is expensive.
E_total = Σᵢ c(iᵢ) ≤ B
where c: Instruction → ℝ⁺ is the per-instruction cost function and B is the energy budget. This is a computational Noether conservation law: total energy is monotone non-decreasing and bounded above.
Each compilation level represents a compression of lower-level operations into higher-level abstractions:
Level 0: Raw (no abstraction)
Level 1: Compiled (task → bytecode)
Level 2: Optimized (learned patterns)
Level 3: Mastery (maximum compression)
Token savings accumulate per level:
S_total = Σₗ Σᵣ s(l, r)
where s(l, r) is the token saving from runtime r at level l.
The VM is a standard stack machine with variable storage:
Stack: [v₁, v₂, ..., vₙ] (top = vₙ)
Memory: {x₁ → w₁, x₂ → w₂, ...}
Push(v): Stack' = Stack ++ [v]
Pop: Stack' = Stack[1..n-1], result = vₙ
Store(x): Memory' = Memory ∪ {x → vₙ}, Stack' = Stack[1..n-1]
Load(x): Stack' = Stack ++ [Memory[x]]
Call(f,k): pop k args, push f(args)
The cap at level 3 reflects the observation that beyond three layers of self-abstraction, diminishing returns set in:
compilation_level = min(compilation_level + 1, 3)
68 tests covering:
- Value truthiness (Number, Text, Bool, Null, List, Map), type accessors, type names, equality, default
- RuntimeId display, From<&str>, clone/hash/equality
- Instruction energy costs (relative ordering)
- MiniRuntime construction, conservation check, abstraction level
- Compilation: simple programs, push-store-load, builtin calls, jump-if-true/false, energy check, delegate, observe, assert
- Step execution: single instruction, energy exhaustion
- Direct builtin call, unknown builtin error
- Save/load state round-trip
- Reset clears execution state
- Compiler: empty source, comments/blanks, all push types, parse errors, task patterns (sensor, motor, crew, analyze, bridge, generic)
- AbstractionLayer: new, promote, savings, record runtime, progress report, default
- AgentShell: new, build runtime, activate/deactivate, unknown activation, execute no-active, execute active, list runtimes, status (with/without active), active_runtime_mut
- Serde round-trips (RuntimeResult, RuntimeSnapshot, ShellStatus, AbstractionLayer, Instruction, Value::Map)
- Full workflow integration, multiple runtimes, compilation level cap
Run: cargo test
MIT