-
Notifications
You must be signed in to change notification settings - Fork 0
Description
Parent: #235 (Swappable Engine)
Summary
A minimal loop-based EngineAdapter that proves the interface works without any graph concepts. This is the simplest possible engine: prompt → LLM → tool calls → repeat until done. No state machine, no graph, no checkpoints.
Inspired by OpenClaw's Docker-based ReAct loop and NanoClaw's VM-isolated agent loop. Proves that Templar's EngineAdapter contract is truly engine-agnostic — if a while(true) loop can implement it, nothing graph-specific has leaked in.
Estimated scope: ~300 LOC + tests
The Loop
class LoopEngineAdapter implements EngineAdapter {
readonly engineId = "loop";
async *stream(input: EngineInput): AsyncGenerator<EngineEvent> {
let messages = toMessages(input);
let turn = 0;
while (turn < this.maxTurns) {
// 1. Call LLM
const response = await this.llm.chat(messages);
// 2. Yield text deltas
for (const delta of response.textDeltas) {
yield { kind: "text_delta", delta };
}
// 3. If no tool calls, we're done
if (!response.toolCalls?.length) {
yield { kind: "done", output: { text: response.text, stopReason: "completed", usage: response.usage } };
return;
}
// 4. Execute tool calls
for (const call of response.toolCalls) {
yield { kind: "tool_call_start", toolName: call.name, callId: call.id };
const result = await this.toolExecutor.execute(call);
yield { kind: "tool_call_end", callId: call.id, result };
messages.push(toolResultMessage(call, result));
}
yield { kind: "turn_end", turnIndex: turn++ };
}
yield { kind: "done", output: { text: "", stopReason: "max_turns" } };
}
}Why This Matters
- Anti-leak proof — If the simplest possible loop implements
EngineAdapter, the interface is clean - OpenClaw parity — OpenClaw's Docker ReAct loop is essentially this pattern
- NanoClaw parity — NanoClaw's VM-isolated agent is a loop inside a sandbox
- Lightweight agents — Not every agent needs a graph state machine. Simple tasks deserve simple engines.
- Testing — Easy to test middleware in isolation (predictable execution)
- Edge/embedded — Minimal footprint for resource-constrained environments
Deliverables
-
@templar/engine-looppackage (~300 LOC)LoopEngineAdapterimplementingEngineAdapter- Configurable:
maxTurns,llmProvider,toolExecutor - No dependencies on LangGraph or DeepAgents
-
Tests (~200 LOC)
- Basic loop: prompt → response → done
- Tool calling: prompt → tool call → tool result → response → done
- Max turns: loop hits limit → stops with
max_turnsreason - Interruption: external signal stops the loop
- State save/load (optional methods)
-
Integration test
- Run same agent manifest with
engine: loopandengine: deepagents - Verify identical tool call sequences for deterministic prompts
- Run same agent manifest with
Manifest Usage
# templar.yaml
name: simple-agent
engine: loop
model:
provider: anthropic
name: claude-sonnet-4-5-20250929
tools:
- name: web_search
description: Search the webDependencies
- Depends on: feat: Swappable Engine — EngineAdapter as 6th core contract (Linux sched_class model) #235 (EngineAdapter interface must exist in core)
- Depends on: refactor: @templar/core — slim kernel to ~20 true kernel types (Linux syscall table model) #243 (kernel slimming — EngineAdapter is part of the 6 contracts)
- No Nexus dependency
- No LangGraph dependency
- No DeepAgents dependency
Linux Parallel
This is like Linux's SCHED_FIFO — the simplest scheduler. No time-slicing, no priorities, no load balancing. Just run until done. It exists because sometimes simple is exactly right, and it proves the sched_class interface doesn't require CFS complexity.