43ea2e562d
Plan A from docs/superpowers/plans/2026-05-19-post-slm-unlock.md. Small local SLMs (<=16k context) waste ~1500 tokens per turn on the full tool catalogue. Two-stage routing replaces round-1 tools with a single synthetic select_category schema; round-2+ sends only the selected category's real tool schemas plus select_category for re-selection. - internal/tool/category.go: Category type, optional Categorized interface, CategoryOf() with meta fallback. fs.read/fs.ls -> read, fs.write/fs.edit -> write, fs.glob/fs.grep -> search, bash -> exec. - internal/engine/twostage.go: synthetic select_category tool, intercept helper, per-turn selectedCategory state under e.mu. - Engine round 1 forces ToolChoiceRequired so SLMs don't fall back to prose. State resets at the top and end of every runLoop. - Activates automatically on a forced local arm with ContextWindow <=16384, or via [router].force_two_stage TOML key. - Integration test drives a 3-round trip and asserts: round 1 emits exactly one schema (synthetic) with ToolChoiceRequired, round 2 contains only write-category schemas + select_category, real fs.write executes. Invalid-category fallback round-trips back to round-1 mode.
53 lines
1.6 KiB
Go
53 lines
1.6 KiB
Go
package tool
|
|
|
|
// Category groups tools by what they do. Used by the two-stage tool routing
|
|
// path for small local models: round 1 picks a category, round 2 only sees
|
|
// schemas in that category.
|
|
type Category string
|
|
|
|
const (
|
|
// CategoryRead — read filesystem state (fs.read, fs.ls).
|
|
CategoryRead Category = "read"
|
|
// CategoryWrite — modify filesystem state (fs.write, fs.edit).
|
|
CategoryWrite Category = "write"
|
|
// CategorySearch — search filesystem content (fs.grep, fs.glob).
|
|
CategorySearch Category = "search"
|
|
// CategoryExec — execute external commands (bash).
|
|
CategoryExec Category = "exec"
|
|
// CategoryMeta — agent orchestration, introspection, and result handling.
|
|
// Default for tools that don't declare a category.
|
|
CategoryMeta Category = "meta"
|
|
)
|
|
|
|
// AllCategories returns the canonical category list in stable order.
|
|
func AllCategories() []Category {
|
|
return []Category{CategoryRead, CategoryWrite, CategorySearch, CategoryExec, CategoryMeta}
|
|
}
|
|
|
|
// IsValidCategory reports whether c is one of the known categories.
|
|
func IsValidCategory(c Category) bool {
|
|
switch c {
|
|
case CategoryRead, CategoryWrite, CategorySearch, CategoryExec, CategoryMeta:
|
|
return true
|
|
}
|
|
return false
|
|
}
|
|
|
|
// Categorized is the optional interface a tool implements to declare its
|
|
// category. Tools that don't implement it fall back to CategoryMeta.
|
|
type Categorized interface {
|
|
Category() Category
|
|
}
|
|
|
|
// CategoryOf returns the tool's declared category, or CategoryMeta if the
|
|
// tool does not implement Categorized.
|
|
func CategoryOf(t Tool) Category {
|
|
if c, ok := t.(Categorized); ok {
|
|
cat := c.Category()
|
|
if IsValidCategory(cat) {
|
|
return cat
|
|
}
|
|
}
|
|
return CategoryMeta
|
|
}
|