A measuring instrument for the agentic/AI market — not a news aggregator, not a GitHub scraper. It's a research platform for accumulating, validating, and evolving structured knowledge about shifts in the AI/MCP/LLM ecosystem: collecting signals from GitHub, HN, Reddit, and AwesomeLists, filtering noise, clustering patterns against external analyst opinions, publishing to Telegram, and rendering an interactive knowledge graph.
This is a personal research instrument built for the author's own analysis — not a growth or audience product.
Live example: @radar_public
Interactive graph: mikkiola.github.io/radar
Architecture: docs/ARCHITECTURE.md — why this pattern works as a measuring instrument
radar/
├── src/ pipeline scripts (see Scripts below)
├── tests/ pytest suite (pythonpath = src, see pyproject.toml)
├── docs/ mkdocs source + canonical docs (ARCHITECTURE/
│ ROADMAP/BACKLOG/CONSTITUTION.md, adr/)
├── scripts/ verify.py, generate_adr_index.py
├── .github/workflows/ CI/CD pipeline (see CI/CD below)
├── .tooltempest.lock pinned ToolTempest commit
├── SPEC.md active /spec target (idle unless a session is in progress)
├── mkdocs.yml Pages build config
├── pyproject.toml pytest config
├── requirements-dev.txt pytest, for the test job
├── requirements-security.txt consolidated runtime+dev deps, for pip-audit
├── requirements_pages.txt mkdocs + mkdocs-material, for the pages job
└── LICENSE MIT
The vault branch (data, separate git history) has its own structure
— see Vault structure below.
GitHub / HN / Reddit / AwesomeLists
↓
radar_step0.py collect projects
↓
filter.py keyword filter by topic
↓
analyze.py Claude: SHIFT or NOISE
↓
01_Assessments/ only SHIFT, URL deduplication
↓
patterns.py Claude clusters assessments into patterns
|
→ fetch_analysts.py external analysts (Builder Radar, ...)
| 04_Analysts/ structured claims from external sources
↓
02_Patterns/ active patterns with confirmation / divergence notes
↓
telegram_post.py post to channel twice a day
↓
GitHub Pages interactive graph of connections
Vault lives in the vault branch of the same repository. Obsidian reads it as a local vault. The graph is built from [[wikilinks]] in MD files and published automatically on every push.
Layer 0 → Sources GitHub / HN / Reddit / AwesomeLists
Layer 1 → Signals repositories, articles, posts
Layer 2 → Assessment SHIFT / NOISE (analyze.py via Haiku)
Layer 3 → Patterns signal clusters (patterns.py via Sonnet)
Layer 4 → Meta our patterns + ExternalAnalyst[] + Forecasts (planned, not implemented)
Layer 4 adds external analysts as a separate input to pattern clustering. patterns.py receives both our assessments and structured claims from external sources, then looks for:
- where opinions align — signal confirmed
- our unique signal — we see it, analysts do not
- external-only signal — analysts see it, we do not
Personal research assistant — swap filter keywords and prompts, get a radar for any domain: biotech, policy, legal, VC deals.
Competitive intelligence — replace GitHub/HN with internal sources (Confluence, Jira, Slack via MCP). Track competitor moves and cluster them into behavioral patterns.
Self-updating knowledge base — the vault is a living Obsidian graph. Patterns connect via [[wikilinks]], the graph builds automatically. A human only reviews assessments in the "Human edit" block.
Falsifiable hypothesis tracker — patterns.py checks each pattern after 6 months: CONFIRMED / REFUTED / TOO_EARLY. Same falsification applies to external analyst claims. Built-in self-correction, not just data accumulation.
Multi-analyst intelligence layer — external analysts plug in via config. Builder Radar today, Simon Willison or Latent Space tomorrow. Each analyst carries a trust weight that affects pattern confirmation scoring when multiple analysts are active.
Public site from a private vault — vault branch → GitHub Pages → public interactive graph. Built with a 60-line custom script, no external graph dependencies.
AI-powered newsletter template — telegram_post.py generates a post from vault assessments via Claude. Change the data source and prompt — get an automated digest for any topic, any channel.
| Script | What it does | Model | Runs via |
|---|---|---|---|
src/analyze.py |
SHIFT/NOISE evaluation, URL dedup | Haiku | radar job, daily |
src/update_assessments.py |
Re-evaluate assessments older than 30 days | Haiku | radar job, daily |
src/radar_step0.py |
Collect: HN + Reddit + GitHub (new + hot) + AwesomeLists | — | helper module, imported by analyze.py |
src/filter.py |
Topic filter (AI / MCP / LLM / automation) + traction check | — | helper module, imported by analyze.py |
src/scorecard.py |
OpenSSF Scorecard lookup, feeds the traction filter | — | helper module, imported by filter.py |
src/vault_write.py |
Frontmatter/vault-file write helpers | — | helper module, imported by most scripts below |
src/vault_language.py |
Detect assessment/pattern body language | — | helper module, imported by patterns.py, update_assessments.py |
src/check_frontmatter.py |
Validate frontmatter status/state values before push | — | pre-push guard in radar, confirm_candidate, promote_candidates, recheck_lifecycle, analysts, check_models, patterns |
src/confirm_candidate.py |
Human-in-the-loop confirm/reject of a CANDIDATE repo |
— | confirm_candidate job, on demand ($CONFIRM_REPO) |
src/promote_candidates.py |
Promote quarantined candidates after 14 days | — | promote_candidates job, daily |
src/recheck_lifecycle.py |
Re-check VALIDATED_SHIFT lifecycle (frozen 6mo / releases stopped 12mo) |
— | recheck_lifecycle job, daily |
src/fetch_analysts.py |
Parse external analysts, extract claims, save to 04_Analysts/ |
Haiku | analysts job, every Friday |
src/check_model_updates.py |
Check for new Claude model releases vs model_config.json |
— | check_models job, every Friday |
src/patterns.py |
Clustering + archiving + falsification + external analyst input | Sonnet | patterns job, every Friday |
src/telegram_post.py |
Generate post and publish to channel | Sonnet | publish job, twice daily |
src/generate_graph.py |
Build graph.json from wikilinks |
— | pages job, on Pages build |
src/generate_indexes.py |
Generate index.md for vault sections |
— | pages job, on Pages build |
src/backfill_frontmatter.py |
One-off frontmatter migration | — | manual only, not CI-invoked |
Repository: github.com/mikkiola/radar, branches main (scripts) and vault (data). CI runs via GitHub Actions workflows in .github/workflows/: security, test, daily-run, monthly-lifecycle, weekly-patterns, publish, lint-vault, confirm-candidate, pages.
| Workflow file | Job(s) | Schedule (UTC) | Manual trigger |
|---|---|---|---|
security.yml |
security_secrets, security_deps |
daily 22:00 | yes |
test.yml |
test |
— (push to main) |
no |
daily-run.yml |
radar, promote_candidates, recheck_lifecycle |
daily 22:00 | yes (with lifecycle_only/promote_only inputs) |
monthly-lifecycle.yml |
recheck_lifecycle |
1st of month, 17:00 | yes |
weekly-patterns.yml |
analysts, check_models, patterns |
Thursdays 22:00 | yes |
publish.yml |
publish |
daily 02:00 and 14:00 | yes |
lint-vault.yml |
lint_vault |
daily 22:00 | yes |
confirm-candidate.yml |
confirm_candidate |
— | yes (requires confirm_repo/confirm_decision inputs) |
pages.yml |
build, deploy |
— (push to main or vault) |
yes |
Schedules were converted from the old GitLab pipeline's Asia/Bangkok cadence to UTC; exact GitHub Actions run times may vary by a few minutes under platform load (documented GitHub behavior, not a defect).
patterns waits on analysts and check_models (needs: in weekly-patterns.yml), so both finish before it runs.
Stored as GitHub Actions repository secrets (Settings → Secrets and variables → Actions).
| Variable | What |
|---|---|
ANTHROPIC_API_KEY |
Anthropic API key |
GH_READ_TOKEN |
GitHub API token for signal collection (read-only) |
GH_VAULT_PUSH_TOKEN |
GitHub token for pushing to the vault branch |
TELEGRAM_BOT_TOKEN |
Telegram bot token |
TELEGRAM_CHANNEL_ID |
Channel ID or username (@radar_public) |
TELEGRAM_OWNER_ID |
Owner Telegram ID for notifications |
vault branch/
├── 00_Inbox/ new projects for manual review
├── 01_Assessments/ SHIFT assessments (created by analyze.py)
├── 02_Patterns/ active patterns (created by patterns.py)
├── 03_Archive/ dormant and refuted patterns
├── 04_Analysts/ external analyst claims (created by fetch_analysts.py)
└── 99_System/ system files, published_posts.log
EXTERNAL_ANALYSTS = [
{
"name": "Builder Radar",
"url": "https://buttondown.com/Builder-Radar/archive",
"parser": "parse_buttondown",
"weight": 0.8,
"cadence": "weekly"
},
# {"name": "Simon Willison", ..., "weight": 1.0},
# {"name": "Latent Space", ..., "weight": 0.9},
]Trust weights are inert with a single analyst. They activate when 3+ analysts are present and affect how strongly external confirmation influences pattern scoring.
Python 3. Core scripts depend on requests, anthropic, and ghapi (installed directly in CI; there is no root requirements.txt). The docs/Pages build uses mkdocs and mkdocs-material, listed in requirements_pages.txt.
You'll need an Anthropic API key (ANTHROPIC_API_KEY) and, for Telegram publishing, a bot token and channel ID — see Environment variables above.
MIT — see LICENSE.
Olga Stroganova, 2026.
This is a personal research tool built for the author's own use. Pull requests are welcome but may not be reviewed quickly, or at all.