| name | yao-agent-development |
| description | Guide for building AI Agents with Yao framework. Use when creating assistants, implementing hooks (Create/Next), defining MCP tools, database models, prompts, i18n, agent-to-agent communication (A2A), or working with the Context API. Trigger when user mentions Yao Agent, assistant development, hooks, MCP tools, or agent pipelines. |
| metadata | {"author":"Yao App Engine","version":"1.0"} |
Yao Agent Development Guide
Build AI Agents (Assistants) with hooks, tools, models, and prompts.
Core Concept
Assistant = Hooks + Tools + Models + Prompts + Configuration
- Hooks: TypeScript functions (
Create, Next) that intercept execution
- Tools: MCP tool implementations called by LLM
- Models: Database schemas with auto-migration
- Prompts: System/user prompt templates
Directory Structure
assistants/<assistant_id>/
├── package.yao # Required: Configuration
├── prompts.yml # Required: System prompts
├── src/
│ ├── index.ts # Hooks (Create, Next)
│ ├── tools.ts # MCP tool implementations
│ └── *_test.ts # Tests
├── mcps/
│ ├── tools.mcp.yao # Tool declarations
│ └── mapping/tools/schemes/
│ └── <tool>.in.yao # Tool input schema
├── models/
│ └── *.mod.yao # Database models
├── locales/
│ ├── en-us.yml
│ └── zh-cn.yml
└── pages/ # SUI pages (optional)
Package Configuration
package.yao:
{
"name": "{{ name }}",
"type": "assistant",
"avatar": "/assets/avatar.png",
"connector": "deepseek.v3",
"connector_options": {
"optional": true,
"connectors": ["deepseek.v3", "openai.gpt-4o"],
"filters": ["tool_calls"]
},
"mcp": {
"servers": [
{ "server_id": "agents.<id>.tools", "tools": ["tool1"] }
]
},
"uses": { "search": "disabled" },
"options": { "temperature": 0.7 },
"public": true,
"modes": ["chat", "task"],
"default_mode": "task",
"mentionable": true,
"share": "team",
"placeholder": {
"title": "{{ chat.title }}",
"prompts": ["{{ chat.prompts.0 }}"]
}
}
| Field | Description |
|---|
connector | Default LLM connector ID. Supports $ENV.VAR syntax |
connector_options | Connector filtering for CUI switcher (see below) |
mcp.servers | MCP tools to enable |
uses.search | Search behavior: "disabled", "builtin", "<agent>" |
modes | Supported modes: "chat", "task" |
share | Sharing: "user", "team", "public" |
tags | Classification tags for CUI display |
mentionable | Whether agent can be @mentioned by others |
sort | Display order in CUI sidebar |
connector_options details:
| Field | Description |
|---|
optional | Show connector switcher in CUI (true=show, false=hide) |
connectors | Whitelist of connectors; empty = show all |
filters | Filter by capability: "tool_calls", "vision", "reasoning", etc. |
Hooks
Execution Flow
User Input → Load History → Create Hook → LLM Call → Tool Execution → Next Hook → Response
Create Hook
Called before LLM call. Configure the request.
function Create(ctx: agent.Context, messages: agent.Message[]): agent.Create {
ctx.memory.context.Set("start_time", Date.now());
return null;
return {
messages, temperature: 0.7, connector: "gpt-4o", prompt_preset: "task",
mcp_servers: [{ server_id: "agents.myapp.tools" }],
uses: { search: "disabled" },
};
return { delegate: { agent_id: "specialist", messages } };
}
Next Hook
Called after LLM response and tool calls.
function Next(ctx: agent.Context, payload: agent.Payload): agent.Next {
const { tools, error } = payload;
if (error) return { data: { status: "error", message: error } };
if (tools?.length > 0) {
if (tools[0].result?.intent === "query") {
return { delegate: { agent_id: "query_agent", messages: payload.messages } };
}
return { data: { status: "success", results: tools.map(t => t.result) } };
}
return null;
}
Return Values
| Return Value | Behavior |
|---|
{ data: {...} } | Return custom data, ends execution |
{ delegate: {...} } | Delegate to agent, continues |
null | Standard response, ends execution |
Context API
Properties
ctx.chat_id
ctx.assistant_id
ctx.locale
ctx.authorized
Get Owner ID
const ownerID = ctx.authorized?.team_id || ctx.authorized?.user_id;
Messaging
ctx.Send({ type: "text", props: { content: "Hello!" } });
ctx.Send("Hello!");
const id = ctx.SendStream("Processing...");
ctx.Append(id, " done!");
ctx.End(id);
ctx.Replace(id, { type: "text", props: { content: "Done!" } });
Memory
| Namespace | Persistence | Use Case |
|---|
ctx.memory.user | Persistent | User preferences |
ctx.memory.team | Persistent | Team settings |
ctx.memory.chat | Persistent | Chat session state |
ctx.memory.context | Request | Pass data between hooks |
ctx.memory.context.Set("key", value);
ctx.memory.context.Set("key", value, 300);
const value = ctx.memory.context.Get("key");
MCP
const result = ctx.mcp.CallTool("server_id", "tool_name", { arg: "value" });
See Context API Reference for Trace, MCP parallel calls, memory counters/lists, and full API.
Agent-to-Agent (A2A)
const result = ctx.agent.Call("yao.keeper.classify", messages, {
skip: { output: true, history: true },
});
const result = Process("agent.Call", {
assistant_id: "yao.keeper.classify",
messages: [{ role: "user", content: "Classify this..." }],
timeout: 120,
});
See Context API for parallel calls, full parameters, and when to use which.
MCP Tools
Tool Server (mcps/tools.mcp.yao)
{
"label": "Tools",
"description": "Custom tools",
"transport": "process",
"tools": {
"recognize": "agents.<id>.tools.Recognize",
"query": "models.agents.<id>.order.Paginate"
}
}
Tool Input Schema (mcps/mapping/tools/schemes/recognize.in.yao)
{
"type": "object",
"description": "Recognize user intent",
"properties": {
"intent": {
"type": "string",
"enum": ["query", "submit", "analyze"],
"description": "Detected intent"
},
"data": {
"type": "object",
"description": "Extracted data"
}
},
"required": ["intent"]
}
Tool Implementation (src/tools.ts)
import { agent } from "@yao/runtime";
export function Recognize(params: { intent: string; data: any }, ctx: agent.Context) {
const ownerID = ctx.authorized?.team_id || ctx.authorized?.user_id;
switch (params.intent) {
case "query":
return { records: Process("models.agents.myapp.order.Get", {
wheres: [{ column: "__yao_created_by", value: ownerID }], limit: 20,
})};
default:
return { error: "Unknown intent" };
}
}
Models
Models in models/ are auto-loaded with prefix agents.<assistant_id>.:
models/order.mod.yao:
{
"name": "Order",
"table": { "name": "order" },
"columns": [
{ "name": "id", "type": "ID", "primary": true },
{ "name": "title", "type": "string", "length": 200 },
{ "name": "amount", "type": "decimal", "precision": 10, "scale": 2 },
{ "name": "status", "type": "enum", "option": ["pending", "completed"] }
],
"option": { "timestamps": true, "soft_deletes": true, "permission": true }
}
Usage: Model ID = agents.myapp.order, Table = agents_myapp_order
Process("models.agents.myapp.order.Get", { limit: 10 });