package run_test import ( "context" "encoding/json" "testing" "gitea.stevedudenhoeffer.com/steve/majordomo/llm" "gitea.stevedudenhoeffer.com/steve/majordomo/provider/fake" "gitea.stevedudenhoeffer.com/steve/executus/run" "gitea.stevedudenhoeffer.com/steve/executus/tool" ) // recordingPalette captures the delegation call it received. type recordingPalette struct { gotName, gotCaller, gotParent string gotInputs map[string]any } func (p *recordingPalette) ResolveSkill(context.Context, string, string) (string, error) { return "", nil } func (p *recordingPalette) InvokeSkill(_ context.Context, callerID, _, name string, inputs map[string]any, parentRunID string) (string, string, string, error) { p.gotName, p.gotCaller, p.gotParent, p.gotInputs = name, callerID, parentRunID, inputs return "the skill output", "child-run-1", "ok", nil } func (p *recordingPalette) ResolveAgent(context.Context, string, string) (string, error) { return "", nil } func (p *recordingPalette) InvokeAgent(context.Context, string, string, string, string, string, string, string, []string, func(context.Context, string, string)) (string, string, string, error) { return "", "", "ok", nil } // TestPaletteDelegation: an agent with a SkillPalette gets a skill__ tool; // the model calls it, the executor routes it through run.Ports.Palette as a // child of the current run, and the result flows back into the loop. func TestPaletteDelegation(t *testing.T) { pal := &recordingPalette{} fp := fake.New("fake") fp.Enqueue("m", fake.ReplyWith(llm.Response{ToolCalls: []llm.ToolCall{{ ID: "c1", Name: "skill__helper", Arguments: json.RawMessage(`{"inputs":{"q":"hi"}}`), }}}), fake.Reply("delegated and done"), ) m, err := fp.Model("m") if err != nil { t.Fatal(err) } ex := run.New(run.Config{ Registry: tool.NewRegistry(), Models: func(ctx context.Context, _ string) (context.Context, llm.Model, error) { return ctx, m, nil }, Ports: run.Ports{Palette: pal}, }) res := ex.Run(context.Background(), run.RunnableAgent{ID: "a1", Name: "boss", ModelTier: "m", SkillPalette: []string{"helper"}}, tool.Invocation{RunID: "parent-run", CallerID: "caller-7", ChannelID: "chan"}, "delegate please") if res.Err != nil { t.Fatalf("run error: %v", res.Err) } if res.Output != "delegated and done" { t.Errorf("output = %q", res.Output) } if pal.gotName != "helper" { t.Errorf("InvokeSkill name = %q, want helper", pal.gotName) } if pal.gotCaller != "caller-7" { t.Errorf("InvokeSkill caller = %q, want caller-7", pal.gotCaller) } if pal.gotParent != "parent-run" { t.Errorf("InvokeSkill parentRunID = %q, want parent-run (child of the current run)", pal.gotParent) } if pal.gotInputs["q"] != "hi" { t.Errorf("InvokeSkill inputs = %+v, want q=hi", pal.gotInputs) } } // TestNoPaletteNoDelegationTools: nil PaletteSource → no delegation tools, run // still works (the agent just has no skill__/agent__ tools). func TestNoPaletteNoDelegationTools(t *testing.T) { fp := fake.New("fake") fp.Enqueue("m", fake.Reply("ok")) m, _ := fp.Model("m") ex := run.New(run.Config{ Registry: tool.NewRegistry(), Models: func(ctx context.Context, _ string) (context.Context, llm.Model, error) { return ctx, m, nil }, }) res := ex.Run(context.Background(), run.RunnableAgent{Name: "x", ModelTier: "m", SkillPalette: []string{"helper"}}, tool.Invocation{RunID: "r"}, "hi") if res.Err != nil || res.Output != "ok" { t.Fatalf("nil-palette run failed: %v / %q", res.Err, res.Output) } }