string, 'inputs' => array, 'result' => mixed ]. * * @var array */ private array $activity = []; /** * Handle an agent/workflow event. * * @param string $event Event name (e.g. 'tool-called'). * @param object $source Component that emitted the event. * @param mixed $data Event payload. * @param string|null $branch_id Parallel branch id, if any. * @return void */ public function onEvent( string $event, object $source, mixed $data = null, ?string $branch_id = null ): void { if ( 'tool-called' !== $event || ! $data instanceof ToolCalled ) { return; } $tool = $data->tool; // The 'tool-called' event is emitted from a finally block in the // ToolNode, so it fires even when the tool threw. In that case the // result was never set and Tool::getResult() (typed ': string') would // raise a TypeError that would mask the real tool exception. Guard the // read so a failed tool records an empty result instead of crashing. try { $result = $tool->getResult(); } catch ( \Throwable $e ) { // getResult() throws only when the tool never set a result — i.e. it // crashed. Record a distinguishable marker instead of '', so next turn // the model can tell a failed tool from a genuinely empty result. $result = 'ERROR: tool execution failed — treat this as a failure, not an empty result. Retry or tell the user you could not complete it.'; } $this->activity[] = [ 'name' => $tool->getName(), 'inputs' => $tool->getInputs(), 'result' => $result, ]; } /** * All tool activity captured this turn. * * @return array */ public function get_activity(): array { return $this->activity; } }