mirror of
https://github.com/prdlk/jobyssey.git
synced 2026-11-02 13:12:49 +00:00
116 lines
5.9 KiB
Markdown
116 lines
5.9 KiB
Markdown
# /gather-recent-context - Sync Job-Email Context into Twenty CRM Notes
|
|
|
|
You are scanning the user's Fastmail inbox for conversations about **active job loops** (interview invites, assessments, scheduling, offers, recruiter follow-ups) and syncing each conversation's context onto its matching Opportunity in Twenty CRM as a **Note**. The mailbox is read-only to this command; Twenty receives classified summaries, never raw email bodies or attachments.
|
|
|
|
Two halves, cleanly split:
|
|
|
|
- **You** (this command) own the judgment: finding relevant threads via the **Fastmail MCP** tools and classifying each one into a structured signal.
|
|
- **`tools/gather_recent_context.py`** owns everything deterministic: matching threads to active opportunities, building the note markdown, deduplicating by thread id (`job_scraper/context_sync.json`), and applying via Twenty REST (`TWENTY_API_KEY`) - or emitting a plan you apply through the Twenty MCP session.
|
|
|
|
Follow these steps **in order**.
|
|
|
|
---
|
|
|
|
## Step 0: Parse Input
|
|
|
|
`$ARGUMENTS` may contain:
|
|
|
|
- Nothing → default lookback: 14 days
|
|
- `since <YYYY-MM-DD>` → override the lookback start
|
|
- A company name → scope the scan to that one company's threads
|
|
|
|
---
|
|
|
|
## Step 1: Preflight (both MCP servers)
|
|
|
|
Silently optional, same bar as `/sync-twenty`: a missing prerequisite ends in one clear message and a clean exit.
|
|
|
|
1. **Fastmail MCP** must be available (tools like `search_email`, `read_thread`). If not, say so and stop.
|
|
2. **Twenty** must be reachable one way or the other: `TWENTY_API_KEY` in the environment (script applies via REST) **or** the Twenty MCP session (you apply the plan). Neither → one line and stop.
|
|
3. Never initiate an OAuth flow from this command.
|
|
|
|
---
|
|
|
|
## Step 2: Find Candidate Threads (Fastmail MCP)
|
|
|
|
1. Build the search set from the CRM's **active loops**: fetch opportunities with stage in `RANKED / DRAFTED / APPLIED / INTERVIEW / OFFER` (via `execute_tool` → `find_many_opportunities`, selecting `id`, `name`, `stage`, `companyId`) and the company names via `find_many_companies` (`id`, `name`). Opportunity names are role-only; the employer comes from the `companyId` relation.
|
|
2. `search_email` with the lookback from Step 0 - query for recruiting/job signals (subject or sender mentioning the tracked companies, plus generic signals like "interview", "assessment", "application", "offer", "recruiter"). Prefer several narrow searches over one broad one; skip newsletters and job-board digests (LinkedIn/Indeed notification blasts are noise, not conversations).
|
|
3. `read_thread` on each candidate to see the whole conversation before classifying.
|
|
|
|
---
|
|
|
|
## Step 3: Classify into the Contract
|
|
|
|
For each relevant thread, produce one entry - this is the exact input contract of the script:
|
|
|
|
```json
|
|
{"threads": [{
|
|
"thread_id": "<Fastmail thread id>",
|
|
"company": "<employer name as the email identifies it>",
|
|
"role": "<role title if the thread names one, else null>",
|
|
"date": "YYYY-MM-DD",
|
|
"from": "<counterparty address>",
|
|
"subject": "<subject line>",
|
|
"summary": "<1-3 sentences: what happened and what's next>",
|
|
"signal": "interview | assessment | offer | rejection | scheduling | other"
|
|
}]}
|
|
```
|
|
|
|
Rules: the summary is your words, not a quote; no email bodies, links, or attachments are copied; a thread you cannot confidently tie to a job conversation is dropped, not guessed. Write the JSON to a temp file (e.g. `/tmp/gather-emails.json`).
|
|
|
|
---
|
|
|
|
## Step 4: Run the Script
|
|
|
|
```bash
|
|
python3 tools/gather_recent_context.py --emails /tmp/gather-emails.json # plan only
|
|
python3 tools/gather_recent_context.py --emails /tmp/gather-emails.json --apply # with TWENTY_API_KEY
|
|
```
|
|
|
|
The script matches threads to active opportunities (normalized company name, role-token tiebreak), groups them into one note per opportunity titled `Email Context — <Company> (YYYY-MM-DD)`, skips every thread id already in `job_scraper/context_sync.json`, and writes `job_scraper/context_plan.json`.
|
|
|
|
- **With `--apply`** (REST key available): notes and note-targets are created and the dedup state is committed. Done - go to Step 6.
|
|
- **Without a key:** the plan file is your work order - go to Step 5.
|
|
|
|
---
|
|
|
|
## Step 5: Apply the Plan via Twenty MCP (keyless path)
|
|
|
|
For each entry in the plan's `notes` array:
|
|
|
|
1. `create_many_notes` with `{position: "last", title, bodyV2: {markdown}}` (batches of ≤ 20).
|
|
2. `create_many_note_targets` linking each returned note id to its `opportunity_id` via `targetOpportunityId`.
|
|
3. After **all** notes land, commit the dedup state:
|
|
```bash
|
|
python3 tools/gather_recent_context.py --commit-plan job_scraper/context_plan.json
|
|
```
|
|
Never commit state for notes that failed to create - the uncommitted thread ids simply reappear next run.
|
|
|
|
---
|
|
|
|
## Step 6: Report
|
|
|
|
```
|
|
## Recent Context Sync - YYYY-MM-DD
|
|
|
|
Scanned <N> threads (lookback: <D> days) · <M> classified as job conversations
|
|
Notes created: <K> (one per opportunity) · Unmatched: <U> · Already processed: <P>
|
|
|
|
| Opportunity | Signal(s) | Threads |
|
|
|-------------|-----------|---------|
|
|
| Backend Engineer — Acme | interview, scheduling | 2 |
|
|
```
|
|
|
|
List unmatched threads (company + subject) so the user can add the opportunity or correct the company name; they are not state-committed and will re-surface next run.
|
|
|
|
---
|
|
|
|
## Important Rules
|
|
|
|
1. **Mailbox read-only.** Never label, archive, delete, or send from this command.
|
|
2. **Summaries only.** Raw email bodies, quoted text, links, and attachments never reach Twenty.
|
|
3. **Active loops only.** Threads about companies with no active-stage opportunity go to `unmatched`, never to a closed loop's record.
|
|
4. **Idempotent by thread id.** `job_scraper/context_sync.json` (personal state, gitignored) guarantees a re-run never duplicates a note.
|
|
5. **Notes are append-only context.** This command creates new notes; it never edits or deletes existing ones - including the `Fit Analysis` notes owned by `/sync-twenty`.
|
|
6. **Never guess a match.** An ambiguous thread is reported, not synced.
|