refactor(skills): own explicit runtime context loading

This commit is contained in:
chengyongru
2026-08-19 18:40:20 +08:00
committed by chengyongru
parent 40cc9395fc
commit 3c41d5e7f3
4 changed files with 65 additions and 35 deletions
+6 -32
View File
@@ -159,33 +159,6 @@ class ContextBuilder:
return "\n\n---\n\n".join(parts)
def build_runtime_context_blocks(
self,
current_message: str,
blocks: Sequence[RuntimeContextBlock] | None = None,
) -> list[RuntimeContextBlock]:
"""Add explicitly invoked skill instructions to this turn's runtime context."""
merged = list(blocks or ())
invoked = self.skills.get_explicitly_invoked_skills(current_message)
if not invoked:
return merged
always_active = set(self.skills.get_always_skills())
skill_names = [name for name in invoked if name not in always_active]
skill_content = self.skills.load_skills_for_context(skill_names)
if not skill_content:
return merged
skill_block = RuntimeContextBlock(
source="explicit_skills",
content=(
"[Active Skills — instructions for this user turn]\n"
f"{skill_content}\n"
"[/Active Skills]"
),
)
if skill_block not in merged:
merged.append(skill_block)
return merged
@staticmethod
def _without_duplicate_session_summary(
entries: list[dict[str, Any]],
@@ -347,11 +320,12 @@ class ContextBuilder:
) -> dict[str, Any]:
"""Build only the fresh turn message without merging it into history."""
content = self.build_user_content(current_message, image_paths=media)
blocks = (
self.build_runtime_context_blocks(current_message, runtime_context_blocks)
if current_role == "user"
else []
)
blocks: list[RuntimeContextBlock] = []
if current_role == "user":
blocks.extend(runtime_context_blocks or ())
skill_context = self.skills.build_explicit_skill_runtime_context(current_message)
if skill_context is not None and skill_context not in blocks:
blocks.append(skill_context)
merged, runtime_context_meta = append_runtime_context(content, blocks)
current: dict[str, Any] = {"role": current_role, "content": merged}
if current_role == "user" and runtime_context_meta is not None:
+5 -3
View File
@@ -797,10 +797,12 @@ class AgentLoop:
]
blocks = runtime_context_blocks_from_metadata(request.metadata)
blocks.extend(await resolve_runtime_context(providers, request))
return self.context.build_runtime_context_blocks(
request.original_user_text or "",
blocks,
skill_context = self.context.skills.build_explicit_skill_runtime_context(
request.original_user_text or ""
)
if skill_context is not None and skill_context not in blocks:
blocks.append(skill_context)
return blocks
async def _dispatch_command_inline(
self,
+24
View File
@@ -9,6 +9,8 @@ from typing import Any, cast
import yaml
from nanobot.runtime_context import RuntimeContextBlock
# Default builtin skills directory (relative to this file)
BUILTIN_SKILLS_DIR = Path(__file__).parent.parent / "skills"
@@ -177,6 +179,28 @@ class SkillsLoader:
invoked.append(name)
return invoked
def build_explicit_skill_runtime_context(
self,
text: str,
) -> RuntimeContextBlock | None:
"""Load non-always skills explicitly invoked by the current message."""
skill_names = self.get_explicitly_invoked_skills(text)
if not skill_names:
return None
always_active = set(self.get_always_skills())
skill_names = [name for name in skill_names if name not in always_active]
content = self.load_skills_for_context(skill_names)
if not content:
return None
return RuntimeContextBlock(
source="explicit_skills",
content=(
"[Active Skills — instructions for this user turn]\n"
f"{content}\n"
"[/Active Skills]"
),
)
def build_skills_summary(self, exclude: set[str] | None = None) -> str:
"""
Build a summary of all skills (name, description, path, availability).
+30
View File
@@ -383,6 +383,36 @@ def test_explicit_skill_references_resolve_available_enabled_names_in_order(
assert invoked == ["alpha"]
def test_multiple_explicit_skills_share_one_ordered_runtime_context(tmp_path: Path) -> None:
workspace = tmp_path / "ws"
skills_root = workspace / "skills"
skills_root.mkdir(parents=True)
_write_skill(skills_root, "alpha", body="Alpha instructions")
_write_skill(skills_root, "beta", body="Beta instructions")
_write_skill(
skills_root,
"always",
metadata_json={"always": True},
body="Always instructions",
)
builtin = tmp_path / "builtin"
builtin.mkdir()
loader = SkillsLoader(workspace, builtin_skills_dir=builtin)
context = loader.build_explicit_skill_runtime_context(
"Use $beta, then $alpha, $beta again, and $always."
)
assert context is not None
assert context.source == "explicit_skills"
assert context.content.count("### Skill: beta") == 1
assert context.content.count("### Skill: alpha") == 1
assert context.content.index("### Skill: beta") < context.content.index(
"### Skill: alpha"
)
assert "### Skill: always" not in context.content
# -- multiline description tests (YAML folded > and literal |) -----------------