Codemode
Let the model write code that orchestrates BashKit tools in batches.
Overview
Codemode wraps selected BashKit tools behind one Cloudflare Codemode tool. Instead of asking the model to call Glob, then Grep, then Read one turn at a time, the model can write a small JavaScript async arrow function that calls those tools with loops, branches, and parallel work.
BashKit does not run the code itself. It delegates to Cloudflare Codemode's executor, while the code still calls BashKit's policy-wrapped tools. That means context layers, plan-mode gates, output policy, and sandbox restrictions keep applying.
Setup
Install Cloudflare Codemode alongside BashKit, then provide an executor through codemode config.
Cloudflare Codemode currently targets AI SDK v6, so BashKit's codemode adapter follows that path.
npm install @cloudflare/codemode
import { DynamicWorkerExecutor } from '@cloudflare/codemode';import { createAgentTools, createPrepareStep } from 'bashkit';import { streamText } from 'ai';const { tools, planModeState } = await createAgentTools(sandbox, {planMode: true,context: {executionPolicy: {}, // plan-mode gating with defaultsoutputPolicy: { maxOutputLength: 30_000 },},codemode: {executor: new DynamicWorkerExecutor({ loader: env.LOADER }),includeTools: ['Read', 'Glob', 'Grep', 'Bash'],},});await streamText({model,tools,messages,prepareStep: createPrepareStep({ planModeState }),});
Batched Workflows
Codemode is useful when the model needs to fan out across files, combine results, or retry/narrow a search. The generated code can batch those steps into one tool call.
// Example of code the model can write inside codemode:async () => {const files = await bashkit.Glob({pattern: 'src/**/*.ts',path: null,});const planModeMatches = await Promise.all(files.matches.map((file) =>bashkit.Grep({pattern: 'PlanMode|planModeState|ExitPlanMode',path: file,glob: null,type: null,output_mode: 'content','-i': null,'-n': true,'-B': null,'-A': null,'-C': 2,head_limit: 20,offset: null,multiline: null,}),),);const interestingFiles = planModeMatches.filter((result) => 'matches' in result && result.matches.length > 0).map((result) => result.matches[0].file);const snippets = await Promise.all(interestingFiles.slice(0, 5).map((file_path) =>bashkit.Read({file_path,offset: 1,limit: 120,}),),);return {scanned: files.count,matchedFiles: interestingFiles,snippets,};};
That would normally take many model/tool turns. With Codemode, the model can express the workflow as code and return a structured result.
Tool Selection
Keep the inner tool set narrow. includeTools is the safest default because generated code can loop and fan out calls.
const { tools } = await createAgentTools(sandbox, {codemode: {executor,includeTools: ['Read', 'Glob', 'Grep'],tools: {// Optional AI SDK tools exposed only inside codemodeSummarizeFile,},},});
BashKit always excludes client-intervention tools from Codemode:AskUser, EnterPlanMode, ExitPlanMode, tools without an execute function, and tools with needsApproval.
Namespaces
The tool exposed to the model is named codemode by default. Inside the generated JavaScript, selected BashKit tools are exposed under BashKit's bashkit.* namespace. Tool methods keep BashKit's public tool names, so the grep tool is bashkit.Grep(...).
// Direct model tool call:tools.codemode({code: `async () => {const files = await bashkit.Glob({pattern: 'src/**/*.ts',path: null,});return files.matches;}`,});
Use providers when you want separate namespaces for custom tool groups. Each provider gets its own object inside the generated code, which keeps domain-specific helpers easier to scan.
const { tools } = await createAgentTools(sandbox, {codemode: {executor,includeTools: ['Read', 'Glob', 'Grep'],providers: [{name: 'github',tools: {ListIssues,FetchPullRequest,},},],},});// Generated code can do this in one codemode call:async () => {const todos = await bashkit.Grep({pattern: 'TODO|FIXME',path: 'src',glob: '*.ts',type: null,output_mode: 'content','-i': null,'-n': true,'-B': null,'-A': null,'-C': 2,head_limit: 20,offset: null,multiline: null,});const issues = await github.ListIssues({labels: ['tech-debt'],state: 'open',});return { todos, issues };};
Executable-only and needsApproval filtering applies to every namespace. Top-level includeTools narrows the default bashkit.* namespace; providers can define their own includeTools or excludeTools.
Policy
Codemode receives the same wrapped tools that normal model tool calls receive. If plan mode is active, Bash, Write, and Edit are still blocked by the execution policy even when generated code calls them.
const { tools, planModeState } = await createAgentTools(sandbox, {planMode: true,context: {executionPolicy: {planModeBlockedTools: ['Bash', 'Write', 'Edit'],},},codemode: {executor,includeTools: ['Read', 'Glob', 'Grep', 'Bash'],},});if (planModeState) {planModeState.isActive = true;}// A codemode-generated call to bashkit.Bash(...) returns the same// "Bash is not available in plan mode" error as a direct Bash tool call.