Skip to content

Commit dd1fab3

Browse files
author
bcode
committed
fix(skill): customize-opencode off by default; promote browser-execute guide to a registered skill
Eval-agent deep-dive on v0.1.6 regressed traces: the only skill registered in browser sessions was upstream's customize-opencode (opencode.json schema authoring) — pure pollution for browser-driving workflows. Meanwhile the genuinely useful browser-execute-guide.md was not a registered skill, only surfaced because the tool description said 'you MUST Read this file first.' Two changes: 1) Gate customize-opencode built-in registration on BCODE_ENABLE_CUSTOMIZE_OPENCODE=1 (default off). A user-disk skill of the same name still loads, since the gate runs before disk discovery. 2) Rename packages/bcode-browser/skills/browser-execute-guide.md → browser-execute/SKILL.md, add frontmatter (name: browser-execute, description front-loads 'Use ONLY when calling browser_execute'), and extend discoverSkills to scan <dataDir>/skills/ where the bcode-browser package already materializes first-party skills. The skill now appears in <available_skills> at planning time and is loaded via the skill tool. Updated browser-execute.txt to instruct 'you MUST use the skill tool first to load the browser-execute skill' — keeps the strong MUST language verbatim per user confirmation that the wording materially improves eval scores.
1 parent 5af4383 commit dd1fab3

8 files changed

Lines changed: 46 additions & 17 deletions

File tree

packages/bcode-browser/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ See `decisions.md §1c` (three-level model) and `§1d` (this package) in the Bro
1212
| `src/browser-execute.ts` | In-process JS-eval `browser_execute` body. |
1313
| `src/session-store.ts` | Per-opencode-session CDP `Session` map. The agent calls `session.connect(...)` from a snippet; subsequent snippets find the same Session. |
1414
| `src/skills.ts` | Runtime resolver for embedded skills (extract on first call in compiled mode; in-tree path in dev). |
15-
| `skills/` | `browser-execute-guide.md` (the agent's prompt for `browser_execute`, covering all three connection Ways including Browser Use cloud provisioning via raw HTTP from inside a snippet). Embedded into the binary by `script/embed-skills.ts`. The interaction-skills set inherited from the Python harness was archived 2026-05-09 — we'll reintroduce only what evals show is needed, one skill at a time. |
15+
| `skills/` | `browser-execute/SKILL.md` (the agent-facing reference for `browser_execute`, covering all three connection Ways including Browser Use cloud provisioning via raw HTTP from inside a snippet). Registered as an opencode skill via frontmatter; surfaced in `<available_skills>` and loaded with the `skill` tool. Embedded into the binary by `script/embed-skills.ts`. The interaction-skills set inherited from the Python harness was archived 2026-05-09 — we'll reintroduce only what evals show is needed, one skill at a time. |
1616
| `script/embed-skills.ts` | Build-time embed; emits `bcode-skills.gen.ts` consumed by the compiled binary. |
1717
| `test/` | `bun test` smoke coverage for the workspace dynamic-import pattern. |
1818

packages/bcode-browser/skills/browser-execute-guide.md renamed to packages/bcode-browser/skills/browser-execute/SKILL.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,8 @@
1+
---
2+
name: browser-execute
3+
description: Use ONLY when calling the `browser_execute` tool or driving a real browser via the Chrome DevTools Protocol. Required reading before the first `browser_execute` call in a session. Covers the three connection methods (local Chrome with remote debugging, isolated debug-port profile, Browser Use cloud), the in-process `session` / `console` snippet model, attaching to a page target, common CDP commands, the per-project `.bcode/agent-workspace/` for reusable scripts, and screenshot auto-attachment.
4+
---
5+
16
The `browser_execute` tool evaluates JavaScript against a connected browser `session` via the Chrome DevTools Protocol.
27
The snippet runs in-process; `session` is bound to a long-lived CDP `Session` that persists. Connect once, then drive many snippets.
38
There is no helper namespace, just `session`, `console`, and standard JS globals.

packages/bcode-browser/src/browser-execute.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ const MAX_TIMEOUT_MS = 10 * 60 * 1000
5656
export const parameters = Schema.Struct({
5757
code: Schema.String.annotate({
5858
description:
59-
"The JavaScript snippet to execute. `session` (CDP Session) and `console` are in scope; see browser-execute-guide.md for the snippet model.",
59+
"The JavaScript snippet to execute. `session` (CDP Session) and `console` are in scope; see the `browser-execute` skill for the snippet model.",
6060
}),
6161
timeout: Schema.optional(Schema.Number).annotate({
6262
description: `Optional timeout in milliseconds (default ${DEFAULT_TIMEOUT_MS}, max ${MAX_TIMEOUT_MS})`,
@@ -145,7 +145,7 @@ const serialize = (v: unknown): string => {
145145

146146
// Snippet executor. The CDP Session is resolved per-call from `SessionStore`
147147
// keyed on `ctx.sessionID`. The agent connects with `await session.connect(...)`
148-
// in one snippet (Way 1 / Way 2 / Way 3 in browser-execute-guide.md); the Session persists
148+
// in one snippet (Way 1 / Way 2 / Way 3 in skills/browser-execute/SKILL.md); the Session persists
149149
// for follow-up snippets in the same opencode session.
150150
//
151151
// `dataDir` is opencode's XDG_DATA_HOME for bcode (~/.local/share/bcode/ on

packages/bcode-browser/src/index.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,10 @@
1111
// src/browser-execute.ts — in-process JS-eval browser_execute body
1212
// src/session-store.ts — per-opencode-session CDP Session map
1313
// src/skills.ts — runtime resolver for embedded skills
14-
// skills/ — browser-execute-guide.md (embedded into binary)
14+
// skills/ — browser-execute/SKILL.md (embedded into binary)
1515
//
1616
// Cloud browser provisioning is intentionally NOT a separate Level-1
17-
// surface. The agent reads Way 3 of `skills/browser-execute-guide.md` and
17+
// surface. The agent reads Way 3 of `skills/browser-execute/SKILL.md` and
1818
// writes the fetch+connect snippet itself, matching how local-browser
1919
// connect works (snippet-side, not tool-side). Decisions trail in
2020
// `memory/browsercode/decisions.md` §3.4.

packages/bcode-browser/test/skills.test.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ test("resolveSkillsDir materializes skills with {{SKILLS_DIR}} substituted", asy
1414
try {
1515
const dir = await Skills.resolveSkillsDir(dataDir)
1616
expect(dir).toBe(path.join(dataDir, "skills"))
17-
const browser = (await fs.readFile(path.join(dir, "browser-execute-guide.md"), "utf8")).replaceAll("\\", "/")
17+
const browser = (await fs.readFile(path.join(dir, "browser-execute", "SKILL.md"), "utf8")).replaceAll("\\", "/")
1818
expect(browser).not.toContain("{{SKILLS_DIR}}")
1919
expect(browser).toContain(`${dir.replaceAll("\\", "/")}/`)
2020
} finally {
@@ -29,8 +29,8 @@ test("different dataDirs get their own substituted paths", async () => {
2929
const dirA = await Skills.resolveSkillsDir(a)
3030
const dirB = await Skills.resolveSkillsDir(b)
3131
const [browserA, browserB] = (await Promise.all([
32-
fs.readFile(path.join(dirA, "browser-execute-guide.md"), "utf8"),
33-
fs.readFile(path.join(dirB, "browser-execute-guide.md"), "utf8"),
32+
fs.readFile(path.join(dirA, "browser-execute", "SKILL.md"), "utf8"),
33+
fs.readFile(path.join(dirB, "browser-execute", "SKILL.md"), "utf8"),
3434
])).map((s) => s.replaceAll("\\", "/"))
3535
const [a2, b2] = [dirA.replaceAll("\\", "/"), dirB.replaceAll("\\", "/")]
3636
expect(browserA).toContain(a2)

packages/bcode-browser/test/workspace-import.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
// at runtime from a `browser_execute` snippet via
44
// `await import("/abs/path?t=" + Date.now())`. We don't run a real
55
// `browser_execute` here — the point is to verify the dynamic-import
6-
// mechanism behaves as the browser-execute-guide.md prompt claims.
6+
// mechanism behaves as the browser-execute skill prompt claims.
77
//
88
// All four scenarios run against a real tmp dir, real .ts files, and
99
// the real Bun module loader. No mocks.

packages/opencode/src/skill/index.ts

Lines changed: 31 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import { ConfigMarkdown } from "@/config/markdown"
1414
import { RuntimeFlags } from "@/effect/runtime-flags"
1515
import { Glob } from "@opencode-ai/core/util/glob"
1616
import * as Log from "@opencode-ai/core/util/log"
17+
import { Skills as BcodeSkills } from "@browser-use/bcode-browser/skills"
1718
import { Discovery } from "./discovery"
1819
import CUSTOMIZE_OPENCODE_SKILL_BODY from "./prompt/customize-opencode.md" with { type: "text" }
1920
import { isRecord } from "@/util/record"
@@ -216,6 +217,16 @@ const discoverSkills = Effect.fnUntraced(function* (
216217
}
217218
}
218219

220+
// BrowserCode-shipped skills (browser-execute and any future first-party
221+
// reference docs) live at <dataDir>/skills/<name>/SKILL.md after the
222+
// bcode-browser materialization step. Scan unconditionally — the dir may
223+
// not exist yet on the very first launch before BrowserExecute.make has
224+
// run, and that's fine (Glob returns empty).
225+
const bcodeSkillsDir = BcodeSkills.skillsDir(global.data)
226+
if (yield* fsys.isDir(bcodeSkillsDir)) {
227+
yield* scan(state, bcodeSkillsDir, SKILL_PATTERN, { scope: "bcode" })
228+
}
229+
219230
return {
220231
matches: Array.from(state.matches),
221232
dirs: Array.from(state.dirs),
@@ -258,13 +269,26 @@ export const layer = Layer.effect(
258269
const state = yield* InstanceState.make(
259270
Effect.fn("Skill.state")(function* () {
260271
const s: State = { skills: {}, dirs: new Set() }
261-
// Register the built-in skill BEFORE disk discovery so a user-disk
262-
// skill with the same name can override it.
263-
s.skills[CUSTOMIZE_OPENCODE_SKILL_NAME] = {
264-
name: CUSTOMIZE_OPENCODE_SKILL_NAME,
265-
description: CUSTOMIZE_OPENCODE_SKILL_DESCRIPTION,
266-
location: "<built-in>",
267-
content: CUSTOMIZE_OPENCODE_SKILL_BODY,
272+
// BrowserCode gate: the upstream `customize-opencode` built-in is
273+
// off-by-default in BrowserCode. The skill describes opencode.json,
274+
// opencode plugins, opencode agents — not useful for browser-driving
275+
// sessions, and registering it unconditionally pollutes the system
276+
// prompt with negative-signal content (eval data showed a measurable
277+
// regression on browser-task scores when this skill was forced on).
278+
// Set BCODE_ENABLE_CUSTOMIZE_OPENCODE=1 to opt back in for sessions
279+
// where the user is actually editing bcode.json or agent configs.
280+
// Skipped registration happens BEFORE disk discovery, so a user-disk
281+
// skill named `customize-opencode` still loads normally.
282+
const customizeEnabled =
283+
process.env.BCODE_ENABLE_CUSTOMIZE_OPENCODE === "1" ||
284+
process.env.BCODE_ENABLE_CUSTOMIZE_OPENCODE?.toLowerCase() === "true"
285+
if (customizeEnabled) {
286+
s.skills[CUSTOMIZE_OPENCODE_SKILL_NAME] = {
287+
name: CUSTOMIZE_OPENCODE_SKILL_NAME,
288+
description: CUSTOMIZE_OPENCODE_SKILL_DESCRIPTION,
289+
location: "<built-in>",
290+
content: CUSTOMIZE_OPENCODE_SKILL_BODY,
291+
}
268292
}
269293
yield* loadSkills(s, yield* InstanceState.get(discovered), bus)
270294
return s

packages/opencode/src/tool/browser-execute.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,5 +3,5 @@ Executes JavaScript in a browser via CDP.
33
Usage:
44
- Use this tool whenever the task requires driving a real browser.
55
- Use this tool to read webpages that block the webfetch tool.
6-
- IMPORTANT: you MUST use the Read tool first to read `{{SKILLS_DIR}}/browser-execute-guide.md`. This tool will fail if you did not read these directions first.
6+
- IMPORTANT: you MUST use the skill tool first to load the `browser-execute` skill. This tool will fail if you did not read those directions first.
77
- Returns console output from the snippet; screenshots taken attach automatically as images.

0 commit comments

Comments
 (0)