The distill agent (Brain v2)¶
Hourly cron (7 * * * *, conversation module) that turns routed, non-sensitive
cap_items into knowledge. It replaced capture's enrich.
Per run, two passes:
- Threads — each routed thread gets one
distill_threadjudgement with a deliberately LOW bar: events, plans, decisions, feelings, facts about people, and commitments are all note-worthy; only machine noise and contentless placeholders are not. Worthy → one atomic note innotes/(## Context/## Outcome, wikilinked to person/project pages) plusbrain_factsrows (kind: date | commitment | preference | contact-info | misc, linked to the entity/project), items →filed. Unworthy → items →skippedwithlinks.skip_reason— retryable, never terminal. - Pages — entities and projects touched by this run's filed threads get
their evolving pages regenerated:
people/<slug>.md(profile, key facts, timeline, linked notes) andprojects/<slug>.md(status, decisions, open threads, activity). These carrymanaged: truefrontmatter and are wholly machine-owned; the sink refuses to touch any file without that marker, so your own notes are never clobbered. The owner (percap_connectors.owner_addresses) and unnamed handle-only entities get no person page.
Everything flows through the audited redaction chokepoint into conv_llm_log
(purpose: distill | distill_page); sensitive items never reach a prompt.
Manual runs:
PYTHONPATH="agent-runtime/src:modules/conversation/agent" \
JARVIS_VAULT_DIR=~/vault LLM_PROVIDER=deepseek DEEPSEEK_API_KEY=... \
POCKETBASE_URL=... DEV_USER_EMAIL=... DEV_USER_PASSWORD=... \
python -m conversation_agent.distill --limit 200 --pages-limit 50
# reconsider what distill previously skipped (only items carrying
# links.skip_reason — sweep/human skips stay put)
python -m conversation_agent.distill --reeval --limit 200
Noise filters, group chats and the fresh start¶
Only one gate runs before distill ever sees an item; the other two rules below shape follow-ups and facts once an item is already in the pipeline.
- Noise gate (email only), runs before distill. Before triage, a deterministic classifier checks
each new email against
cap_noise_rules(verification codes,noreply@senders, newsletters/List-Unsubscribe, CI/bot notification mail). A match archives the item (statearchived,links.archived_by = "noise:<rule>") instead of routing it toraw. Archived mail is never deleted — it stays in the semantic index foraskand search, it just never shows up as work. Keep rules always win: invoices/receipts (by subject/body terms or attachment name) and owner-added bank sender domains are checked first and override every archive rule, even from anoreplyaddress. Manage this in Settings › Noise filters: toggle a rule on/off, see how many items it archived this week, expand it to see (and restore) the last 20 items it archived, and add a bank domain as a keep rule (e.g.mybank.example). Restoring an item sends it back torawfor normal triage. - Group-chat rule, a follow-up rule. In a thread with more than two
participants (a WhatsApp/Discord group, a multi-recipient email, etc.), a
reply is only flagged as owed when you're directly named or @-mentioned, or
a question immediately follows something you said. Outside of groups,
follow-up detection is unchanged. Set your names (and Discord user id) once
in Settings › Follow-up rules › "Your names in group chats"; until you
do, groups behave as before. This decides whether a
reply_owedfollow-up opens — it doesn't gate distill. - Commitments are future-only, a fact rule. A message describing
something already done (e.g. "sent the invoice yesterday") is stored as a
miscdone:fact, never as an open commitment — so the task list only ever holds things still owed. This governs how distill stores a fact, not whether the item reaches distill.
For the one-off backlog cleanup that applies the 30-day cutoff to existing
cap_items and follow-ups, see the
fresh start runbook.