feat(config,curator,recall): memory curator job on opus high, notes head inline in the envelope, portrait in the morning seed
This commit is contained in:
@@ -30,6 +30,7 @@ from beaver_gateway.frontends.mcp_server import McpServerFrontend
|
||||
from beaver_gateway.frontends.telegram import TelegramFrontend
|
||||
from beaver_gateway.mcp.types import HttpMcp, McpServer, McpServerT
|
||||
|
||||
from curator import Watched, briefing
|
||||
from mcps.homeassistant import HomeAssistant
|
||||
from mcps.komodo import Komodo
|
||||
from mcps.komodo_alerts import HOLD, AlertMemory, describe, parse, still_bad
|
||||
@@ -45,7 +46,7 @@ from policy import (
|
||||
skill_tracker,
|
||||
vault_zones,
|
||||
)
|
||||
from recall import Recall, ReplyLog
|
||||
from recall import Recall, ReplyLog, notes_head
|
||||
|
||||
TZ = "Europe/Warsaw"
|
||||
VAULT = Path("/vault")
|
||||
@@ -60,6 +61,8 @@ DIGESTS = BEAVER / "выжимки"
|
||||
INDEX = BEAVER / "индекс.md"
|
||||
NOTES = BEAVER / "наблюдения"
|
||||
REPLIES = BEAVER / "реплики"
|
||||
CURATOR_JOURNAL = BEAVER / "куратор.md"
|
||||
PORTRAIT = NOTES / "Бобёр - наблюдения.md"
|
||||
PEOPLE = VAULT / "👤 люди"
|
||||
BOARDS = VAULT / "📆 доски"
|
||||
STATE = BEAVER / "состояние.md"
|
||||
@@ -118,6 +121,10 @@ _DISTILLER = (PROFILE, environment("джоб"), overlay("distiller", "дисти
|
||||
DISTILLER_PROMPTS = Prompts(fork=_DISTILLER, job=_DISTILLER)
|
||||
# §4.1: триаж без голоса и без vault - профиль, окружение джоба, оверлей.
|
||||
TRIAGE_PROMPTS = Prompts(job=(PROFILE, environment("джоб"), overlay("triage", "триаж")))
|
||||
# Куратор памяти - тот же менеджер бобрения (голос и всё), в окружении джоба.
|
||||
CURATOR_PROMPTS = Prompts(
|
||||
job=(*_BASE, environment("джоб"), overlay("curator", "куратор"))
|
||||
)
|
||||
QUICK_PROMPT = assemble((*VOICE, PROFILE, overlay("quick", "быстрый")))
|
||||
|
||||
# §4.3: наборы скиллов = папки = плагины. Глубокие грузят общие + vault.
|
||||
@@ -321,6 +328,23 @@ def distiller(name: str, model: str, effort: str | None = None) -> ClaudeAgent:
|
||||
)
|
||||
|
||||
|
||||
def curator(name: str, model: str, effort: str | None = None) -> ClaudeAgent:
|
||||
"""Куратор памяти: opus high, файлы в мета/бобер, без MCP, `inject` в мастер."""
|
||||
return ClaudeAgent(
|
||||
name=name,
|
||||
model=model,
|
||||
cwd=VAULT,
|
||||
prompts=CURATOR_PROMPTS,
|
||||
gateway_tools=("inject",),
|
||||
options=ClaudeOptions(
|
||||
effort=effort,
|
||||
tools=("Read", "Write", "Edit", "Grep", "Glob"),
|
||||
disallowed_tools=DISTILLER_DISALLOWED,
|
||||
),
|
||||
policy=(vault_zones(STRICT_ZONES),),
|
||||
)
|
||||
|
||||
|
||||
def triage(name: str, model: str, effort: str | None = None) -> ClaudeAgent:
|
||||
"""§4.1: дешёвый крон-тёрн - sonnet low, без vault, только say/inject."""
|
||||
return ClaudeAgent(
|
||||
@@ -364,6 +388,7 @@ agents: list[BaseAgent] = [
|
||||
deep("beaver-fable-medium", "claude-fable-5", effort="medium"),
|
||||
deep("beaver-opus-xhigh", "claude-opus-5", effort="xhigh"),
|
||||
distiller("beaver-distiller", "claude-opus-5", effort="medium"),
|
||||
curator("beaver-curator", "claude-opus-5", effort="high"),
|
||||
triage("beaver-triage", "claude-sonnet-5", effort="low"),
|
||||
raycast("beaver-gemini-pro-low", "google-gemini-3.1-pro", reasoning_effort="low"),
|
||||
raycast(
|
||||
@@ -442,6 +467,12 @@ def seed_body(ctx: SeedContext) -> str | None:
|
||||
parts.append(
|
||||
f"Состояние (первые строки, полностью в мета/бобер/состояние.md):\n{signal}"
|
||||
)
|
||||
portrait = notes_head(PORTRAIT, ("сейчас", "паттерны"), cap=40)
|
||||
if portrait:
|
||||
parts.append(
|
||||
"Портрет Бобра (шапка "
|
||||
f"`{PORTRAIT.relative_to(VAULT)}`, лента - там же):\n{portrait}"
|
||||
)
|
||||
return "\n\n".join(parts)
|
||||
|
||||
|
||||
@@ -686,6 +717,35 @@ async def memory(run: JobRun) -> None:
|
||||
)
|
||||
|
||||
|
||||
CURATOR_WATCH = [
|
||||
Watched("хендауты", DAYS, "????-??-??.md"),
|
||||
Watched("выжимки", DIGESTS, "*.md"),
|
||||
Watched("наблюдения", NOTES, "*.md"),
|
||||
Watched("состояние", BEAVER, "состояние.md"),
|
||||
Watched("дневник", DIARY, "????-??-??.md"),
|
||||
Watched("карточки", PEOPLE, "**/*.md"),
|
||||
]
|
||||
|
||||
|
||||
async def curate(run: JobRun) -> None:
|
||||
"""§6.0: куратор памяти раз в несколько часов.
|
||||
|
||||
Крон собирает «что нового», модель раскладывает по шапкам и лентам;
|
||||
нечего раскладывать - джоб не спавнится.
|
||||
"""
|
||||
text = briefing(
|
||||
vault=VAULT,
|
||||
journal=CURATOR_JOURNAL,
|
||||
replies_dir=REPLIES,
|
||||
watched=CURATOR_WATCH,
|
||||
tz=TZ,
|
||||
)
|
||||
if text is None:
|
||||
_log.info("curator: nothing new since the last run")
|
||||
return
|
||||
await run.spawn_job(agent="beaver-curator", title="куратор", text=text)
|
||||
|
||||
|
||||
T3CODE_QUESTION_HINT = (
|
||||
"Если ответ следует из задания - ответь сам: t3_answer(thread_id, "
|
||||
"{id вопроса: label}). Не знаешь - спроси Бобра через say своими словами, "
|
||||
@@ -790,6 +850,9 @@ jobs = [
|
||||
Job("вайбграм", vibegram, cron="*/10 * * * *", critical=False),
|
||||
Job("закрытие", close_idle, cron="20 4 * * *", critical=False),
|
||||
Job("память", memory, cron="30 4 * * 0", critical=False),
|
||||
# крон в UTC контейнера: 02/10/14/18/22 по Варшаве летом;
|
||||
# руками - POST /api/jobs/куратор/run
|
||||
Job("куратор", curate, cron="0 0,8,12,16,20 * * *", critical=False),
|
||||
Job("deploy", deploy, webhook=True),
|
||||
# алерты идут пачками (тревога и отбой подряд) - схлопывать нельзя
|
||||
Job("komodo", komodo_alert, webhook=True, dedupe=False),
|
||||
|
||||
Reference in New Issue
Block a user