fix(agent): 修复 skill action 卡片调用约定、memory 强类型校验和死代码清理

- 所有 calendar action .md: skill/action 替换为 module/method + mode 字段
- handler_memory: 新增 Pydantic extra=forbid 模型替代手工 dict 校验
- memory/SKILL.md: 补充 UserMemoryContent/WorkProfileContent 全字段文档
- 移除 handler_calendar 死代码 _batch_status 和 runner 旧别名 AgentScopeReActRunner
- PRD §5.2-5.6 和 sse-events 协议对齐实际 module/method 实现
This commit is contained in:
qzl
2026-04-24 14:10:57 +08:00
parent d060962a5f
commit d2d292a99e
16 changed files with 277 additions and 244 deletions
@@ -324,13 +324,14 @@ The worker still sees only:
## 5.2 New `project_cli` model-facing input contract
The new canonical model-facing payload is:
The canonical model-facing payload is:
```json
{
"skill": "calendar",
"action": "get_event",
"module": "calendar",
"method": "read",
"input": {
"mode": "event",
"event_id": "<uuid>"
}
}
@@ -338,91 +339,79 @@ The new canonical model-facing payload is:
Field meanings:
- `skill`: enabled business skill namespace
- `action`: concrete business operation inside the skill
- `input`: strict action-specific payload
- `module`: enabled business module namespace (calendar, contacts, memory)
- `method`: concrete business operation inside the module
- `input`: strict method-specific payload
This is still one tool call. The worker is not choosing among many tools.
## 5.3 Calendar action protocol
## 5.3 Calendar method protocol
The calendar skill should be redesigned around real business actions derived from `schedule_items` and `schedule_subscriptions`.
The calendar module exposes the following methods registered in the CLI router:
### Event actions
| Module | Method | Handler | Input Shape |
|----------|----------------|----------------------------------|-------------|
| calendar | read | `handle_calendar_list_day` | discriminated by `mode` |
| calendar | create | `handle_calendar_create_event` | title, start_at, timezone, ... |
| calendar | update | `handle_calendar_update_event` | event_id + patch |
| calendar | delete | `handle_calendar_delete_event` | event_id |
| calendar | share | `handle_calendar_invite_subscriber` | event_id, invitee, permissions |
| calendar | accept_invite | `handle_calendar_accept_invite` | event_id |
| calendar | reject_invite | `handle_calendar_reject_invite` | event_id |
1. `list_day`
2. `list_range`
3. `get_event`
4. `create_event`
5. `update_event`
6. `delete_event`
### Subscription actions
1. `invite_subscriber`
2. `accept_invite`
3. `reject_invite`
### Why this action set
This set directly maps to current product behavior:
- user asks what is scheduled today -> `list_day`
- user asks what is scheduled this week -> `list_range`
- user asks for a known event's details -> `get_event`
- user creates or edits a schedule item -> `create_event` / `update_event`
- user removes a schedule item -> `delete_event`
- user invites another person -> `invite_subscriber`
- invite recipient responds -> `accept_invite` / `reject_invite`
The `read` method uses a discriminated union with `mode` field to dispatch to list_day, list_range, or get_event internally.
This avoids overloading one label like `read` for two distinct business tasks.
## 5.4 Canonical calendar action shapes
## 5.4 Canonical calendar method shapes
### `list_day`
### `read` with mode=day (list one day)
```json
{
"skill": "calendar",
"action": "list_day",
"module": "calendar",
"method": "read",
"input": {
"mode": "day",
"date": "2026-04-23",
"timezone": "Asia/Shanghai"
}
}
```
### `list_range`
### `read` with mode=range (list time range)
```json
{
"skill": "calendar",
"action": "list_range",
"module": "calendar",
"method": "read",
"input": {
"mode": "range",
"start_at": "2026-04-23T00:00:00+08:00",
"end_at": "2026-04-24T00:00:00+08:00"
}
}
```
### `get_event`
### `read` with mode=event (get by ID)
```json
{
"skill": "calendar",
"action": "get_event",
"module": "calendar",
"method": "read",
"input": {
"mode": "event",
"event_id": "<uuid>"
}
}
```
### `create_event`
### `create`
```json
{
"skill": "calendar",
"action": "create_event",
"module": "calendar",
"method": "create",
"input": {
"title": "Project sync",
"start_at": "2026-04-23T16:00:00+08:00",
@@ -439,12 +428,12 @@ This avoids overloading one label like `read` for two distinct business tasks.
}
```
### `update_event`
### `update`
```json
{
"skill": "calendar",
"action": "update_event",
"module": "calendar",
"method": "update",
"input": {
"event_id": "<uuid>",
"patch": {
@@ -457,24 +446,24 @@ This avoids overloading one label like `read` for two distinct business tasks.
}
```
### `delete_event`
### `delete`
```json
{
"skill": "calendar",
"action": "delete_event",
"module": "calendar",
"method": "delete",
"input": {
"event_id": "<uuid>"
}
}
```
### `invite_subscriber`
### `share`
```json
{
"skill": "calendar",
"action": "invite_subscriber",
"module": "calendar",
"method": "share",
"input": {
"event_id": "<uuid>",
"invitee": {
@@ -493,8 +482,8 @@ This avoids overloading one label like `read` for two distinct business tasks.
```json
{
"skill": "calendar",
"action": "accept_invite",
"module": "calendar",
"method": "accept_invite",
"input": {
"event_id": "<uuid>"
}
@@ -505,8 +494,8 @@ This avoids overloading one label like `read` for two distinct business tasks.
```json
{
"skill": "calendar",
"action": "reject_invite",
"module": "calendar",
"method": "reject_invite",
"input": {
"event_id": "<uuid>"
}
@@ -556,21 +545,26 @@ This makes `view_skill_file` a real progressive-disclosure mechanism instead of
## 5.6 Error contract for self-correction
The redesigned CLI should return structured action-level validation feedback.
The redesigned CLI returns structured validation feedback with field-level detail.
Canonical error example:
```json
{
"status": "failure",
"ok": false,
"module": "calendar",
"method": "read",
"error": {
"code": "INVALID_ACTION_INPUT",
"message": "action list_range requires start_at and end_at",
"skill": "calendar",
"action": "list_range",
"missing_fields": ["start_at", "end_at"],
"unexpected_fields": ["event_id"],
"suggested_alternative_actions": ["get_event"]
"message": "input does not match method schema",
"retryable": false,
"details": {
"missing_fields": ["start_at", "end_at"],
"invalid_fields": [],
"alias_corrections": {
"start_time": "start_at"
}
}
}
}
```
@@ -1,7 +1,6 @@
__all__ = [
"AgentScopeRuntimeOrchestrator",
"AgentScopeRunner",
"AgentScopeReActRunner",
]
@@ -14,8 +13,4 @@ def __getattr__(name: str):
from core.agentscope.runtime.runner import AgentScopeRunner
return AgentScopeRunner
if name == "AgentScopeReActRunner":
from core.agentscope.runtime.runner import AgentScopeReActRunner
return AgentScopeReActRunner
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
@@ -611,6 +611,3 @@ class SystemAgentRuntimeConfig:
api_key: str
llm_config: SystemAgentLLMConfig
extra_context: str | None = None
AgentScopeReActRunner = AgentScopeRunner
@@ -644,14 +644,6 @@ def _normalize_phone(raw: str) -> str:
return phone
def _batch_status(success: int, failed: int) -> str:
if failed == 0:
return "success"
if success == 0:
return "failure"
return "partial"
def _day_input_to_range_input(input_payload: _CalendarReadDayInput) -> dict[str, str]:
timezone_name = input_payload.timezone.strip() or "Asia/Shanghai"
try:
@@ -1,7 +1,7 @@
from __future__ import annotations
from copy import deepcopy
from typing import Any, cast
from typing import Any, Literal, cast
from uuid import UUID
from core.agentscope.tools.cli.models import CliCommand, CliCommandResult
@@ -9,24 +9,34 @@ from core.agentscope.tools.utils.memory_domain import (
create_memories_service,
map_memory_exception,
)
from pydantic import BaseModel, ConfigDict, Field
from schemas.agent.runtime_models import ErrorInfo
from schemas.enums import MemoryType
from schemas.domain.memory_content import UserMemoryContent, WorkProfileContent
class _UpdateOperation(BaseModel):
model_config = ConfigDict(extra="forbid")
action: Literal["update", "delete"]
memory_type: MemoryType
user_content: dict[str, Any] | None = None
work_content: dict[str, Any] | None = None
forget_paths: list[str] | None = None
class _MemoryUpdateInput(BaseModel):
model_config = ConfigDict(extra="forbid")
operations: list[_UpdateOperation] = Field(..., min_length=1)
async def handle_memory_update(request: CliCommand) -> CliCommandResult:
from core.db.session import AsyncSessionLocal
operations = request.input.get("operations")
if not isinstance(operations, list) or not operations:
return _invalid_argument(
request=request,
message="operations must be a non-empty list",
details={
"required_fields": ["operations"],
"field_types": {"operations": "array of objects"},
},
)
validated = _validate_input(request)
if validated is None:
return _validation_error(request)
async with AsyncSessionLocal() as session:
service = create_memories_service(session=session, owner_id=UUID(request.owner_id))
@@ -37,49 +47,9 @@ async def handle_memory_update(request: CliCommand) -> CliCommandResult:
failed_ops: list[dict[str, Any]] = []
result_items: list[dict[str, Any]] = []
for idx, op in enumerate(operations):
if not isinstance(op, dict):
failed_count += 1
failed_ops.append(
{
"code": "INVALID_ARGUMENT",
"message": "operation item must be object",
"retryable": False,
}
)
result_items.append(
{
"idx": idx,
"memoryType": "unknown",
"action": "invalid",
"status": "failure",
"code": "INVALID_ARGUMENT",
}
)
continue
action = str(op.get("action") or "").strip().lower()
if action not in {"update", "delete"}:
failed_count += 1
failed_ops.append(
{
"code": "INVALID_ARGUMENT",
"message": "action must be update or delete",
"retryable": False,
}
)
result_items.append(
{
"idx": idx,
"memoryType": str(op.get("memory_type") or "unknown"),
"action": action or "invalid",
"status": "failure",
"code": "INVALID_ARGUMENT",
}
)
continue
memory_type = MemoryType(str(op.get("memory_type") or "user"))
for idx, op in enumerate(validated.operations):
memory_type = op.memory_type
action = op.action
try:
if action == "update":
result = await _apply_update_operation(
@@ -153,15 +123,43 @@ async def handle_memory_update(request: CliCommand) -> CliCommandResult:
)
def _validate_input(request: CliCommand) -> _MemoryUpdateInput | None:
try:
return _MemoryUpdateInput.model_validate(request.input)
except Exception:
return None
def _validation_error(request: CliCommand) -> CliCommandResult:
return CliCommandResult(
ok=False,
module=request.module,
method=request.method,
error=ErrorInfo(
code="INVALID_ARGUMENT",
message="operations must be a non-empty list of objects with action (update|delete) and memory_type (user|work)",
retryable=False,
details={
"required_fields": ["operations"],
"field_types": {
"operations": "array of objects",
"operations[].action": "update or delete",
"operations[].memory_type": "user or work",
},
},
),
)
async def _apply_update_operation(
*,
service: Any,
memory_type: MemoryType,
op: dict[str, Any],
op: _UpdateOperation,
) -> dict[str, Any]:
existing = await service.get_memory_model(memory_type=memory_type)
if memory_type == MemoryType.USER:
content_data = op.get("user_content")
content_data = op.user_content
if not isinstance(content_data, dict):
raise ValueError("update action for user memory requires user_content")
base = (
@@ -177,7 +175,7 @@ async def _apply_update_operation(
validated = UserMemoryContent.model_validate(merged)
updated = await service.update_user_memory(content=validated)
else:
content_data = op.get("work_content")
content_data = op.work_content
if not isinstance(content_data, dict):
raise ValueError("update action for work memory requires work_content")
base = (
@@ -205,9 +203,9 @@ async def _apply_delete_operation(
*,
service: Any,
memory_type: MemoryType,
op: dict[str, Any],
op: _UpdateOperation,
) -> dict[str, Any]:
forget_paths_raw = op.get("forget_paths")
forget_paths_raw = op.forget_paths
if not isinstance(forget_paths_raw, list) or not forget_paths_raw:
raise ValueError("delete action requires non-empty forget_paths")
forget_paths = [
@@ -237,25 +235,6 @@ async def _apply_delete_operation(
}
def _invalid_argument(
*,
request: CliCommand,
message: str,
details: dict[str, Any] | None,
) -> CliCommandResult:
return CliCommandResult(
ok=False,
module=request.module,
method=request.method,
error=ErrorInfo(
code="INVALID_ARGUMENT",
message=message,
retryable=False,
details=details,
),
)
def _deep_merge_dict(base: dict[str, Any], patch: dict[str, Any]) -> dict[str, Any]:
merged = deepcopy(base)
for key, value in patch.items():
@@ -1,5 +1,7 @@
# accept_invite
Use when accepting a shared event invitation.
## Input Schema
- `input.event_id`: required, `string`, UUID
@@ -9,12 +11,10 @@
- success: subscription response object
- failure: `error.code`, `error.message`, `error.details`
Use when accepting a shared event invitation.
```json
{
"skill": "calendar",
"action": "accept_invite",
"module": "calendar",
"method": "accept_invite",
"input": {
"event_id": "550e8400-e29b-41d4-a716-446655440000"
}
@@ -5,11 +5,11 @@ Use when creating a new event.
## Input Schema
- `input.title`: required, `string`
- `input.start_at`: required, `string`, ISO 8601 datetime
- `input.start_at`: required, `string`, ISO 8601 datetime with timezone
- `input.timezone`: required, `string`, IANA timezone
- `input.end_at`: optional, `string | null`, ISO 8601 datetime
- `input.description`: optional, `string | null`
- `input.metadata`: optional, `object | null`
- `input.end_at`: optional, `string`, ISO 8601 datetime with timezone
- `input.description`: optional, `string`
- `input.metadata`: optional, `object` with keys `location`, `reminder_minutes`, `color`, `notes`
## Output Shape
@@ -18,8 +18,8 @@ Use when creating a new event.
```json
{
"skill": "calendar",
"action": "create_event",
"module": "calendar",
"method": "create",
"input": {
"title": "Project sync",
"start_at": "2026-04-23T10:00:00+08:00",
@@ -1,5 +1,7 @@
# delete_event
Use when deleting one known event.
## Input Schema
- `input.event_id`: required, `string`, UUID
@@ -9,12 +11,10 @@
- success: `data.status`, `data.success`, `data.failed`, `data.ids`, `data.results`
- failure: `error.code`, `error.message`, `error.details`
Use when deleting one known event.
```json
{
"skill": "calendar",
"action": "delete_event",
"module": "calendar",
"method": "delete",
"input": {
"event_id": "550e8400-e29b-41d4-a716-446655440000"
}
@@ -4,6 +4,7 @@ Use when the user already knows the target event identity.
## Input Schema
- `input.mode`: required, must be `"event"`
- `input.event_id`: required, `string`, UUID
## Output Shape
@@ -13,9 +14,10 @@ Use when the user already knows the target event identity.
```json
{
"skill": "calendar",
"action": "get_event",
"module": "calendar",
"method": "read",
"input": {
"mode": "event",
"event_id": "550e8400-e29b-41d4-a716-446655440000"
}
}
@@ -24,3 +26,4 @@ Use when the user already knows the target event identity.
## Rules
- Prefer this over list actions when an `event_id` is already available.
- Do not use old field names: `command`, `subcommand`, `args`.
@@ -6,11 +6,11 @@ Use when sharing an event with one phone number.
- `input.event_id`: required, `string`, UUID
- `input.invitee`: required, `object`
- `input.invitee.phone`: required, `string`
- `input.invitee.phone`: required, `string`, phone number like `+8613800138000`
- `input.permissions`: optional, `object`
- `input.permissions.view`: optional, `bool`
- `input.permissions.edit`: optional, `bool`
- `input.permissions.invite`: optional, `bool`
- `input.permissions.view`: optional, `bool`, default `true`
- `input.permissions.edit`: optional, `bool`, default `false`
- `input.permissions.invite`: optional, `bool`, default `false`
## Output Shape
@@ -19,8 +19,8 @@ Use when sharing an event with one phone number.
```json
{
"skill": "calendar",
"action": "invite_subscriber",
"module": "calendar",
"method": "share",
"input": {
"event_id": "550e8400-e29b-41d4-a716-446655440000",
"invitee": {
@@ -4,6 +4,7 @@ Use when the user asks about one calendar day in a local timezone.
## Input Schema
- `input.mode`: required, must be `"day"`
- `input.date`: required, `string`, format `YYYY-MM-DD`
- `input.timezone`: optional, `string`, IANA timezone like `Asia/Shanghai`
@@ -14,9 +15,10 @@ Use when the user asks about one calendar day in a local timezone.
```json
{
"skill": "calendar",
"action": "list_day",
"module": "calendar",
"method": "read",
"input": {
"mode": "day",
"date": "2026-04-23",
"timezone": "Asia/Shanghai"
}
@@ -28,4 +30,5 @@ Use when the user asks about one calendar day in a local timezone.
- `input` must not be empty.
- `date` must be a concrete date string, not an empty object.
- For words like today or tomorrow, convert them to a concrete `YYYY-MM-DD` date from `system_time_local` before calling `project_cli`.
- Use `get_event` instead if you already have an `event_id`.
- Use `mode: "event"` instead if you already have an `event_id`.
- Do not use old field names: `start_time`, `end_time`, `event_timezone`.
@@ -4,8 +4,9 @@ Use when the user asks for a specific time range.
## Input Schema
- `input.start_at`: required, `string`, ISO 8601 datetime
- `input.end_at`: required, `string`, ISO 8601 datetime
- `input.mode`: required, must be `"range"`
- `input.start_at`: required, `string`, ISO 8601 datetime with timezone
- `input.end_at`: required, `string`, ISO 8601 datetime with timezone
## Output Shape
@@ -14,9 +15,10 @@ Use when the user asks for a specific time range.
```json
{
"skill": "calendar",
"action": "list_range",
"module": "calendar",
"method": "read",
"input": {
"mode": "range",
"start_at": "2026-04-23T09:00:00+08:00",
"end_at": "2026-04-23T18:00:00+08:00"
}
@@ -25,5 +27,6 @@ Use when the user asks for a specific time range.
## Rules
- `start_at` and `end_at` must both be present.
- `start_at` and `end_at` must both be present with timezone offset.
- Do not send `event_id` to list actions.
- Do not use old field names: `start_time`, `end_time`.
@@ -1,5 +1,7 @@
# reject_invite
Use when rejecting a shared event invitation.
## Input Schema
- `input.event_id`: required, `string`, UUID
@@ -9,12 +11,10 @@
- success: subscription response object
- failure: `error.code`, `error.message`, `error.details`
Use when rejecting a shared event invitation.
```json
{
"skill": "calendar",
"action": "reject_invite",
"module": "calendar",
"method": "reject_invite",
"input": {
"event_id": "550e8400-e29b-41d4-a716-446655440000"
}
@@ -7,11 +7,11 @@ Use when changing one known event.
- `input.event_id`: required, `string`, UUID
- `input.patch`: required, `object`
- `input.patch.title`: optional, `string`
- `input.patch.description`: optional, `string | null`
- `input.patch.start_at`: optional, `string | null`, ISO 8601 datetime
- `input.patch.end_at`: optional, `string | null`, ISO 8601 datetime
- `input.patch.timezone`: optional, `string`
- `input.patch.metadata`: optional, `object | null`
- `input.patch.description`: optional, `string`
- `input.patch.start_at`: optional, `string`, ISO 8601 datetime with timezone
- `input.patch.end_at`: optional, `string`, ISO 8601 datetime with timezone
- `input.patch.timezone`: optional, `string`, IANA timezone
- `input.patch.metadata`: optional, `object`
- `input.patch.status`: optional, `string`
## Output Shape
@@ -21,8 +21,8 @@ Use when changing one known event.
```json
{
"skill": "calendar",
"action": "update_event",
"module": "calendar",
"method": "update",
"input": {
"event_id": "550e8400-e29b-41d4-a716-446655440000",
"patch": {
@@ -37,3 +37,4 @@ Use when changing one known event.
- All mutable fields go inside `patch`.
- Do not put mutable fields at the top level.
- Use `start_at`/`end_at`, not `start_time`/`end_time`.
@@ -18,23 +18,16 @@ description: User memory management - store and forget personal facts and work p
- User wants to remove previously stored information
- Agent needs to recall user preferences for personalization
## Available Tool
Use the single tool `project_cli`.
Read this file first with `view_skill_file` when memory is the relevant skill.
## Calling Contract
- `module`: required, must be `memory`
- `method`: required, must be `update`
- `input.operations`: required, non-empty array
- `input.operations`: required, non-empty array of operation objects
- Each operation: `action` (update|delete), `memory_type` (user|work), plus action-specific fields
- Output success fields: `data.status`, `data.success`, `data.failed`, `data.results`
- Output failure fields: `error.code`, `error.message`, `error.details`
### Update Memory
Call `project_cli` with:
### Update Memory (user)
```json
{
@@ -45,39 +38,113 @@ Call `project_cli` with:
{
"action": "update",
"memory_type": "user",
"user_content": {}
"user_content": {
"occupation": "software engineer",
"timezone": "Asia/Shanghai",
"preferences": {
"communication_style": "concise"
},
"interests": ["reading", "hiking"]
}
}
]
}
}
```
Operation object fields:
- `action`: `update` or `delete`
- `memory_type`: `user` or `work`
- `update` requires matching content payload (`user_content` / `work_content`)
- `delete` requires `forget_paths`
### Update Memory (work)
Field requirements:
- `operations[].action`: required, `string`
- `operations[].memory_type`: required, `string`
- `operations[].user_content`: required for `memory_type=user` and `action=update`, `object`
- `operations[].work_content`: required for `memory_type=work` and `action=update`, `object`
- `operations[].forget_paths`: required for `action=delete`, `array[string]`
```json
{
"module": "memory",
"method": "update",
"input": {
"operations": [
{
"action": "update",
"memory_type": "work",
"work_content": {
"occupation": "software engineer",
"expertise": ["backend", "distributed systems"],
"current_projects": [
{ "name": "Project X", "status": "active" }
]
}
}
]
}
}
```
## Composition Patterns
### Delete Memory Paths
1. When user says "remember that I prefer morning meetings":
- Call `project_cli` with `module="memory"`, `method="update"`, and appropriate content
```json
{
"module": "memory",
"method": "update",
"input": {
"operations": [
{
"action": "delete",
"memory_type": "user",
"forget_paths": ["preferences.communication_style", "interests"]
}
]
}
}
```
2. When user says "forget my old address":
- Call `project_cli` with `module="memory"`, `method="update"`, `operations[0].action="delete"`, and the specific dot-path
## Operation Fields
## Protocol Reminder
| Field | Required | Type | Description |
|-------|----------|------|-------------|
| `action` | yes | `"update"` or `"delete"` | Operation type |
| `memory_type` | yes | `"user"` or `"work"` | Which memory to modify |
| `user_content` | update+user | object | User memory content (see below) |
| `work_content` | update+work | object | Work memory content (see below) |
| `forget_paths` | delete | array of dot-path strings | Paths to remove, e.g. `["interests", "preferences.communication_style"]` |
- Never use old `command/subcommand/args` fields for memory writes.
## User Content Fields (`user_content`)
Top-level fields for `memory_type=user` updates. All fields optional; only include what changes.
| Field | Type | Description |
|-------|------|-------------|
| `occupation` | string | 职业 |
| `timezone` | string | 时区 |
| `primary_language` | string | 主要语言 |
| `people` | array | 重要人物: `{name, relationship?, role?, notes?}` |
| `places` | array | 常去地点: `{name, category?, address?, timezone?, notes?}` |
| `preferences` | object | 偏好: `{communication_style?, language_preference?, work_lifestyle?, notification_preference?}` |
| `scheduling_preferences` | object | 排程偏好: `{productive_windows?, preferred_meeting_windows?, no_meeting_windows?, meeting_buffer_minutes?, max_meetings_per_day?, notes?}` |
| `interests` | array of strings | 兴趣爱好 |
| `avoid_topics` | array of strings | 不想讨论的话题 |
| `custom_rules` | array of strings | 用户自定义规则 |
| `recurring_routines` | array | 周期性安排: `{name, description?, cadence?, time_windows?, importance?}` |
## Work Content Fields (`work_content`)
Top-level fields for `memory_type=work` updates. All fields optional; only include what changes.
| Field | Type | Description |
|-------|------|-------------|
| `occupation` | string | 职业身份 |
| `expertise` | array of strings | 专业领域 |
| `preferred_tools` | array of strings | 惯用工具 |
| `current_projects` | array | 项目: `{name, description?, status?, priority?, deadline?, collaborators?, notes?}` |
| `work_habits` | object | 工作习惯: `{available_hours?, deep_work_blocks?, preferred_meeting_windows?, no_meeting_windows?, notification_channel?, notes?}` |
| `team_members` | array | 团队成员: `{name, role?, relationship?, preferred_contact_channel?, notes?}` |
| `team_context` | string | 团队背景 |
| `work_rules` | array of strings | 工作规则 |
## Rules
- Never use old `command/subcommand/args` fields.
- For `update`, provide only the fields that changed. Existing fields are merged, not replaced.
- For `delete`, use dot-separated paths like `"preferences.communication_style"`.
- Extra fields not listed above will be rejected.
## Failure Recovery
- If write fails, inspect `error.details` and retry with the documented field shape only
- If forget path is invalid, suggest checking the data structure
- If write fails, inspect `error.details` and retry with the documented field shape only.
- If forget path is invalid, suggest checking the data structure.
+10 -11
View File
@@ -216,9 +216,10 @@ data: <json>
```json
{
"skill": "calendar",
"action": "get_event",
"module": "calendar",
"method": "read",
"input": {
"mode": "event",
"event_id": "evt_123"
}
}
@@ -233,18 +234,16 @@ SSE 协议中的工具名字段保持后端原样,不做服务端翻译:
前端展示层统一通过工具名本地化映射进行中文渲染,要求兼容两类命名风格:
- dot 风格:`memory.update``calendar.get_event`
- snake 风格:`memory_update``calendar_get_event`
- dot 风格:`memory.update``calendar.read`
- snake 风格:`memory_update``calendar_read`
当前规范映射(canonical -> 中文)如下:
- `calendar.list_day` -> `读取当日日程`
- `calendar.list_range` -> `读取区间日程`
- `calendar.get_event` -> `读取日程详情`
- `calendar.create_event` -> `创建日程`
- `calendar.update_event` -> `更新日程`
- `calendar.delete_event` -> `删除日程`
- `calendar.invite_subscriber` -> `邀请参与者`
- `calendar.read` -> `读取日程`
- `calendar.create` -> `创建日程`
- `calendar.update` -> `更新日程`
- `calendar.delete` -> `删除日程`
- `calendar.share` -> `邀请参与者`
- `calendar.accept_invite` -> `接受邀请`
- `calendar.reject_invite` -> `拒绝邀请`
- `contacts.read` -> `读取联系人`