AI-powered Telegram job scanner that discovers, deduplicates, filters, classifies, and matches job vacancies against a candidate profile using OpenAI and local LLMs.
Go
1
3 commits
updated Aug 3, 2026
Social Job Scanner is an AI-powered job discovery and candidate-matching platform. The current version searches for software engineering vacancies published in Telegram channels.
The application imports a list of Telegram channels, downloads messages from the previous 30 days, and analyzes their relevance using a configurable candidate profile. The profile is currently defined as a developer-focused prompt, but the matching system can be adapted for other professions and requirements.
During the initial channel analysis, OpenAI classifies detected job postings and ranks each Telegram channel as suitable or unsuitable for continued monitoring.
Approved channels are then monitored automatically. New messages pass through a multi-stage processing pipeline:
A lightweight web interface allows users to:
The project is currently focused on Telegram, with an architecture intended to support additional job sources such as LinkedIn, job boards, RSS feeds, and company career pages.
This repository is intended to run directly on a local machine. It does not use Docker, Compose, or a reverse proxy.
ui/package.json)llama-server binaryiac/download_models.sh downloads the currently expected modelTo build llama.cpp for the host, run:
./iac/llama_cpp.sh
By default, that installs llama-server under ~/.local/bin.
The default build is CPU-only. On a CPU-only binary, TD_GPU_LAYERS
(-ngl) is silently ignored and large models load entirely on the CPU — which
can look like a hang. Building the CUDA backend requires the CUDA toolkit
(nvcc), not just the driver:
nvcc --version # if this fails, install the toolkit first,
# e.g. `sudo apt install nvidia-cuda-toolkit`
CUDA=on ./iac/llama_cpp.sh # `auto` (the default) also builds CUDA when nvcc is found
The script prints the visible GPU devices after installing. Verify your card is
listed, then enable offload in .env:
TD_GPU_LAYERS=99 # layers to offload; 99 = all, 0 = CPU
TD_LLAMA_MMPROJ_PATH=/path/to/mmproj # optional; only for vision models
llama-server --list-devices # confirm the GPU is detected
VRAM budget. Model weights + the F16 mmproj (if set) + the KV cache must
fit in VRAM. If the model fails to load on the GPU, drop TD_LLAMA_MMPROJ_PATH
(job classification is text-only), use a smaller/lower-quant model, or lower
TD_GPU_LAYERS. As a reference, a 9B Q5 model with a 32k context is tight on a
12 GB card.
Copy the example configuration and replace every placeholder:
cp .env.example .env
The most important local paths are:
TD_DATA_PATH=/absolute/path/to/socialJobScanner/data
TD_LLAMA_SERVER_PATH=/absolute/path/to/llama-server
TD_LLAMA_MODEL_PATH=/absolute/path/to/model.gguf
TD_SOURCES_FILE=/absolute/path/to/sources.md
The backend loads variables with the TD_ prefix. The local runner sources
.env before starting the backend. TD_SOURCES_FILE is required only by the
estimate-chats command.
The final-stage OpenAI evaluator (finalStage.md) scores each vacancy against
your candidate profile. Create candidate_profile.json in the data
directory ($TD_DATA_PATH/candidate_profile.json) before starting the backend —
the analyzer loads it at startup and fails fast if it is missing or not valid
JSON. It lives under data/ (git-ignored) so your personal details never enter
the repository.
The content is free-form JSON injected verbatim into the prompt; use whatever keys describe you. A minimal example:
{
"seniority": ["senior", "lead"],
"role_types": ["backend", "full_stack", "platform"],
"years_experience": "8+",
"primary_technologies": ["Go", "Python"],
"additional_technologies": ["TypeScript", "Rust"],
"databases": ["PostgreSQL", "ClickHouse"],
"specialties": ["distributed systems", "backend APIs", "ETL"],
"domains": ["fintech", "logistics"],
"location": "Remote (EU)",
"remote_experience": true,
"languages": { "English": "professional working proficiency" }
}
Editing the file takes effect on the next backend start — it is read at runtime, not embedded, so no rebuild is needed.
Backend dependencies are managed by Go modules. Install UI dependencies once:
pnpm --dir ui install --frozen-lockfile
Start the API, MCP web-page service, managed Chrome process, and UI together:
./run-local.sh
Open http://127.0.0.1:8080. Vite proxies /api requests to the Go API at
http://127.0.0.1:9092, so no TLS or CORS setup is required.
The runner stops all child processes when any one of them exits or when you
press Ctrl+C.
On first run, when a login code is needed the service logs:
stdin is not a terminal; waiting for telegram auth code in file "<DataPath>/telegram_auth_code" ...
Supply the code once Telegram sends it to your account:
echo 12345 > <DataPath>/telegram_auth_code
# or export TELEGRAM_AUTH_CODE=12345 before starting the service
After the first success the DB-backed session storage persists the session, so no code is needed on subsequent runs.
With .env exported into the current shell, start only the backend:
go -C services run ./socials/cmd serve
Start only the UI:
pnpm --dir ui run dev
The analyzer prompts live in services/socials/internal/analyzer/prompts/ and
are embedded into the backend binary at build time (go:embed), so editing a
prompt requires a rebuild. Each drives a different stage of the pipeline:
| File | Model | Used by | Purpose |
|---|---|---|---|
estimateJob.md | local llama-server | per-message prescreen (EstimateMessage) | Fast, high-precision filter: decides whether a single message is a relevant software-engineering vacancy and emits priority, reason, matched_stack, detected_role, detected_level, is_spam. |
estimateJob.gbnf | local llama-server | same | GBNF grammar that constrains the above output to complete, enum-valid JSON (this build's json_schema response format is broken, so the grammar enforces the shape). |
finalStage.md | OpenAI Responses API | final pass (EstimateJob) | Richer second pass on high-priority messages: compares one vacancy against the full candidate profile and returns a scored JobMatch. |
channelQualityEvaluator.md | local llama-server | channel rating (EstimateChannel) | Extracts every unique concrete vacancy from a sample of a channel's messages, feeding the channel-quality verdict (the Load Channels / estimate-chats flow). |
The candidate profile and the accepted technologies/levels/work-types are
substituted into finalStage.md and channelQualityEvaluator.md at runtime
(see prompts.go); the {{...}} placeholders in the files are filled before the
prompt is sent.
go -C services build -buildvcs=false ./...
go -C services test -buildvcs=false ./...
pnpm --dir ui run build
Released under the MIT License.
3 commits
Go
66.5%
JavaScript
24.5%
Vue
6.4%
Shell
1.6%
AI-powered Telegram job scanner that discovers, deduplicates, filters, classifies, and matches job vacancies against a candidate profile using OpenAI and local LLMs.
Go
1
3 commits
updated Aug 3, 2026
Social Job Scanner is an AI-powered job discovery and candidate-matching platform. The current version searches for software engineering vacancies published in Telegram channels.
The application imports a list of Telegram channels, downloads messages from the previous 30 days, and analyzes their relevance using a configurable candidate profile. The profile is currently defined as a developer-focused prompt, but the matching system can be adapted for other professions and requirements.
During the initial channel analysis, OpenAI classifies detected job postings and ranks each Telegram channel as suitable or unsuitable for continued monitoring.
Approved channels are then monitored automatically. New messages pass through a multi-stage processing pipeline:
A lightweight web interface allows users to:
The project is currently focused on Telegram, with an architecture intended to support additional job sources such as LinkedIn, job boards, RSS feeds, and company career pages.
This repository is intended to run directly on a local machine. It does not use Docker, Compose, or a reverse proxy.
ui/package.json)llama-server binaryiac/download_models.sh downloads the currently expected modelTo build llama.cpp for the host, run:
./iac/llama_cpp.sh
By default, that installs llama-server under ~/.local/bin.
The default build is CPU-only. On a CPU-only binary, TD_GPU_LAYERS
(-ngl) is silently ignored and large models load entirely on the CPU — which
can look like a hang. Building the CUDA backend requires the CUDA toolkit
(nvcc), not just the driver:
nvcc --version # if this fails, install the toolkit first,
# e.g. `sudo apt install nvidia-cuda-toolkit`
CUDA=on ./iac/llama_cpp.sh # `auto` (the default) also builds CUDA when nvcc is found
The script prints the visible GPU devices after installing. Verify your card is
listed, then enable offload in .env:
TD_GPU_LAYERS=99 # layers to offload; 99 = all, 0 = CPU
TD_LLAMA_MMPROJ_PATH=/path/to/mmproj # optional; only for vision models
llama-server --list-devices # confirm the GPU is detected
VRAM budget. Model weights + the F16 mmproj (if set) + the KV cache must
fit in VRAM. If the model fails to load on the GPU, drop TD_LLAMA_MMPROJ_PATH
(job classification is text-only), use a smaller/lower-quant model, or lower
TD_GPU_LAYERS. As a reference, a 9B Q5 model with a 32k context is tight on a
12 GB card.
Copy the example configuration and replace every placeholder:
cp .env.example .env
The most important local paths are:
TD_DATA_PATH=/absolute/path/to/socialJobScanner/data
TD_LLAMA_SERVER_PATH=/absolute/path/to/llama-server
TD_LLAMA_MODEL_PATH=/absolute/path/to/model.gguf
TD_SOURCES_FILE=/absolute/path/to/sources.md
The backend loads variables with the TD_ prefix. The local runner sources
.env before starting the backend. TD_SOURCES_FILE is required only by the
estimate-chats command.
The final-stage OpenAI evaluator (finalStage.md) scores each vacancy against
your candidate profile. Create candidate_profile.json in the data
directory ($TD_DATA_PATH/candidate_profile.json) before starting the backend —
the analyzer loads it at startup and fails fast if it is missing or not valid
JSON. It lives under data/ (git-ignored) so your personal details never enter
the repository.
The content is free-form JSON injected verbatim into the prompt; use whatever keys describe you. A minimal example:
{
"seniority": ["senior", "lead"],
"role_types": ["backend", "full_stack", "platform"],
"years_experience": "8+",
"primary_technologies": ["Go", "Python"],
"additional_technologies": ["TypeScript", "Rust"],
"databases": ["PostgreSQL", "ClickHouse"],
"specialties": ["distributed systems", "backend APIs", "ETL"],
"domains": ["fintech", "logistics"],
"location": "Remote (EU)",
"remote_experience": true,
"languages": { "English": "professional working proficiency" }
}
Editing the file takes effect on the next backend start — it is read at runtime, not embedded, so no rebuild is needed.
Backend dependencies are managed by Go modules. Install UI dependencies once:
pnpm --dir ui install --frozen-lockfile
Start the API, MCP web-page service, managed Chrome process, and UI together:
./run-local.sh
Open http://127.0.0.1:8080. Vite proxies /api requests to the Go API at
http://127.0.0.1:9092, so no TLS or CORS setup is required.
The runner stops all child processes when any one of them exits or when you
press Ctrl+C.
On first run, when a login code is needed the service logs:
stdin is not a terminal; waiting for telegram auth code in file "<DataPath>/telegram_auth_code" ...
Supply the code once Telegram sends it to your account:
echo 12345 > <DataPath>/telegram_auth_code
# or export TELEGRAM_AUTH_CODE=12345 before starting the service
After the first success the DB-backed session storage persists the session, so no code is needed on subsequent runs.
With .env exported into the current shell, start only the backend:
go -C services run ./socials/cmd serve
Start only the UI:
pnpm --dir ui run dev
The analyzer prompts live in services/socials/internal/analyzer/prompts/ and
are embedded into the backend binary at build time (go:embed), so editing a
prompt requires a rebuild. Each drives a different stage of the pipeline:
| File | Model | Used by | Purpose |
|---|---|---|---|
estimateJob.md | local llama-server | per-message prescreen (EstimateMessage) | Fast, high-precision filter: decides whether a single message is a relevant software-engineering vacancy and emits priority, reason, matched_stack, detected_role, detected_level, is_spam. |
estimateJob.gbnf | local llama-server | same | GBNF grammar that constrains the above output to complete, enum-valid JSON (this build's json_schema response format is broken, so the grammar enforces the shape). |
finalStage.md | OpenAI Responses API | final pass (EstimateJob) | Richer second pass on high-priority messages: compares one vacancy against the full candidate profile and returns a scored JobMatch. |
channelQualityEvaluator.md | local llama-server | channel rating (EstimateChannel) | Extracts every unique concrete vacancy from a sample of a channel's messages, feeding the channel-quality verdict (the Load Channels / estimate-chats flow). |
The candidate profile and the accepted technologies/levels/work-types are
substituted into finalStage.md and channelQualityEvaluator.md at runtime
(see prompts.go); the {{...}} placeholders in the files are filled before the
prompt is sent.
go -C services build -buildvcs=false ./...
go -C services test -buildvcs=false ./...
pnpm --dir ui run build
Released under the MIT License.
3 commits
Go
66.5%
JavaScript
24.5%
Vue
6.4%
Shell
1.6%