Files
ARTEX/server/task_llm_rotation_test.go
BaaRai 89348b5512 feat(engine): per-intent LLM chain-head rotation (opt-in llm_rotate)
Implements the per-intent rotation shape from #85: a task created with
llm_rotate=true distributes worker intents round-robin over its LLM chain
heads, so lane A can hunt on one model while lane B hunts on another
(different lenses per intent, no extra tokens). Default stays the classic
ordered failover from the active head; planner/goals/mainagent keep the
active head always.

Mechanics: the engine pins the next chain head into the run context when a
worker claims an intent; the pin travels through the provider resolver
(hooks.current now takes ctx). A pin is only a dispatch preference - it must
belong to the task chain and never bypasses failover state (an exhausted
chain stays exhausted). Schema: tasks.llm_rotate boolean, default false,
idempotent ALTER; exposed on the create payload and the task DTO.

Closes #85 (the ensemble/P4 shape stays parked as before)
2026-10-21 15:00:16 +02:00

33 lines
757 B
Go

package server
import (
"context"
"testing"
)
func TestLLMProfilePinRoundTrip(t *testing.T) {
ctx := context.Background()
if _, ok := llmProfilePinFrom(ctx); ok {
t.Fatal("empty context must carry no pin")
}
pinned := withLLMProfilePin(ctx, 42)
got, ok := llmProfilePinFrom(pinned)
if !ok || got != 42 {
t.Fatalf("pin roundtrip got=%d ok=%v", got, ok)
}
}
func TestNextRotatedProfileRoundRobin(t *testing.T) {
tk := &Task{LLMProfileIDs: []int64{3, 1, 2}}
want := []int64{3, 1, 2, 3, 1}
for i, w := range want {
got, ok := tk.nextRotatedProfile()
if !ok || got != w {
t.Fatalf("rotation %d: got=%d ok=%v want=%d", i, got, ok, w)
}
}
if _, ok := (&Task{}).nextRotatedProfile(); ok {
t.Fatal("empty chain must not rotate")
}
}