`ee/` is empty -- the AI agent code moved to `products/insights_ai/` --
but 58 references to `ee/hogai/**` outlived it. None of them is a
cosmetic mention. A path filter that matches nothing is a lint rule that
lints nothing and a CI job that tests nothing, and it fails silently,
which is the only reason it survived this long.
Verified against disk, then removed or repointed:
.semgrep no-init-reexports 19 grandfathered excludes for __init__.py
files that are gone. The rule's own
preamble says to drop a line when its file
goes. Every __init__.py under
products/insights_ai that re-exports is
already excluded, so no new finding
appears.
ci-dagster path filter `ee/hogai/**`, beside a
products/insights_ai/backend line that
already covers what the DAGs import
container-images-cd `^ee/hogai/session_summaries` in two
worker-trigger alternations, dropped with
its separator so the ERE stays well formed
`.github/workflows/ci-ai.yml` is deleted rather than repointed. It is
upstream's Braintrust eval pipeline: gated on `github.repository ==
'Insights/insights'` so it can never run here, needing upstream
secrets, naming eleven eval files that no longer exist, and invoking
them with pytest when the suites that replaced them do not run under
pytest. Repointing the six paths that do have successors would have
produced a job that collects nothing and reports green. Its only
dependent, `eval-summary`, goes with it.
`.agents/skills/implementing-agent-modes` is deleted for the same
reason. It documents `mode_registry`, `AgentModeDefinition`,
`mode_manager.py` and an `agent_modes/presets` directory, none of which
exists in any language in this tree -- only the vestigial `AgentMode`
enum in the schema. Two of its four paths have successors, but
repointing those would leave a skill instructing an agent to edit a
registry that is not there.
Docs keep their meaning rather than their spelling: where a successor
exists it is named, and where one does not, the dead cross-reference
goes rather than being pointed somewhere almost-right. The security note
loses a citation to a stream that no longer exists (the surviving
analogue serializes with json, so naming it would make the sentence
false), and the gateway comment loses a file:line that would now be a
lie.
Left alone: LLM.md and bin/check-imports-are-committed both name
`from ee.hogai...` deliberately, as the import that once sat in a module
the temporal graph loads eagerly. That is the story, not a reference.
The review eval fixtures quote captured upstream diffs and are history.
Co-authored-by: Hanzo Dev <[email protected]>
6.1 KiB
InsightsQL deep dive
lc_query__kind = 'InsightsQLQuery' is its own analysis bucket because, unlike product-generated insights
(TrendsQuery, FunnelsQuery, ...), InsightsQL is arbitrary user- or AI-authored SQL. It runs from the SQL
editor / DataVisualization node (web), the /query/ API (personal_api_key / oauth), the MCP server,
and the Max assistant. It bypasses the guardrails that shape product insights (no enforced date range,
no materialization-aware property access, arbitrary joins), so its slowness causes are more varied and
it is over-represented among OOMs and timeouts.
Two columns matter for InsightsQL:
query— the compiled Datastore SQL that actually ran.lc_query__query— the source InsightsQL the user or AI wrote. Read this to see intent; it is far shorter and clearer than the compiled SQL, and thequery_linkrecipe (query-patterns.md§6) already selects it.
Landscape: who issues the slow InsightsQL
SELECT
lc_product, lc_feature, lc_access_method,
count() AS slow,
uniqExact(team_id) AS teams,
countIf(exception_code = 241) AS ooms,
countIf(exception_code = 159) AS timeouts,
round(avg(query_duration_ms)/1000) AS avg_s,
formatReadableSize(sum(read_bytes)) AS total_read
FROM insights.query_log_archive
WHERE event_time > now() - INTERVAL 14 DAY AND is_initial_query
AND lc_query__kind = 'InsightsQLQuery'
AND (query_duration_ms > 30000 OR exception_code IN (159,160,241))
GROUP BY lc_product, lc_feature, lc_access_method
ORDER BY slow DESC LIMIT 40
The bulk is product_analytics / query / personal_api_key (data integrations and API consumers).
Tight-timeout API noise (avg ~13s, mostly timeouts) and cache_warmup (background insight refresh)
dominate raw counts; weigh by OOMs and cluster-hours as usual.
Identifying AI-written InsightsQL
There is no single boolean "written by AI" column. Identify it from lc_product + lc_feature:
| Signal | Means |
|---|---|
lc_product = 'max_ai' |
Insights's Max assistant and its tools (tagged via tags_context(product=Product.MAX_AI) throughout insights/insightsql_queries/ai/**) |
lc_product = 'mcp' or lc_feature = 'mcp' |
Queries issued by external AI agents through the Insights MCP server |
lc_feature = 'insights_ai' |
The AI feature tag (also exists; less common in practice) |
Filter: lc_product IN ('max_ai','mcp') OR lc_feature IN ('mcp','insights_ai').
Do not use ai_query_source. Despite the name it records which AI-events table the LLM-analytics
resolver chose (dedicated_table / shared_table_fallback), not "AI authored this".
It is also not materialized as an lc_* column in the archive.
Caveats:
- This flags queries executed within an AI/MCP context. An insight Max drafts that a human then saves
and reloads is re-tagged as normal
product_analytics; the archive cannot tell it was AI-originated. - Heuristic tell: AI-written InsightsQL often carries explanatory
-- …comments inlc_query__query(people rarely comment ad-hoc SQL). Useful as a secondary signal, not authoritative. - Source of truth for the tags: the
Product/Featureenums and the product-from-node-kind mapping ininsights/datastore/query_tagging.py; call sites ininsights/insightsql_queries/ai/**and the MCP server.
SELECT
lc_product, lc_feature, lc_access_method,
count() AS slow,
uniqExact(team_id) AS teams,
countIf(exception_code = 241) AS ooms,
countIf(exception_code = 159) AS timeouts,
round(100 * countIf(exception_code = 241) / count()) AS oom_pct
FROM insights.query_log_archive
WHERE event_time > now() - INTERVAL 14 DAY AND is_initial_query
AND lc_query__kind = 'InsightsQLQuery'
AND (lc_product IN ('max_ai','mcp') OR lc_feature IN ('mcp','insights_ai'))
AND (query_duration_ms > 30000 OR exception_code IN (159,160,241))
GROUP BY lc_product, lc_feature, lc_access_method
ORDER BY slow DESC
AI/MCP InsightsQL OOMs and times out at a disproportionately high rate (the MCP-over-OAuth bucket has seen roughly a third of its slow queries OOM in a single week), because these are ambitious analytical queries written without the guardrails of product-generated insights.
What makes AI / ad-hoc InsightsQL slow
Read lc_query__query on the slow set; the recurring causes:
- Unmaterialized JSON extraction. The AI writes
JSONExtractString(properties, 'x')orproperties.xon event or person properties. TheJSONExtract*(...)call form bypasses materialization (see theoptimizing-datastore-and-insightsql-queriesinvestigation playbook andmaterialization-analysis.md), forcing a full JSON-blob read per row. - Self-joins and cross joins on
events. e.g.events e1 JOIN events e2on person within a time window, orCROSS JOINwith a per-person aggregate. Multiplies the scan. - Cross-source joins. Joining warehouse tables (
postgres.*,vitally.*,s3(...)) against events; the external side has no Datastore indexing. - No or wide date range. Ad-hoc SQL often omits a tight
timestampfilter, scanning all history. - Full exports ordered by a wide column. See the function-wrapped sort/filter-key anti-pattern in
the
optimizing-datastore-and-insightsql-queriesinvestigation playbook.
To inspect one AI query, pull both forms:
SELECT lc_query__query AS source_insightsql, query AS compiled_sql, exception
FROM insights.query_log_archive
WHERE query_id = '<id>' AND event_date = '<YYYY-MM-DD>' AND is_initial_query