Skip to main content
CLI-backed agent classes wrap external AI command-line tools. Each implements the AI SDK Agent interface and works anywhere Smithers accepts an agent, including <Task>. The agent spawns the CLI, passes the prompt, captures output, and returns a GenerateTextResult. For API-billed provider wrappers, see SDK Agents.

Import

import {
  ClaudeCodeAgent,
  CodexAgent,
  GeminiAgent,
  PiAgent,
  KimiAgent,
  ForgeAgent,
  AmpAgent,
  type PiAgentOptions,
  type PiExtensionUiRequest,
  type PiExtensionUiResponse,
} from "smithers-orchestrator";

Prerequisites

AgentCLI RequiredInstall
ClaudeCodeAgentclaudeClaude Code CLI
CodexAgentcodexOpenAI Codex CLI
GeminiAgentgeminiGemini CLI
PiAgentpiPI Coding Agent
KimiAgentkimiKimi CLI
ForgeAgentforgeForge CLI
AmpAgentampAmp CLI

Quick Start

import { ClaudeCodeAgent, CodexAgent, GeminiAgent, PiAgent, KimiAgent, ForgeAgent, AmpAgent } from "smithers-orchestrator";

const claude = new ClaudeCodeAgent({ model: "claude-sonnet-4-20250514" });
const codex = new CodexAgent({ model: "gpt-4.1" });
const gemini = new GeminiAgent({ model: "gemini-2.5-pro" });
const pi = new PiAgent({ provider: "openai", model: "gpt-5.2-codex" });
const kimi = new KimiAgent({ model: "kimi-latest" });
const forge = new ForgeAgent({ model: "anthropic/claude-sonnet-4-20250514" });
const amp = new AmpAgent({ model: "claude-sonnet-4-20250514" });
{/* outputs comes from createSmithers() */}
<Task id="analysis" output={outputs.analysis} agent={claude}>
  {`Analyze the codebase and identify potential improvements.`}
</Task>

Hijack Support

All built-in CLI agents support native-session hijack via smithers hijack <runId>.
AgentHijack ModeNative Relaunch
ClaudeCodeAgentNative CLI sessionclaude --resume <session>
CodexAgentNative CLI sessioncodex resume <session> -C <cwd>
GeminiAgentNative CLI sessiongemini --resume <session>
PiAgentNative CLI sessionpi --session <session>
KimiAgentNative CLI sessionkimi --session <session> --work-dir <cwd>
ForgeAgentNative CLI sessionforge --conversation-id <id> -C <cwd>
AmpAgentNative CLI sessionamp threads continue <thread>
Behavior:
  • Live run: Smithers waits until the agent is between blocking tool calls before aborting.
  • Finished/cancelled run: Smithers reopens the latest persisted native session.
  • If the hijacked session exits successfully, the workflow resumes automatically in detached mode.
  • Cross-engine hijack is not supported.
Use smithers hijack <runId> --launch=false to inspect the resumable candidate without opening the session.

Base Options

type BaseCliAgentOptions = {
  id?: string;               // Agent ID (default: random UUID)
  model?: string;            // Model name to pass to the CLI
  systemPrompt?: string;     // System prompt prepended to the user prompt
  instructions?: string;     // Alias for systemPrompt
  cwd?: string;              // Working directory for the CLI process
  env?: Record<string, string>;  // Additional environment variables
  yolo?: boolean;            // Skip permission prompts (default: true)
  timeoutMs?: number;        // Hard wall-clock timeout in milliseconds
  idleTimeoutMs?: number;    // Inactivity timeout (no stdout/stderr) in milliseconds
  maxOutputBytes?: number;   // Max output capture size
  extraArgs?: string[];      // Additional CLI arguments appended to the command
};
OptionDefaultDescription
idRandom UUIDAgent instance identifier
modelundefinedModel name passed to --model
systemPromptundefinedSystem instructions prepended to the prompt
instructionsundefinedAlias for systemPrompt
cwdTool context rootDir or process.cwd()Working directory for the spawned process
env{}Extra environment variables merged with process.env
yolotrueSkip all interactive permission prompts
timeoutMsundefinedHard wall-clock timeout; kills process after this many ms
idleTimeoutMsundefinedInactivity timeout; kills process after this many ms with no output
maxOutputBytesundefinedTruncate captured output to this size
extraArgs[]Additional CLI flags

Timeouts

  • timeoutMs: hard wall-clock cap.
  • idleTimeoutMs: inactivity cap, resets on any stdout/stderr output.
Per-call override:
await agent.generate({
  prompt: "do the thing",
  timeout: { totalMs: 15 * 60 * 1000, idleMs: 2 * 60 * 1000 },
});

ClaudeCodeAgent

Wraps claude CLI with --print mode.
const claude = new ClaudeCodeAgent({
  model: "claude-sonnet-4-20250514",
  systemPrompt: "You are a careful code reviewer.",
  timeoutMs: 30 * 60 * 1000,
  idleTimeoutMs: 2 * 60 * 1000,
});

Claude-Specific Options

type ClaudeCodeAgentOptions = BaseCliAgentOptions & {
  addDir?: string[];
  agent?: string;
  agents?: Record<string, { description?: string; prompt?: string }> | string;
  allowDangerouslySkipPermissions?: boolean;
  allowedTools?: string[];
  appendSystemPrompt?: string;
  betas?: string[];
  chrome?: boolean;
  continue?: boolean;
  dangerouslySkipPermissions?: boolean;
  debug?: boolean | string;
  debugFile?: string;
  disableSlashCommands?: boolean;
  disallowedTools?: string[];
  fallbackModel?: string;
  file?: string[];
  forkSession?: boolean;
  fromPr?: string;
  ide?: boolean;
  includePartialMessages?: boolean;
  inputFormat?: "text" | "stream-json";
  jsonSchema?: string;
  maxBudgetUsd?: number;
  mcpConfig?: string[];
  mcpDebug?: boolean;
  noChrome?: boolean;
  noSessionPersistence?: boolean;
  outputFormat?: "text" | "json" | "stream-json";
  permissionMode?: "acceptEdits" | "bypassPermissions" | "default" | "delegate" | "dontAsk" | "plan";
  pluginDir?: string[];
  replayUserMessages?: boolean;
  resume?: string;
  sessionId?: string;
  settingSources?: string;
  settings?: string;
  strictMcpConfig?: boolean;
  tools?: string[] | "default" | "";
  verbose?: boolean;
};
OptionDescription
outputFormat"text", "json", or "stream-json" (default: "text")
permissionMode"bypassPermissions", "acceptEdits", "default", "delegate", "dontAsk", "plan"
allowedToolsTool name whitelist
disallowedToolsTool name blacklist
maxBudgetUsdSpending cap in USD
mcpConfigMCP server configuration files
addDirAdditional context directories
When yolo is true (default), the agent passes --allow-dangerously-skip-permissions, --dangerously-skip-permissions, and --permission-mode bypassPermissions unless permissionMode is explicitly set.

CodexAgent

Wraps codex CLI using codex exec with stdin input.
const codex = new CodexAgent({
  model: "gpt-4.1",
  sandbox: "workspace-write",
  fullAuto: true,
});

Codex-Specific Options

type CodexAgentOptions = BaseCliAgentOptions & {
  config?: Record<string, string | number | boolean | object | null> | string[];
  enable?: string[];
  disable?: string[];
  image?: string[];
  oss?: boolean;
  localProvider?: string;
  sandbox?: "read-only" | "workspace-write" | "danger-full-access";
  profile?: string;
  fullAuto?: boolean;
  dangerouslyBypassApprovalsAndSandbox?: boolean;
  cd?: string;
  skipGitRepoCheck?: boolean;
  addDir?: string[];
  outputSchema?: string;
  color?: "always" | "never" | "auto";
  json?: boolean;
  outputLastMessage?: string;
};
OptionDescription
sandbox"read-only", "workspace-write", or "danger-full-access"
fullAutoFull auto mode (no confirmations)
configConfiguration overrides as key-value pairs or raw strings
ossUse open-source models
localProviderLocal model provider URL
outputLastMessageFile path to write the last message (auto-generated if not set)
When yolo is true and fullAuto is not set, passes --dangerously-bypass-approvals-and-sandbox. If fullAuto is true, uses --full-auto instead. Prompt is passed via stdin using the - argument.

GeminiAgent

Wraps the gemini CLI.
const gemini = new GeminiAgent({
  model: "gemini-2.5-pro",
  sandbox: true,
  allowedTools: ["read_file", "write_file"],
});

Gemini-Specific Options

type GeminiAgentOptions = BaseCliAgentOptions & {
  debug?: boolean;
  sandbox?: boolean;
  approvalMode?: "default" | "auto_edit" | "yolo" | "plan";
  experimentalAcp?: boolean;
  allowedMcpServerNames?: string[];
  allowedTools?: string[];
  extensions?: string[];
  listExtensions?: boolean;
  resume?: string;
  listSessions?: boolean;
  deleteSession?: string;
  includeDirectories?: string[];
  screenReader?: boolean;
  outputFormat?: "text" | "json" | "stream-json";
};
OptionDescription
sandboxRun in sandbox mode
approvalMode"default", "auto_edit", "yolo", or "plan"
allowedToolsTool name whitelist
extensionsGemini CLI extensions to load
includeDirectoriesAdditional directories to include
outputFormat"text", "json", or "stream-json" (default: "json")
When yolo is true and approvalMode is not set, passes --yolo. Prompt is passed via --prompt.

PiAgent

Wraps the pi CLI.
const pi = new PiAgent({
  provider: "openai",
  model: "gpt-5.2-codex",
  mode: "text",
  noSession: true,
});

PI-Specific Options

type PiAgentOptions = BaseCliAgentOptions & {
  provider?: string;
  model?: string;
  apiKey?: string;
  systemPrompt?: string;
  appendSystemPrompt?: string;
  mode?: "text" | "json" | "rpc";
  print?: boolean;
  continue?: boolean;
  resume?: boolean;
  session?: string;
  sessionDir?: string;
  noSession?: boolean;
  models?: string | string[];
  listModels?: boolean | string;
  tools?: string[];
  noTools?: boolean;
  extension?: string[];
  noExtensions?: boolean;
  skill?: string[];
  noSkills?: boolean;
  promptTemplate?: string[];
  noPromptTemplates?: boolean;
  theme?: string[];
  noThemes?: boolean;
  thinking?: "off" | "minimal" | "low" | "medium" | "high" | "xhigh";
  export?: string;
  files?: string[];
  verbose?: boolean;
  onExtensionUiRequest?: (request: PiExtensionUiRequest) =>
    | Promise<PiExtensionUiResponse | null>
    | PiExtensionUiResponse
    | null;
};
OptionDescription
providerPI provider name (--provider)
modelPI model (--model)
apiKeyPassed to --api-key (prefer env/config for secrets)
modetext, json, or rpc
printForce --print in text mode
continue / resume / sessionSession continuation controls
sessionDirCustom session directory
models / listModelsScoped model patterns and listing
extensionExtension path(s)
skillSkill path(s)
promptTemplatePrompt template path(s)
themeTheme path(s)
tools / noToolsEnable specific tools or disable built-ins
exportExport session HTML
filesFile args passed as @path (text/json modes)
onExtensionUiRequestRPC-only handler for extension UI requests
noSessionDisable session persistence (default true unless session flags set)
In text/json modes, the prompt is a positional argument and files emit as @path arguments. In rpc mode, the prompt is sent as JSON over stdin. Text mode defaults to --print without --mode; json/rpc set --mode and omit --print. For workflow hijack, Smithers automatically uses PI’s structured event stream and keeps session persistence enabled regardless of noSession.

KimiAgent

Wraps kimi CLI using --print mode.
const kimi = new KimiAgent({
  model: "kimi-latest",
  thinking: true,
  timeoutMs: 300_000,
});

Kimi-Specific Options

type KimiAgentOptions = BaseCliAgentOptions & {
  workDir?: string;
  session?: string;
  continue?: boolean;
  thinking?: boolean;
  outputFormat?: "text" | "stream-json";
  finalMessageOnly?: boolean;
  quiet?: boolean;
  agent?: "default" | "okabe";
  agentFile?: string;
  mcpConfigFile?: string[];
  mcpConfig?: string[];
  skillsDir?: string;
  maxStepsPerTurn?: number;
  maxRetriesPerStep?: number;
  maxRalphIterations?: number;
  verbose?: boolean;
  debug?: boolean;
};
OptionDescription
thinkingEnable/disable thinking mode
outputFormat"text" or "stream-json" (default: "text")
finalMessageOnlyOnly print the final assistant message
quietAlias for --print --output-format text --final-message-only
agentBuilt-in agent spec: "default" or "okabe"
agentFilePath to custom agent specification file
skillsDirSkills directory path
mcpConfigFileMCP config file(s)
maxStepsPerTurnMax steps in one turn
maxRetriesPerStepMax retries in one step
maxRalphIterationsExtra iterations after the first turn in Loop mode
When yolo is true (default), passes --print which implicitly adds --yolo. Prompt is passed via --prompt.

ForgeAgent

Wraps forge CLI. Supports 300+ models via --prompt.
const forge = new ForgeAgent({
  model: "anthropic/claude-sonnet-4-20250514",
  provider: "anthropic",
  directory: "/path/to/project",
});

Forge-Specific Options

type ForgeAgentOptions = BaseCliAgentOptions & {
  directory?: string;       // -C, --directory <DIR>
  provider?: string;        // --provider <PROVIDER>
  agent?: string;           // --agent <AGENT>
  conversationId?: string;  // --conversation-id <ID>
  sandbox?: string;         // --sandbox <NAME>
  restricted?: boolean;     // -r, --restricted
  verbose?: boolean;        // --verbose
  workflow?: string;        // -w, --workflow <FILE>
  event?: string;           // -e, --event <JSON>
  conversation?: string;    // --conversation <FILE>
};
OptionDescription
directoryWorking directory (-C); defaults to cwd
providerModel provider name
agentAgent type
conversationIdResume conversation by ID
sandboxSandbox name
restrictedEnable restricted mode
workflowWorkflow file path
eventEvent JSON for workflow triggers
conversationConversation file path
Forge --prompt mode auto-approves tool use; no separate yolo flag. Prompt is passed via --prompt.

AmpAgent

Wraps amp CLI using --execute mode.
const amp = new AmpAgent({
  model: "claude-sonnet-4-20250514",
  visibility: "private",
  logLevel: "info",
});

Amp-Specific Options

type AmpAgentOptions = BaseCliAgentOptions & {
  visibility?: "private" | "public" | "workspace" | "group";
  mcpConfig?: string;
  settingsFile?: string;
  logLevel?: "error" | "warn" | "info" | "debug" | "audit";
  logFile?: string;
  dangerouslyAllowAll?: boolean;
  ide?: boolean;
  jetbrains?: boolean;
};
OptionDescription
visibilityThread visibility: "private", "public", "workspace", "group"
mcpConfigMCP configuration file path
settingsFileCustom settings file path
logLevel"error", "warn", "info", "debug", "audit"
logFileLog output file path
dangerouslyAllowAllAllow all tool calls without confirmation
When yolo is true (default) or dangerouslyAllowAll is true, passes --dangerously-allow-all. Prompt is passed via --execute. Automatically passes --no-ide, --no-jetbrains, --no-color, and --archive for headless execution.

Agent Interface

All CLI agents implement two methods.

generate(options)

Runs the CLI synchronously and returns a GenerateTextResult:
const result = await claude.generate({
  prompt: "Explain the architecture of this codebase.",
});
console.log(result.text);
  1. Extracts prompt from options.prompt (string) or options.messages (array).
  2. Builds the CLI command with all configured flags.
  3. Spawns the process and captures stdout/stderr.
  4. For json/stream-json output, extracts text from the JSON payload.
  5. Returns the result as a GenerateTextResult.

stream(options)

Calls generate() internally and wraps the result as a StreamTextResult. Not truly streamed.
const stream = await claude.stream({ prompt: "Review this code." });
for await (const chunk of stream.textStream) {
  process.stdout.write(chunk);
}

Message Handling

When called with messages, agents convert them to a text prompt:
  • System messages are extracted and prepended as a system prompt.
  • User/assistant messages are formatted as ROLE: content, joined with double newlines.
  • Message system prompt is combined with any systemPrompt on the agent instance.

Example: Multi-Agent Workflow

import { ClaudeCodeAgent, CodexAgent } from "smithers-orchestrator";

const reviewer = new ClaudeCodeAgent({
  model: "claude-sonnet-4-20250514",
  systemPrompt: "You are a thorough code reviewer.",
  timeoutMs: 120_000,
});

const fixer = new CodexAgent({
  model: "gpt-4.1",
  fullAuto: true,
  timeoutMs: 180_000,
});

const { Workflow, smithers, outputs } = createSmithers({
  review: z.object({ summary: z.string() }),
  fix: z.object({ result: z.string() }),
});

export default smithers((ctx) => (
  <Workflow name="review-and-fix">
    <Task id="review" output={outputs.review} agent={reviewer}>
      {`Review the changes in this PR and identify issues.`}
    </Task>
    <Task id="fix" output={outputs.fix} agent={fixer}>
      {`Fix these issues: ${ctx.output(outputs.review, { nodeId: "review" }).summary}`}
    </Task>
  </Workflow>
));