A single agent can do a lot, but some jobs are cleaner as a pipeline: one agent gathers, another refines. The pattern is "agent chain" - the output of agent A becomes the input of agent B. Each agent has a narrow role and a focused system prompt, which is more reliable than one overloaded prompt trying to do both.
This example: a Researcher pulls facts, a Writer turns them into a polished post.
Two agents, one data flow
A mock research tool stands in for a real data source - a lookup table keyed by topic, so the example runs with zero external dependencies.
use NanoAgent\Agent;
use NanoAgent\Tools\FunctionTool;
$researchTool = new FunctionTool(
name: 'fetch_topic_facts',
description: 'Retrieves interesting facts about a given topic.',
parameters: [
'type' => 'object',
'properties' => [
'topic' => ['type' => 'string', 'description' => 'The subject to research']
],
'required' => ['topic']
],
callable: function (array $args) {
$knowledge = [
'php' => ['Created by Rasmus Lerdorf in 1994.',
'Powers over 75% of websites with a known server-side language.',
'The latest version is 8.4.'],
'ai' => ['The term AI was coined in 1956.',
'Transformer architecture (2017) revolutionized NLP.']
];
return $knowledge[strtolower($args['topic'])]
?? ["No specific facts found for '{$args['topic']}'."];
}
);
1. The Researcher - its only job is to gather
Its system prompt is narrow on purpose: fetch facts with the tool, return them as a list, nothing more.
$researcher = new Agent(
llm: $llmConfig,
systemPrompt: "You are a research specialist. Use the 'fetch_topic_facts' tool to "
. "gather raw data. Return only the collected facts in a list.",
tools: [$researchTool]
);
2. The Writer - its only job is to polish, and NOT invent
The Writer gets no tools at all - it only ever sees text the Researcher already gathered, and its prompt explicitly forbids adding facts of its own.
$writer = new Agent(
llm: $llmConfig,
systemPrompt: "You are a creative content writer. Transform raw research facts into an "
. "engaging social media post with hashtags. Do not invent facts that were "
. "not provided."
);
Run the chain
$topic = 'PHP';
$research = $researcher->chat("Research the topic: $topic");
// e.g. "1. Created by Rasmus Lerdorf in 1994. 2. Powers over 75% of websites..."
$post = $writer->chat("Write a post based on these facts:\n\n" . $research);
echo $post;
The seam is trivial: $writer->chat(... $research ...) - the Researcher's string is just the Writer's prompt. There's no framework, no message bus, no orchestration library. A chain is literally one chat() result fed into the next chat().
Why split it instead of one big agent?
- Focus beats scope. A prompt that says "research AND write well AND don't hallucinate" does all three poorly. Two prompts do each one well.
-
Different models per stage. The Researcher can be cheap and fast; the Writer can be your best model. You choose per agent because each is its own
new Agent(llm: ...). - Isolated failure. If research returns junk, you see it before it pollutes the final output. You can validate between stages.
-
Composability. Add a third stage (a Critique agent, a translator) by appending one
chat()call.
Where this goes next
- Researcher → Writer → Critic - a third agent scores the post and the loop retries if it's weak.
- Fan-out - several Researcher agents on sub-topics, then one Writer merges.
- Each stage can carry its own tools - the Researcher with a search tool, the Writer with a brand-voice tool.
The mental model: an agent is a function; a workflow is a composition of functions. PHP makes that composition boring in the best way - just call them in order.
Part of the NanoAgent examples series. Landing + demos.
Top comments (0)