Files
yhh c76dd51824 feat(advisor): an advise tool that consults a stronger model mid-task
agent.advisor_model offers the working model an advise tool. A call
hands the conversation so far and one question to the advising model,
which answers in text with no tools; the answer is the tool result. The
cheap model keeps the loop and pays for the strong one only when it
decides a moment is worth it.

The agent binds its conversation to each tool call through
tool.TranscriptReader, a copy of the canonical transcript. The advisor
renders it as text, since tool calls cannot be replayed into a request
that declares none: the first user message is always kept, the rest
from the newest back within 120 KiB, local-only messages never. Usage
bills the new advisor source at the advising model's rate.

The tool is registered only when advisor_model is set and resolves, so
whether the schema carries it is prefix configuration decided at boot;
there is no fallback, since advice from the same model is none.
2026-09-24 01:03:30 -07:00

38 lines
1.4 KiB
Go

package boot
import (
"fmt"
"log/slog"
"strings"
"reasonix/internal/base/netclient"
"reasonix/internal/contract/config"
"reasonix/internal/contract/event"
"reasonix/internal/contract/tool"
"reasonix/internal/tools/advisor"
)
// addAdvisor offers the advise tool when advisor_model names a model. It is
// decided once at boot, so whether the schema carries it is prefix
// configuration; a model that does not resolve leaves it out with a notice.
func addAdvisor(reg *tool.Registry, cfg *config.Config, proxy netclient.ProxySpec, sink event.Sink) {
ref := strings.TrimSpace(cfg.Agent.AdvisorModel)
if ref == "" {
return
}
entry, ok := cfg.ResolveModel(ref)
if !ok {
report(sink, event.Event{Level: event.LevelWarn, Text: "The advisor was disabled because its model was not found.",
Detail: fmt.Sprintf("advisor_model %q not found — advise tool not offered", ref)})
return
}
prov, err := NewProviderWithProxy(entry, proxy)
if err != nil {
slog.Warn("advisor provider construction failed — advise tool not offered", "model", ref, "err", err)
report(sink, event.Event{Level: event.LevelWarn, Text: "The advisor was disabled because it could not start.",
Detail: fmt.Sprintf("advisor construction failed: %v", err)})
return
}
reg.Add(advisor.New(advisor.Spec{Provider: prov, ModelRef: modelRefFromEntry(entry), Pricing: entry.Price, Sink: sink}))
}