diff --git a/.trellis/tasks/04-23-redesign-single-cli-skill-disclosure/prd.md b/.trellis/tasks/04-23-redesign-single-cli-skill-disclosure/prd.md index 4a2fbd9..c8361d1 100644 --- a/.trellis/tasks/04-23-redesign-single-cli-skill-disclosure/prd.md +++ b/.trellis/tasks/04-23-redesign-single-cli-skill-disclosure/prd.md @@ -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": "" } } @@ -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": "" } } ``` -### `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": "", "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": "" } } ``` -### `invite_subscriber` +### `share` ```json { - "skill": "calendar", - "action": "invite_subscriber", + "module": "calendar", + "method": "share", "input": { "event_id": "", "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": "" } @@ -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": "" } @@ -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" + } + } } } ``` diff --git a/backend/src/core/agentscope/runtime/__init__.py b/backend/src/core/agentscope/runtime/__init__.py index c75dcac..56b2b56 100644 --- a/backend/src/core/agentscope/runtime/__init__.py +++ b/backend/src/core/agentscope/runtime/__init__.py @@ -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}") diff --git a/backend/src/core/agentscope/runtime/runner.py b/backend/src/core/agentscope/runtime/runner.py index 5ac1de1..950c94f 100644 --- a/backend/src/core/agentscope/runtime/runner.py +++ b/backend/src/core/agentscope/runtime/runner.py @@ -611,6 +611,3 @@ class SystemAgentRuntimeConfig: api_key: str llm_config: SystemAgentLLMConfig extra_context: str | None = None - - -AgentScopeReActRunner = AgentScopeRunner diff --git a/backend/src/core/agentscope/tools/cli/handler_calendar.py b/backend/src/core/agentscope/tools/cli/handler_calendar.py index 6b64c2d..2e525a7 100644 --- a/backend/src/core/agentscope/tools/cli/handler_calendar.py +++ b/backend/src/core/agentscope/tools/cli/handler_calendar.py @@ -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: diff --git a/backend/src/core/agentscope/tools/cli/handler_memory.py b/backend/src/core/agentscope/tools/cli/handler_memory.py index ba0c60e..e98a98f 100644 --- a/backend/src/core/agentscope/tools/cli/handler_memory.py +++ b/backend/src/core/agentscope/tools/cli/handler_memory.py @@ -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(): diff --git a/backend/src/core/agentscope/tools/skills/calendar/actions/accept_invite.md b/backend/src/core/agentscope/tools/skills/calendar/actions/accept_invite.md index 8427712..a668b2c 100644 --- a/backend/src/core/agentscope/tools/skills/calendar/actions/accept_invite.md +++ b/backend/src/core/agentscope/tools/skills/calendar/actions/accept_invite.md @@ -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" } diff --git a/backend/src/core/agentscope/tools/skills/calendar/actions/create_event.md b/backend/src/core/agentscope/tools/skills/calendar/actions/create_event.md index 3dafa45..c9e7652 100644 --- a/backend/src/core/agentscope/tools/skills/calendar/actions/create_event.md +++ b/backend/src/core/agentscope/tools/skills/calendar/actions/create_event.md @@ -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", diff --git a/backend/src/core/agentscope/tools/skills/calendar/actions/delete_event.md b/backend/src/core/agentscope/tools/skills/calendar/actions/delete_event.md index 2545b52..1d0d20d 100644 --- a/backend/src/core/agentscope/tools/skills/calendar/actions/delete_event.md +++ b/backend/src/core/agentscope/tools/skills/calendar/actions/delete_event.md @@ -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" } diff --git a/backend/src/core/agentscope/tools/skills/calendar/actions/get_event.md b/backend/src/core/agentscope/tools/skills/calendar/actions/get_event.md index 6a224f5..166703b 100644 --- a/backend/src/core/agentscope/tools/skills/calendar/actions/get_event.md +++ b/backend/src/core/agentscope/tools/skills/calendar/actions/get_event.md @@ -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`. diff --git a/backend/src/core/agentscope/tools/skills/calendar/actions/invite_subscriber.md b/backend/src/core/agentscope/tools/skills/calendar/actions/invite_subscriber.md index 39be86e..da96097 100644 --- a/backend/src/core/agentscope/tools/skills/calendar/actions/invite_subscriber.md +++ b/backend/src/core/agentscope/tools/skills/calendar/actions/invite_subscriber.md @@ -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": { diff --git a/backend/src/core/agentscope/tools/skills/calendar/actions/list_day.md b/backend/src/core/agentscope/tools/skills/calendar/actions/list_day.md index 289c124..f20d239 100644 --- a/backend/src/core/agentscope/tools/skills/calendar/actions/list_day.md +++ b/backend/src/core/agentscope/tools/skills/calendar/actions/list_day.md @@ -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`. diff --git a/backend/src/core/agentscope/tools/skills/calendar/actions/list_range.md b/backend/src/core/agentscope/tools/skills/calendar/actions/list_range.md index 54475d9..55791dd 100644 --- a/backend/src/core/agentscope/tools/skills/calendar/actions/list_range.md +++ b/backend/src/core/agentscope/tools/skills/calendar/actions/list_range.md @@ -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`. diff --git a/backend/src/core/agentscope/tools/skills/calendar/actions/reject_invite.md b/backend/src/core/agentscope/tools/skills/calendar/actions/reject_invite.md index 3496462..4e84bf1 100644 --- a/backend/src/core/agentscope/tools/skills/calendar/actions/reject_invite.md +++ b/backend/src/core/agentscope/tools/skills/calendar/actions/reject_invite.md @@ -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" } diff --git a/backend/src/core/agentscope/tools/skills/calendar/actions/update_event.md b/backend/src/core/agentscope/tools/skills/calendar/actions/update_event.md index 58304a7..c88985f 100644 --- a/backend/src/core/agentscope/tools/skills/calendar/actions/update_event.md +++ b/backend/src/core/agentscope/tools/skills/calendar/actions/update_event.md @@ -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`. diff --git a/backend/src/core/agentscope/tools/skills/memory/SKILL.md b/backend/src/core/agentscope/tools/skills/memory/SKILL.md index 0935261..787d283 100644 --- a/backend/src/core/agentscope/tools/skills/memory/SKILL.md +++ b/backend/src/core/agentscope/tools/skills/memory/SKILL.md @@ -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. diff --git a/docs/protocols/agent/sse-events.md b/docs/protocols/agent/sse-events.md index 58a5620..2dc0c9f 100644 --- a/docs/protocols/agent/sse-events.md +++ b/docs/protocols/agent/sse-events.md @@ -216,9 +216,10 @@ data: ```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` -> `读取联系人`