mirror of
https://github.com/HKUDS/nanobot.git
synced 2026-04-30 23:05:51 +00:00
feat(agent): integrate skill discovery into Dream consolidation
Instead of a separate skill discovery system, extend Dream's two-phase pipeline to also detect reusable behavioral patterns from conversation history and generate SKILL.md files. Phase 1 gains a [SKILL] output type for pattern detection. Phase 2 gains write_file (scoped to skills/) and read access to builtin skills, enabling it to check for duplicates and follow skill-creator's format conventions before creating new skills. Inspired by PR #3039 by @wanghesong2019. Co-authored-by: wanghesong2019 <wanghesong2019@users.noreply.github.com>
This commit is contained in:
parent
5dc238c7ef
commit
2a243bfe4f
@ -582,14 +582,53 @@ class Dream:
|
|||||||
|
|
||||||
def _build_tools(self) -> ToolRegistry:
|
def _build_tools(self) -> ToolRegistry:
|
||||||
"""Build a minimal tool registry for the Dream agent."""
|
"""Build a minimal tool registry for the Dream agent."""
|
||||||
from nanobot.agent.tools.filesystem import EditFileTool, ReadFileTool
|
from nanobot.agent.skills import BUILTIN_SKILLS_DIR
|
||||||
|
from nanobot.agent.tools.filesystem import EditFileTool, ReadFileTool, WriteFileTool
|
||||||
|
|
||||||
tools = ToolRegistry()
|
tools = ToolRegistry()
|
||||||
workspace = self.store.workspace
|
workspace = self.store.workspace
|
||||||
tools.register(ReadFileTool(workspace=workspace, allowed_dir=workspace))
|
# Allow reading builtin skills for reference during skill creation
|
||||||
|
extra_read = [BUILTIN_SKILLS_DIR] if BUILTIN_SKILLS_DIR.exists() else None
|
||||||
|
tools.register(ReadFileTool(
|
||||||
|
workspace=workspace,
|
||||||
|
allowed_dir=workspace,
|
||||||
|
extra_allowed_dirs=extra_read,
|
||||||
|
))
|
||||||
tools.register(EditFileTool(workspace=workspace, allowed_dir=workspace))
|
tools.register(EditFileTool(workspace=workspace, allowed_dir=workspace))
|
||||||
|
# write_file scoped to skills/ directory for skill creation
|
||||||
|
skills_dir = workspace / "skills"
|
||||||
|
skills_dir.mkdir(parents=True, exist_ok=True)
|
||||||
|
tools.register(WriteFileTool(workspace=skills_dir, allowed_dir=skills_dir))
|
||||||
return tools
|
return tools
|
||||||
|
|
||||||
|
# -- skill listing --------------------------------------------------------
|
||||||
|
|
||||||
|
def _list_existing_skills(self) -> list[str]:
|
||||||
|
"""List existing skills as 'name — description' for dedup context."""
|
||||||
|
import re as _re
|
||||||
|
|
||||||
|
from nanobot.agent.skills import BUILTIN_SKILLS_DIR
|
||||||
|
|
||||||
|
_DESC_RE = _re.compile(r"^description:\s*(.+)$", _re.MULTILINE | _re.IGNORECASE)
|
||||||
|
entries: dict[str, str] = {}
|
||||||
|
for base in (self.store.workspace / "skills", BUILTIN_SKILLS_DIR):
|
||||||
|
if not base.exists():
|
||||||
|
continue
|
||||||
|
for d in base.iterdir():
|
||||||
|
if not d.is_dir():
|
||||||
|
continue
|
||||||
|
skill_md = d / "SKILL.md"
|
||||||
|
if not skill_md.exists():
|
||||||
|
continue
|
||||||
|
# Prefer workspace skills over builtin (same name)
|
||||||
|
if d.name in entries and base == BUILTIN_SKILLS_DIR:
|
||||||
|
continue
|
||||||
|
content = skill_md.read_text(encoding="utf-8")[:500]
|
||||||
|
m = _DESC_RE.search(content)
|
||||||
|
desc = m.group(1).strip() if m else "(no description)"
|
||||||
|
entries[d.name] = desc
|
||||||
|
return [f"{name} — {desc}" for name, desc in sorted(entries.items())]
|
||||||
|
|
||||||
# -- main entry ----------------------------------------------------------
|
# -- main entry ----------------------------------------------------------
|
||||||
|
|
||||||
async def run(self) -> bool:
|
async def run(self) -> bool:
|
||||||
@ -615,6 +654,7 @@ class Dream:
|
|||||||
current_memory = self.store.read_memory() or "(empty)"
|
current_memory = self.store.read_memory() or "(empty)"
|
||||||
current_soul = self.store.read_soul() or "(empty)"
|
current_soul = self.store.read_soul() or "(empty)"
|
||||||
current_user = self.store.read_user() or "(empty)"
|
current_user = self.store.read_user() or "(empty)"
|
||||||
|
|
||||||
file_context = (
|
file_context = (
|
||||||
f"## Current Date\n{current_date}\n\n"
|
f"## Current Date\n{current_date}\n\n"
|
||||||
f"## Current MEMORY.md ({len(current_memory)} chars)\n{current_memory}\n\n"
|
f"## Current MEMORY.md ({len(current_memory)} chars)\n{current_memory}\n\n"
|
||||||
@ -622,7 +662,7 @@ class Dream:
|
|||||||
f"## Current USER.md ({len(current_user)} chars)\n{current_user}"
|
f"## Current USER.md ({len(current_user)} chars)\n{current_user}"
|
||||||
)
|
)
|
||||||
|
|
||||||
# Phase 1: Analyze
|
# Phase 1: Analyze (no skills list — dedup is Phase 2's job)
|
||||||
phase1_prompt = (
|
phase1_prompt = (
|
||||||
f"## Conversation History\n{history_text}\n\n{file_context}"
|
f"## Conversation History\n{history_text}\n\n{file_context}"
|
||||||
)
|
)
|
||||||
@ -647,7 +687,14 @@ class Dream:
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
# Phase 2: Delegate to AgentRunner with read_file / edit_file
|
# Phase 2: Delegate to AgentRunner with read_file / edit_file
|
||||||
phase2_prompt = f"## Analysis Result\n{analysis}\n\n{file_context}"
|
existing_skills = self._list_existing_skills()
|
||||||
|
skills_section = ""
|
||||||
|
if existing_skills:
|
||||||
|
skills_section = (
|
||||||
|
"\n\n## Existing Skills\n"
|
||||||
|
+ "\n".join(f"- {s}" for s in existing_skills)
|
||||||
|
)
|
||||||
|
phase2_prompt = f"## Analysis Result\n{analysis}\n\n{file_context}{skills_section}"
|
||||||
|
|
||||||
tools = self._tools
|
tools = self._tools
|
||||||
messages: list[dict[str, Any]] = [
|
messages: list[dict[str, Any]] = [
|
||||||
|
|||||||
@ -3,6 +3,7 @@ Compare conversation history against current memory files. Also scan memory file
|
|||||||
Output one line per finding:
|
Output one line per finding:
|
||||||
[FILE] atomic fact (not already in memory)
|
[FILE] atomic fact (not already in memory)
|
||||||
[FILE-REMOVE] reason for removal
|
[FILE-REMOVE] reason for removal
|
||||||
|
[SKILL] kebab-case-name: one-line description of the reusable pattern
|
||||||
|
|
||||||
Files: USER (identity, preferences), SOUL (bot behavior, tone), MEMORY (knowledge, project context)
|
Files: USER (identity, preferences), SOUL (bot behavior, tone), MEMORY (knowledge, project context)
|
||||||
|
|
||||||
@ -18,6 +19,12 @@ Staleness — flag for [FILE-REMOVE]:
|
|||||||
- Detailed incident info after 14 days — reduce to one-line summary
|
- Detailed incident info after 14 days — reduce to one-line summary
|
||||||
- Superseded: approaches replaced by newer solutions, deprecated dependencies
|
- Superseded: approaches replaced by newer solutions, deprecated dependencies
|
||||||
|
|
||||||
|
Skill discovery — flag [SKILL] when ALL of these are true:
|
||||||
|
- A specific, repeatable workflow appeared 2+ times in the conversation history
|
||||||
|
- It involves clear steps (not vague preferences like "likes concise answers")
|
||||||
|
- It is substantial enough to warrant its own instruction set (not trivial like "read a file")
|
||||||
|
- Do not worry about duplicates — the next phase will check against existing skills
|
||||||
|
|
||||||
Do not add: current weather, transient status, temporary errors, conversational filler.
|
Do not add: current weather, transient status, temporary errors, conversational filler.
|
||||||
|
|
||||||
[SKIP] if nothing needs updating.
|
[SKIP] if nothing needs updating.
|
||||||
|
|||||||
@ -1,11 +1,13 @@
|
|||||||
Update memory files based on the analysis below.
|
Update memory files based on the analysis below.
|
||||||
- [FILE] entries: add the described content to the appropriate file
|
- [FILE] entries: add the described content to the appropriate file
|
||||||
- [FILE-REMOVE] entries: delete the corresponding content from memory files
|
- [FILE-REMOVE] entries: delete the corresponding content from memory files
|
||||||
|
- [SKILL] entries: create a new skill under skills/<name>/SKILL.md using write_file
|
||||||
|
|
||||||
## File paths (relative to workspace root)
|
## File paths (relative to workspace root)
|
||||||
- SOUL.md
|
- SOUL.md
|
||||||
- USER.md
|
- USER.md
|
||||||
- memory/MEMORY.md
|
- memory/MEMORY.md
|
||||||
|
- skills/<name>/SKILL.md (for [SKILL] entries only)
|
||||||
|
|
||||||
Do NOT guess paths.
|
Do NOT guess paths.
|
||||||
|
|
||||||
@ -17,6 +19,17 @@ Do NOT guess paths.
|
|||||||
- Surgical edits only — never rewrite entire files
|
- Surgical edits only — never rewrite entire files
|
||||||
- If nothing to update, stop without calling tools
|
- If nothing to update, stop without calling tools
|
||||||
|
|
||||||
|
## Skill creation rules (for [SKILL] entries)
|
||||||
|
- Use write_file to create skills/<name>/SKILL.md
|
||||||
|
- Before writing, read_file skills/skill-creator/SKILL.md for format reference (frontmatter structure, naming conventions, quality standards)
|
||||||
|
- **Dedup check**: read existing skills listed below to verify the new skill is not functionally redundant. Skip creation if an existing skill already covers the same workflow.
|
||||||
|
- Include YAML frontmatter with name and description fields
|
||||||
|
- Keep SKILL.md under 2000 words — concise and actionable
|
||||||
|
- Include: when to use, steps, output format, at least one example
|
||||||
|
- Do NOT overwrite existing skills — skip if the skill directory already exists
|
||||||
|
- Reference specific tools the agent has access to (read_file, write_file, exec, web_search, etc.)
|
||||||
|
- Skills are instruction sets, not code — do not include implementation code
|
||||||
|
|
||||||
## Quality
|
## Quality
|
||||||
- Every line must carry standalone value
|
- Every line must carry standalone value
|
||||||
- Concise bullets under clear headers
|
- Concise bullets under clear headers
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user