Skip to content

Coding Agent Creates and Repairs an Example

Overview

Run a template-defined coding agent with the bundled CodingAgentPrompt and provider-familiar read, bash, edit, and write tools. The task lives in goal.md, so it can be revised independently from the PHP wiring.

The agent reads examples/A01_Basics/Basic/run.php, creates a different InstructorPHP example under a timestamped /tmp/instructor-php/agent-test/ workspace, runs it, repairs failures, and verifies the result. Repository inputs remain read-only; generated artifacts stay available in /tmp for inspection.

Example

<?php
require 'examples/boot.php';

use Cognesy\Agents\Capability\AgentCapabilityRegistry;
use Cognesy\Agents\Capability\Bash\BashTool;
use Cognesy\Agents\Capability\Coding\UseCodingTools;
use Cognesy\Agents\Capability\Core\UseGuards;
use Cognesy\Agents\Collections\NameList;
use Cognesy\Agents\Data\AgentState;
use Cognesy\Agents\Events\Support\AgentEventConsoleObserver;
use Cognesy\Agents\Prompts\Coding\CodingAgentPrompt;
use Cognesy\Agents\Template\Data\AgentDefinition;
use Cognesy\Agents\Template\Factory\DefinitionLoopFactory;
use Cognesy\Agents\Template\Factory\DefinitionStateFactory;
use Cognesy\Events\Contracts\CanHandleEvents;
use Cognesy\Events\Dispatchers\EventDispatcher;

// --- Reusable coding-agent workflow -----------------------------------------

final readonly class CodingAgentRunResult
{
    public function __construct(
        public AgentState $state,
        public string $workspace,
        public string $generatedExample,
        public string $verificationCommand,
        public string $verificationOutput,
    ) {}
}

final readonly class CodingAgentExample
{
    private function __construct(
        private string $projectRoot,
        private string $workspace,
        private ?CanHandleEvents $events,
    ) {}

    public static function inTemporaryWorkspace(
        string $projectRoot,
        ?CanHandleEvents $events = null,
    ): self {
        $workspace = '/tmp/instructor-php/agent-test/'
            .date('Ymd-His').'-'.bin2hex(random_bytes(3));

        if (! mkdir($workspace, 0755, true) && ! is_dir($workspace)) {
            throw new RuntimeException("Cannot create workspace: {$workspace}");
        }

        return new self($projectRoot, $workspace, $events);
    }

    public function __invoke(): CodingAgentRunResult
    {
        // 1. Resolve the declarative definition into a runnable loop.
        $definition = $this->definition();
        $loop = (new DefinitionLoopFactory(
            capabilities: $this->capabilities(),
            events: $this->events,
        ))->instantiateAgentLoop($definition);

        // 2. Seed the initial conversation and let the loop own tool execution.
        $state = (new DefinitionStateFactory)->instantiateAgentState(
            $definition,
            AgentState::empty()->withUserMessage($this->task()),
        );
        $final = $loop->execute($state);

        // 3. Require a completed run and artifact before trying to execute it.
        $generatedExample = $this->workspace.'/run.php';
        if ($final->status()->value !== 'completed') {
            throw new RuntimeException(
                "Agent stopped with status: {$final->status()->value}",
            );
        }

        if (! is_file($generatedExample)) {
            throw new RuntimeException("Agent did not create: {$generatedExample}");
        }

        // 4. Verify the artifact independently of the agent's own report.
        $verificationCommand = 'php '.escapeshellarg($generatedExample);
        $verificationOutput = BashTool::inDirectory($this->projectRoot)(
            $verificationCommand,
        );

        if (str_contains($verificationOutput, 'Exit code:')) {
            throw new RuntimeException("Generated example failed:\n{$verificationOutput}");
        }

        if (! str_contains($verificationOutput, 'Example status: verified')) {
            throw new RuntimeException(
                "Generated example is not verified:\n{$verificationOutput}",
            );
        }

        return new CodingAgentRunResult(
            state: $final,
            workspace: $this->workspace,
            generatedExample: $generatedExample,
            verificationCommand: $verificationCommand,
            verificationOutput: $verificationOutput,
        );
    }

    public function workspace(): string
    {
        return $this->workspace;
    }

    private function task(): string
    {
        $goalTemplate = file_get_contents(__DIR__.'/goal.md');
        if ($goalTemplate === false) {
            throw new RuntimeException('Cannot read the coding-agent goal.');
        }

        return strtr($goalTemplate, [
            '{{SOURCE_EXAMPLE}}' => $this->projectRoot.'/examples/A01_Basics/Basic/run.php',
            '{{PROJECT_ROOT}}' => $this->projectRoot,
            '{{WORKSPACE}}' => $this->workspace,
        ]);
    }

    private function capabilities(): AgentCapabilityRegistry
    {
        $capabilities = new AgentCapabilityRegistry;
        $capabilities->register('coding.tools', new UseCodingTools($this->projectRoot));
        $capabilities->register(
            'coding.guards',
            new UseGuards(maxSteps: 20, maxTokens: 32768, maxExecutionTime: 240),
        );

        return $capabilities;
    }

    private function definition(): AgentDefinition
    {
        return new AgentDefinition(
            name: 'coding-example-agent',
            description: 'Creates and repairs an isolated InstructorPHP example.',
            systemPrompt: CodingAgentPrompt::with(
                documentation_path: $this->projectRoot.'/packages/agents/README.md',
            )->render(),
            llmConfig: 'openai',
            capabilities: new NameList('coding.tools', 'coding.guards'),
        );
    }
}

// --- Shared event channel ----------------------------------------------------

$events = new EventDispatcher('coding-example-agent');

// --- Raw agent setup ---------------------------------------------------------

$example = CodingAgentExample::inTemporaryWorkspace(
    projectRoot: dirname(__DIR__, 3),
    events: $events,
);

// --- Example instrumentation ------------------------------------------------

$observer = new AgentEventConsoleObserver(
    useColors: true,
    showTimestamps: true,
    showContinuation: true,
    showToolArgs: true,
);
$events->wiretap($observer->wiretap());

// --- Execute and inspect -----------------------------------------------------

echo "=== Coding Agent Execution ===\n";
echo "Workspace: {$example->workspace()}\n\n";

$result = $example();

echo "\n=== Result ===\n";
echo "Generated example: {$result->generatedExample}\n";
echo "Verification: {$result->verificationCommand}\n";
echo 'Agent response: '
    .($result->state->finalResponse()->toString() ?: 'No response')
    ."\n";
echo "\n{$result->verificationOutput}\n";

assert($result->state->status()->value === 'completed');
assert(is_file($result->generatedExample));
assert(str_contains($result->verificationOutput, 'Example status: verified'));
?>