Agent Wrapper
Convenience wrapper that combines ContextEngine with the AI SDK into a streamable agent
The agent() function combines a ContextEngine and an AI SDK model into a single object with generate(), stream(), clone(), asTool(), and asAdvisor() methods. It resolves context, repairs tool calls when needed, and can retry streamed output through guardrails without extra orchestration in your app.
Every agent requires a sandbox — an AgentSandbox that provides bash, readFile, and writeFile tools plus a skills array. The sandbox is created externally and passed in; the agent does not create or dispose it.
Sandbox Options
| Approach | When to use | Docker? | Supports skills |
|---|---|---|---|
createBashTool() | Local dev, tests, lightweight tasks | No | Yes |
createDockerSandbox() + createBashTool() | Real binaries in isolated Docker containers | Yes | Yes |
createDaytonaSandbox() + createBashTool() | Managed Daytona cloud sandboxes | No | Yes |
createBashTool({ sandbox: e2bSandbox }) | Other cloud sandboxes via adapter | No | Yes |
Import createBashTool from @deepagents/context (not the raw bash-tool package). The @deepagents/context wrapper adds the skills option and always returns an AgentSandbox.
import { createBashTool } from '@deepagents/context';
// Simplest: just-bash virtual sandbox (no Docker needed)
const sandbox = await createBashTool();import {
createBashTool,
createDockerSandbox,
pkg,
} from '@deepagents/context';
// Docker with skills: pick the backend, then layer the bash tool.
const backend = await createDockerSandbox({
installers: [pkg(['curl', 'jq'])],
});
const sandbox = await createBashTool({
sandbox: backend,
skills: [{ host: './skills', sandbox: '/workspace/skills' }],
});
// Don't forget: await sandbox.sandbox.dispose() when doneExposing local binaries on PATH
bin() symlinks a binary that already exists inside the container — typically reached via a bind-mounted workspace — onto PATH. The dev loop becomes nx run <pkg>:build → rerun the agent. No docker build, no esbuild bake-in, no re-published package.
import {
bin,
createBashTool,
createDockerSandbox,
} from '@deepagents/context';
const backend = await createDockerSandbox({
image: 'node:lts-alpine',
installers: [bin('/workspace/packages/text2sql/dist/bin/sql.js')],
volumes: [
{
type: 'bind',
hostPath: process.cwd(),
containerPath: '/workspace',
readOnly: true,
},
],
});
const sandbox = await createBashTool({ sandbox: backend });
// `sql` is now on PATH inside the container.
await sandbox.sandbox.executeCommand('sql --help');The command name defaults to the binary basename without its extension (sql.js → sql). Override with { name } or { target }:
bin('/opt/tools/cli.mjs', { name: 'tool' });
bin('/opt/tools/cli.mjs', { target: '/opt/bin/tool' });bin() requires the file to exist at install time — a missing path throws InstallError with source: 'bin' instead of silently creating a dangling symlink. The chmod +x step is skipped when the file is already executable, so read-only bind mounts of pre-built artifacts work without a writable mount.
import {
DAYTONA_DEFAULT_DESTINATION,
createBashTool,
createDaytonaSandbox,
} from '@deepagents/context';
// Daytona reads DAYTONA_* env vars; mkdir the workspace before wiring the bash tool.
const backend = await createDaytonaSandbox();
await backend.executeCommand(`mkdir -p ${DAYTONA_DEFAULT_DESTINATION}`);
const sandbox = await createBashTool({
sandbox: backend,
destination: DAYTONA_DEFAULT_DESTINATION,
});
// Don't forget: await sandbox.sandbox.dispose() when doneThe agent merges sandbox.tools into its toolset automatically. Any tools you pass override sandbox tools on name conflict.
Creating an Agent
import {
agent,
ContextEngine,
SqliteContextStore,
createBashTool,
role,
} from '@deepagents/context';
import { groq } from '@ai-sdk/groq';
const sandbox = await createBashTool();
const store = new SqliteContextStore('./chat.db');
const context = new ContextEngine({
store,
chatId: 'chat-001',
userId: 'user-001',
}).set(role('You are a helpful coding assistant.'));
const assistant = agent({
name: 'coding_assistant',
sandbox,
context,
model: groq('gpt-oss-20b'),
});Configuration
| Option | Type | Default | Description |
|---|---|---|---|
name | string | required | Identifier for logging and error messages |
sandbox | AgentSandbox | required | Sandbox providing bash, readFile, writeFile tools plus skills — created via createBashTool() (optionally chained on top of createDockerSandbox() or createDaytonaSandbox()) from @deepagents/context |
context | ContextEngine | undefined | Engine that resolves system prompt and messages |
model | LanguageModelV3 | undefined | AI SDK model to use for generation |
tools | ToolSet | {} | Additional AI SDK tools — merged with sandbox tools (user tools override sandbox tools on name conflict) |
toolChoice | ToolChoice | undefined | Controls how the model selects tools |
providerOptions | object | undefined | Provider-specific options forwarded to the AI SDK |
experimental_telemetry | object | undefined | AI SDK telemetry settings forwarded unchanged to generateText and streamText |
guardrails | Guardrail[] | [] | Guardrails applied during streaming |
maxGuardrailRetries | number | 3 | Max retry attempts when a guardrail fails |
logging | boolean | undefined | Enable debug logging |
Instance Properties
The agent exposes these readonly properties after creation:
| Property | Type | Description |
|---|---|---|
tools | ToolSet | Merged toolset (sandbox tools + user tools) |
context | ContextEngine | undefined | The context engine bound to this agent |
model | LanguageModelV3 | undefined | The model configured for this agent |
generate()
Resolves the context, calls the model, and returns the full result. Useful when you need the complete response before continuing.
import { user } from '@deepagents/context';
context.set(user('Explain closures in JavaScript'));
await context.save();
const result = await assistant.generate({});
console.log(result.text);generate() accepts two arguments:
| Argument | Type | Description |
|---|---|---|
contextVariables | CIn | Passed as experimental_context to the AI SDK |
config.abortSignal | AbortSignal | Cancel the request |
config.abortSignal is forwarded to the model call and any tool-call repair attempt, so cancellation stops the full request instead of only the outer wrapper.
stream()
Resolves the context and returns a streaming result. When guardrails are configured, toUIMessageStream() is wrapped to intercept parts and retry on failure.
context.set(user('Write a Python quicksort'));
await context.save();
const stream = await assistant.stream({});
for await (const part of stream.toUIMessageStream()) {
if (part.type === 'text-delta') {
process.stdout.write(part.delta);
}
}stream() accepts two arguments:
| Argument | Type | Description |
|---|---|---|
contextVariables | CIn | Passed as experimental_context to the AI SDK |
config.abortSignal | AbortSignal | Cancel the request |
config.transform | StreamTextTransform | StreamTextTransform[] | Custom stream transforms (defaults to smoothStream()) |
config.maxRetries | number | Override maxGuardrailRetries for this call |
config.abortSignal is reused across repair attempts and guardrail retries, so cancelling a stream stops the active turn cleanly.
clone()
Creates a new agent with the same options, selectively overridden. Handy for switching models or tools without rebuilding everything.
const creativeAssistant = assistant.clone({
name: 'creative_assistant',
providerOptions: { temperature: 0.9 },
});
const result = await creativeAssistant.generate({});asTool()
Convert an agent into a regular AI SDK tool so another agent can call it for a
bounded sub-task. asTool() requires both context and model.
When the tool runs, the wrapper:
- Forks the current
ContextEngine - Appends the tool input as a
user(...)message in that fork - Runs a one-shot
generate()call with the child context - Returns either the extracted output or the raw tool results
That keeps the parent agent's persisted thread untouched while reusing the same system fragments and configuration.
const researcher = agent({
name: 'researcher',
sandbox,
context,
model: groq('gpt-oss-20b'),
});
const writer = agent({
name: 'writer',
sandbox,
context,
model: groq('gpt-oss-20b'),
tools: {
research: researcher.asTool({
toolDescription: 'Research the topic and return a concise brief',
outputExtractor: async (result) => result.text,
}),
},
});asTool() Options
| Option | Type | Description |
|---|---|---|
toolDescription | string | Override the generated tool description |
outputExtractor | (result) => string | Promise<string> | Transform the sub-agent result before the parent receives it |
asAdvisor()
Expose the same agent as a no-input advisor tool backed by its configured model. The advisor receives the full executor conversation history automatically, so callers use it for strategy checks rather than raw execution work.
const reviewer = agent({
name: 'reviewer',
sandbox,
context,
model: groq('gpt-oss-20b'),
});
const { tool: advisor, usage } = reviewer.asAdvisor({
concise: true,
maxConversationUses: 2,
});
const worker = agent({
name: 'worker',
sandbox,
context,
model: groq('gpt-oss-20b'),
tools: { advisor },
});
console.log(usage());usage() reports successful advisor calls plus aggregated model usage for the
returned advisor instance.
asAdvisor() Options
| Option | Type | Description |
|---|---|---|
maxUses | number | Hard cap on total tool invocations for this advisor instance |
maxConversationUses | number | Cap on successful advisor responses before the tool returns max_uses_exceeded |
maxOutputTokens | number | Maximum output tokens for the advisor response |
concise | boolean | string | Use the built-in concise prompt, or provide custom advisor instructions |
The tool returns short machine-readable status codes such as
too_many_requests, prompt_too_long, or execution_time_exceeded when the
advisor request cannot complete cleanly.
Guardrails
Pass guardrails and maxGuardrailRetries to add real-time stream interception. When a guardrail fails, the agent emits a finish step, persists the corrected assistant state, and retries with self-correction feedback.
import { agent, errorRecoveryGuardrail } from '@deepagents/context';
const guarded = agent({
name: 'guarded_assistant',
sandbox,
context,
model: groq('gpt-oss-20b'),
guardrails: [errorRecoveryGuardrail],
maxGuardrailRetries: 3,
});See the Guardrails reference for the full API — writing custom guardrails, the pass/fail/stop model, GuardrailContext, chaining, and built-in error patterns. For a practical walkthrough, see the Error Recovery recipe.
structuredOutput()
Returns type-safe structured data parsed against a Zod schema. Separate from agent() because it uses Output.object() under the hood.
import { structuredOutput, ContextEngine, SqliteContextStore, role, user } from '@deepagents/context';
import { groq } from '@ai-sdk/groq';
import z from 'zod';
const store = new SqliteContextStore('./chat.db');
const context = new ContextEngine({
store,
chatId: 'extract-001',
userId: 'user-001',
}).set(role('Extract structured data from the user message.'));
const PersonSchema = z.object({
name: z.string(),
age: z.number(),
occupation: z.string(),
});
const extractor = structuredOutput({
context,
model: groq('gpt-oss-20b'),
schema: PersonSchema,
});
context.set(user('John is a 30-year-old software engineer.'));
await context.save();
const person = await extractor.generate({});
// { name: "John", age: 30, occupation: "software engineer" }structuredOutput Configuration
| Option | Type | Default | Description |
|---|---|---|---|
context | ContextEngine | required | Engine that resolves system prompt and messages |
model | LanguageModelV3 | required | AI SDK model |
schema | z.ZodType | required | Zod schema for the output type |
providerOptions | object | undefined | Provider-specific options |
experimental_telemetry | object | undefined | AI SDK telemetry settings forwarded unchanged to generateText and streamText |
tools | ToolSet | undefined | Tools available during generation |
structuredOutput Methods
generate() resolves context, calls the model with Output.object(), and returns the parsed value directly.
stream() returns the full StreamTextResult with the structured output schema applied. Use this when you need to process stream parts before the final value.
const stream = await extractor.stream({});
for await (const part of stream.toUIMessageStream()) {
if (part.type === 'text-delta') {
process.stdout.write(part.delta);
}
}How Context Resolution Works
Both agent() and structuredOutput() call context.resolve() with an XmlRenderer internally. The resolved system prompt and messages are forwarded to the AI SDK's generateText or streamText. You don't need to call resolve() yourself when using these wrappers.
agent.generate() / agent.stream()
└─ context.resolve({ renderer: XmlRenderer })
└─ { systemPrompt, messages }
└─ generateText() / streamText()Next Steps
- Context Engine - Full ContextEngine API reference
- Fragments - Building context with fragments
- Renderers - How context is rendered for the model
- Checkpoints - Named restore points for branching