Xrary is an automated agent that searches X (Twitter) for Software Development Engineer (SDE) internship hiring posts, intelligently judges them using OpenAI's GPT-4o-mini, filters out duplicates, and sends real-time WhatsApp alerts via Twilio.
- X (Twitter) Search: Reverse-engineered GraphQL client to search tweets without official API limits
- Intelligent Filtering: Two-stage filtering system:
- Cheap Filter: Fast keyword-based pre-filtering (hiring, internship, apply now, etc.)
- Smart Filter: LLM-powered classification with confidence scoring
- Duplicate Detection: SQLite-backed deduplication to avoid alert spam
- WhatsApp Alerts: Sends formatted hiring notifications via Twilio
- Scheduled Polling: Configurable interval-based polling (default: 10 minutes)
- Comprehensive Logging: JSON-structured logs with ISO timestamps (stdout + rotating file)
- Production-Ready: Graceful shutdown, error handling, containerized deployment
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β Xrary Hiring Agent β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β APScheduler (Background Task Scheduler) β
β ββ Runs agent cycle every N minutes β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β LangGraph Workflow (5-Node Pipeline) β
β ββ [Search Node] β XReverseClient (GraphQL search) β
β ββ [Filter Node] β Cheap keyword filter + dedup β
β ββ [Judge Node] β LLMClient (GPT-4o-mini classification) β
β ββ [Notify Node] β WhatsAppClient (Twilio) β
β ββ [Sleep Node] β Record last_run timestamp β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β Data Layer β
β ββ SQLAlchemy ORM (SeenTweet, AgentState models) β
β ββ SQLite Database (data/hiring_agent.db) β
β ββ LangGraph Checkpoint (data/hiring_agent_graph.sqlite) β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β External APIs β
β ββ X (Twitter) GraphQL SearchTimeline endpoint β
β ββ OpenAI API (GPT-4o-mini, structured output) β
β ββ Twilio WhatsApp API β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
START
β
[SEARCH] β Fetch tweets matching query
β
[FILTER] β Keyword-based filtering + deduplication
β
[JUDGE] β LLM classification (is_hiring? confidence > 0.7?)
β
[NOTIFY] β Send WhatsApp alerts + mark seen
β
[SLEEP] β Record timestamp for next cycle
β
END
Error Handling: Each node catches exceptions, logs errors, increments error counter, and continues (no crash).
- Async GraphQL client for X/Twitter search
- Reverse-engineered SearchTimeline endpoint
- Nested response parsing (data β search_by_raw_query β timeline β entries β tweets)
- Built-in retry logic (3 attempts, exponential backoff)
- Error classes:
XRateLimitError,XAuthError,XGraphQLError
- Async OpenAI client using GPT-4o-mini
- Structured output parsing with Pydantic validation
- JudgeResult model:
is_hiring,confidence [0-1],company,role,location,apply_link - Confidence override: If confidence < 0.7, forces
is_hiring=False(conservative) - Graceful fallback to JSON mode if structured output unavailable
- Twilio WhatsApp integration
- Message formatting with WhatsApp markdown (bold, italic)
- Returns message SID for tracking
cheap_filter(tweet) β boolfunction- Keywords: HIRING_KEYWORDS, INTERN_KEYWORDS, SPAM_KEYWORDS
- Rules:
- Text length > 20 chars
- Must contain hiring OR intern keyword
- No spam keywords (crypto, nft, airdrop, etc.)
- Not a retweet
- Author handle not spam-tagged
- SQLAlchemy-backed deduplication
is_seen(tweet_id)β boolmark_seen(tweet, judge_result, notified)β persists to DB
format_hiring_alert(tweet, judge_result)β WhatsApp markdown string- 400-char truncation with metadata (company, role, location, confidence, timestamp)
- Pydantic Settings loading from
.env - Required fields: OpenAI API key, Twilio credentials, X auth cookies, etc.
- Validation: WhatsApp numbers must start with "whatsapp:" prefix
- SQLAlchemy ORM with SQLite backend
- Models:
SeenTweet: tweet_id (unique), text, author_handle, detected_at, notified, confidence_score, company, roleAgentState: last_run, notifications_sent, error_count, updated_at
- Structlog JSON output to stdout
- Rotating file handler to
logs/hiring_agent.log(10MB max, 5 backups) - ISO timestamps, context binding
- Tenacity-based retry with exponential backoff
- Retries on:
httpx.HTTPStatusError,httpx.ConnectError,TimeoutError - 3 attempts, 2-30 second backoff range
- Thread-safe fixed-window rate limiter
RateLimiter(max_requests, window_seconds)acquire() β boolandwait_time() β float
Xrary/
βββ src/ # Main source code
β βββ __init__.py
β βββ config.py # Pydantic Settings (env loading)
β βββ main.py # Entry point (APScheduler, signal handling)
β βββ agents/
β β βββ __init__.py
β β βββ hiring_agent.py # LangGraph pipeline orchestration
β β βββ nodes/
β β βββ __init__.py
β β βββ search_node.py # Search tweets
β β βββ filter_node.py # Cheap filter + dedup
β β βββ judge_node.py # LLM classification
β β βββ notify_node.py # WhatsApp alerts
β βββ clients/
β β βββ __init__.py
β β βββ x_reverse_client.py # X GraphQL client
β β βββ llm_client.py # OpenAI GPT-4o-mini client
β β βββ whatsapp_client.py # Twilio WhatsApp client
β βββ services/
β β βββ __init__.py
β β βββ tweet_filter.py # Cheap keyword filtering
β β βββ deduplicator.py # SQLAlchemy-backed dedup
β β βββ message_formatter.py # WhatsApp message formatting
β βββ storage/
β β βββ __init__.py
β β βββ database.py # SQLAlchemy engine, session factory
β β βββ models.py # ORM models (SeenTweet, AgentState)
β β βββ migrations/ # Alembic migrations (future)
β βββ utils/
β βββ __init__.py
β βββ logging_config.py # Structlog configuration
β βββ retry.py # Tenacity retry decorator
β βββ rate_limiter.py # Thread-safe rate limiter
β
βββ scripts/
β βββ extract_x_auth.py # Extract X cookies from browser/manual input
β βββ test_search.py # Smoke test for XReverseClient
β βββ test_whatsapp.py # Smoke test for WhatsAppClient
β
βββ tests/
β βββ __init__.py
β βββ conftest.py # Pytest fixtures
β βββ test_agent.py # HiringAgent tests
β βββ test_filter.py # Tweet filtering tests
β βββ test_x_client.py # XReverseClient tests
β
βββ ops/
β βββ Dockerfile # Python 3.11-slim, non-root user
β βββ docker-compose.yml # Orchestration config
β βββ systemd/
β βββ sde-agent.service # Systemd service definition
β
βββ pyproject.toml # Build system, project metadata, pytest config
βββ requirements.txt # Pip dependencies (generate from pyproject.toml)
βββ README.md # This file
βββ .env.example # Environment variable template
βββ data/ # SQLite databases (git-ignored)
βββ hiring_agent.db # Main database
βββ hiring_agent_graph.sqlite # LangGraph checkpoint storage
- Python 3.11+
- X (Twitter) account with auth cookies
- OpenAI API key
- Twilio WhatsApp sandbox setup
-
Clone and setup:
git clone https://github.com/Nirvanjha2004/Xrary.git cd Xrary python -m venv venv source venv/bin/activate # Windows: venv\Scripts\activate pip install -r requirements.txt
-
Extract X auth cookies:
python scripts/extract_x_auth.py --browser chrome --output .env # Or manually: python scripts/extract_x_auth.py --browser manual -
Configure environment (
.env):OPENAI_API_KEY=sk-... OPENAI_MODEL=gpt-4o-mini TWILIO_ACCOUNT_SID=ACxxx TWILIO_AUTH_TOKEN=xxx TWILIO_WHATSAPP_FROM=whatsapp:+1234567890 TWILIO_WHATSAPP_TO=whatsapp:+0987654321 X_AUTH_TOKEN=xxx X_CT0=xxx X_SEARCH_QUERY=SDE intern hiring apply join us POLL_INTERVAL_MINUTES=10 LOG_LEVEL=INFO
-
Test clients:
# Test X search python scripts/test_search.py # Test WhatsApp python scripts/test_whatsapp.py
-
Run the agent:
python -m src.main
Expected output (JSON logs to stdout):
{"event": "initializing_hiring_agent_service", "timestamp": "2026-05-01T12:00:00Z"} {"event": "database_initialized", "timestamp": "2026-05-01T12:00:01Z"} {"event": "scheduler_started", "timestamp": "2026-05-01T12:00:02Z"}
# Build image
docker build -t hiring-agent:latest -f ops/Dockerfile .
# Run container
docker run -d \
--name hiring-agent \
--env-file .env \
-v ./data:/app/data \
-v ./logs:/app/logs \
hiring-agent:latest
# View logs
docker logs -f hiring-agentcd ops
docker-compose up -d
# View logs
docker-compose logs -f hiring-agent
# Stop
docker-compose downsudo cp ops/systemd/sde-agent.service /etc/systemd/system/
sudo systemctl daemon-reload
sudo systemctl enable sde-agent
sudo systemctl start sde-agent
sudo journalctl -u sde-agent -fThe agent tracks state via LangGraph checkpoints:
AgentState:
tweets: list[Tweet] # Raw tweets from X
filtered_tweets: list[Tweet] # After cheap filter + dedup
judged_tweets: list[tuple[Tweet, JudgeResult]] # High-confidence hiring posts
notifications_sent: int # Count of WhatsApp alerts
error_count: int # Cumulative errors
last_run: str | None # ISO timestamp of last cycleDatabase persistence (SeenTweet):
SeenTweet:
tweet_id: str (unique)
tweet_text: str
author_handle: str
detected_at: datetime
notified: bool
confidence_score: float | None
company: str | None
role: str | None| Variable | Type | Default | Description |
|---|---|---|---|
OPENAI_API_KEY |
str | β Required | OpenAI API key |
OPENAI_MODEL |
str | gpt-4o-mini |
LLM model to use |
TWILIO_ACCOUNT_SID |
str | β Required | Twilio account SID |
TWILIO_AUTH_TOKEN |
str | β Required | Twilio auth token |
TWILIO_WHATSAPP_FROM |
str | β Required | Format: whatsapp:+... |
TWILIO_WHATSAPP_TO |
str | β Required | Format: whatsapp:+... |
X_AUTH_TOKEN |
str | β Required | X session cookie |
X_CT0 |
str | β Required | X CSRF token |
X_SEARCH_QUERY |
str | SDE intern hiring... |
X search query |
POLL_INTERVAL_MINUTES |
int | 10 |
Polling interval |
SQLITE_DB_PATH |
str | data/hiring_agent.db |
Database path |
LOG_LEVEL |
str | INFO |
Logging level |
pytest tests/ -vtest_agent.py: HiringAgent workflow orchestrationtest_filter.py: cheap_filter logic and edge casestest_x_client.py: XReverseClient parsing and errors
# Test X search
python scripts/test_search.py
# Test WhatsApp (sends real message)
python scripts/test_whatsapp.pyLogs are JSON-structured for easy parsing:
{
"event": "hiring_agent_cycle_complete",
"notifications_sent": 3,
"error_count": 0,
"last_run": "2026-05-01T12:10:00Z",
"timestamp": "2026-05-01T12:10:05Z"
}Key metrics to monitor:
notifications_sent: Count of WhatsApp alerts per cycleerror_count: Cumulative errors (retry exhaustion, API failures)tweet_count(search node): Raw tweets fetchedfiltered_count(filter node): After cheap filter + dedupjudged_count(judge node): High-confidence posts
- Email Alerts: Add Gmail/SendGrid integration alongside WhatsApp
- Web Dashboard: Flask/FastAPI UI for viewing alerts, stats, logs
- Advanced Filtering: Location-aware filtering, role-specific keywords (full-stack vs backend vs ML)
- Webhook Support: POST alerts to external services (Slack, Discord, custom endpoints)
- Database UI: sqlite-web optional container for browsing dedup store
- Batch Notifications: Daily digest instead of real-time alerts
- User Subscriptions: Multi-user support with role/location preferences
- LLM Fine-tuning: Retrain GPT-4o-mini on verified hiring posts
- Database Migrations: Alembic schema versioning and migration management
- CI/CD Pipeline: GitHub Actions for testing, linting, image builds
- Multi-Platform Search: LinkedIn, Indeed, company career pages (web scraping)
- Feedback Loop: User ratings on alerts (relevant? spam?) for model improvement
- Auto-Apply: Integration with application APIs (if available)
- Analytics: BI dashboard (hiring trends, company activity, seasonal patterns)
- Mobile App: Native iOS/Android app for alerts and dashboard
| Component | Technology | Purpose |
|---|---|---|
| Scheduling | APScheduler | Background polling task |
| Workflow | LangGraph | DAG-based pipeline orchestration |
| Search | httpx (async) | X GraphQL queries |
| LLM | OpenAI API | Hiring classification |
| Notifications | Twilio | WhatsApp delivery |
| Database | SQLAlchemy 2.0 + SQLite | ORM and persistence |
| Validation | Pydantic v2 | Data schema + config |
| Logging | structlog | JSON structured logs |
| Retry Logic | tenacity | Resilient API calls |
| Testing | pytest | Unit tests |
| Containerization | Docker | Reproducible deployment |
- Credentials: Store
.envsecurely (use secrets manager in production) - Rate Limiting: X API may throttle; built-in retry handles transient failures
- LLM Costs: Monitor OpenAI usage; each judgment costs ~0.001 USD
- Non-root User: Docker runs as
appuser(not root) for security - Logging: Avoid logging PII or sensitive data
MIT License - See LICENSE file for details.
Contributions welcome! Please:
- Fork the repository
- Create a feature branch (
git checkout -b feature/my-feature) - Commit changes (
git commit -m "Add my feature") - Push to branch (
git push origin feature/my-feature) - Open a Pull Request
Found a bug? Have a feature request? Open an issue on GitHub:
Built with β€οΈ by Nirvanjha2004