Skip to content
Back to skills

2698 2026 02 25 Feat X Handle Resolution Plan A3d0bfc0

ASecurity

When a user searches for a person, brand, or creator (e.g., "Dor Brothers", "Jason Calacanis"), the skill should automatically resolve their X handle and search their posts directly. Currently, Phase 1 only finds posts that *mention* the topic keywords, and Phase 2 only drills into handles that appeared in Phase 1 results. This misses the entity's own posts entirely when they don't literally include the topic string.

  • 9 stars
  • 0 votes
  • 0 copies
  • 0 views
  • Added October 11, 2026
developmentpythongobashdockerapibackend

Works with

  • claude code
  • cli
  • api

Security analysis

A100/100

Scanned October 11, 2026

npx -y skills add tools-only/X-Skills --skill 2698-2026-02-25-feat-x-handle-resolution-plan_a3d0bfc0 --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of 2698 2026 02 25 Feat X Handle Resolution Plan A3d0bfc0?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for 2698 2026 02 25 Feat X Handle Resolution Plan A3d0bfc0
[![Security: A β€” Skills Directory](https://www.skillsdirectory.com/api/skills/tools-only-2698-2026-02-25-feat-x-handle-resolution-plan-a3d0/badge)](https://www.skillsdirectory.com/skills/tools-only-2698-2026-02-25-feat-x-handle-resolution-plan-a3d0)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

SKILL.md
---
title: "feat: Resolve X handles for topic entities"
type: feat
status: completed
date: 2026-02-25
---

# feat: Resolve X handles for topic entities

## Overview

When a user searches for a person, brand, or creator (e.g., "Dor Brothers", "Jason Calacanis"), the skill should automatically resolve their X handle and search their posts directly. Currently, Phase 1 only finds posts that *mention* the topic keywords, and Phase 2 only drills into handles that appeared in Phase 1 results. This misses the entity's own posts entirely when they don't literally include the topic string.

## Problem Statement

**The Dor Brothers example:** Searching "Dor Brothers" found 30 X posts with 149 likes - all posts *about* them. But the Dor Brothers' own account posts constantly about their AI films, tools, and collabs. Those posts wouldn't appear in Phase 1 because the Dor Brothers don't write "Dor Brothers" in every tweet. Phase 2 can't help because entity_extract only finds handles already @mentioned in Phase 1 results.

**The Jason Calacanis example:** His handle is @jason - completely unguessable from his name. No amount of keyword searching or @mention extraction would discover it. You need an actual lookup.

**What's missing:** A handle resolution step that maps `"topic name" -> @handle`, then searches that handle's recent posts directly (without requiring topic keywords in the tweet text).

## User Base Constraints

- **80% Claude Code** - agent is Claude, has WebSearch tool
- **20% OpenClaw** - agent has web search capability
- **Most users have:** OPENAI_API_KEY
- **Some users have:** XAI_API_KEY, Bird (browser cookies)
- **Few users have:** BRAVE_API_KEY, PARALLEL_API_KEY, OPENROUTER_API_KEY
- **Everyone has:** Claude (it's the runtime)

**Critical constraint:** The Python script's web search backends (`brave_search.py`, `parallel_search.py`, `openrouter_search.py`) ALL filter out x.com URLs via `EXCLUDED_DOMAINS`. Building handle resolution inside the Python script would require bypassing this filter, and most users don't have the API keys for those backends anyway.

## Proposed Solution

Move handle resolution to the **SKILL.md agent layer**. The agent (Claude or OpenClaw) already has `WebSearch` as an allowed tool. It does a single WebSearch before running the Python script, extracts the handle, and passes it as a CLI argument.

### Architecture

```
SKILL.md Agent Flow (revised):

1. Parse intent (existing)
2. NEW: If topic looks like a person/brand, WebSearch("{topic} X twitter handle")
3. NEW: Extract @handle from results (agent intelligence, not regex)
4. Run script: python3 last30days.py "Dor Brothers" --x-handle=TheDorBrothers --emit=compact
5. Script uses --x-handle in Phase 2: from:TheDorBrothers (no topic filter)
6. WebSearch supplementary (existing Step 2)
7. Synthesize (existing)
```

### Why agent-level, not Python-level

| Approach | Works for | Problem |
|----------|-----------|---------|
| Brave API in Python | Users with BRAVE_API_KEY (~5%) | Almost nobody has the key |
| OpenAI Responses API in Python | Users with OPENAI_API_KEY (~90%) | Costs money, adds complexity, x.com URLs may be filtered |
| Agent WebSearch (SKILL.md) | **100% of users** | Adds ~5-8 sec before script |
| xAI API in Python | Users with XAI_API_KEY (~30%) | Not universal |

The agent approach wins because:
1. **100% availability** - Claude/OpenClaw always has WebSearch
2. **Zero API key requirements** - WebSearch is built into the agent runtime
3. **Smarter parsing** - Claude is better at "find the X handle for Dor Brothers" than any regex
4. **Simpler implementation** - SKILL.md instruction + `--x-handle` CLI arg, no new Python module
5. **No EXCLUDED_DOMAINS problem** - agent WebSearch is independent of the Python web search backends

The ~5-8 second latency is negligible against the 2-3 minute script runtime.

### SKILL.md Changes (Claude Code variant)

Add between intent parsing and Step 1:

```markdown
## Step 0.5: Resolve X Handle (if topic is a person/brand)

If TOPIC looks like a person, creator, brand, or specific account (1-3 words, proper noun),
do ONE WebSearch to find their X handle:

WebSearch("{TOPIC} X twitter handle")

From the results, extract the X/Twitter handle. Look for:
- Profile URLs: x.com/{handle} or twitter.com/{handle}
- Mentions like "@handle" in bios, articles, or social profiles
- "Follow @handle on X" patterns

If you find a clear, unambiguous handle, pass it to the script:
  --x-handle={handle}

If ambiguous or not found, omit the flag. The script works fine without it.

Skip this step if:
- TOPIC is clearly not an entity (e.g., "best rap songs 2026", "how to use Docker")
- TOPIC already contains @ (e.g., "@elonmusk")
- Using --quick depth
```

### OpenClaw Variant Changes

Same instruction added to `variants/open/references/research.md` (or inline in the open SKILL.md routing). OpenClaw agents also have WebSearch available.

### Python Script Changes

**`last30days.py` - Add `--x-handle` argument:**

```python
parser.add_argument('--x-handle', type=str, default=None,
                    help='Resolved X handle for topic entity (without @)')
```

Pass `resolved_handle` to `_run_supplemental()`.

**`_run_supplemental()` - Accept and use resolved handle:**

```python
def _run_supplemental(
    topic, reddit_items, x_items, from_date, to_date,
    depth, x_source, progress=None, skip_reddit=False,
    resolved_handle=None,  # NEW
):
    # Extract entities from Phase 1 (existing)
    entities = entity_extract.extract_entities(...)

    # Add resolved handle if not already in entity list
    if resolved_handle and resolved_handle.lower() not in {h.lower() for h in entities["x_handles"]}:
        # Search resolved handle separately - unfiltered (no topic keywords)
        # This is the key difference from entity-extracted handles
        resolved_future = executor.submit(
            bird_x.search_handles,
            [resolved_handle],
            None,  # topic=None means unfiltered search
            from_date,
            count_per=10,
        )
```

**`bird_x.search_handles()` - Optional topic parameter:**

```python
def search_handles(handles, topic, from_date, count_per=5):
    # topic is now Optional[str]
    for handle in handles:
        handle = handle.lstrip("@")
        if topic:
            core_topic = _extract_core_subject(topic)
            query = f"from:{handle} {core_topic} since:{from_date}"
        else:
            # Unfiltered: get all recent posts from this handle
            query = f"from:{handle} since:{from_date}"
```

### Why no topic filter for resolved handles

This is the key insight. When you resolve that @DorBrothers IS the Dor Brothers, you want ALL their recent posts - not just ones that literally contain "Dor Brothers." Their post about the Logan Paul collab says "our new AI film with @LoganPaul" - no mention of "Dor Brothers" anywhere. With topic filtering, you'd miss it. Without it, you get their full recent activity, which is exactly what the user wants.

## Technical Considerations

### Handle resolution requires Bird or xAI for Phase 2

The `--x-handle` is only useful if the script can search `from:{handle}`. Currently:
- **Bird:** supports `from:handle` via Twitter GraphQL (free)
- **xAI:** does NOT support `from:handle` (Grok semantic search only)

If the user only has xAI (no Bird), the resolved handle can't be searched in Phase 2. Options:
1. Skip Phase 2 handle search when `x_source == "xai"` (current behavior for entity handles too)
2. Future: Add handle search to xai_x.py using `allowed_x_handles` filter

For v1, accept this limitation. Bird is free and most X-enabled users have it.

### Relevance scoring for unfiltered handle posts

Bird-parsed items default to `relevance: 0.7`. Unfiltered resolved-handle posts have no topic-keyword signal. Set `relevance: 0.5` for these so engagement and recency drive ranking, preventing off-topic viral posts from the entity from outranking genuinely relevant Phase 1 results.

### Stats block display

When `--x-handle` is used and produces results, show it in the stats:

```
β”œβ”€ πŸ”΅ X: 38 posts β”‚ 782+ likes β”‚ 36+ reposts β”‚ via @TheDorBrothers + keyword search
```

Add `resolved_x_handle` field to `Report` schema for this.

### Skip conditions for the agent

The SKILL.md instruction tells the agent to skip handle resolution when:
- Topic is clearly not an entity (multi-word generic phrases)
- Topic already contains @ (user provided the handle)
- Using `--quick` depth
- Agent judges it would be wasted effort

The agent's judgment here is a feature, not a bug. Claude is good at deciding "Dor Brothers = probably has an X account" vs "best rap songs 2026 = definitely not an entity."

## Acceptance Criteria

- [x] SKILL.md updated with Step 0.5 handle resolution instructions
- [x] OpenClaw variant updated with same instructions
- [x] `last30days.py` accepts `--x-handle` argument
- [x] `_run_supplemental()` accepts `resolved_handle` parameter
- [x] `bird_x.search_handles()` accepts `topic=None` for unfiltered search
- [x] Resolved handle searched with `from:{handle}` (no topic filter) when Bird is available
- [x] `Report` schema includes `resolved_x_handle` field
- [x] Stats block shows resolved handle when used
- [x] Graceful when `--x-handle` is provided but Bird is not available (skips silently)
- [ ] "Dor Brothers" search resolves handle and finds their direct posts (requires live test)
- [ ] "Jason Calacanis" search resolves @jason (requires live test)
- [ ] "best rap songs 2026" does NOT trigger handle resolution (agent judgment, not code)

## Test Plan

### Manual test cases

| # | Query | Expected | What it tests |
|---|-------|----------|---------------|
| 1 | "Dor Brothers" | Agent resolves handle, passes --x-handle, script finds their posts | Full happy path |
| 2 | "Jason Calacanis" | Agent resolves @jason (non-obvious handle) | Handle != name |
| 3 | "best rap songs 2026" | Agent skips handle resolution entirely | Non-entity detection |
| 4 | "OpenAI" | Agent may or may not resolve (judgment call) | Generic entity edge case |
| 5 | "Linus Ekenstam" | Agent resolves @LinusEkenstam | Person with matching handle |
| 6 | "Dor Brothers" with `--quick` | No handle resolution | Skip condition |
| 7 | "Dor Brothers" with xAI only (no Bird) | Handle resolved but Phase 2 skips it | Graceful degradation |

### Automated tests (`tests/`)

```python
# test_bird_x.py - new tests
def test_search_handles_unfiltered_mode():
    """bird_x.search_handles(topic=None) omits topic keywords from query."""

def test_search_handles_with_topic():
    """bird_x.search_handles(topic="AI films") includes topic in query (existing behavior)."""

# test_last30days.py - integration
def test_x_handle_arg_parsed():
    """--x-handle=TheDorBrothers is parsed and passed to _run_supplemental."""

def test_resolved_handle_dedup_with_entity_extract():
    """Resolved handle already in entity list is not double-searched."""

def test_resolved_handle_skipped_when_xai_only():
    """When x_source='xai', resolved handle is not searched (no from: support)."""

def test_resolved_handle_relevance_set_lower():
    """Items from resolved handle search get relevance 0.5, not 0.7."""
```

### E2E validation

```bash
# Run with explicit handle to test Python-side changes
python3 scripts/last30days.py "Dor Brothers" --x-handle=TheDorBrothers --emit=compact 2>&1 | grep -E "\[Phase|handle"

# Expected:
# [Phase 2] Drilling into @TheDorBrothers (resolved) + @handle1, @handle2 (extracted)
# [Phase 2] +0 Reddit, +8 X (5 from resolved handle)
```

```bash
# Full agent test (runs SKILL.md flow including handle resolution WebSearch)
claude --print "/last30days Dor Brothers" 2>&1 | grep -i "handle\|x-handle\|resolved"
```

## Files Modified

| File | Change |
|---|---|
| `SKILL.md` | Add Step 0.5: handle resolution via WebSearch |
| `variants/open/references/research.md` | Same handle resolution instructions for OpenClaw |
| `scripts/last30days.py` | Add `--x-handle` CLI arg, pass to `_run_supplemental()` |
| `scripts/lib/bird_x.py` | `search_handles()` gets `topic: Optional[str]` param |
| `scripts/lib/schema.py` | Add `resolved_x_handle: Optional[str]` to `Report` |
| `scripts/lib/render.py` | Show resolved handle in stats block |
| `tests/test_bird_x.py` | Tests for unfiltered search_handles mode |
| `tests/test_last30days.py` | Tests for --x-handle arg handling |

**NOT modified:** No new `handle_resolve.py` module. Resolution is agent intelligence, not Python code.

## Dependencies & Risks

- **Bird availability:** Resolved handle can only be searched when Bird is the X source. xAI doesn't support `from:handle` queries. ~70% of X-enabled users have Bird (free, browser cookies).
- **Agent judgment:** The agent decides whether a topic is an entity worth resolving. Claude is good at this, but it's non-deterministic. Some edge cases will be missed. This is acceptable - the feature is additive (no regression when it doesn't fire).
- **WebSearch latency:** Adds ~5-8 seconds before the script starts. Negligible against 2-3 minute script runtime.
- **Handle accuracy:** The agent could resolve to the wrong handle. Mitigated by the agent's ability to evaluate results (unlike a regex, Claude can tell if a result actually belongs to the queried entity).

## Sources

- Existing entity extraction: `scripts/lib/entity_extract.py`
- Phase 2 supplemental search: `scripts/last30days.py:387-513`
- Bird search handles: `scripts/lib/bird_x.py:273-346`
- SKILL.md agent flow: `SKILL.md:82-120`
- OpenClaw variant: `variants/open/SKILL.md`
- Smart supplemental search plan: `docs/plans/2026-02-07-feat-smart-supplemental-search-plan.md`

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.