Skip to main content

Overview

An agent can handle multiple rounds of execution, where each round builds on the previous conversation history. Just add a new user message to the returned state and call execute() again — AgentLoop automatically resets completed executions before starting a new one. This enables multi-turn interactions where the agent reasons over past tool results to answer follow-up questions without re-executing tools. Key concepts:
  • withUserMessage(): Appends a follow-up user message to the existing conversation
  • The agent sees all prior messages including tool calls and results from previous executions
  • Follow-up questions can reference data gathered in earlier rounds
  • AgentLoop auto-resets terminal execution state (completed/failed) on entry

Example

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

use Cognesy\Agents\AgentLoop;
use Cognesy\Agents\Capability\File\ReadFileTool;
use Cognesy\Agents\Data\AgentState;
use Cognesy\Agents\Events\Support\AgentEventConsoleObserver;

$workDir = dirname(__DIR__, 3);

$logger = new AgentEventConsoleObserver(
    useColors: true,
    showTimestamps: true,
    showContinuation: true,
    showToolArgs: true,
);

// Build an agent with direct file reading tool
$loop = AgentLoop::default()
    ->withTool(ReadFileTool::inDirectory($workDir))
    ->wiretap($logger->wiretap());

// === Execution 1: Ask the agent to read composer.json ===
$query1 = 'Read the composer.json file and tell me the project name and its PHP version requirement.';
echo "=== Execution 1 ===\n";
echo "Query: {$query1}\n\n";

$state = AgentState::empty()->withUserMessage($query1);
$state = $loop->execute($state);

$response1 = $state->finalResponse()->toString() ?: 'No response';
echo "\nResponse: {$response1}\n\n";

// === Execution 2: Follow-up question using context from Execution 1 ===
$query2 = 'Based on what you read, does the project use PSR-4 autoloading? What are the namespace prefixes?';
echo "=== Execution 2 ===\n";
echo "Query: {$query2}\n\n";

// Just add a new message — AgentLoop auto-resets the completed execution
$state = $state->withUserMessage($query2);
$state = $loop->execute($state);

$response2 = $state->finalResponse()->toString() ?: 'No response';
echo "\nResponse: {$response2}\n\n";

// === Execution 3: Another follow-up — agent reasons without tools ===
$query3 = 'Given what you know about this project, what type of project is it — a library, framework, or application? Explain briefly.';
echo "=== Execution 3 ===\n";
echo "Query: {$query3}\n\n";

$state = $state->withUserMessage($query3);
$state = $loop->execute($state);

$response3 = $state->finalResponse()->toString() ?: 'No response';
echo "\nResponse: {$response3}\n";

if ($state->status()->value !== 'completed') {
    echo "Skipping assertions because execution status is {$state->status()->value}.\n";
    return;
}

// Assertions
assert(!empty($response1) && $response1 !== 'No response', 'Expected non-empty response from execution 1');
assert(!empty($response2) && $response2 !== 'No response', 'Expected non-empty response from execution 2');
assert(!empty($response3) && $response3 !== 'No response', 'Expected non-empty response from execution 3');
assert($state->stepCount() >= 1, 'Expected at least 1 step in final execution');
?>