Individual tools describe themselves. The relationship between them belongs at the server level:
#[Instructions('Recommended flow: 1) CollectSiteInformationTool gathers all
business details, 2) SendVerificationEmailTool sends a code to the email,
3) VerifyEmailTool confirms it, 4) CheckUrlAvailabilityTool checks the URL,
5) CreateSiteTool creates the site. CRITICAL: sites are LIVE AND PUBLIC upon
creation. If the user abandons before step 5, their info is already captured.')]
class AgentServer extends Server
{
protected array $tools = [
CollectSiteInformationTool::class,
SendVerificationEmailTool::class,
VerifyEmailTool::class,
CheckUrlAvailabilityTool::class,
SuggestPalettesTool::class,
SuggestOptionsTool::class,
CreateSiteTool::class,
GetSiteStatusTool::class,
// Note: ResetLeadTool intentionally excluded from public tools.
// It's internal-only for debugging/development.
];
}
Two things worth taking.
The flow is stated once, at the top. Repeating "call this after that" in eight descriptions guarantees they will disagree within a month.
Not every tool belongs in the toolset. ResetLeadTool exists, works, and is deliberately not registered. A tool that resets a lead's state is invaluable in development and is, to a model that has been talked into using it, a way to erase a verification. Registration is a decision, not a consequence of the file existing.
That last point deserves its own chapter, and it gets one โ Chapter 11.
What This Chapter Argued
- A tool is an endpoint whose caller cannot read your docs. The description is the entire integration guide.
- Describe when to call it and what not to do afterwards, not just what it does. If your UI renders the result, say so, or the model will repeat it in prose.
- Schema and validation are both required. The schema is how you ask; the validator is how you insist.
- Field descriptions are prompt surface. "Order them most to least likely" is not machine-checkable and improves output anyway.
- Take flat scalars, assemble structure server-side. Nested JSON is a reliability tax.
- Use conditional rules so one tool serves two shapes rather than splitting into two tools.
- Strip inapplicable fields at the boundary, so an occasional stray field never becomes a downstream rejection the model has to recover from.
- Normalise optional fields once, in the tool, rather than defending against absence in every consumer.
- Name result keys after the tool that produced them, so the second tool with options does not collide with the first.
- A tool result is a prompt delivered mid-turn. Instructions land better close to the decision they govern.
- Order the toolset at the server level, and treat registration as a decision โ some tools should exist and never be offered.