feat(agents,claude_sdk): skill sets per conversation kind

This commit is contained in:
hh
2026-09-01 23:13:21 +02:00
parent 1954a6e9f2
commit f6a7ea8f29
3 changed files with 76 additions and 19 deletions
+30 -4
View File
@@ -3,8 +3,10 @@
The system prompt is ``system_prompt`` verbatim or, per conversation kind,
the granules named in ``prompts`` assembled at every session spawn (see
``core/prompt.py``). ``skill_sets`` are directories of ``<skill>/SKILL.md``
folders; each becomes a local SDK plugin. Nothing from disk is loaded
otherwise: the adapter runs with ``setting_sources=[]``.
folders; each becomes a local SDK plugin, either the same tuple for every
kind or a ``SkillSets`` with a tuple per kind (§4.3: the master never sees
what a branch opens). Nothing from disk is loaded otherwise: the adapter
runs with ``setting_sources=[]``.
"""
from __future__ import annotations
@@ -19,7 +21,7 @@ from beaver_gateway.core.kinds import KINDS, Kind
from beaver_gateway.core.policy import PolicyRule # noqa: TC001 - pydantic runtime
from beaver_gateway.core.prompt import PromptSource # noqa: TC001 - pydantic runtime
__all__ = ["ClaudeAgent", "ClaudeOptions", "Prompts"]
__all__ = ["ClaudeAgent", "ClaudeOptions", "Prompts", "SkillSets"]
class ClaudeOptions(BaseModel):
@@ -70,6 +72,21 @@ class Prompts(BaseModel):
return tuple(k for k in KINDS if self.for_kind(k) is not None)
class SkillSets(BaseModel):
"""Skill-set directories per conversation kind; ``None`` means none."""
model_config = ConfigDict(frozen=True)
master: tuple[Path, ...] | None = None
branch: tuple[Path, ...] | None = None
deep: tuple[Path, ...] | None = None
job: tuple[Path, ...] | None = None
fork: tuple[Path, ...] | None = None
def for_kind(self, kind: Kind) -> tuple[Path, ...]:
return getattr(self, kind) or ()
class ClaudeAgent(BaseAgent):
cwd: Path
system_prompt: str = ""
@@ -81,7 +98,11 @@ class ClaudeAgent(BaseAgent):
rest. Defaults to the kinds ``prompts`` covers, or ``("deep",)`` for a
verbatim ``system_prompt``."""
skill_sets: tuple[Path, ...] = ()
skill_sets: tuple[Path, ...] | SkillSets = ()
"""Skill-set directories, each a local plugin: one tuple for every kind,
or ``SkillSets`` to give each kind its own (a kind left ``None`` gets
no skills)."""
gateway_tools: tuple[str, ...] = ()
"""Gateway tools exposed in-process (``read_conversation``, ``spawn``,
``say``, ``schedule``, ``inject``); empty = no gateway MCP server."""
@@ -106,5 +127,10 @@ class ClaudeAgent(BaseAgent):
def prompt_for(self, kind: Kind) -> tuple[PromptSource, ...] | None:
return self.prompts.for_kind(kind)
def skills_for(self, kind: Kind) -> tuple[Path, ...]:
if isinstance(self.skill_sets, SkillSets):
return self.skill_sets.for_kind(kind)
return self.skill_sets
def serves(self, kind: str) -> bool:
return kind in self.kinds