adding-inbox-sources
Skill727 starsChanged yesterdayMITView on GitHub
Add a data warehouse table as a Self-driving Inbox signal source (the feature that ships GitHub, Linear, Zendesk, Jira, PagerDuty, Sentry, Snyk, Canny and others). A source syncs one warehouse table of actionable records (issues, tickets, conversations, incidents, findings, feedback, reviews) and the signals pipeline turns new rows into signals. Use when asked to "add a new inbox/self-driving source", "make <vendor> a signal source", or to find which warehouse sources could become signal sources. Covers candidate discovery, the backend emitter and registry, the signals frontend source catalog, the optional context-mill wizard entry, and created_via attribution.
We split this skill into the rules it gives an agent
19 rules. About 0 are common, 4 are distinctive, and 15 are very specific to this project.
≈0
Common · estimate
Many files like this have a rule of this kind.
≈4
Distinctive · estimate
This author's own choices. Worth a look.
15
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Very specific: line 9
After each sync, the signals pipeline reads the new rows, filters them with an LLM actionability gate, and emits signals.
- Distinctive: line 12
All work happens in this repository:
- Very specific: line 20
Do not edit the desktop app (products/desktop/) or packages/agent/packages/agent-contracts/src/inbox-types.ts for a new source.
- Very specific: line 28
Run it on a fresh origin/master worktree.
- Very specific: line 29
Call get_documented_tables() for each source.
- Distinctive: line 31
Keep tables that hold one actionable record per row: issues, tickets, conversations, incidents, alerts, findings, feedback, reviews, survey …
- Distinctive: line 33
Check that each table has a human-readable text column, a stable id, and a creation timestamp.
- Very specific: line 41
Enum: add a SignalSourceProduct value and a SIGNAL_SOURCE_PRODUCT_LABELS entry in enums.py.
- Very specific: line 44
Emitter: add emission/<product>_<table>.py exporting a SignalSourceTableConfig.
- Very specific: line 50
Use where_clause to skip resolved, closed or empty-text rows only when the status values are certain.
- Distinctive: line 51
Do not select PII (emails, names, user objects, IPs).
- Very specific: line 55
Tests: add a row to TIER1_SOURCES in emission/tests/test_tier1_registry.py.
- Very specific: line 58
Check that the fetch query parses: build the SELECT the fetcher builds and run parse_select from posthog.hogql.parser on it.
- Very specific: line 63
Write watches in sentence case and follow /writing-user-facing-copy.
- Very specific: line 73
Add the tool to the step-5 wizard_ask options.
- Very specific: line 74
Add its responder mapping: the same source_product and record kind as the emitter.
- Very specific: line 75
Add its warehouse source_type to the "already connected" detection list.
- Very specific: line 78
Run node scripts/build.js once to confirm the skill bundles.
- Very specific: line 97
Every canonical actionability prompt keeps the When in doubt, classify as ACTIONABLE line, because steering injection anchors on it.
≈ “Common” is an estimate: at least 1 in 5 of the 308,232 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
Adding a Self-driving Inbox source
The Self-driving Inbox connects a PostHog data warehouse source and syncs one table of actionable records.
After each sync, the signals pipeline reads the new rows, filters them with an LLM actionability gate, and emits signals.
The pipeline is driven by a static registry keyed on (ExternalDataSourceType, table), so each table needs an emitter config.
All work happens in this repository:
| Surface | What changes |
| Backend (required) | SignalSourceProduct value, emitter module, registry entry, contract variant, a test row |
| Frontend (required) | products/signals/frontend/inbox/externalSignalSources.ts entry and the signalSourcesLogic.ts unions |
| Wizard (optional) | PostHog/context-mill self-driving skill, so npx @posthog/wizard self-driving offers the source |
Do not edit the desktop app (products/desktop/) or packages/agent/packages/agent-contracts/src/inbox-types.ts for a new source.
A desktop change in the same PR as a new SignalSourceProduct value fails the desktop/backend coupling check, and the inbox does not need it.
The data warehouse source itself must already exist and be implemented.
Find candidates
- List implemented sources: iterate
SourceRegistry.get_all_sources()and keep a source whentype(src).get_schemas is not _BaseSource.get_schemasandget_source_config.unreleasedSourceis false. Run it on a freshorigin/masterworktree. - Call
get_documented_tables()for each source. It gives table names and incremental fields without credentials.get_canonical_descriptions()gives column names. - Keep tables that hold one actionable record per row: issues, tickets, conversations, incidents, alerts, findings, feedback, reviews, survey responses. Skip CRM tasks and notes, message logs, blog posts and event tickets.
- Check that each table has a human-readable text column, a stable id, and a creation timestamp. Tables with text only in nested JSON need a bespoke emitter. Tables with one row per occurrence need grouping (see below).
- Compare against
_register_all_emitters()inproducts/signals/backend/emission/registry.pyand open PRs (gh pr list --search "inbox source in:title").
Backend checklist (per source)
Code lives in products/signals/backend/. Read emission/CLAUDE.md first.
- Enum: add a
SignalSourceProductvalue and aSIGNAL_SOURCE_PRODUCT_LABELSentry inenums.py. The model's choices are callables, so a new value needs no migration. A new record kind needs aSignalSourceTypevalue, the matchingmodels.pySourceTypechoice, and a prompt pair inemission/_prompts.py. - Emitter: add
emission/<product>_<table>.pyexporting aSignalSourceTableConfig.- Flat tables use
make_flat_emitterfromemission/_common.py(seepagerduty_incidents.py). - Nested JSON: extract in HogQL with
JSONExtractString(col, 'key') AS new_alias, using an alias that differs from the column name, or parse in Python (seejira_issues.py,intercom_conversations.py,emission/_survey.py). - Warehouse columns are snake_case. The import renames API fields (
createdAtbecomescreated_at). Check the source'scanonical_descriptions.pyandsettings.py. partition_fieldis the creation timestamp. Setpartition_field_is_datetime_string=Truefor string columns. The fetcher wraps the column inparseDateTimeBestEffort(toString(...)), so a native timestamp also works. Epoch columns use an expression such asfromUnixTimestamp(intDiv(toInt(col), 1000)).- Tables with one row per occurrence (no grouped issue id) use
fetchers/grouped_warehouse.pywith aGROUP BY, so one issue emits once instead of flooding the inbox. - Use
where_clauseto skip resolved, closed or empty-text rows only when the status values are certain. A filter on a column that a vendor sometimes omits makes the whole query fail. - Do not select PII (emails, names, user objects, IPs). Use ids, urls, statuses and severities.
- Reuse the shared prompt pair for the record kind. Siblings use
max_records=200,first_sync_lookback_days=1anddescription_summarization_threshold_chars=2000.
- Flat tables use
- Register:
register_signal_source(ExternalDataSourceType.X, "<table>", CONFIG)in_register_all_emitters(). The table name must match the schema name the source returns, including casing (Issues,Cases). - Contract: in
contracts.pyadd<Name><Kind>SignalExtra(onestr | Noneper extra field,listfor list fields) and<Name><Kind>SignalInput, then add the Input to theSignalInputunion and theSIGNAL_INPUT_VARIANTStuple. - Tests: add a row to
TIER1_SOURCESinemission/tests/test_tier1_registry.py. It checks registration, the contract and the prompts. A bespoke emitter also gets a small parameterized test file for its parsing edge cases. - Generated types: a new enum value changes
products/signals/frontend/generated/and the MCP generated files. Runhogli build:openapi, or let the CI bot commit the regenerated types to the branch.
Check that the fetch query parses: build the SELECT the fetcher builds and run parse_select from posthog.hogql.parser on it.
Frontend checklist (per source)
products/signals/frontend/inbox/externalSignalSources.ts: add anEXTERNAL_SIGNAL_SOURCESentry withsource,label,category,watches,dwSourceType(the exactExternalDataSourceTypestring),requiredTablesandsourceType(the record kind). Writewatchesin sentence case and follow/writing-user-facing-copy.WAREHOUSE_SOURCE_SETUPderives from this list, so it needs no edit.products/signals/frontend/inbox/signalSourcesLogic.ts: add the product to every hand-writtenexternalSourceStates: Record<...>union, in alphabetical order. There are several, and they must all equal theEXTERNAL_SIGNAL_SOURCESlist.
The self-driving wizard (PostHog/context-mill), optional
The wizard's connected-tools list is hardcoded, so a new source is not offered during npx @posthog/wizard self-driving until you add it.
Edit context/skills/self-driving/references/5-connected-tools.md (plus the source lists in 4-sources.md, 2-read-context.md and description.md):
- Add the tool to the step-5
wizard_askoptions. - Add its responder mapping: the same
source_productand record kind as the emitter. - Add its warehouse
source_typeto the "already connected" detection list. - Credential sources are recorded as a follow-up, with no connector file. One-click OAuth sources get a connector reference (clone
5b-linear.md).
Run node scripts/build.js once to confirm the skill bundles.
created_via attribution
ExternalDataSource.created_via is set server-side from the request transport (get_event_source in posthog/event_usage.py).
Callers cannot set it, and a new source needs no wiring.
The upgrade from mcp to self_driving lives in _create_external_data_source in products/warehouse_sources/backend/presentation/views/external_data_source.py.
PR conventions
- Title:
feat(data-warehouse): add <Source or group> as self-driving inbox sources. - Group related sources (incidents, security, trackers, support, feedback) in one PR per group.
- PRs that edit the same lists (
registry.py,contracts.py,test_tier1_registry.py,externalSignalSources.ts,signalSourcesLogic.ts) conflict with each other after one merges. Keep the entries from both sides when you resolve the conflict, and keep everysignalSourcesLogic.tsunion complete.
Gotchas
source_typeon the warehouse source ("Jira"),source_product("jira") and the record kind ("issue") are three different fields.dwSourceTypemust equal the realExternalDataSourceTypestring.- A column the vendor omits for some accounts (
priority,date_closed,archived_at) may be missing from the synced table. Do not select or filter on it unless it is always present. - Every canonical actionability prompt keeps the
When in doubt, classify as ACTIONABLEline, because steering injection anchors on it.
Other versions1 project uses a version of this.
Word for word · 0
None found.
Edited · 0
None found.
Wrote their own version · 1
- PostHog/posthog★ 40ksame name
How matches are found. Word for word: the exact same text. Edited: shares passages with this file (“99% same” means 99% of its five-word phrases also appear here; 10% or more counts). Their own version: similar meaning, or, weakest, the same name. We can’t tell which project had it first. Projects are counted among the 200 most-starred candidates, and mentions are links to the repository we found, up to 50 per site, so both counts are a floor.
In this repository124 other files it gives its agents.
- AGENTS.mdAGENTS.md
- common/AGENTS.mdAGENTS.md
- hog/AGENTS.mdAGENTS.md
- proto/AGENTS.mdAGENTS.md
- rust/AGENTS.mdAGENTS.md
- .cursor/rules/django-python.mdcCursor rule
- .cursor/rules/react-typescript.mdcCursor rule
- .cursor/rules/rust.mdcCursor rule
- adding-activity-loggingSkill
- adding-api-scopesSkill
- adding-inbound-webhooksSkill
- adding-ingestion-warningsSkill
- adding-mcp-store-serversSkill
- adding-personhog-rpcSkill
- adding-product-alertingSkill
- adding-project-secret-api-key-authSkill
- adding-today-highlightsSkill
- adopting-generated-api-typesSkill
- analyzing-experiment-precompute-canarySkill
- analyzing-experiment-query-performanceSkill
- analyzing-insights-across-teamsSkill
- announcing-behavior-changesSkill
- auditing-llm-gateway-paritySkill
- auditing-warehouse-source-coverageSkill
- authenticating-to-clickhouseSkill
- authoring-ci-workflowsSkill
- building-product-empty-statesSkill
- building-the-toolbarSkill
- clickhouse-migrationsSkill
- debugging-ci-failuresSkill
- debugging-local-replaySkill
- debugging-local-task-agent-runsSkill
- debugging-mcp-analyticsSkill
- debugging-signals-pipelineSkill
- debugging-table-access-deniedSkill
- depot-ciSkill
- depot-container-buildsSkill
- depot-github-runnersSkill
- developing-subscriptionsSkill
- django-migrationsSkill
- django-startup-timeSkill
- documenting-warehouse-sourcesSkill
- editing-agents-mdSkill
- establishing-code-ownershipSkill
- extending-hobby-smoke-testsSkill
- extending-personhog-test-harnessSkill
- finding-llm-gateway-migration-candidatesSkill
- fixing-flaky-testsSkill
- formatting-slack-messagesSkill
- gating-production-deploysSkill
- gating-sensitive-actionsSkill
- generating-clickhouse-query-performance-reportsSkill
- hogliSkill
- implementing-mcp-toolsSkill
- implementing-mcp-ui-appsSkill
- implementing-warehouse-sourcesSkill
- improving-drf-endpointsSkill
- improving-mcp-toolsSkill
- ingestion-pipeline-doctor-nodejsSkill
- instrumenting-first-party-metricsSkill
- The first 60 shown.
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

