From 73e665bef7ec196580aaaff43abfd63675427aa1 Mon Sep 17 00:00:00 2001 From: RC-CHN <1051989940@qq.com> Date: Thu, 26 Feb 2026 16:14:16 +0800 Subject: [PATCH] feat(neo): guide skill lifecycle tool workflow Add explicit Neo lifecycle instructions to the main agent prompt so skill creation and updates follow payload -> candidate -> promotion instead of direct local folder writes. Clarify lifecycle tool descriptions and parameter semantics, including skill_key/source_execution_ids usage and stable release sync_to_local behavior, to reduce ambiguity and improve consistent skill publishing. --- astrbot/core/astr_main_agent.py | 12 +++++++ astrbot/core/computer/tools/neo_skills.py | 39 ++++++++++++++++++----- 2 files changed, 43 insertions(+), 8 deletions(-) diff --git a/astrbot/core/astr_main_agent.py b/astrbot/core/astr_main_agent.py index 216c73909..6f9481035 100644 --- a/astrbot/core/astr_main_agent.py +++ b/astrbot/core/astr_main_agent.py @@ -840,6 +840,18 @@ def _apply_sandbox_tools( "Example: use `baidu_homepage.png` instead of `/workspace/baidu_homepage.png`.\n" ) + req.system_prompt += ( + "\n[Neo Skill Lifecycle Workflow]\n" + "When user asks to create/update a reusable skill in Neo mode, use lifecycle tools instead of directly writing local skill folders.\n" + "Preferred sequence:\n" + "1) Use `astrbot_create_skill_payload` to store canonical payload content and get `payload_ref`.\n" + "2) Use `astrbot_create_skill_candidate` with `skill_key` + `source_execution_ids` (and optional `payload_ref`) to create a candidate.\n" + "3) Use `astrbot_promote_skill_candidate` to release: `stage=canary` for trial; `stage=stable` for production.\n" + "For stable release, set `sync_to_local=true` to sync `payload.skill_markdown` into local `SKILL.md`.\n" + "Do not treat ad-hoc generated files as reusable Neo skills unless they are captured via payload/candidate/release.\n" + "To update an existing skill, create a new payload/candidate and promote a new release version; avoid patching old local folders directly.\n" + ) + # Determine sandbox capabilities from an already-booted session. # If no session exists yet (first request), capabilities is None # and we register all tools conservatively. diff --git a/astrbot/core/computer/tools/neo_skills.py b/astrbot/core/computer/tools/neo_skills.py index 775b8f213..492b6e45e 100644 --- a/astrbot/core/computer/tools/neo_skills.py +++ b/astrbot/core/computer/tools/neo_skills.py @@ -155,7 +155,10 @@ class AnnotateExecutionTool(NeoSkillToolBase): @dataclass class CreateSkillPayloadTool(NeoSkillToolBase): name: str = "astrbot_create_skill_payload" - description: str = "Create a generic skill payload and return payload_ref." + description: str = ( + "Step 1/3 for Neo skill authoring: create immutable payload content and return payload_ref. " + "Use this to store skill_markdown and structured metadata; do NOT write local skill folders directly." + ) parameters: dict = field( default_factory=lambda: { "type": "object", @@ -163,7 +166,8 @@ class CreateSkillPayloadTool(NeoSkillToolBase): "payload": { "anyOf": [{"type": "object"}, {"type": "array"}], "description": ( - "Skill payload JSON. Recommended fields: skill_markdown, commands, meta." + "Skill payload JSON. Typical schema: {skill_markdown, inputs, outputs, meta}. " + "This only stores content and returns payload_ref; it does not create a candidate or release." ), }, "kind": { @@ -221,18 +225,31 @@ class GetSkillPayloadTool(NeoSkillToolBase): @dataclass class CreateSkillCandidateTool(NeoSkillToolBase): name: str = "astrbot_create_skill_candidate" - description: str = "Create a skill candidate from source execution IDs." + description: str = ( + "Step 2/3 for Neo skill authoring: create a candidate by binding execution evidence " + "(source_execution_ids) with skill identity (skill_key) and optional payload_ref." + ) parameters: dict = field( default_factory=lambda: { "type": "object", "properties": { - "skill_key": {"type": "string"}, + "skill_key": { + "type": "string", + "description": "Stable logical identifier, e.g. image-collage-9grid.", + }, "source_execution_ids": { "type": "array", "items": {"type": "string"}, + "description": "Execution evidence IDs captured from sandbox history.", + }, + "scenario_key": { + "type": "string", + "description": "Optional scenario namespace for grouping candidates.", + }, + "payload_ref": { + "type": "string", + "description": "Optional payload reference created by astrbot_create_skill_payload.", }, - "scenario_key": {"type": "string"}, - "payload_ref": {"type": "string"}, }, "required": ["skill_key", "source_execution_ids"], } @@ -338,7 +355,10 @@ class EvaluateSkillCandidateTool(NeoSkillToolBase): @dataclass class PromoteSkillCandidateTool(NeoSkillToolBase): name: str = "astrbot_promote_skill_candidate" - description: str = "Promote one candidate to release stage (canary/stable)." + description: str = ( + "Step 3/3 for Neo skill authoring: promote candidate to canary/stable release. " + "If stage=stable and sync_to_local=true, payload.skill_markdown is synced to local SKILL.md automatically." + ) parameters: dict = field( default_factory=lambda: { "type": "object", @@ -351,7 +371,10 @@ class PromoteSkillCandidateTool(NeoSkillToolBase): }, "sync_to_local": { "type": "boolean", - "description": "When stage is stable, sync payload.skill_markdown to local SKILL.md.", + "description": ( + "Only used with stage=stable. true means sync payload.skill_markdown to local SKILL.md; " + "false means release remains Neo-side only." + ), "default": True, }, },