Skip to content

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:

  1. Threads — each routed thread gets one distill_thread judgement 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 in notes/ (## Context / ## Outcome, wikilinked to person/project pages) plus brain_facts rows (kind: date | commitment | preference | contact-info | misc, linked to the entity/project), items → filed. Unworthy → items → skipped with links.skip_reason — retryable, never terminal.
  2. 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) and projects/<slug>.md (status, decisions, open threads, activity). These carry managed: true frontmatter and are wholly machine-owned; the sink refuses to touch any file without that marker, so your own notes are never clobbered. The owner (per cap_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 (state archived, links.archived_by = "noise:<rule>") instead of routing it to raw. Archived mail is never deleted — it stays in the semantic index for ask and 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 a noreply address. 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 to raw for 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_owed follow-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 misc done: 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.