Handler: tap.builtin_topic_source
Adapter that brings the existing in-repo topic_source scrapers (hackernews, devto, web_search, knowledge, codebase) into the declarative external_taps model.
What it does
Delegates toservices.topic_sources.runner.run_all() (the same runner the scheduler already uses) and filters the summary to the source name specified in row.tap_type. The actual topic persistence (dedup, similarity scoring, content_task row creation) happens inside the existing plugin — this handler is purely a shape adapter.
Row configuration
Seeded rows (all disabled)
The baseline migration seeds one row per current topic_source plugin with sensible schedules:| Name | Schedule |
|---|---|
hackernews | every 1 hour |
devto | every 2 hours |
web_search | every 6 hours |
knowledge | every 12 hours |
codebase | every 1 day |
Operator runbook
Enabling a seeded topic source
last_run_at populates:
Adding a new built-in source
If you write a newservices/topic_sources/*.py plugin and register it under plugins.topic_source, seed it as a tap row:
Disabling
Why this adapter exists
The existing topic_source plugin architecture is already declarative-ish (viaplugin.topic_source.<name> app_settings rows). This adapter just brings the enablement into one table (external_taps) so:
- Grafana can show tap state alongside webhook / retention state.
- Operator CLI is uniform (
poindexter taps …mirrorswebhooks …andretention …). - Future Singer-protocol taps live in the same table — one list of “what is this system ingesting from?”.
Caveats
- Full-runner invocation: each handler call invokes
run_all()and filters to the tap’s name. When multiple built-in taps are enabled, each fires its ownrun_all(). The existing runner is idempotent via content_hash dedup, so this is safe but slightly wasteful. Fixing this would require the topic_sources runner to accept a single-source filter — tracked as a follow-up. - No per-source config isolation in v1: the old
plugin.topic_source.<name>.configapp_settings rows still drive per-source behavior. Moving that config intoexternal_taps.configis a follow-up migration.
Related
- Framework overview: Integrations
- Sibling handler:
tap.singer_subprocess(stub — full external Singer tap support) - Existing runner:
services/topic_sources/runner.py - GH-103 (external taps issue)