Improving Integrations and Agentic Developement to better match the usecase. #431
Improving Integrations and Agentic Developement to better match the usecase. #431
Conversation
…retry, surface unparseable transcripts
WalkthroughThis PR reorganizes documentation navigation, renames and relocates several Guides groups/pages, expands the MCP integration guide, restructures vibecoding content, and adds comprehensive Gmail and Granola integration guides plus their example importer scripts. Changes
Sequence Diagram(s)sequenceDiagram
participant User
participant Script
participant GmailAPI as Gmail API
participant Honcho
User->>Script: run honcho_gmail.py (credentials, query)
Script->>GmailAPI: list_threads/query
GmailAPI-->>Script: thread IDs
Script->>GmailAPI: fetch thread messages
GmailAPI-->>Script: thread messages
Script->>Script: decode/strip/normalize peers & build session/messages
Script->>Honcho: create peers/sessions and send messages
Honcho-->>Script: ack
Script-->>User: report summary
sequenceDiagram
participant User
participant Script
participant GranolaMCP as Granola MCP
participant Honcho
User->>Script: run honcho_granola.py (auth)
Script->>GranolaMCP: DCR + PKCE auth -> token
GranolaMCP-->>Script: access token
Script->>GranolaMCP: list_meetings / get_transcript
GranolaMCP-->>Script: meeting metadata + transcripts
Script->>Script: parse participants/turns, review decision
Script->>Honcho: create session/peers and send messages (two-person or summary)
Honcho-->>Script: ack
Script-->>User: final transfer summary
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Possibly related issues
Possibly related PRs
Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
📝 Coding Plan
Comment |
There was a problem hiding this comment.
Actionable comments posted: 9
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@docs/v3/documentation/introduction/vibecoding.mdx`:
- Around line 64-67: Update this page so the MCP contract matches
docs/v3/guides/integrations/mcp.mdx: change the advertised base URL from
https://mcp.honcho.dev/mcp to https://mcp.honcho.dev and replace shorthand tool
names in the "Workspace", "Peers", "Sessions", and "Conclusions" lists (e.g.
`get/set_workspace_metadata`, `add_messages`, `inspect_session`) with the
explicit tool identifiers used in the MCP guide (use the exact function names
from the guide for workspace metadata, message addition, session inspection,
etc.) so copy/paste setup and agent instructions are identical across both
pages.
In `@docs/v3/guides/gmail.mdx`:
- Around line 605-607: The Card component's copy and link are incorrect: update
the card body text from "See how the Granola integration maps to common Honcho
patterns." to reference Gmail (e.g., "See how the Gmail integration maps to
common Honcho patterns."), and change the absolute href value on the Card
(currently "/v3/documentation/core-concepts/design-patterns") to a relative
Mintlify path (e.g., "../../core-concepts/design-patterns") so the link works
from docs/v3/guides/gmail.mdx; locate the Card invocation (Card title="Design
Patterns" icon="cubes" href="...") and update the inner copy and href
accordingly.
In `@docs/v3/guides/overview.mdx`:
- Around line 14-27: The Card components in this snippet use absolute hrefs
(e.g., href="/v3/guides/integrations/claude-code") which cause Mintlify 404s;
update each Card's href to a relative path from docs/v3/guides/overview.mdx
(e.g., change href="/v3/..." to the appropriate ../../guides/... or similar
relative path) for the entries referencing Claude Code, MCP Server, Hermes
Agent, OpenClaw, and Agent Zero (look for the Card components and their href
attributes) and apply the same relative-path fix to the other Card blocks
indicated (lines 41-49, 56-64, 71-72).
In `@examples/gmail/honcho_gmail.py`:
- Around line 282-290: The dry-run preview assumes each thread in
all_thread_messages has at least one message, but fetch_thread_messages() can
return an empty list when get_thread() returns {}; update the dry-run block
(inside the args.dry_run branch) to skip threads with empty msgs before
accessing msgs[0]—i.e., check if not msgs: continue—so the loop safely ignores
failed/empty thread payloads and only builds body_preview/prints for threads
where m = msgs[0] exists.
- Around line 36-48: The find_credentials function only searches the current
working directory, which breaks when the script is run from elsewhere; update
find_credentials to also search the script's directory (the directory containing
honcho_gmail.py) for files matching "client_secret*.json" before raising
FileNotFoundError. Use the module file location (e.g., via __file__ /
Path(__file__).resolve().parent) along with globbing to prefer a match in the
script directory, then fall back to os.getcwd() (or vice versa) so the helper
finds credentials placed next to the script; keep the same FileNotFoundError
message if no matches are found.
- Around line 123-132: The current regex-based extract_email and extract_name
break on valid RFC 5322 headers (e.g., quoted commas and encoded-word names);
replace the regex logic by first decoding RFC2047 words using
email.header.decode_header and make_header and then parsing addresses with
email.utils.parseaddr / getaddresses: in extract_email decode the header, call
parseaddr to get the email, lowercase and strip it; in extract_name decode the
header, call parseaddr to get the display name, strip and fall back to the
original header when empty; also add a helper like parse_address_list that
decodes the header and uses getaddresses to return cleaned "Name <addr>" or addr
entries and handle empty headers gracefully.
In `@examples/granola/honcho_granola.py`:
- Around line 71-87: The callback currently accepts any code without verifying
the OAuth state; update the flow to validate a per-run state by adding an
expected_state attribute (e.g., _OAuthCallback.expected_state) that is set when
you build the authorize URL, then change _OAuthCallback.do_GET to read the
"state" param from the query and reject the callback if it's missing or does not
match expected_state (set auth_result["error"] and return a 400 with an
explanatory message). Ensure you still populate auth_result["code"] only after
state verification and keep responses (200/400) and headers consistent.
- Around line 665-666: The workspace ID literal is incorrect: change the Honcho
instantiation to use workspace_id="granola" (replace the "granola_test" string
wherever Honcho(workspace_id="granola_test") appears, e.g., the honcho =
Honcho(...) at the top and the other occurrence near the later block), and
update the matching workspace mention in the guide file
docs/v3/guides/granola.mdx so the documented workspace ID and the completion
banner both read "granola" consistently.
- Around line 276-283: The current broad except in the retry loop around
call_mcp_tool and extract_mcp_text swallows all failures and returns None;
narrow the handling by catching specific exceptions (e.g., network/transport
errors, auth errors, and a NotFound/NoTranscript error) instead of Exception:
keep retrying on transient HTTP/transport errors (and log them), surface or
re-raise authentication/contract errors (or log and abort) so they don't
silently fall back to a summary-only transcript, and only return None when you
detect an explicit "no transcript" condition from call_mcp_tool or
extract_mcp_text; reference the retry loop, call_mcp_tool, extract_mcp_text and
the except block to implement these changes.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 6ecdc626-2653-4e92-aff4-46de1ee254b5
📒 Files selected for processing (9)
docs/docs.jsondocs/v2/guides/n8n.mdxdocs/v3/documentation/introduction/vibecoding.mdxdocs/v3/guides/gmail.mdxdocs/v3/guides/granola.mdxdocs/v3/guides/integrations/mcp.mdxdocs/v3/guides/overview.mdxexamples/gmail/honcho_gmail.pyexamples/granola/honcho_granola.py
| - **Workspace:** `inspect_workspace`, `list_workspaces`, `search_workspace`, `get/set_workspace_metadata` | ||
| - **Peers:** `create_peer`, `list_peers`, `chat`, `get_peer_card`, `get_peer_context`, `get_representation`, `search_peer_messages` | ||
| - **Sessions:** `create_session`, `list_sessions`, `clone_session`, `add_messages`, `get_session_context`, `inspect_session`, `get_session_summaries` | ||
| - **Conclusions:** `list_conclusions`, `query_conclusions`, `create_conclusions`, `delete_conclusion` |
There was a problem hiding this comment.
Keep the MCP contract consistent with docs/v3/guides/integrations/mcp.mdx.
This page advertises https://mcp.honcho.dev/mcp and shorthand tool names like get/set_workspace_metadata, add_messages, and inspect_session, but the dedicated MCP guide in the same PR documents https://mcp.honcho.dev and the explicit tool names. One of the two pages is now wrong for copy/paste setup or agent instructions.
Also applies to: 72-80
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@docs/v3/documentation/introduction/vibecoding.mdx` around lines 64 - 67,
Update this page so the MCP contract matches
docs/v3/guides/integrations/mcp.mdx: change the advertised base URL from
https://mcp.honcho.dev/mcp to https://mcp.honcho.dev and replace shorthand tool
names in the "Workspace", "Peers", "Sessions", and "Conclusions" lists (e.g.
`get/set_workspace_metadata`, `add_messages`, `inspect_session`) with the
explicit tool identifiers used in the MCP guide (use the exact function names
from the guide for workspace metadata, message addition, session inspection,
etc.) so copy/paste setup and agent instructions are identical across both
pages.
| <Card title="Claude Code" icon="terminal" href="/v3/guides/integrations/claude-code"> | ||
| Long-term memory that survives context wipes, session restarts, and project switches | ||
| </Card> | ||
| <Card title="LangGraph" icon="diagram-project" href="/v3/guides/integrations/langgraph"> | ||
| Add persistent memory and theory of mind to your LangGraph agents | ||
| <Card title="MCP Server" icon="star-of-life" href="/v3/guides/integrations/mcp"> | ||
| Add Honcho memory to Claude Desktop, Cursor, Windsurf, Cline, and any MCP client | ||
| </Card> | ||
| <Card title="Hermes Agent" icon="bolt" href="/v3/guides/community/hermes"> | ||
| Cross-session memory for Nous Research's Hermes agent | ||
| </Card> | ||
| <Card title="OpenClaw" icon="lobster" href="/v3/guides/integrations/openclaw"> | ||
| Memory across every channel — WhatsApp, Telegram, Discord, Slack, and more | ||
| </Card> | ||
| <Card title="Agent Zero" icon="triangle" href="/v3/guides/community/agent0"> | ||
| Persistent memory plugin for the Agent Zero framework |
There was a problem hiding this comment.
Use relative hrefs for the new guide cards.
These new cards point at internal docs pages via absolute /v3/... paths. This site has already hit Mintlify 404s with absolute cross-references, so these should stay relative from docs/v3/guides/overview.mdx.
Based on learnings: In the Honcho documentation site (Mintlify-based), relative paths like ../../guides/... are the correct pattern for cross-references between documentation sections, not absolute paths like /v2/guides/... which can cause 404s.
Also applies to: 41-49, 56-64, 71-72
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@docs/v3/guides/overview.mdx` around lines 14 - 27, The Card components in
this snippet use absolute hrefs (e.g.,
href="/v3/guides/integrations/claude-code") which cause Mintlify 404s; update
each Card's href to a relative path from docs/v3/guides/overview.mdx (e.g.,
change href="/v3/..." to the appropriate ../../guides/... or similar relative
path) for the entries referencing Claude Code, MCP Server, Hermes Agent,
OpenClaw, and Agent Zero (look for the Card components and their href
attributes) and apply the same relative-path fix to the other Card blocks
indicated (lines 41-49, 56-64, 71-72).
| def extract_email(from_header: str) -> str: | ||
| """Extract bare email from 'Name <email>' format.""" | ||
| match = re.search(r"<([^>]+)>", from_header) | ||
| return match.group(1).lower() if match else from_header.lower().strip() | ||
|
|
||
|
|
||
| def extract_name(from_header: str) -> str: | ||
| """Extract display name from 'Name <email>' format.""" | ||
| match = re.match(r'^"?([^"<]+)"?\s*<', from_header) | ||
| return match.group(1).strip() if match else from_header.strip() |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
fd honcho_gmail.pyRepository: plastic-labs/honcho
Length of output: 1855
🌐 Web query:
email.utils parseaddr Python documentation quoted display names
💡 Result:
email.utils.parseaddr() parses an address header value into (realname, email_address) and returns ('', '') on failure (with strict=True rejecting malformed input by default in modern Pythons). [1]
For quoted display names, parseaddr returns the display name without the surrounding quotes. For example, parsing "Full Name" <foo@example.com> yields ('Full Name', 'foo@example.com'). [2]
If you need to explicitly strip surrounding quotes (or angle brackets) from an already-extracted string, use email.utils.unquote(), which removes surrounding "…" or <…> when present. [1]
Sources: [1] Python docs email.utils (parseaddr/unquote), [2] CPython issue showing parseaddr behavior with quoted display names.
🌐 Web query:
email.utils getaddresses comma in display names email header parsing
💡 Result:
email.utils.getaddresses() parses RFC 5322 address-lists, where commas delimit separate mailboxes (e.g., To, Cc). If a display name contains a comma, it must be a quoted-string, otherwise parsers (including getaddresses) will treat that comma as an address separator and split the value incorrectly. In RFC 5322, , is a “special” character (not allowed in an unquoted atom), and quoted-strings are the mechanism to include such characters. [2]
Correct header examples (comma in display name):
"Doe, John" <john@example.com>"ACME, Inc." <sales@example.com>
Python behavior / what to do:
getaddresses([...])is expected to split on the comma if the name isn’t quoted, because it’s parsing an address-list. [1]- If you’re consuming real-world, non-compliant headers and want the older, more permissive parsing behavior, Python 3.13+ added
strict=; usegetaddresses(fieldvalues, strict=False). [1], [3]
Best practice when generating headers: always quote/format via the stdlib (so commas get quoted properly), e.g. email.utils.formataddr((display_name, addr)). [1]
Sources: Python email.utils.getaddresses() docs (incl. strict=) [1], RFC 5322 “specials” + quoted-string rules [2], Python 3.13 “What’s New” note on strict=False/supports_strict_parsing [3].
🌐 Web query:
regex email header parsing issues quoted display names commas encoded
💡 Result:
Regex-based parsing of email header address fields (e.g., From:, To:, Cc:) breaks in exactly the cases you listed because RFC 5322 “address-list” syntax is not a regular, comma-splittable format once you include the allowed sub-structures:
- Commas are address separators, but commas can also appear inside a quoted display-name (e.g.,
"Doe, John" <john@example.com>). A regex that splits on,(or uses[^,]+) will mis-split unless it correctly implements quoted-string rules. RFC 5322 also allows comments/whitespace in many places (“CFWS”), further complicating it. [1] - Encoded display names (RFC 2047 encoded-words) must be decoded after tokenizing/parsing the structured header, meaning “special” characters (including commas/colons) can be hidden inside encoded-words and only appear after decoding—so “decode then parse” (or regex on decoded text) can become ambiguous/unsafe. [2]
- Real-world parsers often include lots of extra logic for these edge cases (quotes, comments, multiple addresses, robustness deviations), which is hard to reproduce reliably with a single regex. For example, .NET’s
MailAddressParserexplicitly handles quoted display-names and comma-separated lists with special-case logic. [3]
Practical fix: don’t use regex; use a real RFC 5322 address parser for the header, then decode RFC 2047 words in the display name after parsing. Libraries exist specifically for this (e.g., email-addresses parses RFC 5322 address lists and exposes name vs address). [4]
Test cases your solution must handle (minimum):
"Doe, John" <john@example.com>, Alice <alice@example.com>=?UTF-8?Q?Doe=2C_John?= <john@example.com>(comma appears only after RFC 2047 decoding)John (CEO) Doe <john@example.com>(comments/CFWS)
Sources: RFC 5322 address syntax and comma-separated lists [1]; encoded-word decoding occurs after structured parsing and can hide specials [2]; example of production-grade parsing logic for display-name/commas [3]; example RFC 5322 parser library designed to replace regex [4].
🌐 Web query:
Python email.utils parseaddr getaddresses RFC 2047 encoded words
💡 Result:
email.utils.parseaddr() and email.utils.getaddresses() parse RFC 5322 address syntax into (display_name, addr-spec) tuples, but they do not reliably MIME-decode RFC 2047 “encoded-word” display names for you; if a header contains =?utf-8?Q?...?= / =?utf-8?B?...?=, you typically need to decode the display-name part yourself. [1], [2], [3]
Practical pattern (decode then parse, or parse then decode names)
Option A (often simplest): decode the whole header value first, then call getaddresses:
from email.header import decode_header, make_header
from email.utils import getaddresses
raw = header_value # e.g. msg.get('From') / msg.get('To')
decoded = str(make_header(decode_header(raw)))
pairs = getaddresses([decoded]) # [(realname, emailaddr), ...]decode_header() splits a header into encoded/non-encoded chunks; make_header() reassembles them into a Unicode string. [3]
Option B: parse first, then decode just the display names:
from email.header import decode_header, make_header
from email.utils import getaddresses
pairs = getaddresses([raw_header_value])
pairs = [(str(make_header(decode_header(name))), addr) for name, addr in pairs]This is the common workaround discussed for getaddresses() not decoding encoded-words automatically. [2], [3]
Important RFC 2047 edge case (malformed headers)
RFC 2047 requires encoded-words in “text” contexts to be separated from adjacent text/encoded-words by linear whitespace; some real-world mail violates this, which can break decoding unless you normalize/repair the header first. [4], [5]
Sources: Python email.utils docs (parseaddr/getaddresses) [1], Stack Overflow discussion of getaddresses() + RFC 2047 [2], Python email.header.decode_header() usage [3], RFC 2047 encoded-word rules [5], example of whitespace-separation violation [4].
Use email.utils for RFC 5322-compliant header parsing.
Regex-based parsing will mis-split headers with quoted display names containing commas (e.g., "Doe, John" <john@example.com>), a valid RFC 5322 format. This corrupts peer IDs and thread membership for imported conversations. The suggested fix correctly uses parseaddr() and getaddresses() to handle this.
However, Gmail headers may also contain RFC 2047 encoded-word display names (e.g., =?utf-8?Q?Doe=2C_John?= <john@example.com>), which parseaddr does not automatically decode. To handle both quoted commas and encoded names, decode the header first using email.header.make_header() and decode_header() before calling parseaddr:
from email.header import decode_header, make_header
from email.utils import parseaddr, getaddresses
def extract_email(from_header: str) -> str:
"""Extract bare email from 'Name <email>' format."""
decoded = str(make_header(decode_header(from_header)))
_, email = parseaddr(decoded)
return email.lower().strip()
def extract_name(from_header: str) -> str:
"""Extract display name from 'Name <email>' format."""
decoded = str(make_header(decode_header(from_header)))
name, _ = parseaddr(decoded)
return name.strip() or from_header.strip()
def parse_address_list(header: str) -> list[str]:
"""Parse a comma-separated email header into individual addresses."""
if not header.strip():
return []
decoded = str(make_header(decode_header(header)))
return [
f"{name} <{addr}>" if name else addr
for name, addr in getaddresses([decoded])
if addr.strip()
]🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@examples/gmail/honcho_gmail.py` around lines 123 - 132, The current
regex-based extract_email and extract_name break on valid RFC 5322 headers
(e.g., quoted commas and encoded-word names); replace the regex logic by first
decoding RFC2047 words using email.header.decode_header and make_header and then
parsing addresses with email.utils.parseaddr / getaddresses: in extract_email
decode the header, call parseaddr to get the email, lowercase and strip it; in
extract_name decode the header, call parseaddr to get the display name, strip
and fall back to the original header when empty; also add a helper like
parse_address_list that decodes the header and uses getaddresses to return
cleaned "Name <addr>" or addr entries and handle empty headers gracefully.
There was a problem hiding this comment.
♻️ Duplicate comments (4)
docs/v3/guides/gmail.mdx (1)
609-617:⚠️ Potential issue | 🟡 MinorFix the Card href to use a relative path.
The href is an absolute path (
/v3/documentation/core-concepts/design-patterns), which can cause 404s in Mintlify. Use a relative path from this file's location.Suggested fix
- <Card title="Design Patterns" icon="cubes" href="/v3/documentation/core-concepts/design-patterns"> + <Card title="Design Patterns" icon="cubes" href="../documentation/core-concepts/design-patterns">Based on learnings: In the Honcho documentation site (Mintlify-based), relative paths like
../../guides/...are the correct pattern for cross-references between documentation sections, not absolute paths like/v2/guides/...which can cause 404s.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@docs/v3/guides/gmail.mdx` around lines 609 - 617, The Card linking to the Design Patterns page uses an absolute href "/v3/documentation/core-concepts/design-patterns" which breaks in Mintlify; update the Card component's href (the Card inside the CardGroup) to the correct relative path from this file (e.g., adjust the href value to a relative path that navigates from docs/v3/guides/gmail.mdx to the design-patterns page such as using ../../documentation/core-concepts/design-patterns or the project's correct relative traversal) so the link resolves without 404s.examples/gmail/honcho_gmail.py (3)
282-290:⚠️ Potential issue | 🟡 MinorHandle empty thread payloads in dry-run preview.
get_thread()returns{}on API errors, sofetch_thread_messages()can produce[]. Accessingmsgs[0]will raiseIndexError.Suggested fix
if args.dry_run: print("\n[DRY RUN] Would create the above in Honcho. Showing first message per thread:") for tid, msgs in all_thread_messages.items(): + if not msgs: + print(f" Thread {tid}: (no messages fetched)") + continue m = msgs[0]🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@examples/gmail/honcho_gmail.py` around lines 282 - 290, In the dry-run preview inside the args.dry_run block, avoid indexing into empty message lists returned by fetch_thread_messages()/get_thread(): check that msgs is non-empty before accessing msgs[0]; for empty lists (msgs == [] or falsy), either skip that thread or print a placeholder like "No messages (failed to fetch thread)" along with the thread id; update the loop over all_thread_messages to safely handle and log empty payloads instead of letting msgs[0] raise IndexError.
36-48:⚠️ Potential issue | 🟠 MajorSearch beside the script, not just the current working directory.
The docstring and guide tell users to place
client_secret*.jsonnext to the script, butglob.glob()searches the current working directory. Runningpython examples/gmail/honcho_gmail.pyfrom the repo root will fail even when credentials are correctly placed.Suggested fix
-import glob import os import re import time from datetime import datetime, timezone +from pathlib import Path @@ def find_credentials() -> str: - """Find a Google OAuth credentials file in the current directory.""" - matches = glob.glob("client_secret*.json") - if matches: - return matches[0] + """Find a Google OAuth credentials file near the script or current directory.""" + for directory in (Path(__file__).resolve().parent, Path.cwd()): + matches = sorted(directory.glob("client_secret*.json")) + if matches: + return str(matches[0]) raise FileNotFoundError(🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@examples/gmail/honcho_gmail.py` around lines 36 - 48, The find_credentials function currently uses glob.glob on the process current working directory which fails when the script is run from elsewhere; modify find_credentials to search the script's directory instead by deriving script_dir = os.path.dirname(__file__) (or pathlib.Path(__file__).parent) and call glob.glob(os.path.join(script_dir, "client_secret*.json")) (or Path(script_dir).glob("client_secret*.json")) so the function (find_credentials) finds credentials placed next to honcho_gmail.py regardless of where Python is invoked from; return the first match or raise the existing FileNotFoundError as before.
123-133:⚠️ Potential issue | 🟠 MajorUse
email.utilsfor RFC 5322-compliant header parsing.The regex-based parsing will mis-split headers with quoted display names containing commas (e.g.,
"Doe, John" <john@example.com>") and won't decode RFC 2047 encoded words. This corrupts peer IDs and thread membership.Suggested fix using stdlib
+from email.header import decode_header, make_header +from email.utils import parseaddr, getaddresses def extract_email(from_header: str) -> str: """Extract bare email from 'Name <email>' format.""" - match = re.search(r"<([^>]+)>", from_header) - return match.group(1).lower() if match else from_header.lower().strip() + decoded = str(make_header(decode_header(from_header))) + _, email = parseaddr(decoded) + return email.lower().strip() if email else from_header.lower().strip() def extract_name(from_header: str) -> str: """Extract display name from 'Name <email>' format.""" - match = re.match(r'^"?([^"<]+)"?\s*<', from_header) - return match.group(1).strip() if match else from_header.strip() + decoded = str(make_header(decode_header(from_header))) + name, _ = parseaddr(decoded) + return name.strip() if name else from_header.strip() def parse_address_list(header: str) -> list[str]: """Parse a comma-separated email header into individual addresses.""" if not header.strip(): return [] - parts = re.split(r",(?![^<]*>)", header) - return [p.strip() for p in parts if p.strip()] + decoded = str(make_header(decode_header(header))) + return [ + f"{name} <{addr}>" if name else addr + for name, addr in getaddresses([decoded]) + if addr.strip() + ]Also applies to: 168-173
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@examples/gmail/honcho_gmail.py` around lines 123 - 133, The regex parsing in extract_email and extract_name is brittle and breaks on RFC 5322/RFC 2047 cases (quoted names with commas, encoded-words); replace the regex logic with the stdlib email utilities: use email.utils.parseaddr to split the header into (name, addr) and email.header.decode_header + .join/str conversions to decode RFC2047 encoded display names before stripping; ensure extract_email returns the lowercased addr and extract_name returns the decoded, stripped name (and apply the same change to the other similar functions in this file).
🧹 Nitpick comments (3)
examples/gmail/honcho_gmail.py (2)
77-77: Use explicit| Nonefor optional parameters.PEP 484 prohibits implicit
Optional. The parameters with= Nonedefaults should have explicit union types.Suggested fix
-def list_threads(service, query: str = None, label_ids: list = None, max_results: int = 10) -> list[dict]: +def list_threads(service, query: str | None = None, label_ids: list | None = None, max_results: int = 10) -> list[dict]:🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@examples/gmail/honcho_gmail.py` at line 77, The function signature for list_threads uses parameters with default None but lacks explicit Optional unions; update the annotation to use explicit unions (PEP 484) — e.g., change query: str to query: str | None and label_ids: list to label_ids: list[str] | None (keep max_results: int and return type list[dict] as-is) so the signature of list_threads(service, query: str | None, label_ids: list[str] | None, max_results: int = 10) -> list[dict] correctly reflects optional parameters.
333-334: Consider iterable unpacking for cleaner list construction.This is a minor style improvement suggested by static analysis.
Suggested fix
- msg_addresses = [m["from"]] + parse_address_list(m["to"]) + parse_address_list(m["cc"]) + parse_address_list(m["bcc"]) + msg_addresses = [m["from"], *parse_address_list(m["to"]), *parse_address_list(m["cc"]), *parse_address_list(m["bcc"])]🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@examples/gmail/honcho_gmail.py` around lines 333 - 334, The current construction of msg_addresses and msg_peers can be simplified by using iterable unpacking and a single comprehension; replace the explicit list concat in msg_addresses with unpacking (e.g., (*[m["from"]], *parse_address_list(m["to"]), *parse_address_list(m["cc"]), *parse_address_list(m["bcc"])) or equivalent) and build msg_peers with a single comprehension that iterates over that unpacked iterable while calling extract_email and ensure_peer (referencing msg_addresses, msg_peers, ensure_peer, extract_email, and parse_address_list) so the code is more concise and removes the temporary concatenation style.docs/v3/guides/gmail.mdx (1)
240-608: Note: This embedded script is duplicated fromexamples/gmail/honcho_gmail.py.The issues flagged in the example script review (credential search path, RFC 5322 header parsing, dry-run empty thread handling) apply here as well. When fixing those issues in the example file, ensure this embedded copy is updated to match.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@docs/v3/guides/gmail.mdx` around lines 240 - 608, This embedded script is a duplicate of examples/gmail/honcho_gmail.py and must be updated to mirror the fixes: update find_credentials to search common paths (current dir and parent) and fail with a clear error message; make parse_address_list robust to RFC 5322 formats by using email.utils.getaddresses or more tolerant parsing in parse_address_list; and avoid indexing msgs[0] in the dry-run and session creation paths—handle empty msg lists safely (e.g., guard before accessing msgs[0] and skip/label threads with no messages). Apply these changes to the functions find_credentials, parse_address_list, and the dry-run/session creation blocks so the embedded copy matches the corrected example.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Duplicate comments:
In `@docs/v3/guides/gmail.mdx`:
- Around line 609-617: The Card linking to the Design Patterns page uses an
absolute href "/v3/documentation/core-concepts/design-patterns" which breaks in
Mintlify; update the Card component's href (the Card inside the CardGroup) to
the correct relative path from this file (e.g., adjust the href value to a
relative path that navigates from docs/v3/guides/gmail.mdx to the
design-patterns page such as using
../../documentation/core-concepts/design-patterns or the project's correct
relative traversal) so the link resolves without 404s.
In `@examples/gmail/honcho_gmail.py`:
- Around line 282-290: In the dry-run preview inside the args.dry_run block,
avoid indexing into empty message lists returned by
fetch_thread_messages()/get_thread(): check that msgs is non-empty before
accessing msgs[0]; for empty lists (msgs == [] or falsy), either skip that
thread or print a placeholder like "No messages (failed to fetch thread)" along
with the thread id; update the loop over all_thread_messages to safely handle
and log empty payloads instead of letting msgs[0] raise IndexError.
- Around line 36-48: The find_credentials function currently uses glob.glob on
the process current working directory which fails when the script is run from
elsewhere; modify find_credentials to search the script's directory instead by
deriving script_dir = os.path.dirname(__file__) (or
pathlib.Path(__file__).parent) and call glob.glob(os.path.join(script_dir,
"client_secret*.json")) (or Path(script_dir).glob("client_secret*.json")) so the
function (find_credentials) finds credentials placed next to honcho_gmail.py
regardless of where Python is invoked from; return the first match or raise the
existing FileNotFoundError as before.
- Around line 123-133: The regex parsing in extract_email and extract_name is
brittle and breaks on RFC 5322/RFC 2047 cases (quoted names with commas,
encoded-words); replace the regex logic with the stdlib email utilities: use
email.utils.parseaddr to split the header into (name, addr) and
email.header.decode_header + .join/str conversions to decode RFC2047 encoded
display names before stripping; ensure extract_email returns the lowercased addr
and extract_name returns the decoded, stripped name (and apply the same change
to the other similar functions in this file).
---
Nitpick comments:
In `@docs/v3/guides/gmail.mdx`:
- Around line 240-608: This embedded script is a duplicate of
examples/gmail/honcho_gmail.py and must be updated to mirror the fixes: update
find_credentials to search common paths (current dir and parent) and fail with a
clear error message; make parse_address_list robust to RFC 5322 formats by using
email.utils.getaddresses or more tolerant parsing in parse_address_list; and
avoid indexing msgs[0] in the dry-run and session creation paths—handle empty
msg lists safely (e.g., guard before accessing msgs[0] and skip/label threads
with no messages). Apply these changes to the functions find_credentials,
parse_address_list, and the dry-run/session creation blocks so the embedded copy
matches the corrected example.
In `@examples/gmail/honcho_gmail.py`:
- Line 77: The function signature for list_threads uses parameters with default
None but lacks explicit Optional unions; update the annotation to use explicit
unions (PEP 484) — e.g., change query: str to query: str | None and label_ids:
list to label_ids: list[str] | None (keep max_results: int and return type
list[dict] as-is) so the signature of list_threads(service, query: str | None,
label_ids: list[str] | None, max_results: int = 10) -> list[dict] correctly
reflects optional parameters.
- Around line 333-334: The current construction of msg_addresses and msg_peers
can be simplified by using iterable unpacking and a single comprehension;
replace the explicit list concat in msg_addresses with unpacking (e.g.,
(*[m["from"]], *parse_address_list(m["to"]), *parse_address_list(m["cc"]),
*parse_address_list(m["bcc"])) or equivalent) and build msg_peers with a single
comprehension that iterates over that unpacked iterable while calling
extract_email and ensure_peer (referencing msg_addresses, msg_peers,
ensure_peer, extract_email, and parse_address_list) so the code is more concise
and removes the temporary concatenation style.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 5b19d8ea-9293-435e-a582-677736f345b3
📒 Files selected for processing (4)
docs/v3/documentation/core-concepts/design-patterns.mdxdocs/v3/guides/gmail.mdxdocs/v3/guides/granola.mdxexamples/gmail/honcho_gmail.py
🚧 Files skipped from review as they are similar to previous changes (1)
- docs/v3/guides/granola.mdx
|
|
||
| **Peer join order matters** | ||
|
|
||
| Peers don't have to be added to a session all at once. You can call `session.add_peers()` multiple times as participants appear — and Honcho will take join order into account when reasoning. A peer only has context from the point they joined, not from earlier in the session. |
There was a problem hiding this comment.
A Peer will only "reason" over data it sees or directly observes happen, if it joins a session after the fact it won't then schedule all of the previous messages to be reasoned against
Vibecoding page — Integrator. This is "use Claude Code (or Cursor, etc.) to build a Honcho integration faster."
Guides overview — Adopter. "use Honcho in Claude Code". Needs to feel like a menu of "here's how to use Honcho in X" rather than developer docs
Summary by CodeRabbit