| 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 });
Process("models.agents.myapp.order.Find", id);
Process("models.agents.myapp.order.Create", { title: "New", amount: 100 });
Permission Columns
When option.permission: true, auto-added columns: __yao_created_by (user), __yao_team_id (team).
Process("models.agents.myapp.order.Create", {
title: "Order",
__yao_created_by: ctx.authorized.user_id,
__yao_team_id: ctx.authorized.team_id,
});
Prompts
prompts.yml — system prompt with template variables:
- role: system
content: |
# Assistant Name
You are an assistant that helps users with [task].
Current date: {{ $CTX.date }}
User locale: {{ $CTX.locale }}
Prompt presets: Create prompts/chat.yml, prompts/task.yml, then select in Create hook via return { prompt_preset: "task" }.
Locales
locales/en-us.yml and locales/zh-cn.yml — referenced in package.yao via {{ key }}:
name: My Assistant
description: Helps with tasks
chat:
title: New Chat
prompts:
- How can I help?
Common Patterns
Loading Indicator
function Create(ctx: agent.Context, messages: agent.Message[]): agent.Create {
const loadingId = ctx.Send({ type: "loading", props: { message: "Processing..." } });
ctx.memory.context.Set("loading_id", loadingId);
return { messages };
}
function Next(ctx: agent.Context, payload: agent.Payload): agent.Next {
const loadingId = ctx.memory.context.Get("loading_id");
if (loadingId) ctx.Replace(loadingId, { type: "loading", props: { message: "✅ Done", done: true } });
return null;
}
Multi-Agent Delegation
function Next(ctx: agent.Context, payload: agent.Payload): agent.Next {
if (payload.tools?.[0]?.result?.intent) {
return { delegate: { agent_id: `myapp.${payload.tools[0].result.intent}`, messages: payload.messages } };
}
return null;
}
See Hooks Patterns for system init checks, locale detection, A2A patterns, and more.
Testing
Test files: src/*_test.ts. Functions must start with Test and be exported.
export function TestSave(t: testing.T, ctx: agent.Context) {
const tid = ctx.authorized?.team_id || "team-001";
const result = Save(tid, { title: "Test " + Date.now(), content: "test" });
t.assert.True(result.entry_id.length > 0, "should have entry_id");
}
yao agent test -n myapp -i scripts.myapp.store \
--ctx ./assistants/myapp/tests/ctx.json -v
yao agent test -n myapp -i "Hello" -v
Key rules: Run from app root. Use ./ prefix for --ctx and file paths. src/ is implicit in script paths. Use yao-dev instead of yao when developing the engine itself.
See Testing Reference for full CLI flags, assertion API, path resolution, known issues, and patterns.
Quick Reference
Process Calls
Process("models.agents.<id>.<model>.Get", { limit: 10 });
Process("models.agents.<id>.<model>.Find", id);
Process("models.agents.<id>.<model>.Create", data);
Process("models.agents.<id>.<model>.Save", id, data);
Process("models.agents.<id>.<model>.Delete", id);
Process("agent.Call", {
assistant_id: "target.agent.id",
messages: [{ role: "user", content: "..." }],
timeout: 120,
});
Process("text.ExtractJSON", llmOutput);
Process("text.HTMLToMarkdown", htmlString);
const cache = new Store("__yao.cache");
cache.GetSet("key", (k) => expensiveQuery(k), 60);
Process("scripts.mymodule.MyFunction", arg1, arg2);
File Naming
| File Pattern | Purpose |
|---|
package.yao | Assistant configuration |
prompts.yml | Main system prompts |
*.mcp.yao | MCP server declarations |
*.mod.yao | Model definitions |
*.in.yao | Tool input schema |
*_test.ts | Test files |
en-us.yml | English locale |
zh-cn.yml | Chinese locale |
Reference Files
For detailed API documentation, see: