Skip to content
docsv0.42.1

AgentOrchestrator

Agentic tool-use loop: alternates between asking an {@see LlmService} for the next step and executing any tool calls it requests via {@see McpClientService}, until the LLM returns a final response or {@see self::$maxSteps} is reached.

AgentOrchestrator::setSystemPromptProjection()

public function setSystemPromptProjection(?callable $projection): self

Project this request's system instructions from the exact outgoing tool offer. The base prompt and conversation stay unchanged; null preserves the legacy path. Projection failures surface before contacting the model (greenhouse0867).

Parameters

Parameters of setSystemPromptProjection()
NameTypeDescription
$projection(callable(string, list<array<string, mixed>>): string | null)

AgentOrchestrator::setTrailingProjection()

public function setTrailingProjection(?callable $projection): self

Project one message AFTER the conversation, for this request only, from the exact outgoing tool offer. For what a caller must tell the model and GROWS during a run. Written into the system prompt, such text changes what comes BEFORE the conversation, so no request is a prefix of the next and the provider reads the whole prompt again on every step (greenhouse evidence/1109: 13 cold calls of 19). After the conversation, each request prolongs the one before. It rides as a user-role message — provider chat templates reject a system message that is not at the beginning — it is never accumulated into the history, and an empty string adds nothing. Null preserves the legacy path; a projection that fails surfaces before contacting the model.

Parameters

Parameters of setTrailingProjection()
NameTypeDescription
$projection(callable(list<array<string, mixed>>): string | null)

AgentOrchestrator::termination()

public function termination(): ?Milpa\AiGateway\RunTermination

The latest base-loop exit; null before a run or while that run is in progress.

AgentOrchestrator::__construct()

public function __construct(Milpa\AiGateway\LlmService $llm, Milpa\ToolRuntime\Gate\GatedToolCalls $mcpClient, int $maxSteps = 20, ?Psr\Log\LoggerInterface $logger = null, ?Milpa\ToolRuntime\Rendering\RendererRegistry $rendererRegistry = null, ?Milpa\AiGateway\PlanBoard $planBoard = null, bool $lazyTools = false, ?Milpa\AiGateway\ProgressProbe $progressProbe = null, int $contextTokens = 0, ?Milpa\AiGateway\AnswerJudge $answerJudge = null, ?int $outputTokens = null):

Parameters

Parameters of __construct()
NameTypeDescription
$llmMilpa\AiGateway\LlmService
$mcpClientMilpa\ToolRuntime\Gate\GatedToolCalls
$maxStepsint
$logger?Psr\Log\LoggerInterface
$rendererRegistry?Milpa\ToolRuntime\Rendering\RendererRegistry
$planBoard?Milpa\AiGateway\PlanBoard
$lazyToolsbool
$progressProbe?Milpa\AiGateway\ProgressProbe
$contextTokensint
$answerJudge?Milpa\AiGateway\AnswerJudge
$outputTokens?int

AgentOrchestrator::setOpeningMove()

public function setOpeningMove(?array $assistantMessage): self

Open the next run with tool calls the caller already holds, instead of asking the model for them. For a call the caller knows deterministically: the one it recorded, that was refused, and whose refusal a person has since lifted. The loop takes the message as the reply to its first step WITHOUT contacting the provider; the calls then go through the same executor as any other — gate, recording, budget — the step counts against the ceiling, and the model reads the call and its result as its own conversation on the next step. It is played once. Null withdraws it. An opening move is tool calls and nothing else: words are never put in the model's mouth.

Parameters

Parameters of setOpeningMove()
NameTypeDescription
$assistantMessage(array<string, mixed> | null)`role: assistant`, empty `content`, and `tool_calls` in the provider's shape (id, function.name, function.arguments as JSON text)

Throws

\InvalidArgumentException when the message carries content, no call, or a malformed call

AgentOrchestrator::setContinuation()

public function setContinuation(?callable $continuation): self

Let the caller play the calls that follow deterministically from a tool call, instead of asking the model. After every tool call that ran, the continuation is asked with the tool, its arguments and what it answered. It answers null, or a list of `{name, arguments}` calls that follow from it — the promotion of a verified trial of an operation its house applies, say. The loop plays them as its NEXT STEP without contacting the provider: one assistant message with those calls, through the same executor as any other — gate, recording, budget. A continued call spends a step of the ceiling, may be refused like any call, and may itself be continued. One the run did not reach is dropped, never carried into another run. A continuation answers with tool calls or nothing: anything else fails the run.

Parameters

Parameters of setContinuation()
NameTypeDescription
$continuation(callable(string, array<string, mixed>, mixed): mixed | null)

AgentOrchestrator::setUnofferedCall()

public function setUnofferedCall(?callable $told): self

Tell the caller of a call the loop turned away because its tool was not offered in that step. A model can name a tool absent from the catalogue sent on a step — one its caller left out, or one that does not exist. The loop answers that call itself and never asks the registry, so nobody else heard of it: a house that keeps a session's log had no trace that the session tried (greenhouse decisions/0601, evidence/1163 §10). With this the caller is told, once per such call and in the order they came: the tool that was named, its arguments, and the very answer the model reads. Being told changes nothing: the answer is the same, the offer is the same, and the registry is still not asked. A teller that fails loses the telling, never the run. Null withdraws it.

Parameters

Parameters of setUnofferedCall()
NameTypeDescription
$told(callable(string, array<string, mixed>, string): mixed | null)

AgentOrchestrator::getLastToolResult()

public function getLastToolResult(): ?Milpa\ToolRuntime\ToolResult

Get the last ToolResult from tool execution. Used by ProcessTelegramMessageJob to build keyboard from metadata.

AgentOrchestrator::setToolContext()

public function setToolContext(Milpa\ToolRuntime\Contracts\ToolContext $ctx): self

Set the tool context for channel-specific rendering.

Parameters

Parameters of setToolContext()
NameTypeDescription
$ctxMilpa\ToolRuntime\Contracts\ToolContext

AgentOrchestrator::run()

public function run(string $prompt, string $systemPrompt = 'You are a helpful assistant.', array $history = [], ?callable $onStep = null): string

Drive the agent loop until it returns a final answer or pauses on a gate.

Parameters

Parameters of run()
NameTypeDescription
$promptstring
$systemPromptstring
$historylist<array<string, mixed>>prior turns, each a role/content message
$onStep?callable