Calling Reasoners
Execute reasoners with automatic parent-child workflow tracking
Calling Reasoners
Execute reasoners across agents with automatic parent-child workflow relationships
The agent.Call() method executes reasoners via the Agentfield control plane and automatically creates parent-child workflow relationships for visualization and tracking.
Zero-Configuration Workflow Tracking: Parent-child relationships are created automatically. You don't need to manually track execution hierarchies or build DAGs - the SDK and Agentfield control plane handle everything.
Basic Usage
result, err := app.Call(ctx, "agent-name.reasoner-name", map[string]any{
"input_key": "input_value",
})
if err != nil {
log.Fatal(err)
}
// result is map[string]any containing the reasoner's response
fmt.Printf("Result: %v\n", result["output_key"])Method Signature
func (a *Agent) Call(
ctx context.Context,
target string,
input map[string]any,
) (map[string]any, error)Parameters
- ctx: Context for cancellation and timeouts
- target: Reasoner identifier in format
"agent-id.reasoner-name" - input: Input data as a map (must be JSON-serializable)
Target Format
The target string specifies which reasoner to call:
// Call another agent's reasoner
result, err := app.Call(ctx, "data-processor.transform", input)
// Call own reasoner (creates parent-child relationship)
result, err := app.Call(ctx, "my-agent.validate", input)
// Call reasoner on different team (if multi-tenant)
result, err := app.Call(ctx, "team-b-agent.process", input)Automatic Workflow Tracking
When you call a reasoner from within another reasoner, the SDK automatically:
- Extracts current execution context from
ctx - Sets
X-Parent-Execution-IDheader - Agentfield creates parent-child relationship in database
- Builds workflow DAG for UI visualization
Example: Automatic Parent-Child
// Parent reasoner
app.RegisterReasoner("orchestrate", func(ctx context.Context, input map[string]any) (any, error) {
log.Println("Starting orchestration...")
// Call child reasoner - automatically creates parent-child link
result1, err := app.Call(ctx, "my-agent.analyze", input)
if err != nil {
return nil, err
}
// Another child - also linked to parent "orchestrate"
result2, err := app.Call(ctx, "my-agent.summarize", result1)
if err != nil {
return nil, err
}
return map[string]any{
"analysis": result1,
"summary": result2,
}, nil
})Agentfield automatically tracks:
orchestrate (parent)
├─> analyze (child)
└─> summarize (child)Common Patterns
Sequential Workflow
Execute reasoners one after another:
app.RegisterReasoner("pipeline", func(ctx context.Context, input map[string]any) (any, error) {
// Step 1: Extract
extracted, err := app.Call(ctx, "my-agent.extract", input)
if err != nil {
return nil, fmt.Errorf("extract failed: %w", err)
}
// Step 2: Transform (uses result from step 1)
transformed, err := app.Call(ctx, "my-agent.transform", extracted)
if err != nil {
return nil, fmt.Errorf("transform failed: %w", err)
}
// Step 3: Load
loaded, err := app.Call(ctx, "my-agent.load", transformed)
if err != nil {
return nil, fmt.Errorf("load failed: %w", err)
}
// Agentfield tracks: pipeline -> extract -> transform -> load
return loaded, nil
})Parallel Execution
Use goroutines for concurrent reasoner calls:
app.RegisterReasoner("parallel_process", func(ctx context.Context, input map[string]any) (any, error) {
var wg sync.WaitGroup
results := make([]map[string]any, 4)
errors := make([]error, 4)
tasks := []string{"task1", "task2", "task3", "task4"}
for i, task := range tasks {
wg.Add(1)
go func(index int, taskName string) {
defer wg.Done()
// Each call creates its own child execution
result, err := app.Call(ctx, fmt.Sprintf("my-agent.%s", taskName), input)
if err != nil {
errors[index] = err
return
}
results[index] = result
}(i, task)
}
wg.Wait()
// Check for errors
for i, err := range errors {
if err != nil {
return nil, fmt.Errorf("task %d failed: %w", i, err)
}
}
// Agentfield tracks all 4 as parallel children
return map[string]any{"results": results}, nil
})Agentfield visualizes:
parallel_process (parent)
├─> task1 (child)
├─> task2 (child)
├─> task3 (child)
└─> task4 (child)Conditional Execution
Execute different reasoners based on logic:
app.RegisterReasoner("conditional_flow", func(ctx context.Context, input map[string]any) (any, error) {
dataType := input["type"].(string)
var result map[string]any
var err error
switch dataType {
case "text":
result, err = app.Call(ctx, "my-agent.process_text", input)
case "image":
result, err = app.Call(ctx, "my-agent.process_image", input)
case "audio":
result, err = app.Call(ctx, "my-agent.process_audio", input)
default:
return nil, fmt.Errorf("unknown type: %s", dataType)
}
if err != nil {
return nil, err
}
// Agentfield tracks which path was taken
return result, nil
})Multi-Agent Orchestration
Call reasoners across different agents:
app.RegisterReasoner("cross_agent_workflow", func(ctx context.Context, input map[string]any) (any, error) {
// Step 1: Data extraction agent
extracted, err := app.Call(ctx, "data-extractor.extract", input)
if err != nil {
return nil, err
}
// Step 2: AI analysis agent
analyzed, err := app.Call(ctx, "ai-analyzer.analyze", extracted)
if err != nil {
return nil, err
}
// Step 3: Reporting agent
report, err := app.Call(ctx, "reporter.generate", analyzed)
if err != nil {
return nil, err
}
// Agentfield tracks cross-agent workflow
return report, nil
})Error Recovery
Implement fallback logic:
app.RegisterReasoner("resilient_call", func(ctx context.Context, input map[string]any) (any, error) {
// Try primary reasoner
result, err := app.Call(ctx, "my-agent.primary_processor", input)
if err == nil {
return result, nil
}
log.Printf("Primary failed: %v, trying fallback...", err)
// Fallback to simpler processor
result, err = app.Call(ctx, "my-agent.fallback_processor", input)
if err != nil {
return nil, fmt.Errorf("both primary and fallback failed: %w", err)
}
return map[string]any{
"result": result,
"fallback": true,
}, nil
})Execution Context
The ctx parameter carries execution context through the call chain. This context is automatically extracted and used to set parent-child relationships.
Context Propagation
// The context flows through the entire chain
func handler1(ctx context.Context, input map[string]any) (any, error) {
// ctx contains execution metadata
// SDK extracts ExecutionID automatically
// When we call another reasoner, ctx is used to set parent relationship
return app.Call(ctx, "my-agent.handler2", input)
}
func handler2(ctx context.Context, input map[string]any) (any, error) {
// This execution knows its parent is handler1
// All automatic - no manual tracking needed
return processData(input)
}Timeouts and Cancellation
Use context for timeout control:
app.RegisterReasoner("with_timeout", func(ctx context.Context, input map[string]any) (any, error) {
// Create timeout context for child call
timeoutCtx, cancel := context.WithTimeout(ctx, 10*time.Second)
defer cancel()
result, err := app.Call(timeoutCtx, "my-agent.slow_task", input)
if err != nil {
if errors.Is(err, context.DeadlineExceeded) {
return nil, fmt.Errorf("task timed out after 10s")
}
return nil, err
}
return result, nil
})Best Practices
Always Pass Context
// ✅ Good - uses context from parameters
app.RegisterReasoner("handler", func(ctx context.Context, input map[string]any) (any, error) {
return app.Call(ctx, "my-agent.worker", input)
})
// ❌ Bad - creates new context (breaks workflow tracking)
app.RegisterReasoner("handler", func(ctx context.Context, input map[string]any) (any, error) {
newCtx := context.Background() // Don't do this!
return app.Call(newCtx, "my-agent.worker", input)
})Handle Errors Gracefully
// ✅ Good - handles errors with context
result, err := app.Call(ctx, "my-agent.risky", input)
if err != nil {
log.Printf("Reasoner call failed: %v", err)
return map[string]any{
"status": "failed",
"error": err.Error(),
}, nil // Return structured error, don't propagate
}Use Structured Input/Output
// ✅ Good - clear structure
input := map[string]any{
"operation": "analyze",
"data": data,
"options": map[string]any{
"detailed": true,
"format": "json",
},
}
result, err := app.Call(ctx, "my-agent.process", input)Workflow Visualization
All reasoner calls are automatically tracked and visualized in the Agentfield UI:
- Navigate to Workflow tab in Agentfield UI
- Select your execution run
- See the complete DAG with:
- Parent-child relationships
- Execution times
- Status (success/failed)
- Input/output data
The SDK sets these headers automatically:
X-Run-ID: Groups related executionsX-Execution-ID: Unique ID for this executionX-Parent-Execution-ID: Links to parent execution
Agentfield uses these to build the workflow DAG.