Context
System prompt assembly, tool execution gating, and output policies.
Overview
The context layer is an opt-in system that wraps your tools with cross-cutting behavior: blocking tools based on state (execution policy), truncating large outputs with redirection hints (output policy), and assembling a static system prompt from project docs and environment info.
Enable it by passing a context config to createAgentTools:
const { tools, contextLayers } = await createAgentTools(sandbox, {context: {executionPolicy: { /* ... */ },outputPolicy: { maxOutputLength: 50000 },layers: [myCustomLayer],extraTools: { MyTool: myTool },},});
When context is omitted, tools work exactly as before — no wrapping, no overhead.
Context Layers
A ContextLayer intercepts tool execution with two optional hooks:
beforeExecute— return{ error: string }to block a tool call, orundefinedto allow itafterExecute— transform the tool result (e.g., truncate output)
import { withContext, applyContextLayers } from 'bashkit';import type { ContextLayer } from 'bashkit';const loggingLayer: ContextLayer = {beforeExecute: (toolName, params) => {console.log(`Calling ${toolName}`);return undefined; // allow execution},afterExecute: (toolName, params, result) => {console.log(`${toolName} returned`);return result; // pass through unchanged},};// Wrap a single toolconst wrappedTool = withContext(myTool, 'MyTool', [loggingLayer]);// Wrap an entire ToolSetconst wrappedTools = applyContextLayers(tools, [loggingLayer]);
Layers compose in order: first beforeExecute rejection wins, afterExecute transforms pipe through sequentially.
Execution Policy
Gates tool execution based on state. The most common use case is plan mode — blocking write tools while allowing read-only tools.
import { createExecutionPolicy } from 'bashkit';// Plan mode: blocks Bash, Write, Edit by defaultconst policy = createExecutionPolicy(planModeState);// Custom blocked toolsconst policy = createExecutionPolicy(planModeState, {planModeBlockedTools: ['Bash', 'Write', 'Edit', 'WebFetch'],});// Custom predicate (independent of plan mode)const policy = createExecutionPolicy(undefined, {shouldBlock: (toolName, params) => {if (toolName === 'Bash' && String(params.command).includes('rm')) {return 'Destructive commands are not allowed';}return undefined;},});
Tools stay registered in the tool set (prompt cache stable) — only execution is gated.
Output Policy
Handles large tool outputs by truncating and injecting hints that tell the model how to access the full result.
import { createOutputPolicy } from 'bashkit';// Defaults: maxOutputLength 30000, redirectionThreshold 20000const policy = createOutputPolicy();// Custom thresholdsconst policy = createOutputPolicy({maxOutputLength: 50000,redirectionThreshold: 40000,excludeTools: ['Read'], // never truncate Read output});
When output exceeds redirectionThreshold, it gets truncated to maxOutputLength and a _hint field is added with tool-specific guidance (e.g., "use head/tail to see specific parts").
Custom Hints
const policy = createOutputPolicy({// Simple per-tool hint stringshints: {Bash: 'Re-run with | head or | tail to see specific parts.',Grep: 'Narrow your pattern to reduce results.',},// Full control callbackbuildHint: (toolName, params, originalLength, result) => {if (toolName === 'Bash' && params.command === 'git log') {return 'Use git log with --oneline or -n to limit output.';}return undefined; // fall through to hints map / defaults},});
Stash to Disk
Optionally save full output to disk before truncating, so the model can Read the file later:
const policy = createOutputPolicy({stashOutput: {sandbox,tools: ['Bash', 'Grep'], // which tools get disk stashdir: '/tmp/.bashkit/tool-output', // default},});
System Prompt Assembly
buildSystemContext assembles a static system prompt from three sources: discovered project instructions (AGENTS.md / CLAUDE.md files), environment info (cwd, platform, git branch), and tool guidance.
import { buildSystemContext } from 'bashkit';const ctx = await buildSystemContext(sandbox, {instructions: true, // discover AGENTS.md / CLAUDE.md filesenvironment: true, // collect cwd, shell, platform, git infotoolGuidance: {tools: { Bash: 'Run shell commands', Read: 'Read files' },},});// Use in streamTextconst result = await streamText({model,system: ctx.combined, // all sections joinedtools,messages,});// Or access individual sectionsctx.instructions // project instructions textctx.environment // environment XML blockctx.toolGuidance // tool hint list
Call once at init — the output is deterministic and designed to stay stable across turns for Anthropic prompt caching.
prepareStep
createPrepareStep returns a callback for the AI SDK's prepareStep option. It composes message compaction, context status monitoring, and plan mode hints.
import { createPrepareStep } from 'bashkit';const prepareStep = createPrepareStep({compaction: {model,maxTokens: 128000,threshold: 0.7,},contextStatus: {maxTokens: 128000,},planModeState,extend: async (args) => {// Custom logic runs after built-in stepsreturn {};},});const result = await streamText({model,tools,messages,prepareStep,});
Important: prepareStep never touches the system prompt — all dynamic content is injected as user messages to preserve prompt caching.
Full Example
import {createLocalSandbox,createAgentTools,buildSystemContext,createPrepareStep,} from 'bashkit';import { streamText } from 'ai';const sandbox = createLocalSandbox({ workingDirectory: '.' });const { tools, planModeState, contextLayers } = await createAgentTools(sandbox,{context: {executionPolicy: {}, // plan mode gating with defaultsoutputPolicy: {maxOutputLength: 50000,stashOutput: { sandbox, tools: ['Bash'] },},},budget: { maxUsd: 5.0 },},);const ctx = await buildSystemContext(sandbox, {instructions: true,environment: true,});const prepareStep = createPrepareStep({planModeState,contextStatus: { maxTokens: 128000 },});const result = await streamText({model,system: ctx.combined,tools,messages,prepareStep,});