Code Agents¶
The AgentCtrl facade provides a unified interface for invoking CLI-based code agents that can execute code, modify files, and perform complex multi-step tasks. Each agent runs as an external process, and the facade handles process management, output parsing, and response structuring.
Setup¶
Before using AgentCtrl, install the CLI agent you want to run and make sure its executable is available in PATH. The Laravel package does not install or authenticate these tools for you.
| Agent | Required binary | Setup note |
|---|---|---|
| Claude Code | claude |
Install Claude Code separately and sign in with its normal workflow |
| Codex | codex |
Install the Codex CLI and ensure codex resolves on the server running Laravel |
| OpenCode | opencode |
Install OpenCode and ensure opencode resolves on the server running Laravel |
After the binary is available, configure Laravel defaults in config/instructor.php under agents for timeout, working directory, sandbox driver, and per-agent model overrides.
Supported Agents¶
| Agent | Description | Use Case |
|---|---|---|
| Claude Code | Anthropic's Claude agent with code execution | General coding tasks, refactoring, file modifications |
| Codex | OpenAI's Codex agent | Code generation and completion |
| OpenCode | Multi-model code agent | Research and coding with model flexibility |
Quick Start¶
use Cognesy\AgentCtrl\Config\AgentCtrlConfig;
use Cognesy\Instructor\Laravel\Facades\AgentCtrl;
// Execute a task with Claude Code
$response = AgentCtrl::claudeCode()
->withConfig(new AgentCtrlConfig(
timeout: 300,
workingDirectory: base_path(),
))
->execute('Generate a Laravel migration for a users table with name, email, and password fields');
// Check if successful
if ($response->isSuccess()) {
echo $response->text();
}
Agent Selection¶
Claude Code¶
Best for general coding tasks with Anthropic's Claude models. Supports sandbox isolation, session resumption, and streaming output.
use Cognesy\Instructor\Laravel\Facades\AgentCtrl;
$response = AgentCtrl::claudeCode()
->withModel('claude-opus-4-5')
->inDirectory(base_path())
->withTimeout(300)
->execute('Refactor the User model to use DTOs');
echo $response->text();
echo "Session ID: " . (string) ($response->sessionId() ?? '');
Codex¶
Best for OpenAI-powered code generation.
$response = AgentCtrl::codex()
->withModel('codex')
->execute('Write unit tests for the UserService class');
OpenCode¶
Best for multi-model flexibility. Specify the model using the provider/model format.
$response = AgentCtrl::openCode()
->withModel('anthropic/claude-sonnet-4-5')
->execute('Analyze the codebase architecture');
Dynamic Selection¶
Select agent type at runtime based on configuration or business logic.
use Cognesy\AgentCtrl\Enum\AgentType;
$agentType = AgentType::from(config('app.default_agent'));
$response = AgentCtrl::make($agentType)
->execute('Generate API documentation');
Configuration¶
Builder Methods¶
All agents support the same set of builder methods for configuration. Use withConfig() when you want one typed object for the shared options, then layer agent-specific methods on top as needed. Builder methods override any defaults set in the Laravel config file.
use Cognesy\AgentCtrl\Config\AgentCtrlConfig;
use Cognesy\Sandbox\Enums\SandboxDriver;
AgentCtrl::claudeCode()
->withConfig(new AgentCtrlConfig(
model: 'claude-opus-4-5',
timeout: 300,
workingDirectory: '/path/to/project',
sandboxDriver: SandboxDriver::Host,
))
->execute('Your prompt');
AgentCtrlConfig::fromArray() also accepts the Laravel config-style keys used in config/instructor.php, so directory and sandbox are mapped automatically.
Laravel Configuration¶
Configure defaults in config/instructor.php. The facade automatically reads these values and applies them when you create a builder. Builder methods then override any defaults for that specific call.
'agents' => [
// Default timeout for all agents
'timeout' => env('INSTRUCTOR_AGENT_TIMEOUT', 300),
// Default working directory
'directory' => env('INSTRUCTOR_AGENT_DIRECTORY'),
// Default sandbox driver: host, docker, podman, firejail, bubblewrap
'sandbox' => env('INSTRUCTOR_AGENT_SANDBOX', 'host'),
// Claude Code specific
'claude_code' => [
'model' => env('CLAUDE_CODE_MODEL', 'claude-sonnet-4-20250514'),
'timeout' => env('CLAUDE_CODE_TIMEOUT'),
'directory' => env('CLAUDE_CODE_DIRECTORY'),
'sandbox' => env('CLAUDE_CODE_SANDBOX'),
],
// Codex specific
'codex' => [
'model' => env('CODEX_MODEL', 'codex'),
'timeout' => env('CODEX_TIMEOUT'),
'directory' => env('CODEX_DIRECTORY'),
'sandbox' => env('CODEX_SANDBOX'),
],
// OpenCode specific
'opencode' => [
'model' => env('OPENCODE_MODEL', 'anthropic/claude-sonnet-4-20250514'),
'timeout' => env('OPENCODE_TIMEOUT'),
'directory' => env('OPENCODE_DIRECTORY'),
'sandbox' => env('OPENCODE_SANDBOX'),
],
],
Agent-specific settings (e.g., claude_code.timeout) take precedence over the global defaults (e.g., agents.timeout).
Environment Variables¶
# Default agent settings
INSTRUCTOR_AGENT_TIMEOUT=300
INSTRUCTOR_AGENT_DIRECTORY=/path/to/project
INSTRUCTOR_AGENT_SANDBOX=host
# Claude Code
CLAUDE_CODE_MODEL=claude-opus-4-5
# Codex
CODEX_MODEL=codex
# OpenCode
OPENCODE_MODEL=anthropic/claude-sonnet-4-20250514
Streaming¶
Process output in real-time with streaming callbacks. The three callback types fire at different points during execution.
$response = AgentCtrl::claudeCode()
->onText(function (string $text) {
// Called as text is generated -- use for live output
echo $text;
})
->onToolUse(function (string $tool, array $input, ?string $output) {
// Called when agent uses a tool (file read, shell command, etc.)
echo "Tool: $tool\n";
echo "Input: " . json_encode($input) . "\n";
})
->onComplete(function (AgentResponse $response) {
// Called once when execution finishes
echo "\nDone! Exit code: " . $response->exitCode;
})
->executeStreaming('Generate a REST API for products');
Response Object¶
The AgentResponse object contains the agent's output along with metadata about the execution.
$response = AgentCtrl::claudeCode()->execute('...');
// Main content
$response->text(); // string -- Generated text output
$response->isSuccess(); // bool -- True if exitCode is 0
// Metadata
$response->exitCode; // int -- Process exit code
$response->sessionId(); // AgentSessionId|null -- Session ID for resuming
$response->agentType; // AgentType -- Which agent was used
// Usage (when available)
$response->usage; // ?TokenUsage -- Token statistics
$response->usage->input; // int -- Input tokens
$response->usage->output; // int -- Output tokens
$response->usage->total(); // int -- Total tokens
// Cost (when available)
$response->cost; // ?float -- Cost in USD
// Tool calls
$response->toolCalls; // array<ToolCall> -- Tools used during execution
foreach ($response->toolCalls as $call) {
$call->tool; // string -- Tool name
$call->input; // array -- Tool input parameters
$call->output; // ?string -- Tool output
$call->isError; // bool -- Whether the tool call failed
}
Session Management¶
Resume previous sessions for continued work. This is useful for multi-turn interactions where the agent needs context from a prior execution.
// First execution
$response = AgentCtrl::claudeCode()
->execute('Start refactoring the User model');
$sessionId = (string) ($response->sessionId() ?? '');
// Later: Resume the session with full context from the previous run
$response = AgentCtrl::claudeCode()
->resumeSession($sessionId)
->execute('Continue with the Address model');
Error Handling¶
Always check isSuccess() and handle failures gracefully. Agent executions can fail due to timeouts, sandbox errors, or issues in the generated code.
use Cognesy\Instructor\Laravel\Facades\AgentCtrl;
try {
$response = AgentCtrl::claudeCode()
->withTimeout(60)
->execute($prompt);
if (!$response->isSuccess()) {
// Non-zero exit code
Log::error('Agent failed', [
'exit_code' => $response->exitCode,
'output' => $response->text(),
]);
return null;
}
// Check for tool errors
foreach ($response->toolCalls as $call) {
if ($call->isError) {
Log::warning('Tool error', [
'tool' => $call->tool,
'error' => $call->output,
]);
}
}
return $response->text();
} catch (\Throwable $e) {
// Timeout, sandbox error, etc.
Log::error('Agent exception', ['error' => $e->getMessage()]);
return null;
}
Testing¶
Use AgentCtrl::fake() for testing without actual agent execution. See the Testing guide for full documentation of AgentCtrlFake.
use Cognesy\Instructor\Laravel\Facades\AgentCtrl;
test('generates migration code', function () {
// Setup fake with expected responses
$fake = AgentCtrl::fake([
'Generated migration file: 2024_01_01_create_users_table.php',
]);
// Execute code under test
$result = app(MigrationGenerator::class)->generate('users');
// Assertions
$fake->assertExecuted();
$fake->assertExecutedTimes(1);
$fake->assertUsedClaudeCode();
$fake->assertExecutedWith('migration');
expect($result)->toContain('users_table');
});
Real-World Examples¶
Code Generation Service¶
namespace App\Services;
use Cognesy\Instructor\Laravel\Facades\AgentCtrl;
class CodeGenerationService
{
public function generateMigration(array $schema): string
{
$prompt = $this->buildMigrationPrompt($schema);
$response = AgentCtrl::claudeCode()
->inDirectory(database_path('migrations'))
->execute($prompt);
if (!$response->isSuccess()) {
throw new \RuntimeException('Failed to generate migration');
}
return $response->text();
}
public function generateTest(string $className): string
{
$response = AgentCtrl::claudeCode()
->inDirectory(base_path('tests'))
->execute("Generate comprehensive tests for: $className");
return $response->text();
}
private function buildMigrationPrompt(array $schema): string
{
return "Generate a Laravel migration for:\n" . json_encode($schema, JSON_PRETTY_PRINT);
}
}
Queued Code Generation¶
For long-running agent tasks, dispatch them to a queue so the user does not have to wait.
namespace App\Jobs;
use Cognesy\Instructor\Laravel\Facades\AgentCtrl;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
class GenerateCodeJob implements ShouldQueue
{
use Queueable;
public function __construct(
public string $prompt,
public string $outputPath,
) {}
public function handle(): void
{
$response = AgentCtrl::claudeCode()
->withTimeout(600) // 10 minutes for complex tasks
->inDirectory(dirname($this->outputPath))
->execute($this->prompt);
if ($response->isSuccess()) {
file_put_contents($this->outputPath, $response->text());
}
}
}
// Usage
GenerateCodeJob::dispatch(
'Generate a complete CRUD controller for Products',
app_path('Http/Controllers/ProductController.php')
);
Interactive Code Review¶
use Cognesy\Instructor\Laravel\Facades\AgentCtrl;
class CodeReviewer
{
public function review(string $filePath, callable $onProgress = null): array
{
$builder = AgentCtrl::claudeCode()
->inDirectory(dirname($filePath));
if ($onProgress) {
$builder->onText($onProgress);
}
$response = $builder->execute(
"Review this file for bugs, security issues, and improvements: $filePath"
);
return [
'review' => $response->text(),
'success' => $response->isSuccess(),
'session' => (string) ($response->sessionId() ?? ''),
];
}
}
Sandbox Drivers¶
Control the isolation level of agent execution. The sandbox driver determines whether the agent runs directly on the host or inside a container.
| Driver | Description | Use Case |
|---|---|---|
host |
Direct execution (no isolation) | Development, trusted environments |
docker |
Docker container isolation | Production, untrusted code |
podman |
Podman container isolation | Rootless containers |
firejail |
Linux sandbox | Lightweight isolation |
bubblewrap |
Minimal sandbox | CI/CD environments |
use Cognesy\Sandbox\Enums\SandboxDriver;
// Development (direct execution)
AgentCtrl::claudeCode()
->withSandboxDriver(SandboxDriver::Host)
->execute('...');
// Production (Docker isolation)
AgentCtrl::claudeCode()
->withSandboxDriver(SandboxDriver::Docker)
->execute('...');
Best Practices¶
- Set Timeouts -- Always set appropriate timeouts for your use case. Complex code generation can take several minutes.
- Use Sandbox Isolation -- In production, use Docker or another container-based sandbox driver to prevent agents from making unintended changes.
- Handle Errors -- Check
isSuccess()and handle failures gracefully. Agents can fail for many reasons, including API limits, invalid code, and sandbox restrictions. - Log Sessions -- Store session IDs for debugging and continuation. They let you resume work and trace agent behavior.
- Test with Fakes -- Use
AgentCtrl::fake()in tests to avoid API calls and process execution. - Queue Long Tasks -- Use Laravel queues for time-consuming code generation to keep web responses fast.