Watch Contacts
SkillDev toolsCheck what watched contacts are posting on X, surface engagement opportunities. Reads Profiles/Contacts.md for handles.
Available today. Use it from your connected AI after setup.
No other account needed.
Connect ahel once, and every AI you use reads what you have installed.
Then ask your AI: use the Watch Contacts skill
What this skill tells your AI
The instructions your AI receives, as published by jonathanmalkin/jules in .claude/skills/watch-contacts/SKILL.md and read by ahel’s review.
Monitor X/Twitter activity from contacts in the network. Surface posts worth engaging with, batch reply decisions via AskUserQuestion.
Step 1: Load Contacts
Read Profiles/Contacts.md. Extract X/Twitter handles from ALL sections:
- "Active — Following Up" table (personal contacts)
- "X Watch List" tables (public figures, product leaders)
For each handle, also check the Filter column. If a filter string exists, that handle requires keyword-filtered search (see Step 2).
Build a handle list. Skip contacts with no X handle. Report how many handles found.
If no handles found, say so and stop.
Step 1.5: Determine Floor Timestamp (Dedup Across Runs)
Before searching contacts, check whether this skill already ran earlier today:
xurl search "from:[your-handle]" -n 20
Look at the reply timestamps. If there's a cluster of replies today (indicating an earlier watch-contacts session or manual engagement), use the end of the most recent cluster as the floor timestamp. Only surface tweets posted AFTER this floor.
- If no earlier activity today → floor = 24 hours ago (default behavior).
- If earlier cluster found → floor = timestamp of last reply in that cluster.
- Report: "Last engagement session ended ~Xh ago. Filtering to tweets posted after [time]."
If the floor is very recent (< 2 hours ago), warn: "Ran recently — there may be few new posts." and proceed anyway.
Step 2: Search Recent Posts
For each handle, search for the 3 most recent tweets:
# Standard (no filter):
xurl search "from:{handle}" -n 10
# Filtered (high-volume posters — use separate queries, not OR):
xurl search "from:{handle} claude" -n 10
xurl search "from:{handle} openclaw" -n 10
xurl search "from:{handle} agent" -n 10
# Deduplicate by tweet ID across filter queries.
Filter syntax note: The X search API does not scope OR to the from: clause. from:X foo OR bar returns (from:X foo) OR (bar globally). For filtered handles, run one search per keyword and deduplicate results by tweet ID.
From each handle's results, keep only the 3 most recent tweets.
Floor timestamp cutoff: Discard any tweet older than the floor timestamp from Step 1.5. If a handle has no tweets after the floor, skip it entirely.
Run searches in parallel where possible.
Step 3: Filter and Rank
From all remaining tweets (max 3 per handle, last 24h only), identify posts worth engaging with. Prioritize:
- Topic overlap — AI agents, Claude Code, solo founder, building in public, Applied AI, open source
- Engagement opportunity — questions asked, opinions shared, event announcements
- Recency — newer is better
- Relationship signal — replying to someone you want to build a relationship with carries more weight than replying to a stranger
Skip: retweets without commentary, promotional/spam, posts with 0 relevance to [Your Name]'s work, short replies to other people with no standalone value.
Step 4: Present and Batch Approve
Present results in batches of up to 4 using AskUserQuestion (tool limit: 4 questions max). Each batch contains up to 4 questions, one question per post. Each question is a single yes/no decision for one reply.
Question format:
- question: Formatted with all decision context. Use this layout (newlines in the string):
@handle | https://x.com/handle/status/tweet_id | Xh ago | N replies | N likes | N imp "[full tweet text, truncated at ~200 chars if needed]" Draft: [reply text] Post this reply? - header:
@handle(max 12 chars — truncate handle if needed) - options:
[{"label": "Post it", "description": "Reply from @[your-handle]"}, {"label": "Skip", "description": "Don't reply to this one"}] - multiSelect: false
Context fields: Calculate hours since posted from created_at. Use reply_count, like_count, impression_count from public_metrics. Build the tweet link from https://x.com/{handle}/status/{tweet_id}.
[Your Name] decides each reply independently:
- "Post it" → Jules posts the draft via
xurl reply. - "Skip" or question skipped → do nothing, move on. [Your Name] may have clicked the link and posted directly — verification catches this.
Also classify each surfaced post internally as one of:
reply nowsave for laterarticle seednot worth touching
Only present reply now items in the approval batch. Keep the others in the verbal summary.
If more than 4 actionable posts, present multiple batches sequentially (4 questions per batch).
If nothing worth engaging with: "Nothing actionable in the last 24h. [N] contacts checked, [M] posts scanned."
Step 5: Post Approved Replies
For each "Post it" reply, check reply eligibility first:
xurl "/2/tweets/{tweet_id}?tweet.fields=reply_settings,conversation_id,author_id,created_at,public_metrics"
If reply_settings indicates the post is effectively public or otherwise open to reply, proceed.
Do not assume "not tagged" means blocked.
For each eligible "Post it" reply, attempt to post:
xurl reply [tweet_id] "[reply text]"
If xurl reply fails despite passing the eligibility check, fall back to copying the draft to clipboard and providing the link. One at a time — copy draft, show link, wait for [Your Name] to say "next" before copying the next one.
printf '%s' "draft reply text" | pbcopy
Step 6: Verify All Posts
After ALL batches are complete, verify every post that was presented (both approved and skipped). For each original tweet, check whether a reply from @[your-handle] exists:
xurl search "from:[your-handle] to:handle" -n 5
Report results in a summary table:
| Post | Status | Reply |
|---|---|---|
| @handle — [short tweet ref] | Posted by Jules / Posted directly / No reply | [first ~80 chars of reply if found] |
Voice sample capture: For any reply [Your Name] posted directly (i.e., NOT posted by Jules but a reply from @[your-handle] exists on that tweet), read the full reply text and append it as a new sample to Profiles/Voice-Samples-Raw.md. Use this format:
### X Reply — @handle ([date])
[full reply text]
These direct replies are authentic voice samples — [Your Name] wrote them manually, making them high-quality calibration data.
After verification: "Want to add or remove anyone from the watch list? I'll update Profiles/Contacts.md."
Draft Reply Voice
[Your Name]'s voice — practitioner, not fan. Specific, not generic. References real experience building with AI agents, Claude Code, or shipping products. Never sycophantic. Short (1-3 sentences max).
Notes
- This skill only covers X/Twitter. LinkedIn monitoring requires manual review (API doesn't support feed reading).
- To add someone to the watch list: add their X handle to
Profiles/Contacts.md. - Replies are posted from @[your-handle] unless [Your Name] specifies otherwise.
- Treat
@[your-handle]as the authority account.@builtwithjulesis for tagged or assistant-perspective replies, not default relationship building.
Signals
- GitHub stars
- 65
- Forks
- 9
- Last commit
- May 2026
Advanced
- Catalog kind
- skill
- Gateway key
watch-contacts- Source
- github.com/jonathanmalkin/jules