diff --git a/docs/guides/email.mdx b/docs/guides/email.mdx index 2f57a5111..fa1e2bb60 100644 --- a/docs/guides/email.mdx +++ b/docs/guides/email.mdx @@ -127,16 +127,16 @@ Each row carries inline action buttons: If you haven't connected Google yet, the agent surfaces a one-click **Connect Google** button inline in the chat — no need to navigate to Settings → Connections manually. -### In-session preferences (in-memory, wiped on restart) +### Cross-session preferences (persisted via memory store) -Tell the agent how you want classification to behave for this session: +Tell the agent how you want classification to behave: -- *"Treat boss@company.com as urgent"* → calls `set_priority_sender`. That sender bypasses the heuristic and lands in **Urgent** for the rest of the session. +- *"Treat boss@company.com as urgent"* → calls `set_priority_sender`. That sender bypasses the heuristic and lands in **Urgent** across all future sessions. - *"Treat newsletter@stripe.com as low priority"* → calls `set_low_priority_sender`. That sender lands in **Suggested archives**. - *"Default informational mail to archive"* → calls `set_category_default("informational", "archive")`. Informational items lift into **Suggested archives** until you reset. - *"Clear my preferences"* → calls `clear_session_preferences`. -Preferences are stored in process memory only — restarting the agent (or quitting Agent UI) wipes them. This is deliberate: the goal is to prove the value of session-scoped learning before we wire up persistent memory. Once persistent memory ships, the same tools will write through to it without changing this surface. +Preferences persist across agent restarts via the memory store (`email:preferences`). They fall back to session-only (lost on restart) when `GAIA_MEMORY_DISABLED=1` or when the embedding backend is unreachable at startup. Incognito sessions never write preferences to persistent storage. ### Behavioral learning (auto-promotion) @@ -148,7 +148,7 @@ Senders you reply to quickly are automatically promoted to priority on the next `list_inbox`, `get_message`, `get_thread`, `search_messages`, `list_labels`, `triage_inbox`, `pre_scan_inbox` -### Session preferences (in-memory; wiped on agent restart) +### Preferences (persisted across restarts via memory store) `set_priority_sender`, `set_low_priority_sender`, `set_category_default`, `clear_session_preferences` diff --git a/hub/agents/python/code/gaia_agent_code/cli.py b/hub/agents/python/code/gaia_agent_code/cli.py index 5da1c0557..a34638443 100644 --- a/hub/agents/python/code/gaia_agent_code/cli.py +++ b/hub/agents/python/code/gaia_agent_code/cli.py @@ -284,7 +284,7 @@ def _build_index_parser(): parser.add_argument( "--base-url", default=None, - help="Lemonade server URL (default: http://localhost:8000/api/v1)", + help="Lemonade server URL (default: http://localhost:13305/api/v1)", ) parser.add_argument( "--no-lemonade-check", @@ -308,10 +308,30 @@ def _build_index_parser(): default=10, help="Number of results to return (default: 10)", ) + # FIX 1: use SUPPRESS so the parent's default="." is preserved when --repo is omitted + search_p.add_argument( + "--repo", + default=argparse.SUPPRESS, + help="Path to repository root (overrides parent --repo)", + ) sub.add_parser("status", help="Show index status") sub.add_parser("clear", help="Clear the index") - sub.add_parser("chat", help="Interactive code Q&A (CodeAgent + code_index tools)") + + chat_p = sub.add_parser("chat", help="Interactive code Q&A (CodeAgent + code_index tools)") + # FIX 2: use SUPPRESS so the parent's default="." is preserved when --repo is omitted + chat_p.add_argument( + "--repo", + default=argparse.SUPPRESS, + help="Path to repository root (overrides parent --repo)", + ) + # FIX 3: dest="repo" makes --path a true alias for --repo, no changes needed in cmd_index + chat_p.add_argument( + "--path", + dest="repo", + default=argparse.SUPPRESS, + help="Alias for --repo (path to repository root)", + ) return parser @@ -509,7 +529,7 @@ def main(): parser.add_argument( "--path", "-p", - type=str, + type=str, default=None, help="Project directory path. Creates directory if it doesn't exist.", ) @@ -606,4 +626,4 @@ def main(): if __name__ == "__main__": - sys.exit(main()) + sys.exit(main()) \ No newline at end of file diff --git a/hub/agents/python/email/gaia_agent_email/tools/profile_tools.py b/hub/agents/python/email/gaia_agent_email/tools/profile_tools.py index 731169e38..c2a6be4a5 100644 --- a/hub/agents/python/email/gaia_agent_email/tools/profile_tools.py +++ b/hub/agents/python/email/gaia_agent_email/tools/profile_tools.py @@ -19,12 +19,17 @@ Bounded and idempotent by design — ``_record_interaction`` always does an upsert into the single per-sender record, so the record count is O(senders) +<<<<<<< HEAD +regardless of how many emails arrive. #1290 can reuse ``_read_interactions`` +directly to build richer views. +======= regardless of how many emails arrive. Reply behavior is tracked separately in reply records (entity ``email:reply:``). ``_evaluate_promotions()`` reads these to identify senders with consistently fast replies and returns them for promotion to priority senders. Called on-demand at triage time — no background thread. +>>>>>>> upstream/main Tools registered: @@ -52,6 +57,8 @@ _INTERACTION_DOMAIN = "email_agent_interactions" _INTERACTION_CATEGORY = "interaction" +<<<<<<< HEAD +======= # Stable entity prefix for per-sender REPLY behavior records. # Entity = f"{_REPLY_ENTITY_PREFIX}{sender_email}" _REPLY_ENTITY_PREFIX = "email:reply:" @@ -79,6 +86,7 @@ # 50k senders is far beyond any realistic mailbox. _MAX_REPLY_RECORDS: int = 50000 +>>>>>>> upstream/main # Sanity ceiling on how many distinct-sender interaction records we read in # one profiling pass. This is NOT a silent truncation cap: if a real inbox # ever has more distinct senders than this, ``_read_interactions`` logs a @@ -101,13 +109,25 @@ def _now_iso() -> str: def _dominant_category(category_counts: Dict[str, int]) -> str: +<<<<<<< HEAD + """Return the category with the highest count; ties broken by last alphabetically.""" +======= """Return the category with the highest count (ties: lexicographic order).""" +>>>>>>> upstream/main if not category_counts: return "" return max(category_counts, key=lambda cat: (category_counts[cat], cat)) class ProfileToolsMixin: +<<<<<<< HEAD + """Mixin that registers inbox-profiling tools. + + State-free at construction time — reads ``self._memory_store`` via + a closure captured when ``_register_profile_tools()`` is called. + """ + +======= """Mixin that registers inbox-profiling tools and behavioral-learning helpers. State-free at construction time — reads ``self._memory_store`` via @@ -236,6 +256,7 @@ def _evaluate_promotions(self) -> List[str]: ) return qualified +>>>>>>> upstream/main def _record_interaction(self, sender: str, category: str) -> None: """Update the rolling interaction record for *sender*.