"""Synthesize draft-only skills for missing sub-agent guidance.""" from __future__ import annotations import json import re from dataclasses import dataclass from typing import TYPE_CHECKING, Any from beaver.engine.context import SkillContext from beaver.engine.providers import ProviderBundle from beaver.skills.drafts import DraftService from beaver.skills.specs import SkillDraft from beaver.skills.specs.serialization import canonical_hash if TYPE_CHECKING: from beaver.tasks.models import TaskRecord @dataclass(slots=True) class MissingSkillDraftResult: draft: SkillDraft skill_context: SkillContext class MissingSkillSynthesizer: """Create a draft skill and an ephemeral SkillContext for the current run.""" async def synthesize( self, *, task: TaskRecord, user_message: str, attempt_index: int, node_id: str, node_task: str, skill_query: str, required_capabilities: list[str], provider_bundle: ProviderBundle, draft_service: DraftService, ) -> MissingSkillDraftResult: provider = provider_bundle.auxiliary_provider or provider_bundle.main_provider runtime = provider_bundle.auxiliary_runtime or provider_bundle.main_runtime model = getattr(runtime, "model", None) payload = self._fallback_payload(skill_query=skill_query, node_task=node_task, capabilities=required_capabilities) try: response = await provider.chat( messages=[ { "role": "system", "content": ( "You create concise Beaver skill drafts. Return only JSON with keys: " "skill_name, description, content, tags." ), }, { "role": "user", "content": ( "Create a procedural skill draft for this missing Task sub-agent guidance.\n\n" f"Task goal:\n{task.goal}\n\n" f"Current user request:\n{user_message}\n\n" f"Node id: {node_id}\n" f"Node task:\n{node_task}\n\n" f"Skill query:\n{skill_query}\n" f"Required capabilities: {required_capabilities}\n\n" "The content must be actionable guidance for a temporary sub-agent. " "Do not include implementation claims or publish metadata." ), }, ], tools=None, model=model, max_tokens=1200, temperature=0, ) payload = self._parse_payload(response.content or "") or payload except Exception: payload = payload skill_name = _slug(str(payload.get("skill_name") or skill_query or node_id)) content = str(payload.get("content") or "").strip() if not content: content = str(self._fallback_payload(skill_query=skill_query, node_task=node_task, capabilities=required_capabilities)["content"]) frontmatter = { "description": str(payload.get("description") or f"Draft guidance for {skill_query or node_id}").strip(), "tags": [str(item) for item in payload.get("tags") or ["generated", "task-sub-agent"]], "metadata": { "origin": "missing_task_subagent_skill", "task_id": task.task_id, "node_id": node_id, "attempt_index": attempt_index, "skill_query": skill_query, "required_capabilities": list(required_capabilities), }, } draft = draft_service.create_new_skill_draft( skill_name=skill_name, proposed_content=content, proposed_frontmatter=frontmatter, created_by="task-skill-resolver", reason="generated_for_missing_task_subagent_skill", trigger_session_id=task.session_id, evidence_refs=[ { "task_id": task.task_id, "session_id": task.session_id, "attempt_index": attempt_index, "node_id": node_id, "skill_query": skill_query, "required_capabilities": list(required_capabilities), } ], ) context = SkillContext( name=f"draft:{draft.skill_name}", content=draft.proposed_content, version=f"draft:{draft.draft_id}", content_hash=canonical_hash(draft.proposed_content), activation_reason="generated_missing_skill", tool_hints=[], ) return MissingSkillDraftResult(draft=draft, skill_context=context) @staticmethod def _parse_payload(text: str) -> dict[str, Any] | None: cleaned = text.strip() if cleaned.startswith("```"): lines = cleaned.splitlines() if len(lines) >= 3 and lines[0].startswith("```") and lines[-1].startswith("```"): cleaned = "\n".join(lines[1:-1]).strip() if cleaned.lower().startswith("json"): cleaned = cleaned[4:].strip() start = cleaned.find("{") end = cleaned.rfind("}") if start >= 0 and end >= start: cleaned = cleaned[start : end + 1] try: payload = json.loads(cleaned) except json.JSONDecodeError: return None return payload if isinstance(payload, dict) else None @staticmethod def _fallback_payload(*, skill_query: str, node_task: str, capabilities: list[str]) -> dict[str, Any]: title = skill_query or node_task or "task subagent guidance" capability_lines = "\n".join(f"- {item}" for item in capabilities) or "- Follow the node task precisely." return { "skill_name": _slug(title), "description": f"Draft guidance for {title}.", "tags": ["generated", "task-sub-agent"], "content": ( f"# {title}\n\n" "Use this draft guidance only for the current delegated sub-task.\n\n" "## Objective\n" f"{node_task or title}\n\n" "## Capabilities to apply\n" f"{capability_lines}\n\n" "## Output\n" "Return concise evidence, decisions, and unresolved risks for the main Agent to synthesize." ), } def _slug(value: str) -> str: cleaned = re.sub(r"[^a-zA-Z0-9]+", "-", value.strip().lower()).strip("-") return cleaned[:64].strip("-") or "generated-task-subagent-skill"