Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 23 additions & 0 deletions packages/opencode/src/session/llm.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@ import {
} from "ai"
import { mergeDeep, pipe } from "remeda"
import { ProviderTransform } from "@/provider/transform"
// altimate_change start — tool retrieval
import { Retrieval } from "@/tool/retrieval"
// altimate_change end
import { Config } from "@/config/config"
import { Instance } from "@/project/instance"
import type { Agent } from "@/agent/agent"
Expand Down Expand Up @@ -179,6 +182,26 @@ export namespace LLM {
}
// altimate_change end

// altimate_change start — tool retrieval
// Expose only the relevant top-k tools this turn (flag-gated). Keeps the
// always-on core + any in-flight (referenced) tools; no-op for small sets.
if (Retrieval.enabled()) {
const lastUser = [...input.messages].reverse().find((m) => m.role === "user")
const c = lastUser?.content as any
const query =
typeof c === "string"
? c
: Array.isArray(c)
? c.map((p: any) => (typeof p === "string" ? p : (p?.text ?? ""))).join(" ")
: ""
const list = Object.entries(tools).map(([name, t]) => ({ name, description: (t as any)?.description }))
const keep = Retrieval.select(query, list, { keep: referencedTools })
for (const name of Object.keys(tools)) {
if (name !== "invalid" && !keep.has(name)) delete tools[name]
}
}
// altimate_change end

return streamText({
onError(error) {
l.error("stream error", {
Expand Down
77 changes: 77 additions & 0 deletions packages/opencode/src/tool/retrieval.ts
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",
Copy link
Copy Markdown

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
Check if this issue is valid — if so, understand the root cause and fix it. At packages/opencode/src/tool/retrieval.ts, line 18:

<comment>Core retrieval list uses `"ls"`, but the actual tool name is `"list"`; this can cause the list tool to be incorrectly filtered out.</comment>

<file context>
@@ -0,0 +1,77 @@
+export namespace Retrieval {
+  /** Always-available agent essentials — never retrieved out. */
+  export const CORE = [
+    "bash", "read", "write", "edit", "glob", "grep", "ls",
+    "task", "todowrite", "skill",
+  ]
</file context>

"task", "todowrite", "skill",
]
Comment on lines +17 to +20
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

🧩 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 || true

Repository: 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' || true

Repository: AltimateAI/altimate-code

Length of output: 341


Fix Retrieval.CORE tool id mismatch for ls.

packages/opencode/src/tool/retrieval.ts includes "ls" in Retrieval.CORE, but there’s no Tool.define("ls", ...) (the only "ls" occurrence is inside CORE). Since select() force-keeps CORE entries only when they exist in the candidate set (if (all.has(n))), "ls" won’t be reliably “always-on” when retrieval is enabled—update "ls" to the actual registered tool id (or remove it).

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/opencode/src/tool/retrieval.ts` around lines 17 - 20, Retrieval.CORE
contains "ls" which doesn't match any registered Tool id, so update the CORE
array in retrieval.ts: either replace "ls" with the actual registered tool id
used when defining the listing tool (e.g., the id passed to Tool.define for the
filesystem/ls-like tool) or remove "ls" from Retrieval.CORE; ensure the change
aligns with the select() behavior that only keeps CORE entries present in the
candidate set so the tool id in Retrieval.CORE must exactly match a
Tool.define(...) id.


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
Copy link
Copy Markdown

Choose a reason for hiding this comment

The 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
Check if this issue is valid — if so, understand the root cause and fix it. At packages/opencode/src/tool/retrieval.ts, line 44:

<comment>Scoring ignores 3-character keywords, which can miss relevant tools for common queries (e.g., sql/dbt).</comment>

<file context>
@@ -0,0 +1,77 @@
+    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
+    // small boost for a direct name mention
+    if (words.has(t.name.toLowerCase())) s += 3
</file context>

// 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
}
}
41 changes: 41 additions & 0 deletions packages/opencode/test/tool/retrieval.test.ts
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
})
})
Loading