mirror of
https://github.com/HKUDS/nanobot.git
synced 2026-05-03 16:25:53 +00:00
fix(dream): use valid builtin skill template paths
Point Dream skill creation at a readable builtin skill-creator template, keep skill writes rooted at the workspace, and document the new skill discovery behavior in README. Made-with: Cursor
This commit is contained in:
parent
2a243bfe4f
commit
7a7f5c9689
@ -1742,6 +1742,7 @@ time.
|
|||||||
|
|
||||||
- `memory/history.jsonl` stores append-only summarized history
|
- `memory/history.jsonl` stores append-only summarized history
|
||||||
- `SOUL.md`, `USER.md`, and `memory/MEMORY.md` store long-term knowledge managed by Dream
|
- `SOUL.md`, `USER.md`, and `memory/MEMORY.md` store long-term knowledge managed by Dream
|
||||||
|
- `Dream` can also promote repeated workflows into reusable workspace skills under `skills/`
|
||||||
- `Dream` runs on a schedule and can also be triggered manually
|
- `Dream` runs on a schedule and can also be triggered manually
|
||||||
- memory changes can be inspected and restored with built-in commands
|
- memory changes can be inspected and restored with built-in commands
|
||||||
|
|
||||||
|
|||||||
@ -595,10 +595,11 @@ class Dream:
|
|||||||
extra_allowed_dirs=extra_read,
|
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
|
# write_file resolves relative paths from workspace root, but can only
|
||||||
|
# write under skills/ so the prompt can safely use skills/<name>/SKILL.md.
|
||||||
skills_dir = workspace / "skills"
|
skills_dir = workspace / "skills"
|
||||||
skills_dir.mkdir(parents=True, exist_ok=True)
|
skills_dir.mkdir(parents=True, exist_ok=True)
|
||||||
tools.register(WriteFileTool(workspace=skills_dir, allowed_dir=skills_dir))
|
tools.register(WriteFileTool(workspace=workspace, allowed_dir=skills_dir))
|
||||||
return tools
|
return tools
|
||||||
|
|
||||||
# -- skill listing --------------------------------------------------------
|
# -- skill listing --------------------------------------------------------
|
||||||
@ -633,6 +634,8 @@ class Dream:
|
|||||||
|
|
||||||
async def run(self) -> bool:
|
async def run(self) -> bool:
|
||||||
"""Process unprocessed history entries. Returns True if work was done."""
|
"""Process unprocessed history entries. Returns True if work was done."""
|
||||||
|
from nanobot.agent.skills import BUILTIN_SKILLS_DIR
|
||||||
|
|
||||||
last_cursor = self.store.get_last_dream_cursor()
|
last_cursor = self.store.get_last_dream_cursor()
|
||||||
entries = self.store.read_unprocessed_history(since_cursor=last_cursor)
|
entries = self.store.read_unprocessed_history(since_cursor=last_cursor)
|
||||||
if not entries:
|
if not entries:
|
||||||
@ -697,10 +700,15 @@ class Dream:
|
|||||||
phase2_prompt = f"## Analysis Result\n{analysis}\n\n{file_context}{skills_section}"
|
phase2_prompt = f"## Analysis Result\n{analysis}\n\n{file_context}{skills_section}"
|
||||||
|
|
||||||
tools = self._tools
|
tools = self._tools
|
||||||
|
skill_creator_path = BUILTIN_SKILLS_DIR / "skill-creator" / "SKILL.md"
|
||||||
messages: list[dict[str, Any]] = [
|
messages: list[dict[str, Any]] = [
|
||||||
{
|
{
|
||||||
"role": "system",
|
"role": "system",
|
||||||
"content": render_template("agent/dream_phase2.md", strip=True),
|
"content": render_template(
|
||||||
|
"agent/dream_phase2.md",
|
||||||
|
strip=True,
|
||||||
|
skill_creator_path=str(skill_creator_path),
|
||||||
|
),
|
||||||
},
|
},
|
||||||
{"role": "user", "content": phase2_prompt},
|
{"role": "user", "content": phase2_prompt},
|
||||||
]
|
]
|
||||||
|
|||||||
@ -21,7 +21,7 @@ Do NOT guess paths.
|
|||||||
|
|
||||||
## Skill creation rules (for [SKILL] entries)
|
## Skill creation rules (for [SKILL] entries)
|
||||||
- Use write_file to create skills/<name>/SKILL.md
|
- 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)
|
- Before writing, read_file `{{ skill_creator_path }}` 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.
|
- **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
|
- Include YAML frontmatter with name and description fields
|
||||||
- Keep SKILL.md under 2000 words — concise and actionable
|
- Keep SKILL.md under 2000 words — concise and actionable
|
||||||
|
|||||||
@ -6,6 +6,7 @@ from unittest.mock import AsyncMock, MagicMock
|
|||||||
|
|
||||||
from nanobot.agent.memory import Dream, MemoryStore
|
from nanobot.agent.memory import Dream, MemoryStore
|
||||||
from nanobot.agent.runner import AgentRunResult
|
from nanobot.agent.runner import AgentRunResult
|
||||||
|
from nanobot.agent.skills import BUILTIN_SKILLS_DIR
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
@ -95,3 +96,30 @@ class TestDreamRun:
|
|||||||
entries = store.read_unprocessed_history(since_cursor=0)
|
entries = store.read_unprocessed_history(since_cursor=0)
|
||||||
assert all(e["cursor"] > 0 for e in entries)
|
assert all(e["cursor"] > 0 for e in entries)
|
||||||
|
|
||||||
|
async def test_skill_phase_uses_builtin_skill_creator_path(self, dream, mock_provider, mock_runner, store):
|
||||||
|
"""Dream should point skill creation guidance at the builtin skill-creator template."""
|
||||||
|
store.append_history("Repeated workflow one")
|
||||||
|
store.append_history("Repeated workflow two")
|
||||||
|
mock_provider.chat_with_retry.return_value = MagicMock(content="[SKILL] test-skill: test description")
|
||||||
|
mock_runner.run = AsyncMock(return_value=_make_run_result())
|
||||||
|
|
||||||
|
await dream.run()
|
||||||
|
|
||||||
|
spec = mock_runner.run.call_args[0][0]
|
||||||
|
system_prompt = spec.initial_messages[0]["content"]
|
||||||
|
expected = str(BUILTIN_SKILLS_DIR / "skill-creator" / "SKILL.md")
|
||||||
|
assert expected in system_prompt
|
||||||
|
|
||||||
|
async def test_skill_write_tool_accepts_workspace_relative_skill_path(self, dream, store):
|
||||||
|
"""Dream skill creation should allow skills/<name>/SKILL.md relative to workspace root."""
|
||||||
|
write_tool = dream._tools.get("write_file")
|
||||||
|
assert write_tool is not None
|
||||||
|
|
||||||
|
result = await write_tool.execute(
|
||||||
|
path="skills/test-skill/SKILL.md",
|
||||||
|
content="---\nname: test-skill\ndescription: Test\n---\n",
|
||||||
|
)
|
||||||
|
|
||||||
|
assert "Successfully wrote" in result
|
||||||
|
assert (store.workspace / "skills" / "test-skill" / "SKILL.md").exists()
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user