-
Notifications
You must be signed in to change notification settings - Fork 95
feat: add personal assistant quickstart #7
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
Add a new personal assistant quickstart that demonstrates email and calendar management capabilities. Includes: - Email assistant agent with human-in-the-loop support - Gmail integration tools with setup scripts - Calendar management tools - Configurable tool implementations (default and Gmail) - Test notebook and deployment configuration
Replace the separate triage routing step with a triage_email tool that the agent calls directly. This simplifies the architecture from a multi-node workflow to a single deepagent. Key changes: - Add triage_email tool with structured schema (reasoning + classification) - Remove triage_router and triage_interrupt_handler nodes - Update agent prompt to require triage_email as first step - Simplify state schema to accept message strings instead of email_input dict - Update middleware to include triage_instructions in memory retrieval - Add triage_email to tool registry Benefits: - Simpler single-agent architecture vs multi-node workflow - More flexible with triage reasoning in agent context - Cleaner input interface (simple message strings) - Better token efficiency (no separate LLM routing calls)
Address CodeQL security warnings by avoiding logging of file paths to sensitive token and credentials files. Replace specific path logging with generic messages that don't expose filesystem structure. Changes: - gmail_tools.py: Use generic message for token file location - run_ingest.py: Remove TOKEN_PATH from log messages - setup_gmail.py: Remove secrets_path and token_path from logs This prevents potential information disclosure while maintaining useful debugging information.
Remove the unused legacy implementation file that was superseded by the email_assistant_deepagents.py using the deepagents library. The custom HITL middleware in src/personal_assistant/middleware/ is still being used and provides the memory integration functionality. Also update README to remove reference to the legacy file and update the description to reflect the simplified triage architecture.
…-ai/deepagents-quickstarts into rlm/personal-assistant
Replace JSON email_input examples with markdown-formatted email strings in both README and test notebook. This better reflects the simplified agent interface where emails are passed as message strings. Changes: - README: Convert all 3 email examples to markdown format - README: Update Python API usage to show markdown string input - test.ipynb: Remove parse_email/format_email_markdown from imports - test.ipynb: Update all example cells to use markdown strings directly Note: parse_email and format_email_markdown utilities are retained in the codebase as they're still needed for processing actual Gmail API responses.
Simplified PostInterruptMemoryMiddleware to focus solely on rejection detection, removing all edit detection logic since deepagents framework doesn't expose raw tool calls before edits. Key changes: - Remove edit detection (after_model, wrap_tool_call, update_memory_for_edit) - Use before_model hook to detect ToolMessages with status="error" (rejections) - Capture optional rejection message from ToolMessage content - Update triage_preferences with user's rejection feedback - Remove response_preferences and cal_preferences namespaces - Update interrupt config: remove "edit" from allowed_decisions - Add agent instruction to call Done tool immediately on rejection - Add state_schema to middleware for proper state access Middleware now properly detects rejections using before_model hook which runs before next model call, allowing it to see rejection ToolMessages created by built-in HITL middleware. Rejection messages are extracted and included in memory updates for better triage learning.
- Remove email_assistant_hitl.py (legacy Agent Inbox UI middleware) - Clean up middleware/__init__.py exports - Update README code structure to reflect current middleware The deprecated middleware was used with the old Agent Inbox UI and handled interrupts manually with 4 decision types (accept/edit/ignore/response). Current architecture uses built-in interrupt_on parameter with 3 focused middleware classes: - MemoryInjectionMiddleware: inject learned preferences before LLM calls - PostInterruptMemoryMiddleware: detect rejections and update memory - GenUIMiddleware: push UI messages for tool visualization
Simplify memory architecture from 3 separate namespaces to 1 unified profile:
- Single namespace: ("email_assistant", "user_profile")
- Updates on ANY tool rejection (not just write_email/schedule_meeting)
- Profile contains: user context, triage criteria, response style, scheduling
Changes:
- prompts.py: Add default_user_profile (~18 lines), update template to single
{user_profile} variable, update MEMORY_UPDATE_INSTRUCTIONS for unified context
- email_memory_injection.py: Simplify to 1 fetch instead of 3, update both
sync/async methods
- email_post_interrupt.py: Expand interrupt_tools to include Question, update
namespace to user_profile, add tool-agnostic feedback with section guidance
- email_assistant_deepagents.py: Simplify imports and formatting to single variable
- README.md: Update Memory System section, trigger matrix, and middleware docs
Benefits:
- Performance: 1 store fetch vs 3 per model call
- Flexibility: ANY tool rejection triggers learning
- Maintainability: Single profile easier to understand
- Learnability: Background now part of learnable profile
Update test.ipynb to work with simplified memory architecture: - Fix interrupt display format for built-in interrupt_on system (action_requests instead of action_request structure) - Add resume_with_approve_or_reject() helper for new approve/reject decisions - Add get_user_profile_from_store() helper to inspect unified profile - Remove deprecated resume_with_decision() (old 4-decision system) - Update all example cells to use new interrupt format - Add comprehensive memory testing workflow (Steps 1-5): * Check initial profile * Send newsletter email and reject with feedback * Verify profile updates with rejection context * Test rejecting Question tool (ANY tool rejection triggers learning) * Display final profile showing cumulative learning - Add verification steps to confirm rejection messages captured correctly The notebook now demonstrates the single unified profile approach with memory learning from ANY tool rejection (write_email, schedule_meeting, Question).
| updates the user profile to learn from the rejection feedback. | ||
|
|
||
| Memory updates: | ||
| - reject write_email: Updates user_profile (triage and/or response style) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Your approach makes sense to me, I wonder if it's necessary to be this prescriptive as opposed to a generic "When a user rejects a tool call - you should think about whether or not you should update the agent's memory"
Not sure this additional specification per type of rejected tool call is necessary.
It is probably strictly better than the general prompt, but it is also more complex
|
|
||
| all_tools.update({ | ||
| "fetch_emails_tool": fetch_emails_tool, | ||
| "send_email_tool": send_email_tool, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why are write_email and send_email separate?
Originally I think we had this as write_email also sent the email, but was interrupted for user approval. I think not necessary to have these as two separate tools - was there a reason you split?
| __all__ = [ | ||
| "write_email", | ||
| "triage_email", | ||
| "Done", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why do we need this Done tool and why don't we have it just run until it doesn't want to call any more tools, which I believe is the default behavior for both create_agent and deep agents
|
|
||
| ### 3. Connect to Agent Inbox | ||
|
|
||
| After ingestion, you can access your all interrupted threads in Agent Inbox (https://dev.agentinbox.ai/): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we want tutorial to go through inbox? I think probably deepagents ui
| from langchain_core.runnables import RunnableConfig | ||
|
|
||
| @dataclass(kw_only=True) | ||
| class Configuration: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we still need this for this example? Might be easier to remove and simplify
Add a new personal assistant quickstart that demonstrates email and calendar management capabilities. Includes: