-
Notifications
You must be signed in to change notification settings - Fork 63
feat: per-turn tool retrieval (trim tool-definition context flood) #858
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,77 @@ | ||
| /** | ||
| * Tool retrieval — pick a relevant subset of tools per turn. | ||
| * | ||
| * With ~78 tools, sending the full set every turn floods context and adds | ||
| * distractors that hurt tool SELECTION. This picks a relevant subset per turn: | ||
| * a fixed always-on CORE + lexically-ranked top-k of the rest, and NEVER drops a | ||
| * tool that's mid-trajectory (referenced by an in-flight tool call) — dropping | ||
| * those would corrupt the conversation. | ||
| * | ||
| * v1 is lexical (dependency-free, deterministic, testable). An embedding + | ||
| * cross-encoder rerank pass is a later enhancement; the `select` signature is | ||
| * stable so wiring doesn't change. | ||
| */ | ||
|
|
||
| export namespace Retrieval { | ||
| /** Always-available agent essentials — never retrieved out. */ | ||
| export const CORE = [ | ||
| "bash", "read", "write", "edit", "glob", "grep", "ls", | ||
| "task", "todowrite", "skill", | ||
| ] | ||
|
Comment on lines
+17
to
+20
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🏁 Script executed: #!/bin/bash
# List registered tool IDs to compare against CORE.
rg -nP "id:\s*['\"][a-z_]+['\"]" --type=ts -g '!**/*.test.ts' packages/opencode/src/tool || true
# Also look for how tool ids/names are declared
ast-grep --pattern 'Tool.define($$$)'
rg -nP '\b(todowrite|todo_write|todoread)\b' --type=ts -g '!**/*.test.ts'Repository: AltimateAI/altimate-code Length of output: 50381 🏁 Script executed: #!/bin/bash
set -euo pipefail
CORE=(bash read write edit glob grep ls task todowrite skill)
# Show where each CORE id is registered via Tool.define("<id>", ...)
for id in "${CORE[@]}"; do
echo "==== $id ===="
rg -n "Tool\.define\\(\"${id//\//\\/}\"" packages/opencode/src/tool -S --type=ts || true
done
# Show the CORE constant and retrieval selection logic for context
echo "==== CORE constant ===="
rg -n "export const CORE" packages/opencode/src/tool/retrieval.ts
sed -n '1,120p' packages/opencode/src/tool/retrieval.ts | nl -ba | sed -n '1,120p'
echo "==== select() logic (if present) ===="
rg -n "function select|const select|select\\(" packages/opencode/src/tool/retrieval.ts
sed -n '120,260p' packages/opencode/src/tool/retrieval.ts | nl -ba | sed -n '120,260p'Repository: AltimateAI/altimate-code Length of output: 1149 🏁 Script executed: #!/bin/bash
set -euo pipefail
CORE=(bash read write edit glob grep ls task todowrite skill)
# Show where each CORE id is registered via Tool.define("<id>", ...)
for id in "${CORE[@]}"; do
echo "==== $id ===="
rg -n "Tool\.define\\(\"${id//\//\\/}\"" packages/opencode/src/tool -S --type=ts || true
done
# Show the CORE constant and retrieval selection logic for context
echo "==== CORE constant ===="
rg -n "export const CORE" packages/opencode/src/tool/retrieval.ts
sed -n '1,120p' packages/opencode/src/tool/retrieval.ts | nl -ba | sed -n '1,120p'
echo "==== select() logic (if present) ===="
rg -n "function select|const select|select\\(" packages/opencode/src/tool/retrieval.ts
sed -n '120,260p' packages/opencode/src/tool/retrieval.ts | nl -ba | sed -n '120,260p'Repository: AltimateAI/altimate-code Length of output: 1149 🏁 Script executed: #!/bin/bash
set -euo pipefail
# Find the "ls" tool registration id
echo "==== registrations for ls ===="
rg -n "Tool\\.define\\(\"ls\"|Tool\\.define\\('ls'" packages/opencode/src/tool packages/opencode/src -S --type=ts || true
echo "==== any use of id: \"ls\" (Tool.define arg objects etc.) ===="
rg -n "(\"ls\")" packages/opencode/src/tool/retrieval.ts packages/opencode/src/tool -S --type=ts | head -n 200 || true
echo "==== retrieval.ts (CORE + select logic) ===="
cat -n packages/opencode/src/tool/retrieval.ts | sed -n '1,260p'Repository: AltimateAI/altimate-code Length of output: 3714 🏁 Script executed: #!/bin/bash
set -euo pipefail
echo "==== Tool.define(<id>, ...) ids in packages/opencode/src ===="
rg -n "Tool\.define\(\s*(['\"])([a-z_]+)\1" packages/opencode/src -S --type=ts --type=tsx || true
echo "==== Any tool registry mentions of \"ls\" in packages/opencode/src ===="
rg -n "\"ls\"" packages/opencode/src -S --type=ts --type=tsx || trueRepository: AltimateAI/altimate-code Length of output: 480 🏁 Script executed: #!/bin/bash
set -euo pipefail
echo "==== Tool.define IDs (capturing first arg) — filter for ls ===="
rg -n --pcre2 "Tool\.define\(\s*(['\"])([a-z_]+)\1" packages/opencode/src -S -g'*.ts' -g'*.tsx' \
| rg -n "\"ls\"|'ls'" || true
echo "==== Direct Tool.define(\"ls\" / 'ls') matches ===="
rg -n --pcre2 "Tool\.define\(\s*(['\"])ls\1" packages/opencode/src -S -g'*.ts' -g'*.tsx' || true
echo "==== Any tool-id string literal \"ls\" near tool code ===="
rg -n "\"ls\"" packages/opencode/src/tool -S -g'*.ts' -g'*.tsx' || trueRepository: AltimateAI/altimate-code Length of output: 341 Fix
🤖 Prompt for AI Agents |
||
|
|
||
| export interface Tool { | ||
| name: string | ||
| description?: string | ||
| } | ||
|
|
||
| export interface Options { | ||
| /** target number of tools to expose (incl. core). */ | ||
| topk?: number | ||
| /** names that MUST stay (e.g. tools referenced by in-flight tool calls). */ | ||
| keep?: Iterable<string> | ||
| /** only retrieve when the tool count exceeds this (no-op for small sets). */ | ||
| minToolsToRetrieve?: number | ||
| } | ||
|
|
||
| export function enabled(): boolean { | ||
| return process.env["ALTIMATE_TOOL_RETRIEVAL"] === "1" | ||
| } | ||
|
|
||
| function score(query: string, t: Tool): number { | ||
| const words = new Set(query.toLowerCase().match(/[a-z_]+/g) ?? []) | ||
| const hay = (t.name + " " + (t.description ?? "")).toLowerCase() | ||
| let s = 0 | ||
| for (const w of words) if (w.length > 3 && hay.includes(w)) s += 1 | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. P2: Scoring ignores 3-character keywords, which can miss relevant tools for common queries (e.g., sql/dbt). Prompt for AI agents |
||
| // small boost for a direct name mention | ||
| if (words.has(t.name.toLowerCase())) s += 3 | ||
| return s | ||
| } | ||
|
|
||
| /** | ||
| * Return the SUBSET of tool names to expose this turn. Caller deletes the rest. | ||
| * Deterministic: core + forced-keep first, then highest-scoring others up to topk | ||
| * (ties broken by original order for stability). | ||
| */ | ||
| export function select(query: string, tools: Tool[], opts: Options = {}): Set<string> { | ||
| const topk = opts.topk ?? 12 | ||
| const minToRetrieve = opts.minToolsToRetrieve ?? topk | ||
| const all = new Set(tools.map((t) => t.name)) | ||
| // No-op for small tool sets — nothing to gain. | ||
| if (tools.length <= minToRetrieve) return all | ||
|
|
||
| const keep = new Set<string>() | ||
| for (const n of opts.keep ?? []) if (all.has(n)) keep.add(n) | ||
| for (const n of CORE) if (all.has(n)) keep.add(n) | ||
|
|
||
| const rest = tools.filter((t) => !keep.has(t.name)) | ||
| const ranked = rest | ||
| .map((t, i) => ({ name: t.name, s: score(query, t), i })) | ||
| .sort((a, b) => b.s - a.s || a.i - b.i) | ||
|
|
||
| for (const r of ranked) { | ||
| if (keep.size >= topk) break | ||
| keep.add(r.name) | ||
| } | ||
| return keep | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,41 @@ | ||
| import { describe, expect, test } from "bun:test" | ||
| import { Retrieval } from "../../src/tool/retrieval" | ||
|
|
||
| const TOOLS = [ | ||
| ...Retrieval.CORE.map((name) => ({ name })), | ||
| ...Array.from({ length: 20 }, (_, i) => ({ name: `warehouse_op${i}`, description: `warehouse operation ${i}` })), | ||
| { name: "dbt_run", description: "run dbt models build" }, | ||
| { name: "sql_execute", description: "execute SQL query against warehouse" }, | ||
| ] | ||
|
|
||
| describe("Retrieval.select", () => { | ||
| test("always keeps core tools", () => { | ||
| const sel = Retrieval.select("run the dbt models", TOOLS, { topk: 12 }) | ||
| expect(sel.has("bash")).toBe(true) | ||
| expect(sel.has("read")).toBe(true) | ||
| }) | ||
|
|
||
| test("picks lexically relevant tools", () => { | ||
| expect(Retrieval.select("run the dbt models and build", TOOLS, { topk: 12 }).has("dbt_run")).toBe(true) | ||
| expect(Retrieval.select("execute a SQL query on the warehouse", TOOLS, { topk: 12 }).has("sql_execute")).toBe(true) | ||
| }) | ||
|
|
||
| test("never drops in-flight (keep) tools, even if irrelevant", () => { | ||
| const sel = Retrieval.select("hello", TOOLS, { topk: 12, keep: ["warehouse_op19"] }) | ||
| expect(sel.has("warehouse_op19")).toBe(true) | ||
| }) | ||
|
|
||
| test("no-op for small tool sets (returns all)", () => { | ||
| const small = [{ name: "a" }, { name: "b" }] | ||
| expect(Retrieval.select("x", small, { topk: 12 }).size).toBe(2) | ||
| }) | ||
|
|
||
| test("enabled() reads the env flag", () => { | ||
| const prev = process.env["ALTIMATE_TOOL_RETRIEVAL"] | ||
| process.env["ALTIMATE_TOOL_RETRIEVAL"] = "1" | ||
| expect(Retrieval.enabled()).toBe(true) | ||
| delete process.env["ALTIMATE_TOOL_RETRIEVAL"] | ||
| expect(Retrieval.enabled()).toBe(false) | ||
| if (prev !== undefined) process.env["ALTIMATE_TOOL_RETRIEVAL"] = prev | ||
| }) | ||
| }) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
P1: Core retrieval list uses
"ls", but the actual tool name is"list"; this can cause the list tool to be incorrectly filtered out.Prompt for AI agents