- 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`**:
```json
{
"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.
```typescript
function Create(ctx: agent.Context, messages: agent.Message[]): agent.Create {
ctx.memory.context.Set("start_time", Date.now());
return null; // Default behavior
// Or return configuration overrides
return {
messages, temperature: 0.7, connector: "gpt-4o", prompt_preset: "task",
mcp_servers: [{ server_id: "agents.myapp.tools" }],
uses: { search: "disabled" },
};
// Or delegate to another agent
return { delegate: { agent_id: "specialist", messages } };
}
```
## Next Hook
Called after LLM response and tool calls.
```typescript
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; // Standard LLM response
}
```
### Return Values
| Return Value | Behavior |
| -------------------- | --------------------------------- |
| `{ data: {...} }` | Return custom data, ends execution |
| `{ delegate: {...} }`| Delegate to agent, continues |
| `null` | Standard response, ends execution |
---
# Context API
## Properties
```typescript
ctx.chat_id // Chat session ID
ctx.assistant_id // Assistant identifier
ctx.locale // User locale
ctx.authorized // { user_id, team_id, constraints }
```
## Get Owner ID
```typescript
const ownerID = ctx.authorized?.team_id || ctx.authorized?.user_id;
```
## Messaging
```typescript
// Complete message
ctx.Send({ type: "text", props: { content: "Hello!" } });
ctx.Send("Hello!");
// Streaming
const id = ctx.SendStream("Processing...");
ctx.Append(id, " done!");
ctx.End(id);
// Replace/Update
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 |
```typescript
ctx.memory.context.Set("key", value);
ctx.memory.context.Set("key", value, 300); // With TTL
const value = ctx.memory.context.Get("key");
```
## MCP
```typescript
const result = ctx.mcp.CallTool("server_id", "tool_name", { arg: "value" });
```
See [Context API Reference](references/context-api.md) for Trace, MCP parallel calls, memory counters/lists, and full API.
## Agent-to-Agent (A2A)
```typescript
// From hooks — ctx.agent.Call()
const result = ctx.agent.Call("yao.keeper.classify", messages, {
skip: { output: true, history: true },
});
// result.content, result.error
// From any context (YaoJob, MCP, scripts) — Process
const result = Process("agent.Call", {
assistant_id: "yao.keeper.classify",
messages: [{ role: "user", content: "Classify this..." }],
timeout: 120, // optional, default 600s
});
```
See [Context API](references/context-api.md) for parallel calls, full parameters, and when to use which.
---
# MCP Tools
## Tool Server (`mcps/tools.mcp.yao`)
```json
{
"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`)
```json
{
"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`)
```typescript
// @ts-nocheck
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`**:
```json
{
"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`
```typescript
Process("models.agents.myapp.order.Get", { limit: 10 });
عرض على GitHub