RFC Técnico: Briefings Personalizados
Status: Proposta aprovada para implementação
Data: 2026-06-21
Arquivos afetados: briefing.py, engine.py, server.py, app.js, index.html
1. Estado atual do código
briefing_routine() — o que já existe
- Busca os últimos 10 eventos via
store.recent_events(limit=10) - Filtra apenas eventos com
payload.text - Monta
ctx_blockcom as 5 primeiras notas - Usa prompt fixo:
"Gere um briefing das notas recentes. Seja direto e pessoal." - Chama DeepSeek com
tool_useforçado (return_briefing) - Salva o resultado como evento
kind="briefing"no ContextStore - Já aceita
action_idecontextcomo parâmetros — mascontextnunca é usado no prompt
/api/routine/{routine_id} — o que já existe
- Lê
action_idecontextdo body JSON - Passa os dois para
run_routine()→briefing_routine() - Lacuna: não lê
templatedo body — nunca passou esse campo
runRoutine() no frontend
- Faz POST para
/api/routine/briefingcom body{} - Lacuna: não passa nenhum template — body sempre vazio
2. Mudanças mínimas no briefing.py
2a. Dict de templates (substitui o prompt fixo)
BRIEFING_TEMPLATES: dict[str, dict] = {
"geral": {
"label": "Geral",
"prompt": (
"Gere um briefing das notas recentes. "
"Seja direto e pessoal. Destaque padrões, pendências e o que mais chamou atenção."
),
},
"saude": {
"label": "Saúde",
"prompt": (
"Foque em saúde, bem-estar, energia e hábitos. "
"Identifique padrões, alertas e sugira um próximo passo concreto."
),
},
"trabalho": {
"label": "Trabalho",
"prompt": (
"Foque em projetos, tarefas e entregas. "
"Liste o que está em aberto e o que está avançando."
),
},
"amanha": {
"label": "Amanhã",
"prompt": (
"Com base nas notas, identifique pendências e itens inacabados. "
"Monte uma lista do que vale atacar amanhã, em ordem de impacto."
),
},
}
_DEFAULT_TEMPLATE = "geral"
2b. Assinatura atualizada da função
def briefing_routine(
store,
llm_fn=None,
action_id: str | None = None,
context: str = "",
template: str = "geral", # <-- único parâmetro novo
) -> RoutineResult:
2c. Lógica de seleção de prompt (substitui 1 linha)
# ANTES:
prompt = "Gere um briefing das notas recentes. Seja direto e pessoal."
# DEPOIS:
tpl = BRIEFING_TEMPLATES.get(template, BRIEFING_TEMPLATES[_DEFAULT_TEMPLATE])
prompt = tpl["prompt"]
2d. Injeção de perfil (retrocompatível, zero mudança de schema)
Inserir após montar ctx_block, antes de chamar o LLM:
# Buscar perfil se existir (evento kind="setup" com payload.profile)
events_all = store.recent_events(limit=200)
profile = next(
(e["payload"].get("profile") for e in events_all if e.get("kind") == "setup"),
None,
)
system = f"Você é a Brisa, segunda mente pessoal. {ctx_block}"
if profile:
name = profile.get("name", "")
areas = ", ".join(profile.get("areas", []))
tone = profile.get("tone", "direto")
if name:
system += f"\nO usuário se chama {name}."
if areas:
system += f" Áreas de foco: {areas}."
if tone:
system += f" Tom desejado: {tone}."
Quando profile é None (caso atual de 100% dos usuários): zero impacto, sem branch extra, sem erro.
3. Mudança no engine.py
Uma linha: repassar template do run_routine() para a rotina.
# engine.py — run_routine()
def run_routine(
routine_id: str,
store,
llm_fn=None,
action_id=None,
context: str = "",
template: str = "geral", # <-- novo
) -> RoutineResult:
...
return routine_fn(store, llm_fn=llm_fn, action_id=action_id, context=context, template=template)
4. Mudança no server.py
Uma linha: ler template do body e passar para run_routine.
# server.py — api_routine()
action_id = body.get("action_id")
context = body.get("context", "")
template = body.get("template", "geral") # <-- nova linha
result = run_routine(routine_id, store, action_id=action_id, context=context, template=template)
5. UI — seleção de template (máximo 10 linhas de HTML)
HTML (inserir dentro do .card-header, antes do botão + Novo)
<div class="briefing-tabs" id="briefing-tabs">
<button class="btab active" onclick="setBriefingTab('geral',this)">Geral</button>
<button class="btab" onclick="setBriefingTab('saude',this)">Saúde</button>
<button class="btab" onclick="setBriefingTab('trabalho',this)">Trabalho</button>
<button class="btab" onclick="setBriefingTab('amanha',this)">Amanhã</button>
</div>
JS (adicionar 6 linhas no app.js)
var _briefingTemplate = 'geral';
function setBriefingTab(tpl, el) {
_briefingTemplate = tpl;
document.querySelectorAll('#briefing-tabs .btab').forEach(function(b){ b.classList.remove('active'); });
el.classList.add('active');
}
Alterar runRoutine('briefing') para passar o template
// Trocar a linha do fetch body em runRoutine():
body: JSON.stringify(routineId === 'briefing' ? {template: _briefingTemplate} : {})
CSS (3 linhas, inline no <style> existente)
.briefing-tabs { display:flex; gap:6px; margin:6px 0 2px; }
.btab { background:none; border:none; color:var(--dim); font-size:13px; cursor:pointer; padding:2px 8px; border-radius:4px; }
.btab.active { color:var(--fg); background:var(--surface2); font-weight:600; }
6. Estimativa de esforço
| Tarefa | Arquivo | Esforço |
|---|---|---|
Dict BRIEFING_TEMPLATES + seleção de prompt |
briefing.py |
20 min |
Injeção de perfil (busca evento setup) |
briefing.py |
20 min |
Repassar template na cadeia |
engine.py + server.py |
10 min |
| Tabs HTML + JS + CSS | index.html + app.js |
30 min |
| Testes manuais (4 templates, com/sem perfil) | — | 20 min |
| Total | ~1h40 |
Sem migrações. Sem novos endpoints. Sem novos modelos. Retrocompatível: qualquer cliente que não mande template recebe o comportamento atual ("geral").
7. O que NÃO fazer (scope cut)
- Banco de templates customizáveis — dict estático é suficiente
- Onboarding de perfil obrigatório — perfil é opt-in
- Templates por vertente financeira — fora do escopo desta entrega
- Briefings agendados automáticos — é rotina manual, não cron
- Editor de templates no frontend — escopo PO avançado