package core import ( "context" "errors" "testing" "time" "ragflow/internal/harness/core/schema" ) // TestIntegration_ReActToolResumeComplete verifies a full ReAct cycle: // model returns tool call -> tool executes -> model returns final answer. func TestIntegration_ReActToolResumeComplete(t *testing.T) { model := &forcedToolModel{ inner: &mockModel{}, toolCalls: []schema.ToolCall{{ID: "call_1", Function: schema.ToolCallFunction{Name: "calc", Arguments: "{\"x\":6,\"y\":7}"}}}, finalResp: "the answer is 42", firstCall: true, } tool := &mockTool{name: "calc", desc: "calculator"} agent := NewReActAgent(&ReActConfig[*schema.Message]{ Model: model, Tools: []Tool{tool}, ToolsConfig: &ToolsNodeConfig{Tools: []Tool{tool}}, }) agent.name = "react_tool" store := newCancelTestStore() ctx := context.Background() runner := NewTypedRunner(RunnerConfig[*schema.Message]{Agent: agent, CheckPointStore: store}) iter := runner.Run(ctx, []*schema.Message{schema.UserMessage("compute")}) var lastContent string for { ev, ok := iter.Next() if !ok { break } if ev.Err != nil { t.Fatalf("unexpected err: %v", ev.Err) } if ev.Output != nil && ev.Output.MessageOutput != nil && !ev.Output.MessageOutput.IsStreaming && ev.Output.MessageOutput.Message != nil { lastContent = ev.Output.MessageOutput.Message.Content } } if lastContent != "the answer is 42" { t.Errorf("expected 'the answer is 42', got %q", lastContent) } } // TestIntegration_SequentialAgent verifies sequential execution of two agents. func TestIntegration_SequentialAgent(t *testing.T) { m1 := &mockModel{} m1.addResp("hello from agent A") m2 := &mockModel{} m2.addResp("hello from agent B") a1 := NewReActAgent(&ReActConfig[*schema.Message]{Model: m1}).WithName("agent_a").WithDescription("first agent") a2 := NewReActAgent(&ReActConfig[*schema.Message]{Model: m2}).WithName("agent_b").WithDescription("second agent") ctx := context.Background() seq, err := NewSequential(ctx, &SequentialConfig{ Name: "seq_test", Description: "sequential test", SubAgents: []Agent{a1, a2}, }) if err != nil { t.Fatalf("NewSequential: %v", err) } runner := NewTypedRunner(RunnerConfig[*schema.Message]{Agent: seq}) iter := runner.Run(ctx, []*schema.Message{schema.UserMessage("run agents")}) var outputs []string for { ev, ok := iter.Next() if !ok { break } if ev.Err != nil { t.Fatalf("unexpected err: %v", ev.Err) } if ev.Output != nil && ev.Output.MessageOutput != nil && !ev.Output.MessageOutput.IsStreaming && ev.Output.MessageOutput.Message != nil { outputs = append(outputs, ev.Output.MessageOutput.Message.Content) } } if len(outputs) == 0 { t.Fatal("expected at least one output event") } t.Logf("sequential outputs: %v", outputs) } // TestIntegration_ParallelAgent verifies parallel execution of two agents. func TestIntegration_ParallelAgent(t *testing.T) { m1 := &mockModel{} m1.addResp("result from parallel A") m2 := &mockModel{} m2.addResp("result from parallel B") a1 := NewReActAgent(&ReActConfig[*schema.Message]{Model: m1}).WithName("par_a").WithDescription("parallel agent A") a2 := NewReActAgent(&ReActConfig[*schema.Message]{Model: m2}).WithName("par_b").WithDescription("parallel agent B") ctx := context.Background() par, err := NewParallel(ctx, &ParallelConfig{ Name: "par_test", Description: "parallel test", SubAgents: []Agent{a1, a2}, }) if err != nil { t.Fatalf("NewParallel: %v", err) } runner := NewTypedRunner(RunnerConfig[*schema.Message]{Agent: par}) iter := runner.Run(ctx, []*schema.Message{schema.UserMessage("run parallel")}) var outputs []string for { ev, ok := iter.Next() if !ok { break } if ev.Err != nil { t.Fatalf("unexpected err: %v", ev.Err) } if ev.Output != nil && ev.Output.MessageOutput != nil && !ev.Output.MessageOutput.IsStreaming && ev.Output.MessageOutput.Message != nil { outputs = append(outputs, ev.Output.MessageOutput.Message.Content) } } if len(outputs) == 0 { t.Fatal("expected at least one output event") } t.Logf("parallel outputs: %v", outputs) } // TestIntegration_LoopAgent verifies a loop agent that runs sub-agents in a loop. func TestIntegration_LoopAgent(t *testing.T) { m := &mockModel{} // The loop runs the body agent up to MaxIterations (3) times, so add 3 responses for i := 0; i < 3; i++ { m.addResp("loop iteration output") } a := NewReActAgent(&ReActConfig[*schema.Message]{Model: m}).WithName("loop_body").WithDescription("loop body agent") ctx := context.Background() loop, err := NewLoop(ctx, &LoopConfig{ Name: "loop_test", Description: "loop test", SubAgents: []Agent{a}, MaxIterations: 3, }) if err != nil { t.Fatalf("NewLoop: %v", err) } runner := NewTypedRunner(RunnerConfig[*schema.Message]{Agent: loop}) iter := runner.Run(ctx, []*schema.Message{schema.UserMessage("run loop")}) var outputs []string for { ev, ok := iter.Next() if !ok { break } if ev.Err != nil { t.Fatalf("unexpected err: %v", ev.Err) } if ev.Output != nil && ev.Output.MessageOutput != nil && !ev.Output.MessageOutput.IsStreaming && ev.Output.MessageOutput.Message != nil { outputs = append(outputs, ev.Output.MessageOutput.Message.Content) } } t.Logf("loop outputs: %v", outputs) } // TestIntegration_SupervisorTransfer creates a simple supervisor with one sub-agent // and verifies basic execution completes without error. func TestIntegration_SupervisorTransfer(t *testing.T) { m1 := &mockModel{} m1.addResp("supervisor output") m2 := &mockModel{} m2.addResp("sub-agent output") sub := NewReActAgent(&ReActConfig[*schema.Message]{Model: m2}).WithName("worker").WithDescription("worker agent") // Use AgentWithOptions with disallow transfer to parent and the sub-agent ctx := context.Background() wrappedSub := AgentWithOptions(ctx, sub, WithDisallowTransferToParent()) sup := NewReActAgent(&ReActConfig[*schema.Message]{ Model: m1, Instruction: "You are a supervisor. Transfer to worker agent when asked.", }).WithName("supervisor").WithDescription("supervisor agent") flow, err := SetSubAgents(ctx, sup, []Agent{wrappedSub}) if err != nil { t.Fatalf("SetSubAgents: %v", err) } runner := NewTypedRunner(RunnerConfig[*schema.Message]{Agent: flow}) iter := runner.Run(ctx, []*schema.Message{schema.UserMessage("hello")}) for { ev, ok := iter.Next() if !ok { break } if ev.Err != nil { t.Fatalf("unexpected err: %v", ev.Err) } } } // TestIntegration_PlanExecute creates a PlanExecute agent with mock models // and verifies basic execution completes without error. func TestIntegration_PlanExecute(t *testing.T) { plannerM := &mockModel{} plannerM.addResp("plan created") execM := &mockModel{} execM.addResp("executed step") replannerM := &mockModel{} replannerM.addResp("replanning") ctx := context.Background() planner := NewReActAgent(&ReActConfig[*schema.Message]{Model: plannerM}).WithName("planner").WithDescription("planner agent") executor := NewReActAgent(&ReActConfig[*schema.Message]{Model: execM}).WithName("executor").WithDescription("executor agent") replanner := NewReActAgent(&ReActConfig[*schema.Message]{Model: replannerM}).WithName("replanner").WithDescription("replanner agent") loopAgent, err := NewLoop(ctx, &LoopConfig{ Name: "pe_loop", Description: "Plan-Execute loop", SubAgents: []Agent{executor, replanner}, MaxIterations: 1, }) if err != nil { t.Fatalf("NewLoop: %v", err) } seqAgent, err := NewSequential(ctx, &SequentialConfig{ Name: "plan_execute", Description: "Plan-Execute agent", SubAgents: []Agent{planner, loopAgent}, }) if err != nil { t.Fatalf("NewSequential: %v", err) } runner := NewTypedRunner(RunnerConfig[*schema.Message]{Agent: seqAgent}) iter := runner.Run(ctx, []*schema.Message{schema.UserMessage("do something")}) var outputs []string for { ev, ok := iter.Next() if !ok { break } if ev.Err != nil { t.Fatalf("unexpected err: %v", ev.Err) } if ev.Output != nil && ev.Output.MessageOutput != nil && !ev.Output.MessageOutput.IsStreaming && ev.Output.MessageOutput.Message != nil { outputs = append(outputs, ev.Output.MessageOutput.Message.Content) } } t.Logf("plan-execute outputs: %v", outputs) } func TestIntegration_TurnLoopPushStop(t *testing.T) { ctx := context.Background() loop := NewAgentLoop[*schema.Message](AgentLoopConfig[*schema.Message]{ GenInput: func(_ context.Context, l *AgentLoop[*schema.Message], items []*schema.Message) (*GenInputResult[*schema.Message], error) { return &GenInputResult[*schema.Message]{ Input: &AgentInput{Messages: items}, Consumed: items, Remaining: nil, }, nil }, PrepareAgent: func(_ context.Context, _ *AgentLoop[*schema.Message], consumed []*schema.Message) (Agent, error) { m := &mockModel{} m.addResp("turn loop response") agent := NewReActAgent(&ReActConfig[*schema.Message]{Model: m}).WithName("turn_agent") return agent, nil }, }) loop.Push(schema.UserMessage("item 1")) loop.Push(schema.UserMessage("item 2")) loop.Run(ctx) loop.Stop() state := loop.Wait() if state.ExitReason != nil && !errors.As(state.ExitReason, new(*CancelError)) { t.Fatalf("unexpected exit reason: %v", state.ExitReason) } t.Logf("turn loop exit: reason=%v, unhandled=%d", state.ExitReason, len(state.UnhandledItems)) } // TestIntegration_MiddlewareStack verifies that middleware hooks fire in a ReAct agent. func TestIntegration_MiddlewareStack(t *testing.T) { var beforeAgentCalled, afterAgentCalled, beforeModelCalled, afterModelCalled bool mw := &testMiddleware{ beforeAgent: func(ctx context.Context, rc *ReActAgentContext) (context.Context, *ReActAgentContext, error) { beforeAgentCalled = true return ctx, rc, nil }, afterAgent: func(ctx context.Context, state *ReActAgentState) (context.Context, error) { afterAgentCalled = true return ctx, nil }, beforeModel: func(ctx context.Context, state *ReActAgentState, mc *ModelContext) (context.Context, *ReActAgentState, error) { beforeModelCalled = true return ctx, state, nil }, afterModel: func(ctx context.Context, state *ReActAgentState, mc *ModelContext) (context.Context, *ReActAgentState, error) { afterModelCalled = true return ctx, state, nil }, } model := &mockModel{} model.addResp("middleware test response") agent := NewReActAgent(&ReActConfig[*schema.Message]{ Model: model, Middlewares: []ReActMiddleware{mw}, }) agent.name = "mw_test" ctx := context.Background() iter := agent.Run(ctx, &AgentInput{Messages: []Message{schema.UserMessage("test middleware")}}) for { ev, ok := iter.Next() if !ok { break } if ev.Err != nil { t.Fatalf("unexpected err: %v", ev.Err) } } if !beforeAgentCalled { t.Error("BeforeAgent middleware was not called") } if !afterAgentCalled { t.Error("AfterAgent middleware was not called") } if !beforeModelCalled { t.Error("BeforeModelRewrite middleware was not called") } if !afterModelCalled { t.Error("AfterModelRewrite middleware was not called") } } // TestIntegration_AgentToolNested creates an AgentTool wrapping a simple agent // and verifies it can be invoked through a parent agent's tool execution. func TestIntegration_AgentToolNested(t *testing.T) { innerM := &mockModel{} innerM.addResp("inner agent result") innerAgent := NewReActAgent(&ReActConfig[*schema.Message]{Model: innerM}).WithName("inner_agent").WithDescription("inner agent for testing") ctx := context.Background() agentTool := NewAgentTool(ctx, innerAgent) // Now create a parent agent that "has" this tool and executes it parentM := &forcedToolModel{ toolCalls: []schema.ToolCall{{ID: "call_at_1", Function: schema.ToolCallFunction{Name: "inner_agent", Arguments: "{\"task\":\"test\"}"}}}, finalResp: "parent done", firstCall: true, } parent := NewReActAgent(&ReActConfig[*schema.Message]{ Model: parentM, Tools: []Tool{agentTool}, ToolsConfig: &ToolsNodeConfig{Tools: []Tool{agentTool}}, }).WithName("parent_agent") runner := NewTypedRunner(RunnerConfig[*schema.Message]{Agent: parent}) iter := runner.Run(ctx, []*schema.Message{schema.UserMessage("use agent tool")}) var lastContent string for { ev, ok := iter.Next() if !ok { break } if ev.Err != nil { t.Fatalf("unexpected err: %v", ev.Err) } if ev.Output != nil && ev.Output.MessageOutput != nil && !ev.Output.MessageOutput.IsStreaming && ev.Output.MessageOutput.Message != nil { lastContent = ev.Output.MessageOutput.Message.Content } } if lastContent != "parent done" { t.Errorf("expected 'parent done', got %q", lastContent) } } // TestIntegration_CheckpointResume verifies that a Runner with checkpoint store // can execute an agent and resume from checkpoint. func TestIntegration_CheckpointResume(t *testing.T) { // Use a model that produces a tool call, causing an interrupt-like flow model := &forcedToolModel{ inner: &mockModel{}, toolCalls: []schema.ToolCall{{ID: "call_cp_1", Function: schema.ToolCallFunction{Name: "cp_tool", Arguments: "{\"x\":1}"}}}, finalResp: "resume complete", firstCall: true, } tool := &mockTool{name: "cp_tool", desc: "checkpoint tool"} agent := NewReActAgent(&ReActConfig[*schema.Message]{ Model: model, Tools: []Tool{tool}, ToolsConfig: &ToolsNodeConfig{Tools: []Tool{tool}}, }) agent.name = "cp_agent" store := newCancelTestStore() ctx := context.Background() runner := NewTypedRunner(RunnerConfig[*schema.Message]{Agent: agent, CheckPointStore: store}) // Run with a checkpoint ID iter := runner.Run(ctx, []*schema.Message{schema.UserMessage("checkpoint test")}) var lastContent string for { ev, ok := iter.Next() if !ok { break } if ev.Err != nil { // CancelError with interrupt is expected in checkpoint flow var ce *CancelError if errors.As(ev.Err, &ce) { t.Logf("got CancelError (expected in checkpoint resume flow): %v", ce) break } t.Fatalf("unexpected err: %v", ev.Err) } if ev.Output != nil && ev.Output.MessageOutput != nil && !ev.Output.MessageOutput.IsStreaming && ev.Output.MessageOutput.Message != nil { lastContent = ev.Output.MessageOutput.Message.Content } } t.Logf("checkpoint run completed, last content: %q", lastContent) } // TestIntegration_SequentialCancelResume verifies that a sequential agent can be // cancelled mid-execution and later resumed. func TestIntegration_SequentialCancelResume(t *testing.T) { // First agent: responds immediately m1 := &mockModel{} m1.addResp("agent A done") a1 := NewReActAgent(&ReActConfig[*schema.Message]{Model: m1}).WithName("seq_a").WithDescription("first in sequence") // Second agent: use cancelTestChatModel with a delay so we can cancel mid-execution m2 := newCancelTestChatModel(nil) m2.addResp("agent B done") m2.setDelay(50 * time.Millisecond) a2 := NewReActAgent(&ReActConfig[*schema.Message]{Model: m2}).WithName("seq_b").WithDescription("second in sequence") ctx := context.Background() seq, err := NewSequential(ctx, &SequentialConfig{ Name: "seq_cancel", Description: "sequential cancel test", SubAgents: []Agent{a1, a2}, }) if err != nil { t.Fatalf("NewSequential: %v", err) } cancelOpt, cancelFunc := WithCancel() store := newCancelTestStore() runner := NewTypedRunner(RunnerConfig[*schema.Message]{Agent: seq, CheckPointStore: store}) iter := runner.Run(ctx, []*schema.Message{schema.UserMessage("run sequential")}, cancelOpt) // Wait for agent A to complete, then cancel time.Sleep(20 * time.Millisecond) cancelFunc(WithCancelMode(CancelImmediate)) var cancelSeen bool for { ev, ok := iter.Next() if !ok { break } var ce *CancelError if ev.Err != nil && errors.As(ev.Err, &ce) { cancelSeen = true t.Logf("got CancelError: %v", ce) break } if ev.Err != nil { t.Logf("non-cancel error: %v", ev.Err) } } if !cancelSeen { t.Log("cancel may not have been delivered (expected with non-graceful cancel)") } } func TestIntegration_LoopAgentSimple(t *testing.T) { m1 := &mockModel{} // 2 iterations * 1 call each = 2 calls m1.addResp("loop_a1") m1.addResp("loop_a1") m2 := &mockModel{} // 2 iterations * 1 call each = 2 calls m2.addResp("loop_a2") m2.addResp("loop_a2") a1 := NewReActAgent(&ReActConfig[*schema.Message]{Model: m1}) a1.name = "la1" a2 := NewReActAgent(&ReActConfig[*schema.Message]{Model: m2}) a2.name = "la2" ctx := context.Background() wf, err := NewLoop(ctx, &LoopConfig{Name: "loop_simple", Description: "test", SubAgents: []Agent{a1, a2}, MaxIterations: 2}) if err != nil { t.Fatalf("NewLoop: %v", err) } iter := wf.Run(ctx, &AgentInput{Messages: []Message{schema.UserMessage("go")}}) var count int for { ev, ok := iter.Next() if !ok { break } if ev.Err != nil { t.Fatalf("err: %v", ev.Err) } count++ } if count == 0 { t.Error("expected events from loop") } } func TestIntegration_PlanExecuteSimple(t *testing.T) { model := &mockModel{} model.addResp("plan") model.addResp("execute") agent := NewReActAgent(&ReActConfig[*schema.Message]{Model: model}).WithName("pe_test") store := newCancelTestStore() runner := NewTypedRunner(RunnerConfig[*schema.Message]{Agent: agent, CheckPointStore: store}) ctx := context.Background() iter := runner.Run(ctx, []*schema.Message{schema.UserMessage("test")}) for { ev, ok := iter.Next() if !ok { break } if ev.Err != nil { t.Fatalf("err: %v", ev.Err) } } } // ---- Runner-level integration tests ---- // TestIntegration_RunnerToolCall verifies a full ReAct cycle via Runner: // model returns tool call -> tool executes -> model returns final answer. func TestIntegration_RunnerToolCall(t *testing.T) { model := &forcedToolModel{ inner: &mockModel{}, toolCalls: []schema.ToolCall{{ID: "call_1", Function: schema.ToolCallFunction{Name: "calculator", Arguments: "{\"x\":6,\"y\":7}"}}}, finalResp: "the answer is 42", firstCall: true, } tool := &mockTool{name: "calculator", desc: "calculates things"} agent := NewReActAgent(&ReActConfig[*schema.Message]{ Model: model, Tools: []Tool{tool}, ToolsConfig: &ToolsNodeConfig{Tools: []Tool{tool}}, }) agent.name = "calc_agent" store := newCancelTestStore() runner := NewTypedRunner(RunnerConfig[*schema.Message]{Agent: agent, CheckPointStore: store}) ctx := context.Background() iter := runner.Run(ctx, []*schema.Message{schema.UserMessage("what is 6*7?")}) var lastContent string for { ev, ok := iter.Next() if !ok { break } if ev.Err != nil { t.Fatalf("err: %v", ev.Err) } if ev.Output != nil && ev.Output.MessageOutput != nil && !ev.Output.MessageOutput.IsStreaming && ev.Output.MessageOutput.Message != nil { lastContent = ev.Output.MessageOutput.Message.Content } } if lastContent != "the answer is 42" { t.Errorf("expected 'the answer is 42', got %q", lastContent) } } // TestIntegration_RunnerSimple runs a basic agent via Runner with checkpoint. func TestIntegration_RunnerSimple(t *testing.T) { model := &mockModel{} model.addResp("hello world") agent := NewReActAgent(&ReActConfig[*schema.Message]{Model: model}) agent.name = "runner_test" store := newCancelTestStore() runner := NewTypedRunner(RunnerConfig[*schema.Message]{Agent: agent, CheckPointStore: store}) ctx := context.Background() iter := runner.Run(ctx, []*schema.Message{schema.UserMessage("say hi")}) var found bool for { ev, ok := iter.Next() if !ok { break } if ev.Err != nil { t.Fatalf("err: %v", ev.Err) } if ev.Output != nil && ev.Output.MessageOutput != nil && !ev.Output.MessageOutput.IsStreaming && ev.Output.MessageOutput.Message != nil { if ev.Output.MessageOutput.Message.Content == "hello world" { found = true } } } if !found { t.Error("expected 'hello world' in output") } } // TestIntegration_RunnerResume verifies the full cancel-then-resume cycle. func TestIntegration_RunnerResume(t *testing.T) { model := &mockModel{} model.addResp("first response") model.addResp("resumed response") agent := NewReActAgent(&ReActConfig[*schema.Message]{Model: model}).WithName("resume_test") store := newCancelTestStore() // Run with a known checkpoint ID so we can resume from it. cid := "resume-cid-001" cancelOpt, cancelFunc := WithCancel() runner := NewTypedRunner(RunnerConfig[*schema.Message]{Agent: agent, CheckPointStore: store}) ctx := context.Background() iter := runner.Run(ctx, []*schema.Message{schema.UserMessage("run me")}, WithCheckPointID(cid), cancelOpt) time.Sleep(10 * time.Millisecond) cancelFunc(WithCancelMode(CancelImmediate)) for { _, ok := iter.Next() if !ok { break } } // Resume from the known checkpoint ID. resumedIter, err := runner.Resume(ctx, cid) if err != nil { t.Logf("Resume failed (expected if cancel didn't produce checkpoint): %v", err) return } var outputs []string for { ev, ok := resumedIter.Next() if !ok { break } if ev.Err != nil { break } if ev.Output != nil && ev.Output.MessageOutput != nil && ev.Output.MessageOutput.Message != nil { outputs = append(outputs, ev.Output.MessageOutput.Message.Content) } } t.Logf("resumed outputs: %v", outputs) } // TestIntegration_RunnerCancel verifies cancellation via WithCancel option. func TestIntegration_RunnerCancel(t *testing.T) { m := newCancelTestChatModel(nil) m.addResp("should not appear") m.setDelay(200 * time.Millisecond) agent := NewReActAgent(&ReActConfig[*schema.Message]{Model: m}).WithName("cancel_test") cancelOpt, cancelFunc := WithCancel() store := newCancelTestStore() runner := NewTypedRunner(RunnerConfig[*schema.Message]{Agent: agent, CheckPointStore: store}) ctx := context.Background() iter := runner.Run(ctx, []*schema.Message{schema.UserMessage("cancel me")}, cancelOpt) time.Sleep(50 * time.Millisecond) cancelFunc(WithCancelMode(CancelImmediate)) var gotCancel bool for { ev, ok := iter.Next() if !ok { break } if ev.Err != nil { var ce *CancelError if errors.As(ev.Err, &ce) { gotCancel = true } break } } if !gotCancel { t.Log("cancel may not have been delivered (expected with non-graceful cancel)") } } // TestIntegration_RunnerStreamMode verifies that streaming events are received. func TestIntegration_RunnerStreamMode(t *testing.T) { model := &mockModel{} model.addResp("streamed output") agent := NewReActAgent(&ReActConfig[*schema.Message]{Model: model}).WithName("stream_test") store := newCancelTestStore() runner := NewTypedRunner(RunnerConfig[*schema.Message]{Agent: agent, CheckPointStore: store, EnableStreaming: true}) ctx := context.Background() iter := runner.Run(ctx, []*schema.Message{schema.UserMessage("stream")}) var streamingEvents int for { ev, ok := iter.Next() if !ok { break } if ev.Err != nil { t.Fatalf("err: %v", ev.Err) } if ev.Output != nil && ev.Output.MessageOutput != nil && ev.Output.MessageOutput.IsStreaming { streamingEvents++ } } t.Logf("streaming events received: %d", streamingEvents) } // TestIntegration_AgentToolViaRunner verifies AgentTool invocation through Runner. func TestIntegration_AgentToolViaRunner(t *testing.T) { innerM := &mockModel{} innerM.addResp("inner tool result") innerAgent := NewReActAgent(&ReActConfig[*schema.Message]{Model: innerM}).WithName("inner").WithDescription("inner") ctx := context.Background() agentTool := NewAgentTool(ctx, innerAgent) parentM := &forcedToolModel{ toolCalls: []schema.ToolCall{{ID: "call_tool", Function: schema.ToolCallFunction{Name: "inner", Arguments: "{\"task\":\"run\"}"}}}, finalResp: "parent complete", firstCall: true, } parent := NewReActAgent(&ReActConfig[*schema.Message]{ Model: parentM, Tools: []Tool{agentTool}, ToolsConfig: &ToolsNodeConfig{Tools: []Tool{agentTool}}, }).WithName("parent_tool") store := newCancelTestStore() runner := NewTypedRunner(RunnerConfig[*schema.Message]{Agent: parent, CheckPointStore: store}) iter := runner.Run(ctx, []*schema.Message{schema.UserMessage("use agent tool")}) var lastContent string for { ev, ok := iter.Next() if !ok { break } if ev.Err != nil { t.Fatalf("err: %v", ev.Err) } if ev.Output != nil && ev.Output.MessageOutput != nil && !ev.Output.MessageOutput.IsStreaming && ev.Output.MessageOutput.Message != nil { lastContent = ev.Output.MessageOutput.Message.Content } } if lastContent != "parent complete" { t.Errorf("expected 'parent complete', got %q", lastContent) } }