Skip to content

[PLAT-334] AI reports#8673

Merged
pjain1 merged 37 commits intomainfrom
ai_reports
Feb 19, 2026
Merged

[PLAT-334] AI reports#8673
pjain1 merged 37 commits intomainfrom
ai_reports

Conversation

@pjain1
Copy link
Copy Markdown
Member

@pjain1 pjain1 commented Jan 20, 2026

  • Add AI-powered scheduled insight reports with new format (format: ai_session) on existing report.
  • Implement AI resolver that creates AI sessions by default with analyst agent for completing user prompt

On reconciliation, an AI resolver is created which generates an AI session with analyst_agent and a slightly modified system prompt for scheduled report, upon competition it include a summary and send out the session id link to the recipient.

Report modes behaviour

Recipient mode - In this mode, AI report is run with each recipient attributes and a separate session is created for them.

Creator mode - In this mode, a single session is created with owners attribute (without any charts as it required mv agg for rendering), this session is a shared session and a magic token is used for viewing this session (this mgc token has no access just used for authentication), for continuing conversation user should be logged in and conversation should be forked.

Format

Reports can now use format: ai_session to generate AI-powered insights instead of traditional query exports. Example YAML:

  type: report
  format: ai_session
  data:
    ai:
      agent: analyst_agent
      prompt: 
      time_range:
        iso_duration: P7D
        time_zone: UTC
      comparison_time_range:
        iso_duration: P7D
        iso_offset: P7D
      context:
        explore: my_dashboard

Checklist:

  • Covered by tests
  • Ran it and it works as intended
  • Reviewed the diff before requesting a review
  • Checked for unhandled edge cases
  • Linked the issues it closes
  • Checked if the docs need to be updated. If so, create a separate Linear DOCS issue
  • Intend to cherry-pick into the release branch
  • I'm proud of this work!

@pjain1 pjain1 marked this pull request as draft January 20, 2026 13:33
@pjain1 pjain1 changed the title Ai reports AI reports Jan 20, 2026
@pjain1 pjain1 changed the title AI reports [PLAT-334] AI reports Jan 20, 2026
Comment thread runtime/ai/ai.go Outdated
@pjain1 pjain1 requested a review from begelundmuller January 26, 2026 05:13
@pjain1 pjain1 marked this pull request as ready for review January 26, 2026 05:13
Comment thread proto/rill/runtime/v1/resources.proto Outdated
bool intervals_check_unclosed = 15;
// AI report configuration
string format = 17; // "query" (default) or "ai_session"
AIReportConfig ai_config = 18; // Configuration for AI-powered reports (only used when format = "ai_session")
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you look at alerts, they have generic resolver and resolver_properties fields:

  • string resolver = 13;
    google.protobuf.Struct resolver_properties = 14;
    // DEPRECATED: Use resolver and resolver_properties instead.
    string query_name = 3;
    // DEPRECATED: Use resolver and resolver_properties instead.
    string query_args_json = 4;
  • string resolver = 22;
    google.protobuf.Struct resolver_properties = 23;

We also intended to do the same for reports, but we never got around to it. However, with this change, rather than hard-code the AI resolver properties here, can we take a generic approach similar to alerts?

Copy link
Copy Markdown
Member Author

@pjain1 pjain1 Jan 29, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

made the change for ai report, need to fix the export request and download api for standard report

Comment thread proto/rill/runtime/v1/resources.proto Outdated
Comment on lines +648 to +654
// AITimeRange defines a time range using ISO 8601 duration strings.
// The time range is resolved at report execution time.
message AITimeRange {
string iso_duration = 1; // ISO 8601 duration (e.g., "P7D" for 7 days, "P1M" for 1 month)
string iso_offset = 2; // Optional ISO 8601 offset for comparison ranges (e.g., "P7D" to offset by 7 days)
string time_zone = 3; // IANA timezone (e.g., "America/New_York")
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This appears not to support Rill time expressions? I believe the UI only uses those for new reports (or otherwise, they will probably migrate to that soon).

Instead of a custom type, can it use our main TimeRange proto type, which we already have utils for converting into a concrete time range? Thinking about this one:

// 2 of the (start, end, iso_duration) should be set
message TimeRange {
// Optional. Defaults to min
google.protobuf.Timestamp start = 1;
// Optional. Defaults to max
google.protobuf.Timestamp end = 2;
// Optional, ie PT1M
string iso_duration = 3;
// Optional, ie PT1M
string iso_offset = 4;
TimeGrain round_to_grain = 5;
// Optional. IANA format, ie Europe/Copenhagen. Defaults to UTC
string time_zone = 6;
// Optional. Rill format time range. Should only be used for alerts and reports.
// For dashboard call ResolveTimeRanges.
string expression = 7;
string time_dimension = 8; // Optional. If not specified, falls back to the primary time dimension in the metrics view spec
}

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Aa far as I remember, at the time of coding UI was not using expressions so used this, related to this during tool calls LLM would most of the times use expressions in wrong way like adding other fields along with it, causing validation errors. Was not aware its the way going forward, I can make that change and possibly add some concrete examples with expression so that it uses them correctly

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This would always lead to failed tool calls thus wasting tokens

Comment thread runtime/ai/analyst_agent.go Outdated
Comment thread runtime/ai/analyst_agent.go Outdated
Comment thread runtime/ai/analyst_agent.go Outdated
Comment on lines +207 to +209
// Add scheduled insight mode context
data["is_scheduled_insight"] = args.IsScheduledInsight
data["is_scheduled_insight_user_prompt"] = args.IsScheduledInsight && !(strings.EqualFold(strings.TrimSpace(args.Prompt), "Generate the scheduled insight report."))
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. Doing a strings.EqualFold seems kind of unsafe in case we change that text upstream. Would it be possible to keep the prompt empty (so we can do if eq .prompt ""), and inject the "Generate ..." prompt in userPrompt when prompt is empty and IsScheduledInsight is true?
  2. Nit: Can you move this up to the initial data := statement like the other props?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Adding a custom flag to indicate if user has provided custom prompt. The reason for not injecting userPrompt is because it won't be visible in session messages then, if we want in session msgs then we will have add router and agent call msgs along with their full json args.

It won't matter much anyways as per discussion with Nishant, we would always want a prompt here so I think as user keep on selecting time period, measures and dims in the report modal, UI can dynamically create prompt like it does for Explain feature - prompt can be like this for comparative analysis

You are doing comparative analysis between two time periods <t1, t2> in scheduled insight report mode, your analysis should:
1. Compare current period to the comparison period for <all key> or <m1, m2...> measures
2. Identify which measures changed significantly (>10%)
3. For each significant change, identify the dimensional drivers
4. Highlight any ranking changes in top dimensions
5. Generate 3-5 key insights with supporting charts

Focus areas:
- **Overall changes**: Which measures changed the most between periods?
- **Drivers of change**: Which dimensions contributed most to increases/decreases?
- **Ranking shifts**: Did any top dimensions change rank significantly?
- **Anomalies**: Any unusual patterns unique to one period?

For single period analysis -

You are doing analysis on time period <t1> in scheduled insight report mode, your analysis should:
1. Show totals for the <most impactful> or <m1, m2...> measures in the period
2. Identify interesting trends within the time range (use time series)
3. Find anomalies - unusual spikes, drops, or outliers
4. Highlight top performers and notable dimension values
5. Generate 3-5 key insights with supporting charts

Focus areas:
- **Totals**: What are the key numbers for this period?
- **Trends**: How did metrics change over the period? Any acceleration/deceleration?
- **Anomalies**: Are there any unusual data points that stand out?
- **Distribution**: Which dimensions dominate? Any concentration issues?

Comment thread runtime/reconcilers/report.go Outdated
Comment thread runtime/reconcilers/report.go Outdated
Comment thread runtime/reconcilers/report.go
Comment thread runtime/resolvers/ai.go Outdated
Comment thread runtime/resolvers/ai.go Outdated
@pjain1 pjain1 requested a review from begelundmuller January 29, 2026 18:45
Comment thread admin/server/reports.go Outdated
Comment on lines +81 to +83
if webOpenMode == WebOpenModeRecipient || req.Resolver == "ai" {
// in recipient mode tokens are used for unsubscribing and for ai reports, shared sessions are created so token is just used for authentication, so no access to resources is needed
tokens, err = s.createMagicTokensWithoutResources(ctx, proj.ID, req.Report, req.OwnerId, recipients)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does it matter about the resources? Like if it had resource access to the report, would it be a problem? Basically, it would be nice to keep it generic and avoid custom checks like req.Resolver == "ai" if possible.

If the checks cannot be avoided, can it then pass the report format instead of the resolver? (And maybe use a proto enum for the report format.) Since the goal of resolvers is to keep them generic.

Copy link
Copy Markdown
Member Author

@pjain1 pjain1 Feb 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah it should not matter if they get access to report, resolving transitive access with ai resolver is not supported so added this check. But you are correct, we can simplify this and I can return nil from InferRequiredSecurityRules of ai resolver for now (meaning no access) as we cannot directly know the referred metrics views and accessible fields without going through get_metrics_view tool call results, can be done probably in a follow up.

Comment thread admin/server/reports.go Outdated
Comment on lines +594 to +606
// Handle resolver-based reports (new style) vs legacy query-based reports
if opts.Resolver != "" && opts.ResolverProperties != nil {
res.Data = map[string]any{
opts.Resolver: opts.ResolverProperties.AsMap(),
}
} else {
// Legacy query-based report
res.Query.Name = opts.QueryName
res.Query.ArgsJSON = opts.QueryArgsJson
res.Export.Format = opts.ExportFormat.String()
res.Export.IncludeHeader = opts.ExportIncludeHeader
res.Export.Limit = uint(opts.ExportLimit)
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't the res.Export fields be set in both cases? Like if you pass resolver: metrics, then the export fields are still relevant.

Copy link
Copy Markdown
Member Author

@pjain1 pjain1 Feb 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes I was imagining resolver support for only ai session for now and add support generally in later version but its causing complexity and confusion. Will fix this.

Copy link
Copy Markdown
Member Author

@pjain1 pjain1 Feb 7, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just noticed that metrics resolver has ResolveExport unimplemented, may be its a miss? also its not parsed in parse_partial_data.go

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ResolveExport is not currently used anywhere, but was intended to replace the current hard-coded exports (see the ugly switch in downloadHandler). The alerts use resolvers, but they don't use exports, only queries. And since reports and UI downloads haven't migrated to resolvers yet, there probably hasn't been a reason to implement it before. It would be great to implement it, and to migrate reports over to resolvers fully.

Comment thread admin/server/reports.go Outdated
Comment thread admin/server/reports.go Outdated
Comment thread admin/server/reports.go Outdated
Comment thread runtime/resolvers/ai.go Outdated
Comment thread runtime/resolvers/ai.go
Comment thread runtime/resolvers/ai.go Outdated

// if metrics view is provided, we can get the metrics view's time bounds
if r.metricsView != "" {
mv, security, err := queries.ResolveMVAndSecurityFromAttributes(ctx, r.runtime, r.instanceID, r.metricsView, r.claims)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider inlining this function. We're trying to deprecate the runtime/queries package in favor of runtime/resolvers, so dependencies like this make it more complicated.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see that its also available in server package so exporting that and using it.

Copy link
Copy Markdown
Contributor

@begelundmuller begelundmuller Feb 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure this is a good idea from an abstractions perspective. The the server/API layer is logically the "outermost" layer, so inner service layers should not import from that.

If you want to have it in a central place, it should probably be as a member function on runtime.Runtime.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So seems like in server package, its only used in the downloadHandler, after moving to resolvers, that will get removed anyways so inlined the function in this file only.

Comment thread runtime/resolvers/ai.go Outdated
Comment on lines +604 to +605
// Generic resolver configuration (preferred for new reports)
string resolver = 17;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As a general reflection, I think the removal of the query|ai_session format has made some things more complicated, and made it more difficult to support Markdown reports.

It's okay to have some custom logic like resolver == "ai" in one or two specific places if needed, ideally inside the report reconciler itself, but to have it spread out in many packages feels like a problem. Then it's better to have some structured/typed option, such as a report_type enum or something like that.

@pjain1 pjain1 requested a review from begelundmuller February 8, 2026 19:13
Comment thread admin/server/reports.go Outdated
Comment thread runtime/ai/analyst_agent.go Outdated
Comment thread runtime/drivers/slack/templates/slack/scheduled_report.slack
Comment thread runtime/parser/parse_report.go Outdated
Comment thread runtime/resolvers/ai.go Outdated
InstanceID: r.instanceID,
CreateIfNotExists: true,
Claims: r.claims,
UserAgent: "rill/report", // TODO change it to system/report or similar so that its not shown in AI sessions list, keeping it rill prefixed for now so that access checks pass
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This TODO should probably be handled. Maybe just patch the UI to filter out this user agent in the conversation listing? Then I think it's fine.

Comment thread runtime/reconcilers/report.go Outdated
Comment on lines +651 to +654
content, ok := notificationsContent[""] // non-email recipients are treated as anon users
if !ok {
return false, fmt.Errorf("failed to get notification content for non-email notifier %q", notifier.Connector)
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I forgot the handling of this, but for Slack we support a users field. Do we handle that in non-creator modes at all anywhere? See here:

Users []string `mapstructure:"users"`

Not necessarily something to handle in this PR, but might just take a backlog note to handle it.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sure can do in a follow up

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually not all slack users may be Rill users, so we just sent to Rill users and skip others?

Copy link
Copy Markdown
Collaborator

@AdityaHegde AdityaHegde left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

UI changes look good!

@pjain1 pjain1 merged commit 6d2ca1a into main Feb 19, 2026
14 of 15 checks passed
@pjain1 pjain1 deleted the ai_reports branch February 19, 2026 06:11
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants