feat(config,compose): sdk agents with vault prompt granules and skill plugins, ro vault with rw sub-mounts, beaver-runner

This commit is contained in:
hh
2026-08-28 01:56:40 +02:00
parent 856c5234fb
commit f2dc07a5fd
5 changed files with 104 additions and 277 deletions
+61 -44
View File
@@ -3,8 +3,9 @@ from datetime import date
from pathlib import Path
from beaver_gateway.agents.base import ExposedMcp
from beaver_gateway.agents.claude import ClaudeAgent, ClaudeCodeOptions
from beaver_gateway.agents.claude import ClaudeAgent, ClaudeOptions
from beaver_gateway.agents.raycast import RaycastAgent, RemoteTool, UserPreferences
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
@@ -16,6 +17,30 @@ from beaver_gateway.mcp.types import HttpMcp, McpServer
VAULT = Path("/vault")
CHATS_DIR = VAULT / "💬 чаты"
BEAVER = VAULT / "мета" / "бобер"
PROMPTS = BEAVER / "промпты"
GRANULES = PROMPTS / "гранулы"
SKILLS = BEAVER / "скиллы"
VOICE = GRANULES / "голос.md"
PROFILE = PROMPTS / "профиль.md"
CORRECTIONS = PROMPTS / "поправки.md"
VAULT_MAP = GRANULES / "карта-vault.md"
# §4.2: сборки промптов. Окружение kind'а - константная гранула, экземплярное
# (файл, топик) едет первым сообщением. Диспетчер/дистиллятор/триаж - M1b+.
DEEP_PROMPT = (
VOICE,
PROFILE,
CORRECTIONS,
VAULT_MAP,
GRANULES / "окружения" / "глубокий.md",
GRANULES / "глубокий.md",
)
QUICK_PROMPT = (VOICE, PROFILE, GRANULES / "быстрый.md")
# §4.3: наборы скиллов = папки = плагины. Глубокие грузят общие + vault.
DEEP_SKILLS = (SKILLS / "общие", SKILLS / "vault")
def chat_log_path(record: TurnRecord, vault: Path) -> Path:
@@ -46,6 +71,8 @@ def _calendar_mcps() -> list[HttpMcp]:
calendar_mcps = _calendar_mcps()
calendar_exposed = tuple(ExposedMcp(name=m.name) for m in calendar_mcps)
# Секреты MCP - только через env подпроцесса (mcp stdio даёт ему белый список
# + это), никогда argv: процесс модели видит `ps` всего контейнера.
mcps = [
McpServer.stdio(
name="obsidian-fs",
@@ -59,25 +86,24 @@ mcps = [
),
McpServer.stdio(
name="firefly",
command=[
"bunx",
"-y",
"@firefly-iii-mcp/local",
"--pat",
os.environ["FIREFLY_PAT"],
"--baseUrl",
os.environ["FIREFLY_BASE_URL"],
"--preset",
"default",
],
command=["bunx", "-y", "@firefly-iii-mcp/local", "--preset", "default"],
env={
"FIREFLY_III_PAT": os.environ["FIREFLY_PAT"],
"FIREFLY_III_BASE_URL": os.environ["FIREFLY_BASE_URL"],
},
lenient=True,
),
McpServer.http(name="telegram", url=os.environ["BEAVERGRAM_MCP"]),
*calendar_mcps,
]
CBO_PROMPT = (Path(__file__).parent / "prompt.md").read_text(encoding="utf-8")
# Руки глубокого (§4.4): firefly без delete_*, obsidian-fs не даётся - свои
# файловые тулзы есть, vault смонтирован в /vault.
CLAUDE_MCPS = (
ExposedMcp(name="firefly", deny=("delete_*",)),
ExposedMcp(name="telegram"),
*calendar_exposed,
)
UserPrefsRu = lambda: UserPreferences( # noqa: E731
@@ -87,34 +113,25 @@ UserPrefsRu = lambda: UserPreferences( # noqa: E731
)
def claude(name: str, model: str, effort: str | None = None) -> ClaudeAgent:
def deep(name: str, model: str, effort: str | None = None) -> ClaudeAgent:
return ClaudeAgent(
name=name,
model=model,
system_prompt=CBO_PROMPT,
cwd=VAULT,
options=ClaudeCodeOptions(
prompt_sources=DEEP_PROMPT,
skill_sets=DEEP_SKILLS,
options=ClaudeOptions(
effort=effort,
# Headless `claude -p` instead of driving the TUI over a
# pseudo-tty. The pi is exactly the machine the PTY path hurt
# most: it spent seconds per spawn waiting for an Ink render
# loop to settle (hence startup_delay creeping to 60s) and
# occasionally lost a prompt to a swallowed paste. Piped
# stdin has neither problem, and gives us token streaming.
#
# Dropped with it: `--remote-control`, which only exists for
# interactive sessions, and the admin terminal's ability to
# type into a stuck session by hand — the failure mode that
# was for is what this transport removes.
transport="stream_json",
include_partial_messages=True,
disallowed_tools=("AskUserQuestion", "ExitPlanMode", "EnterPlanMode"),
),
expose_mcps=(
ExposedMcp(name="firefly"),
ExposedMcp(name="telegram"),
*calendar_exposed,
# §3.7: у глубоких вопросы текстом, планов и сабагентов нет.
disallowed_tools=(
"AskUserQuestion",
"ExitPlanMode",
"EnterPlanMode",
"NotebookEdit",
"Task",
),
),
expose_mcps=CLAUDE_MCPS,
)
@@ -122,13 +139,13 @@ def raycast(name: str, model: str, reasoning_effort: str | None = None) -> Rayca
return RaycastAgent(
name=name,
model=model,
system_prompt=CBO_PROMPT,
system_prompt=assemble(QUICK_PROMPT),
reasoning_effort=reasoning_effort,
available_native_tools=(RemoteTool.WEB_SEARCH, RemoteTool.READ_PAGE),
user_preferences=UserPrefsRu,
expose_mcps=(
ExposedMcp(name="obsidian-fs"),
ExposedMcp(name="firefly"),
ExposedMcp(name="firefly", deny=("delete_*",)),
ExposedMcp(name="telegram"),
*calendar_exposed,
),
@@ -136,12 +153,12 @@ def raycast(name: str, model: str, reasoning_effort: str | None = None) -> Rayca
agents = [
claude("beaver-opus-high", "claude-opus-5", effort="high"),
deep("beaver-opus-high", "claude-opus-5", effort="high"),
raycast("beaver-gemini-pro-high", "google-gemini-3.1-pro", reasoning_effort="high"),
claude("beaver-fable-high", "claude-fable-5", effort="high"),
claude("beaver-opus-medium", "claude-opus-5", effort="medium"),
claude("beaver-fable-medium", "claude-fable-5", effort="medium"),
claude("beaver-opus-xhigh", "claude-opus-5", effort="xhigh"),
deep("beaver-fable-high", "claude-fable-5", effort="high"),
deep("beaver-opus-medium", "claude-opus-5", effort="medium"),
deep("beaver-fable-medium", "claude-fable-5", effort="medium"),
deep("beaver-opus-xhigh", "claude-opus-5", effort="xhigh"),
raycast("beaver-gemini-pro-low", "google-gemini-3.1-pro", reasoning_effort="low"),
raycast(
"beaver-gemini-flash-high", "google-gemini-3.5-flash", reasoning_effort="high"
@@ -168,7 +185,7 @@ frontends = [
host="0.0.0.0",
port=62993,
vault_path=CHATS_DIR,
default_agent="research",
default_agent="beaver-opus-high",
log_all_chats=True,
log_path=chat_log_path,
public_base_url=_public("/md"),