Skip to content

feat(email): multi-account audit — the chat assistant is now configured per account, not just scoped to one - #316

Merged
vjvarada merged 1 commit into
mainfrom
claude/email-salutation-missing-pm3hne
Aug 1, 2026
Merged

feat(email): multi-account audit — the chat assistant is now configured per account, not just scoped to one#316
vjvarada merged 1 commit into
mainfrom
claude/email-salutation-missing-pm3hne

Conversation

@vjvarada

@vjvarada vjvarada commented Aug 1, 2026

Copy link
Copy Markdown
Contributor

Audit result

Checked the multi-account plumbing end to end. The data and automation layers were already correct; there was one real gap, in the assistant.

Verified per-account (no change needed)

  • Schema — every email table is keyed by account_id and cascades from email_accounts: rules + actions, executed rules, assistant settings, knowledge, learned patterns, rule patterns, rule guidance, thread status, AI drafts, senders, newsletters, contacts, embeddings.
  • Rules engine — rules are loaded WHERE account_id = :aid and evaluated per account, so each mailbox genuinely has its own rules engine. Same for the cleaner's label projection and the auto-categorize sweep.
  • Drafting — already resolves About / signature / models (rule, draft, compose, chat) / voice profile / learned patterns / reply memories by account_id, with a per-account Mem0 scope (email_memory_scope).

The gap

The chat assistant knew which account to act on but nothing about how that mailbox should be handled. The persona carried the account list, the active account_id and the open email — never the account's About, standing instructions, or writing style. So the same assistant conversed identically on a work mailbox and a personal one, and only its tool calls differed. Drafts were already per-account (the server loads settings by account_id), which made the inconsistency easy to miss.

  • buildEmailAssistantPersona now takes the active account's settings and emits them as a scoped block, stating they apply to the ACTIVE account only so the model doesn't carry them across a switch. Standing instructions are marked binding; the auto-derived learned style is marked advisory — matching the precedence the drafter already uses.
  • Both entry points feed it: the email app (which already fetched these settings for the chat model and discarded the rest) and the chat app, which now also passes the resolved account id.
  • /assistant/settings returns learned_writing_style so the chat can match the voice the drafter already writes in.

Left as-is deliberately

The chat agent's remember() namespace stays user-global. That ContextVar doubles as the agent's gateway-auth identity, so an account suffix would break every tool call — the per-account scope lives on the gateway side (email_memory_scope), where drafting uses it. Flagging it as a known, documented trade-off rather than changing it silently.

Testing

  • Backend: 952 email tests passed.
  • Frontend: new emailAssistantPersona.test.ts (7 cases) — the persona names the active account, carries its configuration, marks instructions binding and learned style advisory, swaps cleanly when the account changes, and degrades to account-awareness when settings haven't loaded or are blank. tsc --noEmit clean, next build clean, vitest 141/141, no new eslint issues.

🤖 Generated with Claude Code

https://claude.ai/code/session_0122zNo8kiZooLu49jsaSrQ6


Generated by Claude Code

…coped to one

Audit of the multi-account plumbing found the data and automation layers
already correct, and one real gap in the assistant.

Verified per-account (no change needed): every email table is keyed by
account_id and cascades from email_accounts — rules + actions, executed
rules, assistant settings, knowledge, learned patterns, rule patterns and
guidance, thread status, AI drafts, senders, newsletters, contacts,
embeddings. Rules are loaded WHERE account_id and evaluated per account,
so each mailbox genuinely has its own rules engine. Drafting already
resolves About / signature / models / voice profile / reply memories by
account_id, with a per-account Mem0 scope.

The gap: the CHAT assistant knew WHICH account to act on but nothing
about HOW that mailbox should be handled. The persona carried the
account list, the active account_id and the open email — never the
account's About, standing instructions, or writing style. So the same
assistant conversed identically on a work mailbox and a personal one,
and only its tool calls differed. Drafts were already per-account
(the server loads settings by account_id), which made the inconsistency
easy to miss.

- buildEmailAssistantPersona takes the active account's settings and
  emits them as a scoped block, stating they apply to the ACTIVE account
  only so the model doesn't carry them across a switch. Standing
  instructions are marked binding; the auto-derived learned style is
  marked advisory, matching the precedence the drafter already uses.
- Both entry points feed it: the email app (which already fetched these
  settings for the chat model and discarded the rest) and the chat app,
  which now also passes the resolved account id.
- /assistant/settings returns learned_writing_style so the chat can
  match the voice the drafter already writes in.

Left as-is deliberately: the chat agent's remember() namespace stays
user-global. That ContextVar doubles as the agent's gateway-auth
identity, so an account suffix would break every tool call — the
per-account scope lives on the gateway side (email_memory_scope), where
drafting uses it.

Tests: the persona names the active account, carries its configuration,
marks instructions binding and learned style advisory, swaps cleanly
when the account changes, and degrades to account-awareness when the
settings haven't loaded or are blank.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0122zNo8kiZooLu49jsaSrQ6
@vjvarada
vjvarada merged commit 46ef2e1 into main Aug 1, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants