Registering Reasoners
Define and register reasoners that Agentfield can execute
Registering Reasoners
Define executable reasoners that can be called by Agentfield or other agents
Reasoners are the core execution units in Agentfield. Register them with your agent to make them callable via the Agentfield control plane.
Basic Registration
app.RegisterReasoner("reasoner_name", func(ctx context.Context, input map[string]any) (any, error) {
// Process input
data := input["key"].(string)
// Return result
return map[string]any{
"result": "processed: " + data,
}, nil
})Method Signature
func (a *Agent) RegisterReasoner(
name string,
handler HandlerFunc,
opts ...ReasonerOption,
)
type HandlerFunc func(context.Context, map[string]any) (any, error)Handler Requirements
Your handler function must:
- Accept
context.Contextas first parameter - Accept
map[string]anyas input - Return
(any, error) - Return JSON-serializable types
The returned value must be JSON-serializable. Use map[string]any, structs with JSON tags, or primitive types.
Common Patterns
Simple Processing
app.RegisterReasoner("greet", func(ctx context.Context, input map[string]any) (any, error) {
name, ok := input["name"].(string)
if !ok {
return nil, fmt.Errorf("name is required")
}
return map[string]any{
"message": fmt.Sprintf("Hello, %s!", name),
}, nil
})With AI Integration
app.RegisterReasoner("analyze_text", func(ctx context.Context, input map[string]any) (any, error) {
text := input["text"].(string)
// Use AI for intelligent processing
response, err := app.AI(ctx,
fmt.Sprintf("Analyze this text: %s", text),
ai.WithSchema(Analysis{}))
if err != nil {
return nil, err
}
var analysis Analysis
response.Into(&analysis)
return map[string]any{
"analysis": analysis,
"tokens": response.Usage.TotalTokens,
}, nil
})Calling Other Reasoners
app.RegisterReasoner("orchestrate", func(ctx context.Context, input map[string]any) (any, error) {
// Call child reasoners
result1, err := app.Call(ctx, "my-agent.step1", input)
if err != nil {
return nil, err
}
result2, err := app.Call(ctx, "my-agent.step2", result1)
if err != nil {
return nil, err
}
return result2, nil
})Input Validation
app.RegisterReasoner("process_order", func(ctx context.Context, input map[string]any) (any, error) {
// Validate required fields
orderID, ok := input["order_id"].(string)
if !ok || orderID == "" {
return nil, fmt.Errorf("order_id is required")
}
amount, ok := input["amount"].(float64)
if !ok || amount <= 0 {
return nil, fmt.Errorf("amount must be positive number")
}
// Process order
return map[string]any{
"order_id": orderID,
"status": "processed",
"total": amount * 1.1, // Add tax
}, nil
})Reasoner Options
CLI Options
Enable reasoners as CLI commands for dual-mode binaries.
WithCLI()
Expose this reasoner as a CLI command:
app.RegisterReasoner("greet", greetHandler, agent.WithCLI())
// Usage: ./binary greet --set name=AliceWithDefaultCLI()
Make this reasoner the default CLI command (must be used with WithCLI()):
app.RegisterReasoner("process", processHandler,
agent.WithCLI(),
agent.WithDefaultCLI(),
)
// Usage: ./binary --set input=data (no command name needed)WithCLIFormatter(fn)
Customize CLI output formatting:
app.RegisterReasoner("analyze", analyzeHandler,
agent.WithCLI(),
agent.WithCLIFormatter(func(ctx context.Context, result any, err error) {
if err != nil {
fmt.Fprintf(os.Stderr, "❌ Error: %v\n", err)
os.Exit(1)
return
}
data := result.(map[string]any)
fmt.Printf("✓ Sentiment: %s (%.1f%% confidence)\n",
data["sentiment"], data["confidence"].(float64)*100)
}),
)WithDescription(desc)
Add description for help text:
app.RegisterReasoner("analyze", handler,
agent.WithCLI(),
agent.WithDescription("Analyze text with AI"),
)Complete CLI Example:
app.RegisterReasoner("greet", func(ctx context.Context, input map[string]any) (any, error) {
name := input["name"].(string)
// Detect CLI mode for custom behavior
if agent.IsCLIMode(ctx) {
return fmt.Sprintf("Hello, %s!", name), nil
}
// Server mode: structured response
return map[string]any{
"greeting": fmt.Sprintf("Hello, %s!", name),
"timestamp": time.Now().Unix(),
}, nil
},
agent.WithCLI(),
agent.WithDefaultCLI(),
agent.WithDescription("Greet a user by name"),
)CLI Input Methods:
--set key=value- Individual parameters (repeatable)--input '{"key":"value"}'- JSON string--input-file data.json- JSON file- Stdin pipe (implicit)
Priority: --set > --input > --input-file > stdin
See CLI-Enabled Reasoners for complete documentation.
Best Practices
Return Structured Data
// ✅ Good - structured response
return map[string]any{
"status": "success",
"data": result,
"meta": map[string]any{
"processed_at": time.Now().Unix(),
"version": "1.0",
},
}, nil
// ❌ Bad - unclear structure
return result, nilHandle Errors Gracefully
// ✅ Good - returns error
if err != nil {
return nil, fmt.Errorf("processing failed: %w", err)
}
// ✅ Also good - returns structured error response
if err != nil {
return map[string]any{
"status": "error",
"error": err.Error(),
"code": "PROCESSING_FAILED",
}, nil
}Use Context
// ✅ Good - respects context cancellation
select {
case <-ctx.Done():
return nil, ctx.Err()
case result := <-resultChan:
return result, nil
}