Skip to content

add nld decision telemetry event#10875

Open
evelyn-with-warp wants to merge 26 commits into
masterfrom
evelyn/add-nld-submission-telemetry
Open

add nld decision telemetry event#10875
evelyn-with-warp wants to merge 26 commits into
masterfrom
evelyn/add-nld-submission-telemetry

Conversation

@evelyn-with-warp
Copy link
Copy Markdown
Contributor

@evelyn-with-warp evelyn-with-warp commented May 14, 2026

Description

Linked Issue

  • The linked issue is labeled ready-to-spec or ready-to-implement.
  • Where appropriate, screenshots or a short video of the implementation are included below (especially for user-visible or UI changes).

Testing

./script/run --features warpui/log_named_telemetry_events

  • I have manually tested my changes locally with ./script/run

Screenshots / Videos

ls
image

ls with file attached
image

** ls with file attached and ⌘ I**
image

hello in terminal mode without enabling onnx classifier
image

** approve after hello, should trigger AgentFollowUp whitelist**
image

natural language without enabling onnx classifier
image
hello with enabling onnx classifier, should trigger whitelisting
image
!hello with enabling onnx classifier, should trigger shell prefix
image
what's the weather should trigger nldclassifier
image
/plan should trigger app level heuristics
image

some video on latest commit
https://www.loom.com/share/480d640e565d4d28a40e1a5d15fc709c

Agent Mode

  • Warp Agent Mode - This PR was created via Warp's AI Agent Mode

@cla-bot cla-bot Bot added the cla-signed label May 14, 2026
@oz-for-oss
Copy link
Copy Markdown
Contributor

oz-for-oss Bot commented May 14, 2026

@evelyn-with-warp

I'm starting a first review of this pull request.

You can view the conversation on Warp.

I completed the review and no human review was requested for this pull request.

Comment /oz-review on this pull request to retrigger a review (up to 3 times on the same pull request).

Powered by Oz

Copy link
Copy Markdown
Contributor

@oz-for-oss oz-for-oss Bot left a comment

Choose a reason for hiding this comment

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

Overview

This PR adds input-decision source tracking through the NLD classifiers and includes decision metadata plus optionally collected input text on InputBufferSubmitted telemetry.

Concerns

  • Locked config updates that go through the compatibility set_input_config path can retain a stale prior input_decision_source, so later submissions can report an unrelated source.
  • The image attachment path sets AttachmentForcedAi and then immediately calls the default helper, which overwrites the source back to ManualToggle.

Verdict

Found: 0 critical, 2 important, 0 suggestions

Request changes

Comment /oz-review on this pull request to retrigger a review (up to 3 times on the same pull request).

Powered by Oz

Comment thread app/src/ai/blocklist/input_model.rs Outdated
self.input_config = new_config;
if let Some(decision_source) = decision_source {
self.input_decision_source = Some(decision_source);
} else if !new_config.is_locked {
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.

⚠️ [IMPORTANT] When decision_source is None and the new config is locked, this preserves the previous source. Existing callers that still use set_input_config can leave stale values like ShellPrefix or AttachmentForcedAi, so later submit telemetry can report a source from an unrelated prior transition. Clear the source when no source is provided, or require every locked transition to pass an explicit source.

Comment thread app/src/terminal/input.rs Outdated
InputDecisionSource::AttachmentForcedAi,
ctx,
);
self.ensure_agent_mode_for_ai_features(true, ctx);
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.

⚠️ [IMPORTANT] This second helper call overwrites the AttachmentForcedAi source set just above because the config is already in AI mode and set_input_config_internal_with_source still updates the source for unchanged configs. Remove this duplicate call so image submissions keep the attachment-specific decision source.

@evelyn-with-warp evelyn-with-warp marked this pull request as draft May 14, 2026 19:49
@evelyn-with-warp
Copy link
Copy Markdown
Contributor Author

evelyn-with-warp commented May 18, 2026

@szgupta I occasionally saw decision source is Null, but it did not reproduce robustly.

09:48:39.634 [INFO] [warpui_core::telemetry::event_store] Recorded telemetry event: Event {
    payload: NamedEvent {
        user_id: Some(
            "paKnCQIb4NVDXhTQambFVn6dnej1",
        ),
        anonymous_id: "9a826897-8e46-4716-afa7-3fcfc7bc1c42",
        name: "AgentMode.NaturalLanguageDetection.InputBufferSubmitted",
        value: Some(
            Object {
                "decision_source": Null,
                "input": String("approve"),
                "input_type": String("Shell"),
                "is_locked": Bool(false),
                "was_lock_set_with_empty_buffer": Bool(false),
            },
        ),
    },
    session_created_at: 2026-05-18T16:34:43.407171Z,
    timestamp: 2026-05-18T16:48:39.633855Z,
    contains_ugc: true,

Despite our code does imply if no NLD decision applied when is_locked is False the decision source will be Null, it's not clear to me why there's no NLD decision for unlocked sessions.
I'm okay with current implementation having coverage less than 100%, as long as the logged decision source is accurate

@evelyn-with-warp evelyn-with-warp marked this pull request as ready for review May 18, 2026 17:20
@evelyn-with-warp evelyn-with-warp requested a review from szgupta May 18, 2026 17:20
@oz-for-oss
Copy link
Copy Markdown
Contributor

oz-for-oss Bot commented May 18, 2026

@evelyn-with-warp

I'm starting a first review of this pull request.

You can view the conversation on Warp.

I completed the review and no human review was requested for this pull request.

Comment /oz-review on this pull request to retrigger a review (up to 3 times on the same pull request).

Powered by Oz

Copy link
Copy Markdown
Contributor

@oz-for-oss oz-for-oss Bot left a comment

Choose a reason for hiding this comment

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

Overview

This PR adds a decision-source enum for natural language detection, threads that source through input mode state, and expands InputBufferSubmitted telemetry to include gated UGC plus the final decision source. The implementation preserves UGC routing via contains_ugc() and includes targeted tests for manual toggles, shell prefix handling, and attachment-forced AI.

Concerns

  • Suggestion: in crates/input_classifier/src/onnx/mod.rs, the detect_input_decision error path reports decision_source as NldClassifier while falling back to context.current_input_type. If inference returns an error, telemetry will look like a successful model decision and mask classifier failures; consider using the heuristic fallback path or adding a distinct source for current-input fallback.

Verdict

Found: 0 critical, 0 important, 1 suggestions

Approve with nits

Comment /oz-review on this pull request to retrigger a review (up to 3 times on the same pull request).

Powered by Oz

@evelyn-with-warp
Copy link
Copy Markdown
Contributor Author

/oz-review

@oz-for-oss
Copy link
Copy Markdown
Contributor

oz-for-oss Bot commented May 18, 2026

@evelyn-with-warp

I'm re-reviewing this pull request in response to a review request.

You can view the conversation on Warp.

I completed the review and no human review was requested for this pull request.

Comment /oz-review on this pull request to retrigger a review (up to 3 times on the same pull request).

Powered by Oz

Copy link
Copy Markdown
Contributor

@oz-for-oss oz-for-oss Bot left a comment

Choose a reason for hiding this comment

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

Overview

This PR adds decision-source tracking to input classification and includes that metadata in the InputBufferSubmitted telemetry event, with the submitted input attached only through the existing AI UGC telemetry gate.

Concerns

  • No blocking correctness or security concerns found in the annotated diff.

Verdict

Found: 0 critical, 0 important, 0 suggestions

Approve

Comment /oz-review on this pull request to retrigger a review (up to 3 times on the same pull request).

Powered by Oz

Copy link
Copy Markdown
Member

@szgupta szgupta left a comment

Choose a reason for hiding this comment

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

Initial thoughts

Comment thread app/src/ai/blocklist/input_model.rs Outdated
Comment on lines +143 to +144
/// The source of the final input decision that is currently reflected in `input_config`.
input_decision_source: Option<InputDecisionSource>,
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

When is this None?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

like the case i got #10875 (comment), although not robustly reproducible;
i'd rather leave it as none (trade coverage for accuracy) if there's some decision path that I am not aware of.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

also for some case if it's set to enable auto detection, then it's okay to be None for NLD to fill in later e.g. in enable_auto_detection

Comment thread app/src/ai/blocklist/input_model.rs Outdated
Comment on lines +143 to +144
/// The source of the final input decision that is currently reflected in `input_config`.
input_decision_source: Option<InputDecisionSource>,
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

"input_decision" is vague - can we be more clear here?

Copy link
Copy Markdown
Contributor Author

@evelyn-with-warp evelyn-with-warp May 19, 2026

Choose a reason for hiding this comment

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

agreed on this. maybe nld_decision_source is better?

Comment thread crates/input_classifier/src/onnx/mod.rs Outdated
#[cfg_attr(not(target_family = "wasm"), async_trait)]
#[cfg_attr(target_family = "wasm", async_trait(?Send))]
impl InputClassifier for OnnxClassifier {
async fn detect_input_decision(
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Does this mean checking the input decision happens separately from actually running NLD? Feel like the detect_input_type API ought to return the type and why that decision was made

Copy link
Copy Markdown
Contributor Author

@evelyn-with-warp evelyn-with-warp May 20, 2026

Choose a reason for hiding this comment

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

it was mostly a rename. we did append source besides decision. Reverted it back to avoid confusion 557db91

Comment thread app/src/ai/blocklist/input_model.rs Outdated
/// The timestamp of the last time the input mode was switched, if the switch was to AI mode and
/// it was autodetected. Else, `None`.
last_ai_autodetection_ts: Option<Instant>,
/// The source of the final input decision that is currently reflected in `input_config`.
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 this is tied to some data in InputConfig, why is it not part of InputConfig?

also seems like it might simplify some other code

Copy link
Copy Markdown
Contributor Author

@evelyn-with-warp evelyn-with-warp May 19, 2026

Choose a reason for hiding this comment

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

@vorporeal it was intended to say the source will be set in set_input_config_internal. Will update the comment to be less confusing; The decision source is dependent on user input text, and will be decided in runtime (e.g if there's ONNX panic, the source will be NLDHeuristicClassifier instead of NLDClassifier..etc), and therefore i think it's not appropriate to put it in input config, which aims to track the mode state (it's logically possible to have different decision source in one mode state)
i also didn't get why put nld_decision_source in inputConfig will simplify this implementation, as we will still need to update everywhere we call set_input_config.
Pls lmk if i oversee some potential shortcut here

Comment thread app/src/ai/blocklist/input_model.rs Outdated
/// it was autodetected. Else, `None`.
last_ai_autodetection_ts: Option<Instant>,
/// The source of the final input decision upon submission
nld_decision_source: Option<NldDecisionSource>,
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

nit: should we call this last_ai_autodetection_decision_source to be consistent with the property above? even better, wrap the two into a mini struct!

Comment thread app/src/ai/blocklist/input_model.rs Outdated
self.set_input_config_internal(current_config.with_input_type(input_type), ctx);
self.set_input_config_internal(
current_config.with_input_type(input_type),
Some(NldDecisionSource::ManualToggle),
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

hrm, how are we sure this is a manual toggle? this API is in a model, not a ui view

Comment thread app/src/terminal/input.rs Outdated
let index = HINT_INDEX.fetch_add(1, Ordering::Relaxed) % AGENT_MODE_HINT_OPTIONS.len();
AGENT_MODE_HINT_OPTIONS[index]
}
fn submitted_nld_decision_source(input_model: &BlocklistAIInputModel) -> Option<NldDecisionSource> {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

What is this doing?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

removed; it seems not necessary b6f6fe2

Comment thread app/src/terminal/input.rs Outdated

self.ai_input_model.update(ctx, |ai_input_model, ctx| {
ai_input_model.set_input_config(
ai_input_model.set_input_config_with_source(
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

nit: i'd leave this API as set_input_config and make NldDecisionSource required. There shouldn't be a single callsite that can't specify why we chose to change the input type, right?

Comment thread crates/input_classifier/src/lib.rs Outdated

/// The source of the final input type decision applied to the user input.
#[derive(Debug, Copy, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub enum NldDecisionSource {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I think we should decouple the decision source in the input_classifier crate vs the application level decision source. The one coming from this crate should only include the sources that it can actually produce (stuff in the NLD pipeline), whereas the one in the application should be like

enum AiAutodetectionSource {
    ManualToggle,
    ...,
    ClassifierSource(ClassifierSource)
}

(naming TBD)

Comment thread crates/input_classifier/src/onnx/mod.rs Outdated
}

async fn classify_input(
async fn classify_input_with_source(
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

nit: let's keep the name as classify_input

Comment thread crates/input_classifier/src/onnx/mod.rs Outdated
_context: &Context,
) -> anyhow::Result<ClassificationResult> {
context: &Context,
) -> anyhow::Result<ClassificationWithSource> {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Can we just add the source to the existing ClassificationResult?

@evelyn-with-warp evelyn-with-warp force-pushed the evelyn/add-nld-submission-telemetry branch from 557db91 to 5c54c6b Compare May 20, 2026 17:41
@evelyn-with-warp evelyn-with-warp requested a review from szgupta May 21, 2026 17:36
Comment thread crates/input_classifier/src/lib.rs Outdated

/// Sources produced by the NLD pipeline.
#[derive(Debug, Copy, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub enum NldDecision {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

nit: InputClassifierDecisionSource

let's avoid "nld" since we don't really use that term in the codebase today

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Comment thread crates/input_classifier/src/lib.rs Outdated
NldClassifierFallbackHeuristic,
NldClassifierFallbackCurrentInput,
Denylist,
HistoryMatch,
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

The input_classifier crate doesn't check for history

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Comment thread crates/input_classifier/src/lib.rs Outdated
NldClassifierFallbackCurrentInput,
Denylist,
HistoryMatch,
OneOffWhitelist,
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Can you break this out into the two allowlists: "ShellCommandAllowlist" vs "NaturalLanguageAllowlist" vs "NaturalLanguageFollowupAllowlist"

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Comment thread crates/input_classifier/src/lib.rs Outdated
NldClassifier,
NldClassifierFallbackHeuristic,
NldClassifierFallbackCurrentInput,
Denylist,
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Which denylist?

Copy link
Copy Markdown
Contributor Author

@evelyn-with-warp evelyn-with-warp May 22, 2026

Choose a reason for hiding this comment

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

AISettings.autodetection_command_denylist to block auto detection run ; also moved to app level tracking now

Comment thread crates/input_classifier/src/lib.rs Outdated

/// Sources produced by the NLD pipeline.
#[derive(Debug, Copy, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub enum NldDecision {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

All of these variants should be more specific and documented please!

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

&self,
input: ParsedTokensSnapshot,
context: &Context,
) -> (InputType, NldDecision) {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Instead of a tuple, can you create a new return type (struct) with these two properties?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Comment thread app/src/ai/blocklist/input_model.rs Outdated
Comment on lines +81 to +84
/// App level settings from explicit user actions.
AppLevelOverride(AppLevelOverride),
/// App level implicit / side-effect transitions from other UI flows.
AppLevelHeuristic(AppLevelHeuristic),
Copy link
Copy Markdown
Member

@szgupta szgupta May 21, 2026

Choose a reason for hiding this comment

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

Hrm, I feel like we should collapse these into one. In fact, I would change this type to

enum InputTypeAutoDetectionSource {
    InputClassifier(InputClassifierDecisionSource),
    HistoryMatch,
    ... // all the other app types inlined here
}

@evelyn-with-warp evelyn-with-warp requested a review from szgupta May 22, 2026 06:47
Copy link
Copy Markdown
Member

@szgupta szgupta left a comment

Choose a reason for hiding this comment

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

Awesome, thank you for iterating!


/// The source of the final input type decision applied to the user input.
#[derive(Debug, Copy, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub enum InputTypeAutoDetectionSource {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

nit: this is more like InputTypeDecisionSource - because a lot of these don't go through "auto-detection" (e.g. voice input)

Comment on lines +31 to +32
/// Image / file attachment in progress force-locked AI mode.
AttachmentForcedAi,
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

nit: I would just call this "AttachedFile"

Comment on lines +33 to +34
/// First token matched the autodetection command denylist.
Denylist,
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Can you make this more specific (i.e. which denylist)

} else {
AISettings::as_ref(ctx).is_ai_autodetection_enabled(ctx)
};
let initial_decision_source = None;
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Can you just inline this? no need for variable declaration

pub fn input_config(&self) -> InputConfig {
self.input_config
}
pub fn last_ai_autodetection_source(&self) -> Option<InputTypeAutoDetectionSource> {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

nit: newline above

Comment on lines +22 to +23
/// The source of the final input type decision applied to the user input.
#[derive(Debug, Copy, Clone, PartialEq, Eq, Serialize, Deserialize)]
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Can you move this down, past the imports

TelemetryEvent::AgentExitedShellProcess { .. } => true,
TelemetryEvent::CreateProjectPromptSubmitted { .. } => false,
TelemetryEvent::CreateProjectPromptSubmittedContent { .. } => true,
TelemetryEvent::InputBufferSubmitted { .. } => false,
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

revert this? it was fine before

return InputType::Shell;
return InputClassificationResult::new(
InputType::Shell,
InputClassifierDecisionSource::ShellHeuristic,
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Should we break this up into two types rather than use the same one here and in is_likely_shell_command?

Comment on lines +73 to +74
/// The classifier source that produced this classification.
pub source: InputClassifierDecisionSource,
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

nit: instead of making this pub, define a getter

Comment on lines +227 to +228
#[cfg(test)]
mod tests {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

nit: move to mod_tests file

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants