Overview
The ReasoningEngine interface is the contract that all reasoning engines must implement. It defines how agents orchestrate model calls, tool usage, and decision-making during execution.
Built-in engines include:
Interface Definition
export interface ReasoningEngine<TData = unknown> {
readonly name: string
execute(rCtx: ReasoningContext<TData>): Promise<string>
}
Properties
Unique identifier for the engine. Used in step emissions and error messages.
Methods
execute
(rCtx: ReasoningContext<TData>) => Promise<string>
required
Main execution method. Receives a ReasoningContext with full runtime environment and returns the final string output.
ReasoningContext
The ReasoningContext provides engines with everything they need to execute:
export interface ReasoningContext<TData = unknown> {
ctx: ExecutionContext<TData> // Enclosing execution context
model: ModelProvider // Configured model provider
tools: ToolRegistry // All registered tools
policy: AgentPolicy // Agent policy constraints
systemPrompt?: string // System prompt (if set)
pushStep(step: ReasoningStep): void // Append step and emit event
callTool(name: string, args: Record<string, unknown>, callId: string): Promise<unknown>
}
Key Context Members
ctx.state.messages: Current conversation history (ModelMessage[])
ctx.input: User’s input for this run
ctx.data: User-defined typed state
model.complete(): Call the model with messages and tools
tools.getSchemas(): Get all available tool schemas
pushStep(): Emit reasoning steps for observability
callTool(): Execute a tool and record the result
Creating a Custom Engine
Basic Example
Here’s a minimal custom engine that calls the model once:
import type { ReasoningEngine, ReasoningContext, ResponseStep } from '@agentlib/core'
export class SimpleEngine<TData = unknown> implements ReasoningEngine<TData> {
readonly name = 'simple'
async execute(rCtx: ReasoningContext<TData>): Promise<string> {
const { ctx, model, tools, policy } = rCtx
// Get available tool schemas
const toolSchemas = tools
.getSchemas()
.filter(t => tools.isAllowed(t.name, policy.allowedTools))
// Call model
const response = await model.complete({
messages: ctx.state.messages,
tools: toolSchemas
})
// Append response to messages
ctx.state.messages.push(response.message)
// Track token usage
if (response.usage) {
ctx.state.usage.promptTokens += response.usage.promptTokens
ctx.state.usage.completionTokens += response.usage.completionTokens
ctx.state.usage.totalTokens += response.usage.totalTokens
}
// Emit response step
const step: ResponseStep = {
type: 'response',
content: response.message.content,
engine: this.name
}
rCtx.pushStep(step)
return response.message.content
}
}
Advanced Example: Custom Loop
Here’s an engine with a custom reasoning loop:
import type { ReasoningEngine, ReasoningContext, ThoughtStep, ResponseStep } from '@agentlib/core'
import { callModel, executeToolCalls } from '@agentlib/reasoning/utils'
export interface CustomEngineConfig {
maxIterations?: number
retryOnError?: boolean
}
export class CustomEngine<TData = unknown> implements ReasoningEngine<TData> {
readonly name = 'custom'
private maxIterations: number
private retryOnError: boolean
constructor(config: CustomEngineConfig = {}) {
this.maxIterations = config.maxIterations ?? 5
this.retryOnError = config.retryOnError ?? false
}
async execute(rCtx: ReasoningContext<TData>): Promise<string> {
const { ctx } = rCtx
let iteration = 0
while (iteration < this.maxIterations) {
// Emit thought step
rCtx.pushStep({
type: 'thought',
content: `Starting iteration ${iteration + 1}`,
engine: this.name
} satisfies ThoughtStep)
try {
// Call model
const response = await callModel(rCtx, ctx.state.messages)
ctx.state.messages.push(response.message)
// No tool calls → done
if (!response.toolCalls?.length) {
rCtx.pushStep({
type: 'response',
content: response.message.content,
engine: this.name
} satisfies ResponseStep)
return response.message.content
}
// Execute tools
await executeToolCalls(rCtx, response)
iteration++
} catch (error) {
if (!this.retryOnError) throw error
// Add error context to messages and retry
ctx.state.messages.push({
role: 'system',
content: `Error occurred: ${error.message}. Please try a different approach.`
})
iteration++
}
}
throw new Error(`[CustomEngine] Max iterations (${this.maxIterations}) reached`)
}
}
Utility Functions
The @agentlib/reasoning package exports helper utilities:
callModel
import { callModel } from '@agentlib/reasoning/utils'
const response = await callModel(rCtx, messages, { noTools: false })
Handles model calls with automatic:
- Tool schema filtering by policy
- Token usage tracking
- Budget enforcement
import { executeToolCalls } from '@agentlib/reasoning/utils'
await executeToolCalls(rCtx, response)
Executes all tool calls from a model response and appends results to messages.
import { extractText } from '@agentlib/reasoning/utils'
const clean = extractText(response.message.content)
Removes XML tags like <thinking> from content.
Registering Custom Engines
Register your engine to use string aliases:
import { registerEngine } from '@agentlib/core'
import { CustomEngine } from './custom-engine'
registerEngine('custom', () => new CustomEngine())
// Now you can use:
const agent = new Agent({
name: 'my-agent',
reasoning: 'custom'
})
Or use the instance directly:
const agent = new Agent({
name: 'my-agent',
reasoning: new CustomEngine({ maxIterations: 10 })
})
Step Types
Engines can emit various step types via rCtx.pushStep():
ThoughtStep
{ type: 'thought', content: string, engine: string }
ResponseStep
{ type: 'response', content: string, engine: string }
PlanStep
{ type: 'plan', tasks: PlanTask[], engine: string }
ReflectionStep
{ type: 'reflection', assessment: string, needsRevision: boolean, engine: string }
{ type: 'tool_call', toolName: string, args: Record<string, unknown>, callId: string, engine: string }
{ type: 'tool_result', toolName: string, callId: string, result: unknown, error?: string, engine: string }
Best Practices
- Always track token usage: Increment
ctx.state.usage after model calls
- Respect policy constraints: Check
policy.tokenBudget, policy.allowedTools, etc.
- Emit steps for observability: Use
pushStep() liberally for debugging
- Handle errors gracefully: Provide clear error messages with engine name
- Append messages to ctx.state.messages: Keep conversation history up to date
- Use utility functions: Leverage
callModel and executeToolCalls to reduce boilerplate
Implementation References
- ReactEngine:
packages/reasoning/src/engines/react.ts:31
- ChainOfThoughtEngine:
packages/reasoning/src/engines/cot.ts:53
- PlannerEngine:
packages/reasoning/src/engines/planner.ts:70
- ReflectEngine:
packages/reasoning/src/engines/reflect.ts:65
- AutonomousEngine:
packages/reasoning/src/engines/autonomous.ts:49
- Types:
packages/core/src/types/index.ts:206