feat(config,compose): dispatcher agent, api frontend, merge prompt and morning seed

This commit is contained in:
hh
2026-08-28 03:08:30 +02:00
parent e0a9af8d1a
commit 9c46d4ee55
4 changed files with 98 additions and 2 deletions
+66 -2
View File
@@ -1,15 +1,17 @@
import os
from datetime import date
from datetime import date, timedelta
from pathlib import Path
from beaver_gateway.agents.base import BaseAgent, ExposedMcp
from beaver_gateway.agents.claude import ClaudeAgent, ClaudeOptions
from beaver_gateway.agents.raycast import RaycastAgent, RemoteTool, UserPreferences
from beaver_gateway.core.conversations import ConversationTexts, SeedContext
from beaver_gateway.core.prompt import assemble
from beaver_gateway.core.registry import Gateway
from beaver_gateway.core.turn_record import TurnRecord, slugify
from beaver_gateway.frontends.admin import AdminFrontend
from beaver_gateway.frontends.anthropic import AnthropicMessagesFrontend
from beaver_gateway.frontends.api import ApiFrontend
from beaver_gateway.frontends.base import Frontend
from beaver_gateway.frontends.markdown import MarkdownFrontend
from beaver_gateway.frontends.mcp_server import McpServerFrontend
@@ -60,8 +62,31 @@ DEEP_PROMPT = (
)
QUICK_PROMPT = assemble((*VOICE, PROFILE, overlay("quick", "быстрый")))
# Диспетчер: тот же голос, окружение по kind (§3.12), оверлей по роли.
# Форк ветки (слив) думает как дистиллятор в окружении джоба.
_DISPATCHER_BASE = (*VOICE, PROFILE, CORRECTIONS, VAULT_MAP)
DISPATCHER_PROMPTS = {
"master": (
*_DISPATCHER_BASE,
environment("мастер"),
overlay("dispatcher", "диспетчер"),
),
"branch": (*_DISPATCHER_BASE, environment("ветка"), overlay("branch", "ветка")),
"fork": (
*_DISPATCHER_BASE,
environment("джоб"),
overlay("distiller", "дистиллятор"),
),
"job": (
*_DISPATCHER_BASE,
environment("джоб"),
overlay("distiller", "дистиллятор"),
),
}
# §4.3: наборы скиллов = папки = плагины. Глубокие грузят общие + vault.
DEEP_SKILLS = (SKILLS / "общие", SKILLS / "vault")
DISPATCHER_SKILLS = (SKILLS / "общие", SKILLS / "диспетчер", SKILLS / "vault")
def chat_log_path(record: TurnRecord, vault: Path) -> Path:
@@ -128,6 +153,24 @@ UserPrefsRu = lambda: UserPreferences( # noqa: E731
)
def dispatcher(name: str, model: str, effort: str | None = None) -> ClaudeAgent:
return ClaudeAgent(
name=name,
model=model,
cwd=VAULT,
prompt_sources=DISPATCHER_PROMPTS["master"],
prompt_sources_by_kind=DISPATCHER_PROMPTS,
skill_sets=DISPATCHER_SKILLS,
gateway_tools=("read_conversation", "spawn", "say", "schedule"),
options=ClaudeOptions(
effort=effort,
# §3.7: у диспетчера AskUserQuestion остаётся, планов и ноутбуков нет.
disallowed_tools=("ExitPlanMode", "EnterPlanMode", "NotebookEdit"),
),
expose_mcps=CLAUDE_MCPS,
)
def deep(name: str, model: str, effort: str | None = None) -> ClaudeAgent:
return ClaudeAgent(
name=name,
@@ -168,6 +211,8 @@ def raycast(name: str, model: str, reasoning_effort: str | None = None) -> Rayca
agents: list[BaseAgent] = [
# §12.D4: диспетчер на medium; high - по руке или в ветке.
dispatcher("beaver-dispatcher", "claude-opus-5", effort="medium"),
deep("beaver-opus-high", "claude-opus-5", effort="high"),
raycast("beaver-gemini-pro-high", "google-gemini-3.1-pro", reasoning_effort="high"),
deep("beaver-fable-high", "claude-fable-5", effort="high"),
@@ -196,6 +241,7 @@ frontends: list[Frontend] = [
),
McpServerFrontend(host="0.0.0.0", port=62991, public_base_url=_public("/mcp")),
AdminFrontend(host="0.0.0.0", port=62992, public_base_url=_public("/admin")),
ApiFrontend(host="0.0.0.0", port=62994, public_base_url=_public("/api")),
MarkdownFrontend(
host="0.0.0.0",
port=62993,
@@ -208,4 +254,22 @@ frontends: list[Frontend] = [
]
gateway = Gateway(agents=agents, mcps=mcps, frontends=frontends)
def _read(path: Path) -> str | None:
return path.read_text(encoding="utf-8").strip() if path.exists() else None
def seed_body(ctx: SeedContext) -> str | None:
"""§8.2: утренний сид - вчерашний хендаут из `дни/`; остальные сиды - gateway."""
if ctx.seed != "morning":
return None
yesterday = date.today() - timedelta(days=1)
handout = _read(BEAVER / "дни" / f"{yesterday:%Y-%m-%d}.md")
return handout or "Хендаут за вчера не приехал."
texts = ConversationTexts(
merge_prompt=_read(GRANULES / "слив.md") or ConversationTexts().merge_prompt,
seed=seed_body,
)
gateway = Gateway(agents=agents, mcps=mcps, frontends=frontends, texts=texts)