Skip to content

[recipes] Gmail smart pull — sensitivity routing + contact entities + atomize fixes - #20

Closed
alanshurafa wants to merge 313 commits into
mainfrom
contrib/alanshurafa/gmail-smart-pull
Closed

[recipes] Gmail smart pull — sensitivity routing + contact entities + atomize fixes#20
alanshurafa wants to merge 313 commits into
mainfrom
contrib/alanshurafa/gmail-smart-pull

Conversation

@alanshurafa

Copy link
Copy Markdown
Owner

Summary

Adds recipes/gmail-smart-pull/ — a Gmail puller that emits an ingest-ready pack with local sensitivity routing, engagement filtering, contact-based relationship tiers, and LLM atomization of long messages. Ports the EXO-0129 through EXO-0137 work from Alan's ExoCortex second brain into a generalized OB1 recipe.

Complements the existing recipes/email-history-import/ (one-email-one-thought onboarding). This recipe is for users whose mailbox is big enough that they need careful filtering, routing, and splitting before ingest.

What's ported

  • Core puller (scripts/pull-gmail.mjs): read-only Gmail API fetch, quoted-reply + signature stripping, auto-generated-noise filter, engagement gate (threads where you've replied), RFC 2822 threading headers captured at source, structured correspondents parsed once at pull time.
  • Local sensitivity detection (scripts/lib/sensitivity.mjs): two pattern sets — restricted (SSN, passport, bank, API keys, passwords, credit cards) and personal (email/phone/health/financial) — tag-only, no enforcement.
  • Relationship tier: contact / known / unknown, metadata-only (does not gate routing), driven by a JSON contacts cache you can produce from a CRM schema, the Google Contacts API, or a vCard export.
  • LLM atomization (scripts/lib/atomize-text.mjs): long messages (>= 150 words default) split into atomic thoughts; providers anthropic / openrouter / claude-cli / codex; graceful fallback to whole-message on failure.
  • RFC 2822 header parser (scripts/lib/entity-resolver.mjs): pure parsing only — the pack carries { name, email } arrays so a downstream job can upsert correspondents as first-class entities.
  • Two idempotent migrations (sql/): merge_thought_metadata RPC for targeted metadata backfills, and entities.canonical_email column + indexes so the correspondents the pack carries can be upserted as entities. Both CREATE OR REPLACE / IF NOT EXISTS.

Atomize fixes included

scripts/lib/atomize-text.mjs carries two fixes that surfaced during real-world use:

  1. Multi-line prompts now pipe via stdin instead of the -p command-line flag. Under Windows shell:true, cmd.exe mangled multi-line prompts containing quotes/newlines so the child received a truncated string and the LLM replied conversationally ("Looks like your message got cut off..."). Same fix applied to the codex provider.
  2. A new codex provider shells out to codex exec so users orchestrating from a Codex session can atomize without crossing streams with a nested claude-cli (which fails nested-process detection).

[!NOTE] Coordination with candidate #1 (Atomizer)
These fixes may overlap with #1 Atomizer (recipes/atomizer/), which ports the separate re-atomization/audit batch scripts (re-atomize-gmail-thought.mjs, atomize-packs.mjs, etc.). Both branches reference the same underlying atomize-text.mjs library. If #1 ships first with its own copy of scripts/lib/atomize-text.mjs, this recipe should be updated to import from the shared location during final review. I've kept a local copy here so this recipe is self-contained and can merge in either order.

Dependencies on other candidates

What this recipe does NOT do

  • It does not ingest into Supabase itself — it produces a pack file. Your ingest pipeline consumes it. The separation keeps the recipe portable across Open Brain deployments.
  • It does not enforce a sensitivity routing policy. The pack records carry sensitivity + sensitiveReasons; your ingest pipeline decides what to do with restricted / personal atoms. The README spells this out explicitly because OB1 is cloud-first and "restricted stays local" isn't automatic.
  • It does not ship a contacts-export step. Different deployments have different authoritative sources; the README documents three options.

Pre-review status

This is the fork PR. Not pushing upstream yet — waiting on cross-AI review (gsd-code-reviewer + codex exec) per Alan's OB1 PR protocol before opening the upstream PR to NateBJones-Projects/OB1.

Test plan

  • node --check passes on all four JS files (pull-gmail.mjs, atomize-text.mjs, entity-resolver.mjs, sensitivity.mjs) — verified locally
  • metadata.json parses as valid JSON — verified locally
  • Whole-repo markdownlint-cli2 error count stays at 57 (baseline on origin/main; this branch adds 0 new errors) — verified locally
  • No OAuth credentials, real email addresses, or personal data embedded anywhere — verified by inspection
  • Gmail scope scoped to gmail.readonly only — verified in SCOPES constant
  • Migrations are idempotent (CREATE OR REPLACE, IF NOT EXISTS) and contain no DROP TABLE, TRUNCATE, or unqualified DELETE FROM — verified by inspection
  • Smoke test the full OAuth flow + dry-run on a small STARRED window against a test Gmail account
  • Smoke test atomization with provider=anthropic on a >= 150-word synthetic email

🤖 Generated with Claude Code

alanshurafa and others added 28 commits April 18, 2026 14:14
…test_path + traverse_graph)

The previous recursive-CTE implementations enumerated every path to every
reachable node, which exploded on densely connected graphs (a hub with 1k+
neighbours at depth 2 produced tens of thousands of rows) and depended on
the per-path ANY(path) check to break cycles. On a cyclic graph that
exceeded the statement_timeout the planner never actually pruned the walk.

Replace both functions with iterative plpgsql BFS:

- Global seen-set (UUID[]) so each node is visited at most once
- JSONB parent-pointer map records the first (parent, relation) that
  reached each node; BFS's "first discovery wins" invariant is enforced
  with DISTINCT ON (next_id)
- Shared reconstruct_bfs_path() helper walks the parent map end -> start
  with a safety guard against malformed maps
- find_shortest_path keeps bidirectional edge traversal; traverse_graph
  keeps outgoing-only with optional relationship_type filter
- Signatures, argument order, RETURNS shape, and language (plpgsql, no
  SECURITY DEFINER) match the original so this is a body-only rewrite
- All queries scope by p_user_id to preserve the multi-tenant isolation
  the edge function relies on (service_role bypasses RLS)

Update the README's "How It Works" section and function table to describe
the new implementation so the docs don't contradict the SQL.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Standalone Node script that exercises traverse_graph and find_shortest_path
against a live Supabase project with ob-graph installed. Picks an arbitrary
edge, calls both RPCs at depth 1/2/6, and prints row counts + timings so
maintainers can confirm the iterative-BFS rewrite stays inside the
statement_timeout and returns sensible shapes before shipping.

Reads SUPABASE_PROJECT_REF, SUPABASE_SERVICE_ROLE_KEY, and OB_GRAPH_USER_ID
from recipes/ob-graph/.env.local so the service-role key never lands in
repo history. Sets process.exitCode=1 on any RPC failure so CI or shell
chaining picks up a non-zero exit.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…gth === 2 to protect bidirectional shortest-path
Auto-run: markdownlint-cli2 --fix --config .github/.markdownlint.jsonc
Manual: 2 MD028 in life-engine/README.md (HTML comment separator)

Brings `markdown-lint` CI check from 55 pre-existing errors to 0.

Files:
- recipes/adaptive-capture-classification/README.md
- recipes/adaptive-capture-classification/classifier_prompt.md
- recipes/life-engine/README.md
- recipes/life-engine/life-engine-skill.md
- recipes/ob-graph/README.md (same fix as PR NateBJones-Projects#210; harmless duplicate)
- recipes/obsidian-vault-import/README.md
- recipes/vercel-neon-telegram/README.md
- schemas/workflow-status/README.md

No content changes. Pure formatting: blank lines around fences/headings,
HTML-comment separators between adjacent blockquotes, list-indent/
numbering normalization.

Precedent: PR NateBJones-Projects#161 did the same for 15 files.
…from thoughts

Adds a new recipe that ships two Node scripts for wiki-style synthesis over
the core `thoughts` table, plus optional Next.js dashboard snippets.

- `scripts/synthesize-wiki.mjs` — topic-scoped synthesizer with a built-in
  `autobiography` mode that groups thoughts by year and asks an
  OpenAI-compatible Chat Completions endpoint to produce second-person
  biographical prose per year. Extend the catalogue to add more topics.
- `scripts/backfill-gmail-wikis.mjs` — resume-safe per-thread wiki
  generator for Gmail-imported thoughts. Groups by `metadata.gmail.thread_id`,
  filters by word-count + message/atom thresholds, writes wiki thoughts
  with `derived_from` edges to their source atoms. Prefers an
  `upsert_thought` RPC when present, falls back to plain inserts.
- `dashboard-snippets/` — optional Next.js components (Server Action +
  `/wiki` index + `/wiki/[slug]` detail) to copy into a dashboard.

Generalized from ExoCortex origin: replaced the MCP edge-function client
with direct PostgREST + service role, swapped the Anthropic-direct call
for OpenAI-compatible Chat Completions, removed personal data references,
introduced SUBJECT_NAME and SOURCE_TYPE_FILTER env knobs. Complements the
in-flight entity-wiki recipe — this one does corpus-slice synthesis and
only requires the core `thoughts` table.

README documents the thought_edges / upsert_thought prerequisites so users
know which OB1 layers they need before running the email-thread pipeline.
@github-actions github-actions Bot added dashboard documentation Improvements or additions to documentation labels Apr 21, 2026
justfinethanku and others added 28 commits June 7, 2026 20:46
…lanshurafa/smart-ingest

[integrations] Smart ingest Edge Function
…wicegood/dashboard-open-next-cloudflare

[dashboards] open-brain-dashboard-next: add Cloudflare Workers deploy support
…lanshurafa/enhanced-thoughts

[schemas] Enhanced thoughts columns and utility RPCs
…lanshurafa/brain-backup

[recipes] Brain backup and export
…lanshurafa/brain-health-monitoring

[recipes] Brain health monitoring views
…ns1002/per-request-mcp

[integrations] Fix per-request McpServer instantiation for stable MCP connections
…xcfi-scott/delete-thought-mcp

[integrations] delete_thought MCP
…er/typed-reasoning-edges-comment-syntax-fix

[schemas] Fix typed-reasoning-edges COMMENT ON syntax error
…lanshurafa/auto-capture-claude-code

[skills] Auto-capture Claude Code adapter
@alanshurafa

Copy link
Copy Markdown
Owner Author

Closing as redundant — merged upstream as NateBJones-Projects#220. The fork's main carries it once synced from upstream.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.